From c43588bf03cc05b2eae724751b6652949e5c9caa Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Tue, 21 Mar 2017 13:04:17 +0100 Subject: [PATCH 106/116] zramctl: backport from v2.29 Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1358755 Signed-off-by: Karel Zak --- .gitignore | 1 + configure.ac | 6 + include/Makemodule.am | 1 + include/c.h | 8 + include/strutils.h | 9 + include/strv.h | 55 ++++ include/sysfs.h | 3 + lib/Makemodule.am | 3 +- lib/strutils.c | 129 +++++++++ lib/strv.c | 403 ++++++++++++++++++++++++++ lib/sysfs.c | 45 ++- sys-utils/Makemodule.am | 7 + sys-utils/zramctl.8 | 123 ++++++++ sys-utils/zramctl.c | 736 ++++++++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 1524 insertions(+), 5 deletions(-) create mode 100644 include/strv.h create mode 100644 lib/strv.c create mode 100644 sys-utils/zramctl.8 create mode 100644 sys-utils/zramctl.c diff --git a/configure.ac b/configure.ac index f87a885..db7095a 100644 --- a/configure.ac +++ b/configure.ac @@ -826,6 +826,12 @@ UL_REQUIRES_LINUX([losetup]) AM_CONDITIONAL(BUILD_LOSETUP, test "x$build_losetup" = xyes) +UL_BUILD_INIT([zramctl], [check]) +UL_REQUIRES_LINUX([zramctl]) +UL_REQUIRES_BUILD([zramctl], [libsmartcols]) +AM_CONDITIONAL([BUILD_ZRAMCTL], [test "x$build_zramctl" = xyes]) + + AC_ARG_ENABLE([cytune], AS_HELP_STRING([--disable-cytune], [do not build cytune]), [], enable_cytune=check diff --git a/include/Makemodule.am b/include/Makemodule.am index 757f317..1680296 100644 --- a/include/Makemodule.am +++ b/include/Makemodule.am @@ -39,6 +39,7 @@ dist_noinst_HEADERS += \ include/readutmp.h \ include/setproctitle.h \ include/strutils.h \ + include/strv.h \ include/swapheader.h \ include/sysfs.h \ include/timer.h \ diff --git a/include/c.h b/include/c.h index a2779a5..3754e75 100644 --- a/include/c.h +++ b/include/c.h @@ -309,6 +309,14 @@ static inline int usleep(useconds_t usec) #endif /* + * Macros to convert #define'itions to strings, for example + * #define XYXXY 42 + * printf ("%s=%s\n", stringify(XYXXY), stringify_value(XYXXY)); + */ +#define stringify_value(s) stringify(s) +#define stringify(s) #s + +/* * Note that sysconf(_SC_GETPW_R_SIZE_MAX) returns *initial* suggested size for * pwd buffer and in some cases it is not large enough. See POSIX and * getpwnam_r man page for more details. diff --git a/include/strutils.h b/include/strutils.h index 709fcad..aa7b95f 100644 --- a/include/strutils.h +++ b/include/strutils.h @@ -5,6 +5,7 @@ #include #include #include +#include /* default strtoxx_or_err() exit code */ #ifndef STRTOXX_EXIT_CODE @@ -102,4 +103,12 @@ extern int parse_range(const char *str, int *lower, int *upper, int def); extern int streq_except_trailing_slash(const char *s1, const char *s2); +extern char *strnappend(const char *s, const char *suffix, size_t b); +extern char *strappend(const char *s, const char *suffix); +extern char *strfappend(const char *s, const char *format, ...) + __attribute__ ((__format__ (__printf__, 2, 0))); +extern const char *split(const char **state, size_t *l, const char *separator, int quoted); + +extern int skip_fline(FILE *fp); + #endif diff --git a/include/strv.h b/include/strv.h new file mode 100644 index 0000000..260ad12 --- /dev/null +++ b/include/strv.h @@ -0,0 +1,55 @@ +#ifndef UTIL_LINUX_STRV +#define UTIL_LINUX_STRV + +#include + +#include "c.h" + +char **strv_free(char **l); +void strv_clear(char **l); +char **strv_copy(char * const *l); +unsigned strv_length(char * const *l); + +int strv_extend_strv(char ***a, char **b); +int strv_extend_strv_concat(char ***a, char **b, const char *suffix); +int strv_extend(char ***l, const char *value); +int strv_extendv(char ***l, const char *format, va_list ap); +int strv_extendf(char ***l, const char *format, ...) + __attribute__ ((__format__ (__printf__, 2, 0))); +int strv_push(char ***l, char *value); +int strv_push_prepend(char ***l, char *value); +int strv_consume(char ***l, char *value); +int strv_consume_prepend(char ***l, char *value); + +char **strv_remove(char **l, const char *s); + +char **strv_new(const char *x, ...); +char **strv_new_ap(const char *x, va_list ap); + +static inline const char* STRV_IFNOTNULL(const char *x) { + return x ? x : (const char *) -1; +} + +static inline int strv_isempty(char * const *l) { + return !l || !*l; +} + +char **strv_split(const char *s, const char *separator); +char *strv_join(char **l, const char *separator); + +#define STRV_FOREACH(s, l) \ + for ((s) = (l); (s) && *(s); (s)++) + +#define STRV_FOREACH_BACKWARDS(s, l) \ + STRV_FOREACH(s, l) \ + ; \ + for ((s)--; (l) && ((s) >= (l)); (s)--) + + +#define STRV_MAKE_EMPTY ((char*[1]) { NULL }) + +char **strv_reverse(char **l); + +#endif /* UTIL_LINUX_STRV */ + + diff --git a/include/sysfs.h b/include/sysfs.h index 0a9c218..a547005 100644 --- a/include/sysfs.h +++ b/include/sysfs.h @@ -58,6 +58,9 @@ extern int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res) extern int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res); extern int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res); +extern int sysfs_write_string(struct sysfs_cxt *cxt, const char *attr, const char *str); +extern int sysfs_write_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t num); + extern char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz); extern char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr); diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 73280f9..faf9d74 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -27,7 +27,8 @@ libcommon_la_SOURCES = \ lib/ttyutils.c \ lib/xgetpass.c \ lib/exec_shell.c \ - lib/readutmp.c + lib/readutmp.c \ + lib/strv.c if LINUX libcommon_la_SOURCES += \ diff --git a/lib/strutils.c b/lib/strutils.c index f9cdcbb..4b8a813 100644 --- a/lib/strutils.c +++ b/lib/strutils.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "c.h" #include "nls.h" @@ -687,6 +688,134 @@ int streq_except_trailing_slash(const char *s1, const char *s2) return equal; } +char *strnappend(const char *s, const char *suffix, size_t b) +{ + size_t a; + char *r; + + if (!s && !suffix) + return strdup(""); + if (!s) + return strndup(suffix, b); + if (!suffix) + return strdup(s); + + assert(s); + assert(suffix); + + a = strlen(s); + if (b > ((size_t) -1) - a) + return NULL; + + r = malloc(a + b + 1); + if (!r) + return NULL; + + memcpy(r, s, a); + memcpy(r + a, suffix, b); + r[a+b] = 0; + + return r; +} + +char *strappend(const char *s, const char *suffix) +{ + return strnappend(s, suffix, suffix ? strlen(suffix) : 0); +} + +char *strfappend(const char *s, const char *format, ...) +{ + va_list ap; + char *val, *res; + int sz; + + va_start(ap, format); + sz = vasprintf(&val, format, ap); + va_end(ap); + + if (sz < 0) + return NULL; + + res = strnappend(s, val, sz); + free(val); + return res; +} + +static size_t strcspn_escaped(const char *s, const char *reject) +{ + int escaped = 0; + int n; + + for (n=0; s[n]; n++) { + if (escaped) + escaped = 0; + else if (s[n] == '\\') + escaped = 1; + else if (strchr(reject, s[n])) + break; + } + + /* if s ends in \, return index of previous char */ + return n - escaped; +} + +/* Split a string into words. */ +const char *split(const char **state, size_t *l, const char *separator, int quoted) +{ + const char *current; + + current = *state; + + if (!*current) { + assert(**state == '\0'); + return NULL; + } + + current += strspn(current, separator); + if (!*current) { + *state = current; + return NULL; + } + + if (quoted && strchr("\'\"", *current)) { + char quotechars[2] = {*current, '\0'}; + + *l = strcspn_escaped(current + 1, quotechars); + if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || + (current[*l + 2] && !strchr(separator, current[*l + 2]))) { + /* right quote missing or garbage at the end */ + *state = current; + return NULL; + } + *state = current++ + *l + 2; + } else if (quoted) { + *l = strcspn_escaped(current, separator); + if (current[*l] && !strchr(separator, current[*l])) { + /* unfinished escape */ + *state = current; + return NULL; + } + *state = current + *l; + } else { + *l = strcspn(current, separator); + *state = current + *l; + } + + return current; +} + +/* Rewind file pointer forward to new line. */ +int skip_fline(FILE *fp) +{ + int ch; + + do { + if ((ch = fgetc(fp)) == EOF) + return 1; + if (ch == '\n') + return 0; + } while (1); +} #ifdef TEST_PROGRAM diff --git a/lib/strv.c b/lib/strv.c new file mode 100644 index 0000000..ddc2a0c --- /dev/null +++ b/lib/strv.c @@ -0,0 +1,403 @@ +/* + * + * Copyright 2010 Lennart Poettering + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * + * Copyright (C) 2015 Karel Zak + * Modified the original version from systemd project for util-linux. + */ + +#include +#include +#include +#include +#include +#include + +#include "strutils.h" +#include "strv.h" + +void strv_clear(char **l) { + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + + *l = NULL; +} + +char **strv_free(char **l) { + strv_clear(l); + free(l); + return NULL; +} + +char **strv_copy(char * const *l) { + char **r, **k; + + k = r = malloc(sizeof(char *) * (strv_length(l) + 1)); + if (!r) + return NULL; + + if (l) + for (; *l; k++, l++) { + *k = strdup(*l); + if (!*k) { + strv_free(r); + return NULL; + } + } + + *k = NULL; + return r; +} + +unsigned strv_length(char * const *l) { + unsigned n = 0; + + if (!l) + return 0; + + for (; *l; l++) + n++; + + return n; +} + +char **strv_new_ap(const char *x, va_list ap) { + const char *s; + char **a; + unsigned n = 0, i = 0; + va_list aq; + + /* As a special trick we ignore all listed strings that equal + * (const char*) -1. This is supposed to be used with the + * STRV_IFNOTNULL() macro to include possibly NULL strings in + * the string list. */ + + if (x) { + n = x == (const char*) -1 ? 0 : 1; + + va_copy(aq, ap); + while ((s = va_arg(aq, const char*))) { + if (s == (const char*) -1) + continue; + + n++; + } + + va_end(aq); + } + + a = malloc(sizeof(char *) * (n + 1)); + if (!a) + return NULL; + + if (x) { + if (x != (const char*) -1) { + a[i] = strdup(x); + if (!a[i]) + goto fail; + i++; + } + + while ((s = va_arg(ap, const char*))) { + + if (s == (const char*) -1) + continue; + + a[i] = strdup(s); + if (!a[i]) + goto fail; + + i++; + } + } + + a[i] = NULL; + + return a; + +fail: + strv_free(a); + return NULL; +} + +char **strv_new(const char *x, ...) { + char **r; + va_list ap; + + va_start(ap, x); + r = strv_new_ap(x, ap); + va_end(ap); + + return r; +} + +int strv_extend_strv(char ***a, char **b) { + int r; + char **s; + + STRV_FOREACH(s, b) { + r = strv_extend(a, *s); + if (r < 0) + return r; + } + + return 0; +} + +int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { + int r; + char **s; + + STRV_FOREACH(s, b) { + char *v; + + v = strappend(*s, suffix); + if (!v) + return -ENOMEM; + + r = strv_push(a, v); + if (r < 0) { + free(v); + return r; + } + } + + return 0; +} + + +#define _FOREACH_WORD(word, length, s, separator, quoted, state) \ + for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted))) + +#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ + _FOREACH_WORD(word, length, s, separator, false, state) + + +char **strv_split(const char *s, const char *separator) { + const char *word, *state; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_SEPARATOR(word, l, s, separator, state) + n++; + + r = malloc(sizeof(char *) * (n + 1)); + if (!r) + return NULL; + + i = 0; + FOREACH_WORD_SEPARATOR(word, l, s, separator, state) { + r[i] = strndup(word, l); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + } + + r[i] = NULL; + return r; +} + +char *strv_join(char **l, const char *separator) { + char *r, *e; + char **s; + size_t n, k; + + if (!separator) + separator = " "; + + k = strlen(separator); + + n = 0; + STRV_FOREACH(s, l) { + if (n != 0) + n += k; + n += strlen(*s); + } + + r = malloc(n + 1); + if (!r) + return NULL; + + e = r; + STRV_FOREACH(s, l) { + if (e != r) + e = stpcpy(e, separator); + + e = stpcpy(e, *s); + } + + *e = 0; + + return r; +} + +int strv_push(char ***l, char *value) { + char **c; + unsigned n, m; + + if (!value) + return 0; + + n = strv_length(*l); + + /* Increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = realloc(*l, sizeof(char *) * m); + if (!c) + return -ENOMEM; + + c[n] = value; + c[n+1] = NULL; + + *l = c; + return 0; +} + +int strv_push_prepend(char ***l, char *value) { + char **c; + unsigned n, m, i; + + if (!value) + return 0; + + n = strv_length(*l); + + /* increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = malloc(sizeof(char *) * m); + if (!c) + return -ENOMEM; + + for (i = 0; i < n; i++) + c[i+1] = (*l)[i]; + + c[0] = value; + c[n+1] = NULL; + + free(*l); + *l = c; + + return 0; +} + +int strv_consume(char ***l, char *value) { + int r; + + r = strv_push(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_consume_prepend(char ***l, char *value) { + int r; + + r = strv_push_prepend(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_extend(char ***l, const char *value) { + char *v; + + if (!value) + return 0; + + v = strdup(value); + if (!v) + return -ENOMEM; + + return strv_consume(l, v); +} + +char **strv_remove(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of s in the string list, edits + * in-place. */ + + for (f = t = l; *f; f++) + if (strcmp(*f, s) == 0) + free(*f); + else + *(t++) = *f; + + *t = NULL; + return l; +} + +int strv_extendf(char ***l, const char *format, ...) { + va_list ap; + char *x; + int r; + + va_start(ap, format); + r = vasprintf(&x, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return strv_consume(l, x); +} + +int strv_extendv(char ***l, const char *format, va_list ap) { + char *x; + int r; + + r = vasprintf(&x, format, ap); + if (r < 0) + return -ENOMEM; + + return strv_consume(l, x); +} + +char **strv_reverse(char **l) { + unsigned n, i; + + n = strv_length(l); + if (n <= 1) + return l; + + for (i = 0; i < n / 2; i++) { + char *t; + + t = l[i]; + l[i] = l[n-1-i]; + l[n-1-i] = t; + } + + return l; +} diff --git a/lib/sysfs.c b/lib/sysfs.c index 0bfd622..65a8394 100644 --- a/lib/sysfs.c +++ b/lib/sysfs.c @@ -10,6 +10,7 @@ #include "at.h" #include "pathnames.h" #include "sysfs.h" +#include "all-io.h" char *sysfs_devno_attribute_path(dev_t devno, char *buf, size_t bufsiz, const char *attr) @@ -203,9 +204,9 @@ int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr) return sysfs_stat(cxt, attr, &st) == 0; } -static int sysfs_open(struct sysfs_cxt *cxt, const char *attr) +static int sysfs_open(struct sysfs_cxt *cxt, const char *attr, int flags) { - int fd = open_at(cxt->dir_fd, cxt->dir_path, attr, O_RDONLY|O_CLOEXEC); + int fd = open_at(cxt->dir_fd, cxt->dir_path, attr, flags); if (fd == -1 && errno == ENOENT && strncmp(attr, "queue/", 6) == 0 && cxt->parent) { @@ -238,7 +239,7 @@ DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr) int fd = -1; if (attr) - fd = sysfs_open(cxt, attr); + fd = sysfs_open(cxt, attr, O_RDONLY|O_CLOEXEC); else if (cxt->dir_fd >= 0) /* request to open root of device in sysfs (/sys/block/) @@ -263,7 +264,7 @@ DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr) static FILE *sysfs_fopen(struct sysfs_cxt *cxt, const char *attr) { - int fd = sysfs_open(cxt, attr); + int fd = sysfs_open(cxt, attr, O_RDONLY|O_CLOEXEC); return fd < 0 ? NULL : fdopen(fd, "r" UL_CLOEXECSTR); } @@ -417,6 +418,42 @@ int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res) return -1; } +int sysfs_write_string(struct sysfs_cxt *cxt, const char *attr, const char *str) +{ + int fd = sysfs_open(cxt, attr, O_WRONLY|O_CLOEXEC); + int rc, errsv; + + if (fd < 0) + return -errno; + rc = write_all(fd, str, strlen(str)); + + errsv = errno; + close(fd); + errno = errsv; + return rc; +} + +int sysfs_write_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t num) +{ + char buf[sizeof(stringify_value(ULLONG_MAX))]; + int fd, rc = 0, len, errsv; + + fd = sysfs_open(cxt, attr, O_WRONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + len = snprintf(buf, sizeof(buf), "%" PRIu64, num); + if (len < 0 || (size_t) len >= sizeof(buf)) + rc = len < 0 ? -errno : -E2BIG; + else + rc = write_all(fd, buf, len); + + errsv = errno; + close(fd); + errno = errsv; + return rc; +} + char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr) { char buf[1024]; diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index 6badd17..408e884 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -184,6 +184,13 @@ losetup_static_LDADD = $(losetup_LDADD) endif endif # BUILD_LOSETUP +if BUILD_ZRAMCTL +sbin_PROGRAMS += zramctl +dist_man_MANS += sys-utils/zramctl.8 +zramctl_SOURCES = sys-utils/zramctl.c +zramctl_LDADD = $(LDADD) libcommon.la libsmartcols.la +zramctl_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) +endif if BUILD_PRLIMIT usrbin_exec_PROGRAMS += prlimit diff --git a/sys-utils/zramctl.8 b/sys-utils/zramctl.8 new file mode 100644 index 0000000..f6fc45c --- /dev/null +++ b/sys-utils/zramctl.8 @@ -0,0 +1,123 @@ +.TH ZRAMCTL 8 "July 2014" "util-linux" "System Administration" +.SH NAME +zramctl \- set up and control zram devices +.SH SYNOPSIS +.ad l +Get info: +.sp +.in +5 +.BR zramctl " [options]" +.sp +.in -5 +Reset zram: +.sp +.in +5 +.B "zramctl \-r" +.IR zramdev ... +.sp +.in -5 +Print name of first unused zram device: +.sp +.in +5 +.B "zramctl \-f" +.sp +.in -5 +Set up a zram device: +.sp +.in +5 +.B zramctl +.RB [ \-f " | "\fIzramdev\fP ] +.RB [ \-s +.IR size ] +.RB [ \-t +.IR number ] +.RB [ \-a +.IR algorithm ] +.sp +.in -5 +.ad b +.SH DESCRIPTION +.B zramctl +is used to quickly set up zram device parameters, to reset zram devices, and to +query the status of used zram devices. If no option is given, all zram devices +are shown. + +.SH OPTIONS +.TP +.BR \-a , " \-\-algorithm lzo" | lz4 +Set the compression algorithm to be used for compressing data in the zram device. +.TP +.BR \-f , " \-\-find" +Find the first unused zram device. If a \fB--size\fR argument is present, then +initialize the device. +.TP +.BR \-n , " \-\-noheadings" +Do not print a header line in status output. +.TP +.BR \-o , " \-\-output " \fIlist +Define the status output columns to be used. If no output arrangement is +specified, then a default set is used. +Use \fB\-\-help\fP to get a list of all supported columns. +.TP +.B \-\-raw +Use the raw format for status output. +.TP +.BR \-r , " \-\-reset" +Reset the options of the specified zram device(s). Zram device settings +can be changed only after a reset. +.TP +.BR \-s , " \-\-size " \fIsize +Create a zram device of the specified \fIsize\fR. +Zram devices are aligned to memory pages; when the requested \fIsize\fR is +not a multiple of the page size, it will be rounded up to the next multiple. +When not otherwise specified, the unit of the \fIsize\fR parameter is bytes. +.IP +The \fIsize\fR argument may be followed by the multiplicative suffixes KiB (=1024), +MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB (the "iB" +is optional, e.g., "K" has the same meaning as "KiB") or the suffixes +KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB. +.TP +.BR \-t , " \-\-streams " \fInumber +Set the maximum number of compression streams that can be used for the device. +The default is one stream. +.TP +.BR \-V , " \-\-version" +Display version information and exit. +.TP +.BR \-h , " \-\-help" +Display help text and exit. + +.SH RETURN VALUE +.B zramctl +returns 0 on success, nonzero on failure. + +.SH FILES +.TP +.I /dev/zram[0..N] +zram block devices + +.SH EXAMPLE +The following commands set up a zram device with a size of one gigabyte +and use it as swap device. +.nf +.IP +# zramctl --find --size 1024M +/dev/zram0 +# mkswap /dev/zram0 +# swapon /dev/zram0 + ... +# swapoff /dev/zram0 +# zramctl --reset /dev/zram0 +.fi +.SH SEE ALSO +.UR http://git.\:kernel.\:org\:/cgit\:/linux\:/kernel\:/git\:/torvalds\:/linux.git\:/tree\:/Documentation\:/blockdev\:/zram.txt +Linux kernel documentation +.UE . +.SH AUTHORS +.nf +Timofey Titovets +Karel Zak +.fi +.SH AVAILABILITY +The zramctl command is part of the util-linux package and is available from +https://www.kernel.org/pub/linux/utils/util-linux/. diff --git a/sys-utils/zramctl.c b/sys-utils/zramctl.c new file mode 100644 index 0000000..853401c --- /dev/null +++ b/sys-utils/zramctl.c @@ -0,0 +1,736 @@ +/* + * zramctl - control compressed block devices in RAM + * + * Copyright (c) 2014 Timofey Titovets + * Copyright (C) 2014 Karel Zak + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +#include + +#include "c.h" +#include "nls.h" +#include "closestream.h" +#include "strutils.h" +#include "xalloc.h" +#include "sysfs.h" +#include "optutils.h" +#include "ismounted.h" +#include "strv.h" +#include "path.h" +#include "pathnames.h" + +/*#define CONFIG_ZRAM_DEBUG*/ + +#ifdef CONFIG_ZRAM_DEBUG +# define DBG(x) do { fputs("zram: ", stderr); x; fputc('\n', stderr); } while(0) +#else +# define DBG(x) +#endif + +/* status output columns */ +struct colinfo { + const char *name; + double whint; + int flags; + const char *help; +}; + +enum { + COL_NAME = 0, + COL_DISKSIZE, + COL_ORIG_SIZE, + COL_COMP_SIZE, + COL_ALGORITHM, + COL_STREAMS, + COL_ZEROPAGES, + COL_MEMTOTAL, + COL_MEMLIMIT, + COL_MEMUSED, + COL_MIGRATED, + COL_MOUNTPOINT +}; + +static const struct colinfo infos[] = { + [COL_NAME] = { "NAME", 0.25, 0, N_("zram device name") }, + [COL_DISKSIZE] = { "DISKSIZE", 5, SCOLS_FL_RIGHT, N_("limit on the uncompressed amount of data") }, + [COL_ORIG_SIZE] = { "DATA", 5, SCOLS_FL_RIGHT, N_("uncompressed size of stored data") }, + [COL_COMP_SIZE] = { "COMPR", 5, SCOLS_FL_RIGHT, N_("compressed size of stored data") }, + [COL_ALGORITHM] = { "ALGORITHM", 3, 0, N_("the selected compression algorithm") }, + [COL_STREAMS] = { "STREAMS", 3, SCOLS_FL_RIGHT, N_("number of concurrent compress operations") }, + [COL_ZEROPAGES] = { "ZERO-PAGES", 3, SCOLS_FL_RIGHT, N_("empty pages with no allocated memory") }, + [COL_MEMTOTAL] = { "TOTAL", 5, SCOLS_FL_RIGHT, N_("all memory including allocator fragmentation and metadata overhead") }, + [COL_MEMLIMIT] = { "MEM-LIMIT", 5, SCOLS_FL_RIGHT, N_("memory limit used to store compressed data") }, + [COL_MEMUSED] = { "MEM-USED", 5, SCOLS_FL_RIGHT, N_("memory zram have been consumed to store compressed data") }, + [COL_MIGRATED] = { "MIGRATED", 5, SCOLS_FL_RIGHT, N_("number of objects migrated by compaction") }, + [COL_MOUNTPOINT]= { "MOUNTPOINT",0.10, SCOLS_FL_TRUNC, N_("where the device is mounted") }, +}; + +static int columns[ARRAY_SIZE(infos) * 2] = {-1}; +static int ncolumns; + +enum { + MM_ORIG_DATA_SIZE = 0, + MM_COMPR_DATA_SIZE, + MM_MEM_USED_TOTAL, + MM_MEM_LIMIT, + MM_MEM_USED_MAX, + MM_ZERO_PAGES, + MM_NUM_MIGRATED +}; + +static const char *mm_stat_names[] = { + [MM_ORIG_DATA_SIZE] = "orig_data_size", + [MM_COMPR_DATA_SIZE] = "compr_data_size", + [MM_MEM_USED_TOTAL] = "mem_used_total", + [MM_MEM_LIMIT] = "mem_limit", + [MM_MEM_USED_MAX] = "mem_used_max", + [MM_ZERO_PAGES] = "zero_pages", + [MM_NUM_MIGRATED] = "num_migrated" +}; + + +struct zram { + char devname[32]; + struct sysfs_cxt sysfs; + char **mm_stat; + + unsigned int mm_stat_probed : 1, + control_probed : 1, + has_control : 1; /* has /sys/class/zram-control/ */ +}; + +#define ZRAM_EMPTY { .devname = { '\0' }, .sysfs = UL_SYSFSCXT_EMPTY } + +static unsigned int raw, no_headings, inbytes; + + +static int get_column_id(int num) +{ + assert(num < ncolumns); + assert(columns[num] < (int) ARRAY_SIZE(infos)); + return columns[num]; +} + +static const struct colinfo *get_column_info(int num) +{ + return &infos[ get_column_id(num) ]; +} + +static int column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(infos); i++) { + const char *cn = infos[i].name; + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return i; + } + warnx(_("unknown column: %s"), name); + return -1; +} + +static void zram_reset_stat(struct zram *z) +{ + if (z) { + strv_free(z->mm_stat); + z->mm_stat = NULL; + z->mm_stat_probed = 0; + } +} + +static void zram_set_devname(struct zram *z, const char *devname, size_t n) +{ + assert(z); + + if (!devname) + snprintf(z->devname, sizeof(z->devname), "/dev/zram%zu", n); + else { + strncpy(z->devname, devname, sizeof(z->devname)); + z->devname[sizeof(z->devname) - 1] = '\0'; + } + + DBG(fprintf(stderr, "set devname: %s", z->devname)); + sysfs_deinit(&z->sysfs); + zram_reset_stat(z); +} + +static int zram_get_devnum(struct zram *z) +{ + int n; + + assert(z); + + if (sscanf(z->devname, "/dev/zram%d", &n) == 1) + return n; + return -EINVAL; +} + +static struct zram *new_zram(const char *devname) +{ + struct zram *z = xcalloc(1, sizeof(struct zram)); + + DBG(fprintf(stderr, "new: %p", z)); + if (devname) + zram_set_devname(z, devname, 0); + return z; +} + +static void free_zram(struct zram *z) +{ + if (!z) + return; + DBG(fprintf(stderr, "free: %p", z)); + sysfs_deinit(&z->sysfs); + zram_reset_stat(z); + free(z); +} + +static struct sysfs_cxt *zram_get_sysfs(struct zram *z) +{ + assert(z); + + if (!z->sysfs.devno) { + dev_t devno = sysfs_devname_to_devno(z->devname, NULL); + if (!devno) + return NULL; + if (sysfs_init(&z->sysfs, devno, NULL)) + return NULL; + if (*z->devname != '/') { + /* canonicalize the device name according to /sys */ + char name[PATH_MAX]; + if (sysfs_get_devname(&z->sysfs, name, sizeof(name))) + snprintf(z->devname, sizeof(z->devname), "/dev/%s", name); + } + } + + return &z->sysfs; +} + +static inline int zram_exist(struct zram *z) +{ + assert(z); + + errno = 0; + if (zram_get_sysfs(z) == NULL) { + errno = ENODEV; + return 0; + } + + DBG(fprintf(stderr, "%s exists", z->devname)); + return 1; +} + +static int zram_set_u64parm(struct zram *z, const char *attr, uint64_t num) +{ + struct sysfs_cxt *sysfs = zram_get_sysfs(z); + if (!sysfs) + return -EINVAL; + DBG(fprintf(stderr, "%s writing %ju to %s", z->devname, num, attr)); + return sysfs_write_u64(sysfs, attr, num); +} + +static int zram_set_strparm(struct zram *z, const char *attr, const char *str) +{ + struct sysfs_cxt *sysfs = zram_get_sysfs(z); + if (!sysfs) + return -EINVAL; + DBG(fprintf(stderr, "%s writing %s to %s", z->devname, str, attr)); + return sysfs_write_string(sysfs, attr, str); +} + + +static int zram_used(struct zram *z) +{ + uint64_t size; + struct sysfs_cxt *sysfs = zram_get_sysfs(z); + + if (sysfs && + sysfs_read_u64(sysfs, "disksize", &size) == 0 && + size > 0) { + + DBG(fprintf(stderr, "%s used", z->devname)); + return 1; + } + DBG(fprintf(stderr, "%s unused", z->devname)); + return 0; +} + +static int zram_has_control(struct zram *z) +{ + if (!z->control_probed) { + z->has_control = access(_PATH_SYS_CLASS "/zram-control/", F_OK) == 0 ? 1 : 0; + z->control_probed = 1; + DBG(fprintf(stderr, "zram-control: %s", z->has_control ? "yes" : "no")); + } + + return z->has_control; +} + +static int zram_control_add(struct zram *z) +{ + int n; + + if (!zram_has_control(z)) + return -ENOSYS; + + n = path_read_s32(_PATH_SYS_CLASS "/zram-control/hot_add"); + if (n < 0) + return n; + + DBG(fprintf(stderr, "hot-add: %d", n)); + zram_set_devname(z, NULL, n); + return 0; +} + +static int zram_control_remove(struct zram *z) +{ + char str[sizeof stringify_value(INT_MAX)]; + int n; + + if (!zram_has_control(z)) + return -ENOSYS; + + n = zram_get_devnum(z); + if (n < 0) + return n; + + DBG(fprintf(stderr, "hot-remove: %d", n)); + snprintf(str, sizeof(str), "%d", n); + return path_write_str(str, _PATH_SYS_CLASS "/zram-control/hot_remove"); +} + +static struct zram *find_free_zram(void) +{ + struct zram *z = new_zram(NULL); + size_t i; + int isfree = 0; + + for (i = 0; isfree == 0; i++) { + DBG(fprintf(stderr, "find free: checking zram%zu", i)); + zram_set_devname(z, NULL, i); + if (!zram_exist(z) && zram_control_add(z) != 0) + break; + isfree = !zram_used(z); + } + if (!isfree) { + free_zram(z); + z = NULL; + } + return z; +} + +static char *get_mm_stat(struct zram *z, size_t idx, int bytes) +{ + struct sysfs_cxt *sysfs; + const char *name; + uint64_t num; + + assert(idx < ARRAY_SIZE(mm_stat_names)); + assert(z); + + sysfs = zram_get_sysfs(z); + if (!sysfs) + return NULL; + + /* Linux >= 4.1 uses /sys/block/zram/mm_stat */ + if (!z->mm_stat && !z->mm_stat_probed) { + char *str; + + str = sysfs_strdup(sysfs, "mm_stat"); + if (str) { + z->mm_stat = strv_split(str, " "); + if (strv_length(z->mm_stat) < ARRAY_SIZE(mm_stat_names)) + errx(EXIT_FAILURE, _("Failed to parse mm_stat")); + } + z->mm_stat_probed = 1; + free(str); + + } + + if (z->mm_stat) { + if (bytes) + return xstrdup(z->mm_stat[idx]); + + num = strtou64_or_err(z->mm_stat[idx], _("Failed to parse mm_stat")); + return size_to_human_string(SIZE_SUFFIX_1LETTER, num); + } + + /* Linux < 4.1 uses /sys/block/zram/ */ + name = mm_stat_names[idx]; + if (bytes) + return sysfs_strdup(sysfs, name); + else if (sysfs_read_u64(sysfs, name, &num) == 0) + return size_to_human_string(SIZE_SUFFIX_1LETTER, num); + return NULL; +} + +static void fill_table_row(struct libscols_table *tb, struct zram *z) +{ + static struct libscols_line *ln; + struct sysfs_cxt *sysfs; + size_t i; + uint64_t num; + + assert(tb); + assert(z); + + DBG(fprintf(stderr, "%s: filling status table", z->devname)); + + sysfs = zram_get_sysfs(z); + if (!sysfs) + return; + + ln = scols_table_new_line(tb, NULL); + if (!ln) + err(EXIT_FAILURE, _("failed to initialize output line")); + + for (i = 0; i < (size_t) ncolumns; i++) { + char *str = NULL; + + switch (get_column_id(i)) { + case COL_NAME: + str = xstrdup(z->devname); + break; + case COL_DISKSIZE: + if (inbytes) + str = sysfs_strdup(sysfs, "disksize"); + else if (sysfs_read_u64(sysfs, "disksize", &num) == 0) + str = size_to_human_string(SIZE_SUFFIX_1LETTER, num); + break; + case COL_ALGORITHM: + { + char *alg = sysfs_strdup(sysfs, "comp_algorithm"); + if (!alg) + break; + if (strstr(alg, "[lzo]") == NULL) { + if (strstr(alg, "[lz4]") == NULL) + ; + else + str = xstrdup("lz4"); + } else + str = xstrdup("lzo"); + free(alg); + break; + } + case COL_MOUNTPOINT: + { + char path[PATH_MAX] = { '\0' }; + int fl; + + check_mount_point(z->devname, &fl, path, sizeof(path)); + if (*path) + str = xstrdup(path); + break; + } + case COL_STREAMS: + str = sysfs_strdup(sysfs, "max_comp_streams"); + break; + case COL_ZEROPAGES: + str = get_mm_stat(z, MM_ZERO_PAGES, 1); + break; + case COL_ORIG_SIZE: + str = get_mm_stat(z, MM_ORIG_DATA_SIZE, inbytes); + break; + case COL_COMP_SIZE: + str = get_mm_stat(z, MM_COMPR_DATA_SIZE, inbytes); + break; + case COL_MEMTOTAL: + str = get_mm_stat(z, MM_MEM_USED_TOTAL, inbytes); + break; + case COL_MEMLIMIT: + str = get_mm_stat(z, MM_MEM_LIMIT, inbytes); + break; + case COL_MEMUSED: + str = get_mm_stat(z, MM_MEM_USED_MAX, inbytes); + break; + case COL_MIGRATED: + str = get_mm_stat(z, MM_NUM_MIGRATED, inbytes); + break; + } + if (str) + scols_line_refer_data(ln, i, str); + } +} + +static void status(struct zram *z) +{ + struct libscols_table *tb; + size_t i; + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, _("failed to initialize output table")); + + scols_table_enable_raw(tb, raw); + scols_table_enable_noheadings(tb, no_headings); + + for (i = 0; i < (size_t) ncolumns; i++) { + const struct colinfo *col = get_column_info(i); + + if (!scols_table_new_column(tb, col->name, col->whint, col->flags)) + err(EXIT_FAILURE, _("failed to initialize output column")); + } + + if (z) + fill_table_row(tb, z); /* just one device specified */ + else { + /* list all used devices */ + z = new_zram(NULL); + + for (i = 0; ; i++) { + zram_set_devname(z, NULL, i); + if (!zram_exist(z)) + break; + if (zram_used(z)) + fill_table_row(tb, z); + } + free_zram(z); + } + + scols_print_table(tb); + scols_unref_table(tb); +} + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _( " %1$s [options] \n" + " %1$s -r [...]\n" + " %1$s [options] -f | -s \n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Set up and control zram devices.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --algorithm lzo|lz4 compression algorithm to use\n"), out); + fputs(_(" -b, --bytes print sizes in bytes rather than in human readable format\n"), out); + fputs(_(" -f, --find find a free device\n"), out); + fputs(_(" -n, --noheadings don't print headings\n"), out); + fputs(_(" -o, --output columns to use for status output\n"), out); + fputs(_(" --raw use raw status output format\n"), out); + fputs(_(" -r, --reset reset all specified devices\n"), out); + fputs(_(" -s, --size device size\n"), out); + fputs(_(" -t, --streams number of compression streams\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + + fputs(_("\nAvailable columns (for --output):\n"), out); + for (i = 0; i < ARRAY_SIZE(infos); i++) + fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help)); + + fprintf(out, USAGE_MAN_TAIL("zramctl(8)")); + exit(out == stderr ? 1 : EXIT_SUCCESS); +} + +/* actions */ +enum { + A_NONE = 0, + A_STATUS, + A_CREATE, + A_FINDONLY, + A_RESET +}; + +int main(int argc, char **argv) +{ + uintmax_t size = 0, nstreams = 0; + char *algorithm = NULL; + int rc = 0, c, find = 0, act = A_NONE; + struct zram *zram = NULL; + + enum { OPT_RAW = CHAR_MAX + 1 }; + + static const struct option longopts[] = { + { "algorithm", required_argument, NULL, 'a' }, + { "bytes", no_argument, NULL, 'b' }, + { "find", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "output", required_argument, NULL, 'o' }, + { "noheadings",no_argument, NULL, 'n' }, + { "reset", no_argument, NULL, 'r' }, + { "raw", no_argument, NULL, OPT_RAW }, + { "size", required_argument, NULL, 's' }, + { "streams", required_argument, NULL, 't' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + static const ul_excl_t excl[] = { + { 'f', 'o', 'r' }, + { 'o', 'r', 's' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "a:bfho:nrs:t:V", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch (c) { + case 'a': + if (strcmp(optarg,"lzo") && strcmp(optarg,"lz4")) + errx(EXIT_FAILURE, _("unsupported algorithm: %s"), + optarg); + algorithm = optarg; + break; + case 'b': + inbytes = 1; + break; + case 'f': + find = 1; + break; + case 'o': + ncolumns = string_to_idarray(optarg, + columns, ARRAY_SIZE(columns), + column_name_to_id); + if (ncolumns < 0) + return EXIT_FAILURE; + break; + case 's': + size = strtosize_or_err(optarg, _("failed to parse size")); + act = A_CREATE; + break; + case 't': + nstreams = strtou64_or_err(optarg, _("failed to parse streams")); + break; + case 'r': + act = A_RESET; + break; + case OPT_RAW: + raw = 1; + break; + case 'n': + no_headings = 1; + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case 'h': + usage(stdout); + default: + usage(stderr); + } + } + + if (find && optind < argc) + errx(EXIT_FAILURE, _("option --find is mutually exclusive " + "with ")); + if (act == A_NONE) + act = find ? A_FINDONLY : A_STATUS; + + if (act != A_RESET && optind + 1 < argc) + errx(EXIT_FAILURE, _("only one at a time is allowed")); + + if ((act == A_STATUS || act == A_FINDONLY) && (algorithm || nstreams)) + errx(EXIT_FAILURE, _("options --algorithm and --streams " + "must be combined with --size")); + + switch (act) { + case A_STATUS: + if (!ncolumns) { /* default columns */ + columns[ncolumns++] = COL_NAME; + columns[ncolumns++] = COL_ALGORITHM; + columns[ncolumns++] = COL_DISKSIZE; + columns[ncolumns++] = COL_ORIG_SIZE; + columns[ncolumns++] = COL_COMP_SIZE; + columns[ncolumns++] = COL_MEMTOTAL; + columns[ncolumns++] = COL_STREAMS; + columns[ncolumns++] = COL_MOUNTPOINT; + } + if (optind < argc) { + zram = new_zram(argv[optind++]); + if (!zram_exist(zram)) + err(EXIT_FAILURE, "%s", zram->devname); + } + status(zram); + free_zram(zram); + break; + case A_RESET: + if (optind == argc) + errx(EXIT_FAILURE, _("no device specified")); + while (optind < argc) { + zram = new_zram(argv[optind]); + if (!zram_exist(zram) + || zram_set_u64parm(zram, "reset", 1)) { + warn(_("%s: failed to reset"), zram->devname); + rc = 1; + } + zram_control_remove(zram); + free_zram(zram); + optind++; + } + break; + case A_FINDONLY: + zram = find_free_zram(); + if (!zram) + errx(EXIT_FAILURE, _("no free zram device found")); + printf("%s\n", zram->devname); + free_zram(zram); + break; + case A_CREATE: + if (find) { + zram = find_free_zram(); + if (!zram) + errx(EXIT_FAILURE, _("no free zram device found")); + } else if (optind == argc) + errx(EXIT_FAILURE, _("no device specified")); + else { + zram = new_zram(argv[optind]); + if (!zram_exist(zram)) + err(EXIT_FAILURE, "%s", zram->devname); + } + + if (zram_set_u64parm(zram, "reset", 1)) + err(EXIT_FAILURE, _("%s: failed to reset"), zram->devname); + + if (nstreams && + zram_set_u64parm(zram, "max_comp_streams", nstreams)) + err(EXIT_FAILURE, _("%s: failed to set number of streams"), zram->devname); + + if (algorithm && + zram_set_strparm(zram, "comp_algorithm", algorithm)) + err(EXIT_FAILURE, _("%s: failed to set algorithm"), zram->devname); + + if (zram_set_u64parm(zram, "disksize", size)) + err(EXIT_FAILURE, _("%s: failed to set disksize (%ju bytes)"), + zram->devname, size); + if (find) + printf("%s\n", zram->devname); + free_zram(zram); + break; + } + + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} -- 2.9.3