/*
* 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, " ");
/* make sure kernel provides mm_stat as expected */
if (strv_length(z->mm_stat) < ARRAY_SIZE(mm_stat_names)) {
strv_free(z->mm_stat);
z->mm_stat = NULL;
}
}
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 allocate 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))
err(EXIT_FAILURE, _("failed to add output data"));
}
}
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 allocate 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(void)
{
FILE *out = stdout;
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);
printf(USAGE_HELP_OPTIONS(27));
fputs(USAGE_COLUMNS, out);
for (i = 0; i < ARRAY_SIZE(infos); i++)
fprintf(out, " %11s %s\n", infos[i].name, _(infos[i].help));
printf(USAGE_MAN_TAIL("zramctl(8)"));
exit(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();
default:
errtryhelp(EXIT_FAILURE);
}
}
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;
}