You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1722 lines
43 KiB
1722 lines
43 KiB
From c43588bf03cc05b2eae724751b6652949e5c9caa Mon Sep 17 00:00:00 2001 |
|
From: Karel Zak <kzak@redhat.com> |
|
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 <kzak@redhat.com> |
|
--- |
|
.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 <inttypes.h> |
|
#include <string.h> |
|
#include <sys/types.h> |
|
+#include <stdio.h> |
|
|
|
/* 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 <stdarg.h> |
|
+ |
|
+#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 <errno.h> |
|
#include <sys/stat.h> |
|
#include <string.h> |
|
+#include <assert.h> |
|
|
|
#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 <kzak@redhat.com> |
|
+ * Modified the original version from systemd project for util-linux. |
|
+ */ |
|
+ |
|
+#include <stdlib.h> |
|
+#include <stdarg.h> |
|
+#include <string.h> |
|
+#include <errno.h> |
|
+#include <stdbool.h> |
|
+#include <assert.h> |
|
+ |
|
+#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/<dev>) |
|
@@ -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 <nefelim4ag@gmail.com> |
|
+Karel Zak <kzak@redhat.com> |
|
+.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 <Nefelim4ag@gmail.com> |
|
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com> |
|
+ * |
|
+ * 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 <getopt.h> |
|
+#include <stdlib.h> |
|
+#include <string.h> |
|
+#include <stdarg.h> |
|
+#include <assert.h> |
|
+ |
|
+#include <libsmartcols.h> |
|
+ |
|
+#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<id>/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<id>/<attrname> */ |
|
+ 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] <device>\n" |
|
+ " %1$s -r <device> [...]\n" |
|
+ " %1$s [options] -f | <device> -s <size>\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 <list> 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 <size> device size\n"), out); |
|
+ fputs(_(" -t, --streams <number> 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 <device>")); |
|
+ if (act == A_NONE) |
|
+ act = find ? A_FINDONLY : A_STATUS; |
|
+ |
|
+ if (act != A_RESET && optind + 1 < argc) |
|
+ errx(EXIT_FAILURE, _("only one <device> 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
|
|
|