nilfs-utils/sbin/nilfs-tune.c
Ryusuke Konishi dc7e116ddf treewide: embed git revision into binaries
Apply the NILFS_UTILS_GITID() macro to all major executables and
libraries to enable git revision tracking.

Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
2026-01-26 01:44:10 +09:00

630 lines
15 KiB
C

/*
* nilfs-tune.c - adjust tunable filesystem parameters on NILFS filesystem
*
* Copyright (C) 2010 Jiro SEKIBA <jir@unicus.jp>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */
#if HAVE_ERR_H
#include <err.h>
#endif /* HAVE_ERR_H */
#if HAVE_FCNTL_H
#include <fcntl.h> /* open, O_RDWR, O_RDONLY, etc */
#endif /* HAVE_FCNTL_H */
#if HAVE_UNISTD_H
#include <unistd.h> /* getopt, close, etc */
#endif /* HAVE_UNISTD_H */
#if HAVE_STRINGS_H
#include <strings.h>
#endif /* HAVE_STRINGS_H */
#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif /* HAVE_SYS_IOCTL_H */
#if HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */
#if HAVE_LIMITS_H
#include <limits.h>
#endif /* HAVE_LIMITS_H */
#if HAVE_GRP_H
#include <grp.h>
#endif /* HAVE_GRP_H */
#if HAVE_PWD_H
#include <pwd.h>
#endif /* HAVE_PWD_H */
/* compat.h must be included before on-disk definitions for sparse checks */
#include "compat.h"
#include <linux/nilfs2_ondisk.h>
#include <sys/stat.h>
#include <ctype.h>
#include <assert.h>
#include <errno.h>
#include "nilfs.h"
#include "nilfs_feature.h"
#include "check_mount.h"
#include "util.h" /* NILFS_UTILS_GITID() */
struct nilfs_tune_options {
int flags;
int display;
int mask;
int force;
uint32_t c_interval;
uint32_t c_block_max;
char label[80];
uint8_t uuid[16];
char *fs_features;
};
NILFS_UTILS_GITID();
static void nilfs_tune_usage(FILE *stream)
{
fprintf(stream,
"Usage: nilfs-tune -l device\n"
" nilfs-tune [-f] [-i interval] [-m block_max] [-L volume_name]\n"
" [-O [^]feature[,...]] [-U UUID] device\n"
" nilfs-tune [-h|-V]\n");
}
static const uint64_t ok_features[NILFS_MAX_FEATURE_TYPES] = {
/* Compat */
0,
/* Read-only compat */
NILFS_FEATURE_COMPAT_RO_BLOCK_COUNT,
/* Incompat */
0
};
static const uint64_t clear_ok_features[NILFS_MAX_FEATURE_TYPES] = {
/* Compat */
0,
/* Read-only compat */
NILFS_FEATURE_COMPAT_RO_BLOCK_COUNT,
/* Incompat */
0
};
static int parse_uuid(const char *uuid_string, uint8_t *uuid)
{
int i;
char p[3];
if (strlen(uuid_string) != 36)
return -1;
for (i = 0, p[2] = '\0'; i < 36; i++) {
if ((i == 8) || (i == 13) || (i == 18) || (i == 23)) {
if (uuid_string[i] == '-')
continue;
else
return -1;
}
if (!isxdigit(uuid_string[i]) || !isxdigit(uuid_string[i+1]))
return -1;
p[0] = uuid_string[i++];
p[1] = uuid_string[i];
*uuid = strtoul(p, NULL, 16);
uuid++;
}
return 0;
}
static void parse_options(int argc, char *argv[],
struct nilfs_tune_options *opts)
{
int c;
opts->flags = O_RDONLY;
opts->display = 0;
opts->mask = 0;
opts->force = 0;
opts->fs_features = NULL;
while ((c = getopt(argc, argv, "flhi:L:m:O:U:V")) != EOF) {
switch (c) {
case 'f':
opts->force = 1;
break;
case 'h':
nilfs_tune_usage(stdout);
exit(EXIT_SUCCESS);
break;
case 'i':
opts->c_interval = atol(optarg);
opts->mask |= NILFS_SB_COMMIT_INTERVAL;
opts->flags = O_RDWR;
break;
case 'L':
/*
* For the 'label' buffer, truncation is
* explicitly OK without a terminating '\0'.
*/
#if defined(__GNUC__) && __GNUC__ >= 8
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif
strncpy(opts->label, optarg, sizeof(opts->label));
#if defined(__GNUC__) && __GNUC__ >= 8
#pragma GCC diagnostic pop
#endif
opts->mask |= NILFS_SB_LABEL;
opts->flags = O_RDWR;
break;
case 'm':
opts->c_block_max = atol(optarg);
opts->mask |= NILFS_SB_BLOCK_MAX;
opts->flags = O_RDWR;
break;
case 'l':
opts->display = 1;
break;
case 'O':
if (opts->fs_features) {
warnx("-O may only be specified once");
nilfs_tune_usage(stderr);
exit(EXIT_FAILURE);
}
opts->fs_features = optarg;
opts->mask |= NILFS_SB_FEATURES;
opts->flags = O_RDWR;
break;
case 'U':
if (parse_uuid(optarg, opts->uuid))
errx(EXIT_FAILURE, "Invalid UUID format");
opts->mask |= NILFS_SB_UUID;
opts->flags = O_RDWR;
break;
case 'V':
printf("nilfs-tune (nilfs-utils %s)\n", VERSION);
exit(0);
break;
default:
nilfs_tune_usage(stderr);
exit(EXIT_FAILURE);
}
}
if (optind == argc) {
nilfs_tune_usage(stderr);
exit(EXIT_FAILURE);
}
}
#define MINUTE (60)
#define HOUR (MINUTE * 60)
#define DAY (HOUR * 24)
#define WEEK (DAY * 7)
#define MONTH (DAY * 30)
#define DIV_SECS(v, C) \
do { \
if (secs > (C)) { \
v = secs / C; \
secs -= v * C; \
} else { \
v = 0; \
} \
} while (0)
#define FORMAT_VARIABLE(v) \
do { \
if (v##s) { \
sprintf(tmp, "%s%d " #v "%s", buf[0] ? ", " : "", \
v##s, (v##s > 1) ? "s" : ""); \
strcat(buf, tmp); \
} \
} while (0)
#if 0 /* filesystem check is not implemented yet */
static const char *interval_string(unsigned int secs)
{
static char buf[512], tmp[128];
int months, weeks, days, hours, minutes;
if (secs == 0)
return "none";
buf[0] = 0;
DIV_SECS(months, MONTH);
DIV_SECS(weeks, WEEK);
DIV_SECS(days, DAY);
DIV_SECS(hours, HOUR);
DIV_SECS(minutes, MINUTE);
FORMAT_VARIABLE(month);
FORMAT_VARIABLE(week);
FORMAT_VARIABLE(day);
FORMAT_VARIABLE(hour);
FORMAT_VARIABLE(minute);
FORMAT_VARIABLE(sec);
return buf;
}
#endif
static const char *user_string(uid_t uid)
{
static char tmp[LOGIN_NAME_MAX];
static char buf[LOGIN_NAME_MAX + 8];
struct passwd *pwd;
pwd = getpwuid(uid);
strncpy(tmp, pwd ? pwd->pw_name : "unknown", sizeof(tmp));
tmp[sizeof(tmp) - 1] = '\0';
snprintf(buf, sizeof(buf), "user %s", tmp);
buf[sizeof(buf) - 1] = '\0';
return buf;
}
static const char *group_string(gid_t gid)
{
static char tmp[LOGIN_NAME_MAX];
static char buf[LOGIN_NAME_MAX + 8];
struct group *grp;
grp = getgrgid(gid);
strncpy(tmp, grp ? grp->gr_name : "unknown", sizeof(tmp));
tmp[sizeof(tmp) - 1] = '\0';
snprintf(buf, sizeof(buf), "group %s", tmp);
buf[sizeof(buf) - 1] = '\0';
return buf;
}
static const char *uuid_string(const unsigned char *uuid)
{
static char buf[256];
sprintf(buf, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6],
uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12],
uuid[13], uuid[14], uuid[15]);
return buf;
}
static const char *state_string(unsigned int state)
{
static char buf[256];
if (state & NILFS_VALID_FS)
strcpy(buf, "valid");
else
strcpy(buf, "invalid or mounted");
if (state & NILFS_ERROR_FS)
strcat(buf, ",error");
if (state & NILFS_RESIZE_FS)
strcat(buf, ",resize");
return buf;
}
static const char *creator_os_string(unsigned int creator)
{
static char buf[64];
switch (creator) {
case NILFS_OS_LINUX:
strcpy(buf, "Linux");
break;
default:
strcpy(buf, "Unknown");
break;
}
return buf;
}
static char *time_string(time_t t)
{
return ctime(&t);
}
static void print_features(FILE *f, struct nilfs_super_block *sbp)
{
__le64 *feature_p[3] = {
&sbp->s_feature_compat,
&sbp->s_feature_compat_ro,
&sbp->s_feature_incompat
};
int printed = 0;
int i, j;
fputs("Filesystem features: ", f);
for (i = 0; i < 3; i++) {
uint64_t b, mask = le64_to_cpu(*(feature_p[i]));
for (j = 0, b = 1; j < 64; j++, b <<= 1) {
if (mask & b) {
fputc(' ', f);
fputs(nilfs_feature2string(i, b), f);
printed++;
}
}
}
if (!printed)
fputs(" (none)", f);
fputs("\n", f);
}
static void show_nilfs_sb(struct nilfs_super_block *sbp)
{
char label[sizeof(sbp->s_volume_name) + 1];
gid_t gid;
uid_t uid;
memset(label, 0, sizeof(label));
memcpy(label, sbp->s_volume_name, sizeof(sbp->s_volume_name));
if (!label[0])
strcpy(label, "(none)");
printf("Filesystem volume name:\t %s\n", label);
printf("Filesystem UUID:\t %s\n", uuid_string(sbp->s_uuid));
printf("Filesystem magic number: 0x%04" PRIx16 "\n",
le16_to_cpu(sbp->s_magic));
printf("Filesystem revision #:\t %" PRIu32 ".%" PRIu16 "\n",
le32_to_cpu(sbp->s_rev_level),
le16_to_cpu(sbp->s_minor_rev_level));
print_features(stdout, sbp);
/* sbp->s_flags is not used */
printf("Filesystem state:\t %s\n",
state_string(le16_to_cpu(sbp->s_state)));
/* sbp->s_errors is not used */
printf("Filesystem OS type:\t %s\n",
creator_os_string(le32_to_cpu(sbp->s_creator_os)));
printf("Block size:\t\t %u\n",
1 << (le32_to_cpu(sbp->s_log_block_size) + 10));
printf("Filesystem created:\t %s",
time_string(le64_to_cpu(sbp->s_ctime)));
printf("Last mount time:\t %s",
time_string(le64_to_cpu(sbp->s_mtime)));
printf("Last write time:\t %s",
time_string(le64_to_cpu(sbp->s_wtime)));
printf("Mount count:\t\t %" PRIu16 "\n", le16_to_cpu(sbp->s_mnt_count));
printf("Maximum mount count:\t %" PRIu16 "\n",
le16_to_cpu(sbp->s_max_mnt_count));
#if 0 /* filesystem check is not implemented yet */
{
time_t t;
unsigned int interval;
t = (time_t)le64_to_cpu(sbp->s_lastcheck);
printf("Last checked:\t\t %s", ctime(&t));
interval = le32_to_cpu(sbp->s_checkinterval);
printf("Check interval:\t\t %u (%s)\n", interval,
interval_string(interval));
if (interval)
printf("Next check after:\t %s",
time_string(t+interval));
}
#endif
uid = (uid_t)le16_to_cpu(sbp->s_def_resuid);
printf("Reserve blocks uid:\t %u (%s)\n", uid, user_string(uid));
gid = (gid_t)le16_to_cpu(sbp->s_def_resgid);
printf("Reserve blocks gid:\t %u (%s)\n", gid, group_string(gid));
printf("First inode:\t\t %" PRIu32 "\n", le32_to_cpu(sbp->s_first_ino));
printf("Inode size:\t\t %" PRIu16 "\n", le16_to_cpu(sbp->s_inode_size));
printf("DAT entry size:\t\t %" PRIu16 "\n",
le16_to_cpu(sbp->s_dat_entry_size));
printf("Checkpoint size:\t %" PRIu16 "\n",
le16_to_cpu(sbp->s_checkpoint_size));
printf("Segment usage size:\t %" PRIu16 "\n",
le16_to_cpu(sbp->s_segment_usage_size));
printf("Number of segments:\t %" PRIu64 "\n",
(uint64_t)le64_to_cpu(sbp->s_nsegments));
printf("Device size:\t\t %" PRIu64 "\n",
(uint64_t)le64_to_cpu(sbp->s_dev_size));
printf("First data block:\t %" PRIu64 "\n",
(uint64_t)le64_to_cpu(sbp->s_first_data_block));
printf("# of blocks per segment: %" PRIu32 "\n",
le32_to_cpu(sbp->s_blocks_per_segment));
printf("Reserved segments %%:\t %" PRIu32 "\n",
le32_to_cpu(sbp->s_r_segments_percentage));
printf("Last checkpoint #:\t %" PRIu64 "\n",
(uint64_t)le64_to_cpu(sbp->s_last_cno));
printf("Last block address:\t %" PRIu64 "\n",
(uint64_t)le64_to_cpu(sbp->s_last_pseg));
printf("Last sequence #:\t %" PRIu64 "\n",
(uint64_t)le64_to_cpu(sbp->s_last_seq));
printf("Free blocks count:\t %" PRIu64 "\n",
(uint64_t)le64_to_cpu(sbp->s_free_blocks_count));
printf("Commit interval:\t %" PRIu32 "\n",
le32_to_cpu(sbp->s_c_interval));
printf("# of blks to create seg: %" PRIu32 "\n",
le32_to_cpu(sbp->s_c_block_max));
printf("CRC seed:\t\t 0x%08" PRIx32 "\n", le32_to_cpu(sbp->s_crc_seed));
printf("CRC check sum:\t\t 0x%08" PRIx32 "\n", le32_to_cpu(sbp->s_sum));
printf("CRC check data size:\t 0x%04" PRIx16 "\n",
le16_to_cpu(sbp->s_bytes));
}
static int update_feature_set(struct nilfs_super_block *sbp,
struct nilfs_tune_options *opts)
{
uint64_t features[NILFS_MAX_FEATURE_TYPES];
uint64_t bad_mask;
int bad_type;
int ret;
features[NILFS_FEATURE_TYPE_COMPAT] =
le64_to_cpu(sbp->s_feature_compat);
features[NILFS_FEATURE_TYPE_COMPAT_RO] =
le64_to_cpu(sbp->s_feature_compat_ro);
features[NILFS_FEATURE_TYPE_INCOMPAT] =
le64_to_cpu(sbp->s_feature_incompat);
assert(opts->fs_features != NULL);
ret = nilfs_edit_feature(opts->fs_features, features, ok_features,
clear_ok_features, &bad_type, &bad_mask);
if (ret < 0) {
if (!bad_type) {
warn("cannot parse features");
} else if (bad_type & NILFS_FEATURE_TYPE_NEGATE_FLAG) {
bad_type &= NILFS_FEATURE_TYPE_MASK;
warnx("feature %s is not allowed to be cleared",
nilfs_feature2string(bad_type, bad_mask));
} else {
warnx("feature %s is not allowed to be set",
nilfs_feature2string(bad_type, bad_mask));
}
} else {
sbp->s_feature_compat =
cpu_to_le64(features[NILFS_FEATURE_TYPE_COMPAT]);
sbp->s_feature_compat_ro =
cpu_to_le64(features[NILFS_FEATURE_TYPE_COMPAT_RO]);
sbp->s_feature_incompat =
cpu_to_le64(features[NILFS_FEATURE_TYPE_INCOMPAT]);
}
return ret;
}
static int modify_nilfs(const char *device, struct nilfs_tune_options *opts)
{
int devfd;
int ret = EXIT_SUCCESS;
struct nilfs_super_block *sbp;
uint64_t features;
errno = 0;
devfd = open(device, opts->flags);
if (devfd == -1) {
warn("cannot open device %s", device);
ret = EXIT_FAILURE;
goto out;
}
sbp = nilfs_sb_read(devfd);
if (!sbp) {
warn("%s: cannot open NILFS", device);
ret = EXIT_FAILURE;
goto close_fd;
}
features = le64_to_cpu(sbp->s_feature_incompat);
if (features & ~NILFS_FEATURE_INCOMPAT_SUPP)
warnx("Warning: %s: unknown incompatible features: 0x%" PRIx64,
device, features);
features = le64_to_cpu(sbp->s_feature_compat_ro);
if (opts->flags == O_RDWR &&
(features & ~NILFS_FEATURE_COMPAT_RO_SUPP))
warnx("Warning: %s: unknown read-only compatible features: 0x%"
PRIx64, device, features);
if (opts->mask & NILFS_SB_LABEL)
memcpy(sbp->s_volume_name, opts->label,
sizeof(opts->label));
if (opts->mask & NILFS_SB_UUID)
memcpy(sbp->s_uuid, opts->uuid, sizeof(opts->uuid));
if (opts->mask & NILFS_SB_COMMIT_INTERVAL)
sbp->s_c_interval = cpu_to_le32(opts->c_interval);
if (opts->mask & NILFS_SB_BLOCK_MAX)
sbp->s_c_block_max = cpu_to_le32(opts->c_block_max);
if (opts->mask & NILFS_SB_FEATURES) {
if (update_feature_set(sbp, opts) < 0) {
ret = EXIT_FAILURE;
goto free_sb;
}
}
if (opts->mask) {
if (nilfs_sb_write(devfd, sbp, opts->mask) < 0) {
warnx("%s: cannot write super blocks", device);
ret = EXIT_FAILURE;
}
}
if (opts->display)
show_nilfs_sb(sbp);
free_sb:
free(sbp);
close_fd:
close(devfd);
out:
return ret;
}
int main(int argc, char *argv[])
{
struct nilfs_tune_options opts;
const char *device;
if (argc < 2) {
nilfs_tune_usage(stderr);
exit(EXIT_FAILURE);
}
parse_options(argc, argv, &opts);
device = argv[argc-1];
if (!device) {
nilfs_tune_usage(stderr);
exit(EXIT_FAILURE);
}
if (!opts.force && opts.flags == O_RDWR) {
int ret = check_mount(device);
if (ret < 0)
err(EXIT_FAILURE, "ERROR checking mount status of %s",
device);
if (ret > 0)
errx(EXIT_FAILURE,
"ERROR: %s is mounted. Abort execution.\n"
" Running nilfs-tune on a mounted file system may cause SEVERE damage.\n"
" You can use the \"-f\" option to force this operation.",
device);
}
return modify_nilfs(device, &opts);
}