]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/zramctl.c
zrmactl: add new command to control /dev/zramN devices
[thirdparty/util-linux.git] / sys-utils / zramctl.c
1 /*
2 * zramctl - purpose of it
3 *
4 * Copyright (c) 2014 Timofey Titovets <Nefelim4ag@gmail.com>
5 * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it would be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include <getopt.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdarg.h>
25 #include <assert.h>
26
27 #include <libsmartcols.h>
28
29 #include "c.h"
30 #include "nls.h"
31 #include "closestream.h"
32 #include "strutils.h"
33 #include "xalloc.h"
34 #include "sysfs.h"
35 #include "optutils.h"
36 #include "ismounted.h"
37
38 /*#define CONFIG_ZRAM_DEBUG*/
39
40 #ifdef CONFIG_ZRAM_DEBUG
41 # define DBG(x) do { fputs("zram: ", stderr); x; fputc('\n', stderr); } while(0)
42 #else
43 # define DBG(x)
44 #endif
45
46 /* status output columns */
47 struct colinfo {
48 const char *name;
49 double whint;
50 int flags;
51 const char *help;
52 };
53
54 enum {
55 COL_NAME = 0,
56 COL_DISKSIZE,
57 COL_ORIG_SIZE,
58 COL_COMP_SIZE,
59 COL_ALGORITHM,
60 COL_STREAMS,
61 COL_ZEROPAGES,
62 COL_MEMTOTAL,
63 COL_MOUNTPOINT
64 };
65
66 static const struct colinfo infos[] = {
67 [COL_NAME] = { "NAME", 0.25, 0, N_("zram device name") },
68 [COL_DISKSIZE] = { "DISKSIZE", 5, SCOLS_FL_RIGHT, N_("limit on the uncompressed worth of data") },
69 [COL_ORIG_SIZE] = { "DATA", 5, SCOLS_FL_RIGHT, N_("uncompressed size of stored data") },
70 [COL_COMP_SIZE] = { "COMPR", 5, SCOLS_FL_RIGHT, N_("compressed size of stored data") },
71 [COL_ALGORITHM] = { "ALGORITHM", 3, 0, N_("selected compression algorithms") },
72 [COL_STREAMS] = { "STREAMS", 3, SCOLS_FL_RIGHT, N_("number of concurrent compress operations") },
73 [COL_ZEROPAGES] = { "ZERO-PAGES", 3, SCOLS_FL_RIGHT, N_("empty pages with no allocated memory") },
74 [COL_MEMTOTAL] = { "TOTAL", 5, SCOLS_FL_RIGHT, N_("all memory including allocator fragmentation and metadata overhead") },
75 [COL_MOUNTPOINT]= { "MOUNTPOINT",0.10, SCOLS_FL_TRUNC, N_("where the device is mounted") },
76
77 };
78
79 static int columns[ARRAY_SIZE(infos) * 2] = {-1};
80 static int ncolumns;
81
82 struct zram {
83 char devname[32];
84 struct sysfs_cxt sysfs;
85 };
86
87 #define ZRAM_EMPTY { .devname = { '\0' }, .sysfs = UL_SYSFSCXT_EMPTY }
88
89 static unsigned int raw, no_headings, inbytes;
90
91
92 static int get_column_id(int num)
93 {
94 assert(num < ncolumns);
95 assert(columns[num] < (int) ARRAY_SIZE(infos));
96 return columns[num];
97 }
98
99 static const struct colinfo *get_column_info(int num)
100 {
101 return &infos[ get_column_id(num) ];
102 }
103
104 static int column_name_to_id(const char *name, size_t namesz)
105 {
106 size_t i;
107
108 for (i = 0; i < ARRAY_SIZE(infos); i++) {
109 const char *cn = infos[i].name;
110
111 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
112 return i;
113 }
114 warnx(_("unknown column: %s"), name);
115 return -1;
116 }
117
118 static void zram_set_devname(struct zram *z, const char *devname, size_t n)
119 {
120 assert(z);
121
122 if (!devname)
123 snprintf(z->devname, sizeof(z->devname), "/dev/zram%zu", n);
124 else {
125 strncpy(z->devname, devname, sizeof(z->devname));
126 z->devname[sizeof(z->devname) - 1] = '\0';
127 }
128
129 DBG(fprintf(stderr, "set devname: %s", z->devname));
130 sysfs_deinit(&z->sysfs);
131 }
132
133 static struct zram *new_zram(const char *devname)
134 {
135 struct zram *z = xcalloc(1, sizeof(struct zram));
136
137 DBG(fprintf(stderr, "new: %p", z));
138 if (devname)
139 zram_set_devname(z, devname, 0);
140 return z;
141 }
142
143 static void free_zram(struct zram *z)
144 {
145 if (!z)
146 return;
147 DBG(fprintf(stderr, "free: %p", z));
148 sysfs_deinit(&z->sysfs);
149 free(z);
150 }
151
152 static struct sysfs_cxt *zram_get_sysfs(struct zram *z)
153 {
154 assert(z);
155
156 if (!z->sysfs.devno) {
157 dev_t devno = sysfs_devname_to_devno(z->devname, NULL);
158 if (!devno)
159 return NULL;
160 if (sysfs_init(&z->sysfs, devno, NULL))
161 return NULL;
162 }
163
164 return &z->sysfs;
165 }
166
167 static inline int zram_exist(struct zram *z)
168 {
169 assert(z);
170
171 if (zram_get_sysfs(z) == NULL)
172 return 0;
173
174 DBG(fprintf(stderr, "%s exists", z->devname));
175 return 1;
176 }
177
178 static int zram_set_u64parm(struct zram *z, const char *attr, uint64_t num)
179 {
180 struct sysfs_cxt *sysfs = zram_get_sysfs(z);
181 if (!sysfs)
182 return -EINVAL;
183 DBG(fprintf(stderr, "%s writing %ju to %s", z->devname, num, attr));
184 return sysfs_write_u64(sysfs, attr, num);
185 }
186
187 static int zram_set_strparm(struct zram *z, const char *attr, const char *str)
188 {
189 struct sysfs_cxt *sysfs = zram_get_sysfs(z);
190 if (!sysfs)
191 return -EINVAL;
192 DBG(fprintf(stderr, "%s writing %s to %s", z->devname, str, attr));
193 return sysfs_write_string(sysfs, attr, str);
194 }
195
196
197 static int zram_used(struct zram *z)
198 {
199 uint64_t size;
200 struct sysfs_cxt *sysfs = zram_get_sysfs(z);
201
202 if (sysfs &&
203 sysfs_read_u64(sysfs, "disksize", &size) == 0 &&
204 size > 0) {
205
206 DBG(fprintf(stderr, "%s used", z->devname));
207 return 1;
208 }
209 DBG(fprintf(stderr, "%s unused", z->devname));
210 return 0;
211 }
212
213 static struct zram *find_free_zram(void)
214 {
215 struct zram *z = new_zram(NULL);
216 size_t i;
217 int isfree = 0;
218
219 for (i = 0; isfree == 0; i++) {
220 DBG(fprintf(stderr, "find free: checking zram%zu", i));
221 zram_set_devname(z, NULL, i);
222 if (!zram_exist(z))
223 break;
224 isfree = !zram_used(z);
225 }
226 if (!isfree) {
227 free_zram(z);
228 z = NULL;
229 }
230 return z;
231 }
232
233 static void fill_table_row(struct libscols_table *tb, struct zram *z)
234 {
235 static struct libscols_line *ln;
236 struct sysfs_cxt *sysfs;
237 size_t i;
238 uint64_t num;
239
240 assert(tb);
241 assert(z);
242
243 DBG(fprintf(stderr, "%s: filling status table", z->devname));
244
245 sysfs = zram_get_sysfs(z);
246 if (!sysfs)
247 return;
248
249 ln = scols_table_new_line(tb, NULL);
250 if (!ln)
251 err(EXIT_FAILURE, _("failed to initialize output line"));
252
253 for (i = 0; i < (size_t) ncolumns; i++) {
254 char *str = NULL;
255
256 switch (get_column_id(i)) {
257 case COL_NAME:
258 str = xstrdup(z->devname);
259 break;
260 case COL_DISKSIZE:
261 if (inbytes)
262 str = sysfs_strdup(sysfs, "disksize");
263 else if (sysfs_read_u64(sysfs, "disksize", &num) == 0)
264 str = size_to_human_string(SIZE_SUFFIX_1LETTER, num);
265 break;
266 case COL_ORIG_SIZE:
267 if (inbytes)
268 str = sysfs_strdup(sysfs, "orig_data_size");
269 else if (sysfs_read_u64(sysfs, "orig_data_size", &num) == 0)
270 str = size_to_human_string(SIZE_SUFFIX_1LETTER, num);
271 break;
272 case COL_COMP_SIZE:
273 if (inbytes)
274 str = sysfs_strdup(sysfs, "compr_data_size");
275 else if (sysfs_read_u64(sysfs, "compr_data_size", &num) == 0)
276 str = size_to_human_string(SIZE_SUFFIX_1LETTER, num);
277 break;
278 case COL_ALGORITHM:
279 {
280 char *alg = sysfs_strdup(sysfs, "comp_algorithm");
281 if (!alg)
282 break;
283 if (strstr(alg, "[lzo]") == NULL) {
284 if (strstr(alg, "[lz4]") == NULL)
285 ;
286 else
287 str = xstrdup("lz4");
288 } else
289 str = xstrdup("lzo");
290 free(alg);
291 break;
292 }
293 case COL_MOUNTPOINT:
294 {
295 char path[PATH_MAX] = { '\0' };
296 int fl;
297
298 check_mount_point(z->devname, &fl, path, sizeof(path));
299 if (*path)
300 str = xstrdup(path);
301 break;
302 }
303 case COL_STREAMS:
304 str = sysfs_strdup(sysfs, "max_comp_streams");
305 break;
306 case COL_ZEROPAGES:
307 str = sysfs_strdup(sysfs, "zero_pages");
308 break;
309 case COL_MEMTOTAL:
310 if (inbytes)
311 str = sysfs_strdup(sysfs, "mem_used_total");
312 else if (sysfs_read_u64(sysfs, "mem_used_total", &num) == 0)
313 str = size_to_human_string(SIZE_SUFFIX_1LETTER, num);
314 break;
315 }
316
317 if (str)
318 scols_line_refer_data(ln, i, str);
319 }
320 }
321
322 static void status(struct zram *z)
323 {
324 struct libscols_table *tb;
325 size_t i;
326
327 scols_init_debug(0);
328
329 tb = scols_new_table();
330 if (!tb)
331 err(EXIT_FAILURE, _("failed to initialize output table"));
332
333 scols_table_enable_raw(tb, raw);
334 scols_table_enable_noheadings(tb, no_headings);
335
336 for (i = 0; i < (size_t) ncolumns; i++) {
337 const struct colinfo *col = get_column_info(i);
338
339 if (!scols_table_new_column(tb, col->name, col->whint, col->flags))
340 err(EXIT_FAILURE, _("failed to initialize output column"));
341 }
342
343 if (z)
344 fill_table_row(tb, z); /* just one device specified */
345 else {
346 size_t i; /* list all used devices */
347 z = new_zram(NULL);
348
349 for (i = 0; ; i++) {
350 zram_set_devname(z, NULL, i);
351 if (!zram_exist(z))
352 break;
353 if (zram_used(z))
354 fill_table_row(tb, z);
355 }
356 free_zram(z);
357 }
358
359 scols_print_table(tb);
360 scols_unref_table(tb);
361 }
362
363 static void __attribute__ ((__noreturn__)) usage(FILE * out)
364 {
365 size_t i;
366
367 fputs(USAGE_HEADER, out);
368 fprintf(out, _( " %1$s [options] <device>\n"
369 " %1$s -r <device> [...]\n"
370 " %1$s [options] -f | <device> -s <size>\n"),
371 program_invocation_short_name);
372
373 fputs(USAGE_OPTIONS, out);
374 fputs(_(" -a, --algorithm <lzo|lz4> compression algorithm\n"), out);
375 fputs(_(" -b, --bytes print sizes in bytes rather than in human readable format\n"), out);
376 fputs(_(" -f, --find find free device\n"), out);
377 fputs(_(" -n, --noheadings don't print headings\n"), out);
378 fputs(_(" -o, --output <list> columns to use for status output\n"), out);
379 fputs(_(" -r, --reset reset all specified devices\n"), out);
380 fputs(_(" -s, --size <size> device size\n"), out);
381 fputs(_(" --raw use raw status output format\n"), out);
382 fputs(_(" -t, --streams <number> number of compressoin streams\n\n"), out);
383
384 fputs(USAGE_SEPARATOR, out);
385 fputs(USAGE_HELP, out);
386 fputs(USAGE_VERSION, out);
387
388 fputs(_("\nAvailable columns (for --output):\n"), out);
389 for (i = 0; i < ARRAY_SIZE(infos); i++)
390 fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
391
392 fprintf(out, USAGE_MAN_TAIL("zramctl(8)"));
393 exit(out == stderr ? 1 : EXIT_SUCCESS);
394 }
395
396 /* actions */
397 enum {
398 A_NONE = 0,
399 A_STATUS,
400 A_CREATE,
401 A_FINDONLY,
402 A_RESET
403 };
404
405 int main(int argc, char **argv)
406 {
407 uintmax_t size = 0, nstreams = 0;
408 char *algorithm = NULL;
409 int rc = 0, c, find = 0, act = A_NONE;
410 struct zram *zram = NULL;
411
412 enum { OPT_RAW = CHAR_MAX + 1 };
413
414 static const struct option longopts[] = {
415 { "algorithm", required_argument, NULL, 'a' },
416 { "find", no_argument, NULL, 'f' },
417 { "help", no_argument, NULL, 'h' },
418 { "bytes", no_argument, NULL, 'b' },
419 { "output", required_argument, NULL, 'o' },
420 { "noheadings",no_argument, NULL, 'n' },
421 { "reset", no_argument, NULL, 'r' },
422 { "raw", no_argument, NULL, OPT_RAW },
423 { "size", required_argument, NULL, 's' },
424 { "streams", required_argument, NULL, 't' },
425 { "version", no_argument, NULL, 'V' },
426 { NULL, 0, NULL, 0 }
427 };
428
429 static const ul_excl_t excl[] = {
430 { 'f', 'o', 'r' },
431 { 'o', 'r', 's' },
432 { 0 }
433 };
434 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
435
436 setlocale(LC_ALL, "");
437 bindtextdomain(PACKAGE, LOCALEDIR);
438 textdomain(PACKAGE);
439 atexit(close_stdout);
440
441 while ((c = getopt_long(argc, argv, "a:bfho:nrs:t:V", longopts, NULL)) != -1) {
442
443 err_exclusive_options(c, longopts, excl, excl_st);
444
445 switch (c) {
446 case 'a':
447 if (strcmp(optarg,"lzo") && strcmp(optarg,"lz4"))
448 errx(EXIT_FAILURE, _("unsupported algorithm: %s"),
449 optarg);
450 algorithm = optarg;
451 break;
452 case 'b':
453 inbytes = 1;
454 break;
455 case 'f':
456 find = 1;
457 break;
458 case 'o':
459 ncolumns = string_to_idarray(optarg,
460 columns, ARRAY_SIZE(columns),
461 column_name_to_id);
462 if (ncolumns < 0)
463 return EXIT_FAILURE;
464 break;
465 case 's':
466 size = strtosize_or_err(optarg, _("failed to parse size"));
467 act = A_CREATE;
468 break;
469 case 't':
470 nstreams = strtou64_or_err(optarg, _("failed to parse streams"));
471 break;
472 case 'r':
473 act = A_RESET;
474 break;
475 case OPT_RAW:
476 raw = 1;
477 break;
478 case 'n':
479 no_headings = 1;
480 break;
481 case 'V':
482 printf(UTIL_LINUX_VERSION);
483 return EXIT_SUCCESS;
484 case 'h':
485 usage(stdout);
486 default:
487 usage(stderr);
488 }
489 }
490
491 if (find && optind < argc)
492 errx(EXIT_FAILURE, _("option --find is mutually exclusive "
493 "with <device>."));
494 if (act == A_NONE)
495 act = find ? A_FINDONLY : A_STATUS;
496
497 if (act != A_RESET && optind + 1 < argc)
498 usage(stderr); /* only --reset holds more devices */
499
500 switch (act) {
501 case A_STATUS:
502 if (algorithm || find || nstreams)
503 errx(EXIT_FAILURE, _("options --algorithm, --find and "
504 "--streams are mutually exclusive."));
505 if (!ncolumns) { /* default columns */
506 columns[ncolumns++] = COL_NAME;
507 columns[ncolumns++] = COL_ALGORITHM;
508 columns[ncolumns++] = COL_DISKSIZE;
509 columns[ncolumns++] = COL_ORIG_SIZE;
510 columns[ncolumns++] = COL_COMP_SIZE;
511 columns[ncolumns++] = COL_MEMTOTAL;
512 columns[ncolumns++] = COL_STREAMS;
513 columns[ncolumns++] = COL_MOUNTPOINT;
514 }
515 if (optind < argc)
516 zram = new_zram(argv[optind++]);
517 status(zram);
518 free_zram(zram);
519 break;
520 case A_RESET:
521 if (optind == argc)
522 errx(EXIT_FAILURE, _("no device specified"));
523 while (optind < argc) {
524 zram = new_zram(argv[optind]);
525 if (zram_set_u64parm(zram, "reset", 1)) {
526 warn(_("%s: failed to reset"), zram->devname);
527 rc = 1;
528 }
529 free_zram(zram);
530 optind++;
531 }
532 break;
533 case A_FINDONLY:
534 zram = find_free_zram();
535 if (!zram)
536 errx(EXIT_FAILURE, _("no found free zram device"));
537 printf("%s\n", zram->devname);
538 free_zram(zram);
539 break;
540 case A_CREATE:
541 if (find) {
542 zram = find_free_zram();
543 if (!zram)
544 errx(EXIT_FAILURE, _("no found free zram device"));
545 } else if (optind == argc)
546 errx(EXIT_FAILURE, _("no device specified"));
547 else
548 zram = new_zram(argv[optind]);
549
550 if (zram_set_u64parm(zram, "reset", 1))
551 err(EXIT_FAILURE, _("%s: failed to reset"), zram->devname);
552
553 if (nstreams &&
554 zram_set_u64parm(zram, "max_comp_streams", nstreams))
555 err(EXIT_FAILURE, _("%s: failed to set number of streams"), zram->devname);
556
557 if (algorithm &&
558 zram_set_strparm(zram, "comp_algorithm", algorithm))
559 err(EXIT_FAILURE, _("%s: failed to set algorithm"), zram->devname);
560
561 if (zram_set_u64parm(zram, "disksize", size))
562 err(EXIT_FAILURE, _("%s: failed to set disksize (%ju bytes)"),
563 zram->devname, size);
564 if (find)
565 printf("%s\n", zram->devname);
566 free_zram(zram);
567 break;
568 }
569
570 return rc ? EXIT_FAILURE : EXIT_SUCCESS;
571 }