diff --git a/Documentation/CmdLineConventions b/Documentation/CmdLineConventions deleted file mode 100644 index df6bf42f1e..0000000000 --- a/Documentation/CmdLineConventions +++ /dev/null @@ -1,66 +0,0 @@ -# Command line, formatting, UI guidelines - -## Command line options - -### Short options - -* reserved for the most common operations -* should follow some common scheme - * verbose, recursive, force, output redirection, ... - * same option means the same thing in a group of related options -* most have an equivalent long option - -### Long options - -* short but descriptive -* long-worded long options are acceptable for rare but seemingly unique operations - * example: `btrfs check --clear-space-cache v1` - -### Help text - -* short help for *--help* output: `btrfs subcommand [options] param1 param2` - * the number of options gets unwieldy for many options so it's better to - insert the stub and document properly all of them in the detailed section -* short description after command line: terse but understandable explanation - what the command does -* long description after the short description - * explain in more detail what the command does, different use cases, things to notice - * more complex things should be documented in the manual pages and pointed to - -## Command output, verbosity - -### Structured output - -* `Key: value` -* indentation used for visual separation -* value column alignment for quick skimming -* must be parseable by scripts but primary consumer of the output is a human, and greppable for logs - -### Default output - -* unix commands do one thing and say nothing, we may diverge a bit from that -* default output is one line shortly describing the action - * why: running commands from scripts or among many other commands should be - noticeable due to progress tracking or for analysis if something goes wrong - * there's a global option to make the commands silent `btrfs -q subcommand`, - this can be easily scripted e.g. storing the global verbosity option in a - variable, `btrfs $quiet subcommand` and then enabling or disabling as needed -* line length should not exceed 80 columns if possible but this is not a strict - limit and we want to put the relevant information there rather abbreviate too - much - -### Formatting - -* numeric values are not quoted -* string values are quoted if they would be confused when parsed word by word - (e.g. file paths) - * quoting is by single apostrophe on both ends -* all messages follow a few known and understood schemes - -### Verbosity levels - -* 0 - quiet, only errors to stderr, nothing to stdout -* 1 - default, one line, see above -* 2 - inform about main steps that happen -* 3 - a little bit more details about what happens during level 2 -* 4 - lots of information, for debugging and close inspection what happens diff --git a/Documentation/btrfs-scrub.rst b/Documentation/btrfs-scrub.rst index 86c39b20cb..be106a551a 100644 --- a/Documentation/btrfs-scrub.rst +++ b/Documentation/btrfs-scrub.rst @@ -89,6 +89,11 @@ start [-BdrRf] | -r run in read-only mode, do not attempt to correct anything, can be run on a read-only filesystem + + Note that a read-only scrub on a read-write filesystem can + still cause writes into the filesystem due to some internal + limitations. Only a read-only scrub on a read-only filesystem + can avoid writes from scrub. -R raw print mode, print full data instead of summary -f diff --git a/Documentation/ch-scrub-intro.rst b/Documentation/ch-scrub-intro.rst index 2276bc2632..c688cfeacc 100644 --- a/Documentation/ch-scrub-intro.rst +++ b/Documentation/ch-scrub-intro.rst @@ -46,6 +46,16 @@ read-write mounted filesystem. used, with expert guidance, to rebuild certain corrupted filesystem structures in the absence of any good replica. +.. note:: + Read-only scrub on a read-write filesystem will cause some writes into the + filesystem. + + This is due to the design limitation to prevent race between marking block + group read-only and writing back block group items. + + To avoid any writes from scrub, one has to run read-only scrub on read-only + filesystem. + The user is supposed to run it manually or via a periodic system service. The recommended period is a month but it could be less. The estimated device bandwidth utilization is about 80% on an idle filesystem. diff --git a/Documentation/dev/CmdLineConventions.rst b/Documentation/dev/CmdLineConventions.rst new file mode 100644 index 0000000000..72ac875ca7 --- /dev/null +++ b/Documentation/dev/CmdLineConventions.rst @@ -0,0 +1,148 @@ +Command line, formatting, UI guidelines +======================================= + +The guidelines try to follow common principles and build on experience based on +user feedback or practices seen in other projects. There are no strict rules +but rather vague statements or principles that is recommended to follow. +It's recommended to follow them or use them as review checklist. Optimize for +humans. + +- *sane defaults* +- *principle of least surprise* +- *it does what it says* +- *it says what it does* +- *frequently performed actions have shortcuts* +- *easy thing easy, hard things possible* +- *dangerous actions are explicit or need a confirmation* +- *same name means the same thing everywhere* +- *it's hard to change things once they are released* + +Command line options +-------------------- + +Unless there's a precedent for using a well known short option name, using +long option for first implementation is always safe. + +All options parsers use :manref:`getopt(3)`, the behaviour is known and +consistent with most tools and utilities found elsewhere. Options and parameters +are sorted (options first) up to the ``--`` delimiter. Global options follow right +after the main command and are parsed separately from the subcommand, :command:`btrfs`, +following syntax +:command:`btrfs [global options] subcommand [options] `. + +Short options +^^^^^^^^^^^^^ + +Short options are in short supply, ``a-z`` and ``A-Z``. + +* reserved for the most common operations +* should follow some common scheme + + * verbose (-v), recursive (-r, -R), force (-f), output redirection (-o), ... + * same option means the same thing in a group of related options + * mnemonic naming when possible, e.g. first letter of the action like + *-l* for *length* but with growing number of features clashes can and will + happen + +* *upper case* could mean negating or extending meaning of lower case if it + exists, using both upper and lower case for different purposes can be + confusing +* most short options should have an equivalent long option +* rarely done actions do not need short options at all + +Long options +^^^^^^^^^^^^ + +Long options are meant to be descriptive, e.g. when used in scripts, documentation +or change descriptions. + +* brief but descriptive +* long and descriptive can be used in justified cases (e.g. conversion options + in :doc:`btrfstune` because of the single command without subcommands) + +Option parameters +^^^^^^^^^^^^^^^^^ + +.. note:: + **Avoid using optional parameter.** This is a usability misfeature of + *getopt()* because of the mandatory short option syntax where the parameter + *must* be glued to the option name, like *-czstd* so this looks like a group + of short options but it is not. In this example *-c zstd* is not the same, + the parameter will take default value and *zstd* will be understood as + another parameter. Unfortunate examples are :command:`btrfs filesystem + defrag -c` and :command:`btrfs balance start -d`. Both quite common and we + probably cannot fix this without breaking existing scripts. + +Help text +^^^^^^^^^ + +Description in the help text should be long enough to describe what it does, mention default +value and should not be too long or detailed. Referring to documentation is recommended +when it's really wise to read it first before using it. Otherwise it's a reminder +to user who has probably used it in the past. + +* short help for *--help* output: :command:`btrfs subcommand [options] param1 param2` + + * the number of options gets unwieldy for a command with many tunable features + so it's better to write a short description and document properly everything + in the manual page or in the followup text + +* short description after command line: terse but understandable explanation + what the command does, mention dangers + +* long description after the short description + + * explain in more detail what the command does, different use cases, things to notice + * more complex things should be documented in the manual pages and pointed to + (examples) + +Command output, verbosity +------------------------- + +Structured output +^^^^^^^^^^^^^^^^^ + +If the output consists of a lot of information, try to present it in a concise +way and structure related information together using some known formats +or already used ones in this project. + +* `Key: value`, spacing by tabs or spaces +* use indentation and empty lines for visual separation +* value column alignment for quick skimming +* must be parseable and also visually comprehensible, related information + on one line usually satisfies both (*greppable*) + +Default output +^^^^^^^^^^^^^^ + +* UNIX commands do one thing and say nothing, we diverge from that as it does + not work well for a multi-command tools +* default output is one line shortly describing the action + + * why: running commands from scripts or among many other commands should be + noticeable due to progress tracking or for analysis if something goes wrong + * there's a global option to make the commands silent :command:`btrfs -q subcommand`, + this can be easily scripted e.g. storing the global verbosity option in a + variable, :command:`btrfs $quiet subcommand` and then enabling or disabling as needed + +* there should be a line length limit so the output visually fits to one line without + wrapping, there's no exact number of columns, assume something around 100, + keep related information on one line (printed) rather then breaking it. + +Formatting +^^^^^^^^^^ + +* numeric values are not quoted +* string values are quoted if they would be confused when parsed word by word + (e.g. file paths) + * quoting is by single apostrophe on both ends, no fancy backtick+quote +* all messages follow a few known and understood schemes + +Verbosity levels +^^^^^^^^^^^^^^^^ + +* 0 - quiet, only errors to *stderr*, nothing to *stdout* +* 1 - default, one line, see above +* 2 - inform about main steps that happen +* 3 - a little bit more detailed about what happens during level 2 +* 4 - possibly lots of information, for debugging and close inspection what happens diff --git a/Documentation/dev/Development-notes.rst b/Documentation/dev/Development-notes.rst index a0e7b27627..76c492caeb 100644 --- a/Documentation/dev/Development-notes.rst +++ b/Documentation/dev/Development-notes.rst @@ -413,6 +413,7 @@ Please refer to the option documentation for further details. - **CONFIG_BTRFS_DEBUG** - **CONFIG_BTRFS_ASSERT** + - **CONFIG_BTRFS_EXPERIMENTAL** - **CONFIG_BTRFS_FS_RUN_SANITY_TESTS** -- basic tests on module load - **CONFIG_BTRFS_FS_CHECK_INTEGRITY** -- block integrity checker enabled by mount options diff --git a/Documentation/index.rst b/Documentation/index.rst index f55b664f59..c4ab32e5d9 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -84,6 +84,7 @@ is in the :doc:`manual pages`. dev/dev-internal-apis dev/ReleaseChecklist dev/GithubReviewWorkflow + dev/CmdLineConventions btrfs-ioctl diff --git a/Documentation/mkfs.btrfs.rst b/Documentation/mkfs.btrfs.rst index b830f8c5be..e3bcd4f25f 100644 --- a/Documentation/mkfs.btrfs.rst +++ b/Documentation/mkfs.btrfs.rst @@ -155,6 +155,19 @@ OPTIONS contain the files from *rootdir*. Since version 4.14.1 the filesystem size is not minimized. Please see option *--shrink* if you need that functionality. +--compress [:] + Try to compress files when using *--rootdir*. Supported values for *algo* are + *no* (the default), *zstd*, *lzo* or *zlib*. The optional value *level* is a + compression level, 1..15 for *zstd*, 1..9 for *zlib*. + + As with the kernel, :command:`mkfs.btrfs` won't write compressed extents when + they would be larger than the uncompressed versions, and will set file attribute + *NOCOMPRESS* if its beginning is found to be incompressible. + + .. note:: + The support for ZSTD and LZO is a compile-time option, please check + the output of :command:`mkfs.btrfs --help` for the actual support. + -u|--subvol : Specify that *subdir* is to be created as a subvolume rather than a regular directory. The option *--rootdir* must also be specified, and *subdir* must be an @@ -212,15 +225,6 @@ OPTIONS $ mkfs.btrfs -O list-all ---compress [:] - Try to compress files when using *--rootdir*. Supported values for *algo* are - *no* (the default), *zlib*, *lzo*, and *zstd*. The optional value *level* is a - compression level, from 1 to 9 for ZLIB and from 1 to 15 for ZSTD. - - As with the kernel, :command:`mkfs.btrfs` won't write compressed extents when - they would be larger than the uncompressed versions, and will mark a file as - `nocompress` if its beginning is found to be incompressible. - -f|--force Forcibly overwrite the block devices when an existing filesystem is detected. By default, :command:`mkfs.btrfs` will utilize *libblkid* to check for any known @@ -389,7 +393,7 @@ block-group-tree .. _mkfs-feature-raid-stripe-tree: raid-stripe-tree - (kernel support since 6.7, CONFIG_BTRFS_DEBUG) + (kernel support since 6.7, CONFIG_BTRFS_DEBUG/CONFIG_BTRFS_EXPERIMENTAL) Separate tree for logical file extent mapping where the physical mapping may not match on multiple devices. This is now used in zoned mode to @@ -400,8 +404,9 @@ raid-stripe-tree .. note:: Due to the status of implementation it is enabled only in - builds with CONFIG_BTRFS_DEBUG. Support by the kernel module - can be found in the sysfs feature list. + builds with CONFIG_BTRFS_DEBUG/CONFIG_BTRFS_EXPERIMENTAL. + Support by the kernel module can be found in the sysfs feature + list. squota (kernel support since 6.7) diff --git a/cmds/balance.c b/cmds/balance.c index 4c73273dfd..5c17fbc1ac 100644 --- a/cmds/balance.c +++ b/cmds/balance.c @@ -376,6 +376,45 @@ static const char * const cmd_balance_start_usage[] = { NULL }; +/* + * Return 0 if no missing device found for the fs at @mnt. + * Return >0 if there is any missing device for the fs at @mnt. + * Return <0 if we hit other errors during the check. + */ +static int check_missing_devices(const char *mnt) +{ + struct btrfs_ioctl_fs_info_args fs_info_arg; + struct btrfs_ioctl_dev_info_args *dev_info_args = NULL; + bool found_missing = false; + int ret; + + ret = get_fs_info(mnt, &fs_info_arg, &dev_info_args); + if (ret < 0) + return ret; + + for (int i = 0; i < fs_info_arg.num_devices; i++) { + struct btrfs_ioctl_dev_info_args *cur_dev_info; + int fd; + + cur_dev_info = (struct btrfs_ioctl_dev_info_args *)&dev_info_args[i]; + + /* + * Kernel will report the device path even if we can no + * longer access it anymore. So we have to manually check it. + */ + fd = open((char *)cur_dev_info->path, O_RDONLY); + if (fd < 0) { + found_missing = true; + break; + } + close(fd); + } + free(dev_info_args); + if (found_missing) + return 1; + return 0; +} + static int cmd_balance_start(const struct cmd_struct *cmd, int argc, char **argv) { @@ -387,6 +426,7 @@ static int cmd_balance_start(const struct cmd_struct *cmd, bool enqueue = false; unsigned start_flags = 0; bool raid56_warned = false; + bool convert_warned = false; int i; memset(&args, 0, sizeof(args)); @@ -481,6 +521,53 @@ static int cmd_balance_start(const struct cmd_struct *cmd, args.flags |= BTRFS_BALANCE_TYPE_MASK; } + /* + * If we are using convert, and there is a missing/failed device at + * runtime (e.g. mounted then remove a device using sysfs interface), + * btrfs has no way to avoid using that failing/removed device. + * + * In that case converting the profile is very dangerous, e.g. + * converting RAID1 to SINGLE/DUP, and new SINGLE/DUP chunks can + * be allocated to that failing/removed device, and cause the + * fs to flip RO due to failed metadata writes. + * + * Meanwhile the fs may work completely fine due to the extra + * duplication (e.g. all RAID1 profiles). + * + * So here warn if one is trying to convert with missing devices. + */ + for (i = 0; ptrs[i]; i++) { + int delay = 10; + int ret; + + if (!(ptrs[i]->flags & BTRFS_BALANCE_ARGS_CONVERT) || convert_warned) + continue; + + ret = check_missing_devices(argv[optind]); + if (ret < 0) { + errno = -ret; + warning("skipping missing devices check due to failure: %m"); + break; + } + if (ret == 0) + continue; + convert_warned = true; + printf("WARNING:\n\n"); + printf("\tConversion with missing device(s) can be dangerous.\n"); + printf("\tPlease use `btrfs replace` or `btrfs device remove` instead.\n"); + if (force) { + printf("\tSafety timeout skipped due to --force\n\n"); + continue; + } + printf("\tThe operation will continue in %d seconds.\n", delay); + printf("\tUse Ctrl-C to stop.\n"); + while (delay) { + printf("%2d", delay--); + fflush(stdout); + sleep(1); + } + } + /* drange makes sense only when devid is set */ for (i = 0; ptrs[i]; i++) { if ((ptrs[i]->flags & BTRFS_BALANCE_ARGS_DRANGE) && diff --git a/cmds/device.c b/cmds/device.c index 65b5fc2c46..3e45a72094 100644 --- a/cmds/device.c +++ b/cmds/device.c @@ -73,7 +73,7 @@ static int cmd_device_add(const struct cmd_struct *cmd, int c; enum { GETOPT_VAL_ENQUEUE = GETOPT_VAL_FIRST }; static const struct option long_options[] = { - { "nodiscard", optional_argument, NULL, 'K'}, + { "nodiscard", no_argument, NULL, 'K' }, { "force", no_argument, NULL, 'f'}, { "enqueue", no_argument, NULL, GETOPT_VAL_ENQUEUE}, { NULL, 0, NULL, 0} diff --git a/cmds/qgroup.c b/cmds/qgroup.c index 5705286120..a612130c94 100644 --- a/cmds/qgroup.c +++ b/cmds/qgroup.c @@ -1317,6 +1317,26 @@ static bool key_in_range(const struct btrfs_key *key, return true; } +static int quota_enabled(int fd) { + struct btrfs_tree_search_args args = { 0 }; + struct btrfs_ioctl_search_key *sk; + int ret; + + sk = btrfs_tree_search_sk(&args); + sk->tree_id = BTRFS_QUOTA_TREE_OBJECTID; + sk->min_type = 0; + sk->max_type = (u8)-1; + sk->max_objectid = (u64)-1; + sk->max_offset = (u64)-1; + sk->max_transid = (u64)-1; + sk->nr_items = 1; + + ret = btrfs_tree_search_ioctl(fd, &args); + if (ret < 0) + ret = -errno; + return ret; +} + static int __qgroups_search(int fd, struct btrfs_tree_search_args *args, struct qgroup_lookup *qgroup_lookup) { @@ -2209,6 +2229,18 @@ static int cmd_qgroup_clear_stale(const struct cmd_struct *cmd, int argc, char * if (fd < 0) return 1; + /* Do the check first so the sync is not done if quotas are not enabled. */ + ret = quota_enabled(fd); + if (ret == -ENOENT) { + warning("qgroups not enabled"); + ret = 0; + goto out_close; + } else if (ret < 0) { + errno = -ret; + error("cannot check qgroup status: %m"); + goto out_close; + } + /* Sync the fs so that the qgroup numbers are uptodate. */ err = btrfs_util_sync_fd(fd); if (err) @@ -2217,11 +2249,11 @@ static int cmd_qgroup_clear_stale(const struct cmd_struct *cmd, int argc, char * ret = qgroups_search_all(fd, &qgroup_lookup); if (ret == -ENOTTY) { error("can't list qgroups: quotas not enabled"); - goto out; + goto out_close; } else if (ret < 0) { errno = -ret; error("can't list qgroups: %m"); - goto out; + goto out_close; } node = rb_first(&qgroup_lookup.root); @@ -2235,9 +2267,9 @@ static int cmd_qgroup_clear_stale(const struct cmd_struct *cmd, int argc, char * node = rb_next(node); } -out: - close(fd); __free_all_qgroups(&qgroup_lookup); +out_close: + close(fd); return !!ret; } static DEFINE_SIMPLE_COMMAND(qgroup_clear_stale, "clear-stale"); diff --git a/common/help.c b/common/help.c index 0ee00a3a13..a77b47ed55 100644 --- a/common/help.c +++ b/common/help.c @@ -87,13 +87,14 @@ int check_argc_max(int nargs, int expected) /* * Preprocess @argv with getopt_long to reorder options and consume the "--" * option separator. - * Unknown short and long options are reported, optionally the @usage is printed - * before exit. + * Unknown short and long options are reported. Also consume the --help + * option in case it's for a command without any options. */ void clean_args_no_options(const struct cmd_struct *cmd, int argc, char *argv[]) { static const struct option long_options[] = { - {NULL, 0, NULL, 0} + { "help", no_argument, NULL, GETOPT_VAL_HELP }, + { NULL, 0, NULL, 0 } }; while (1) { @@ -103,9 +104,13 @@ void clean_args_no_options(const struct cmd_struct *cmd, int argc, char *argv[]) break; switch (c) { - default: + case GETOPT_VAL_HELP: if (cmd->usagestr) usage(cmd, 1); + break; + default: + if (cmd->usagestr) + usage_unknown_option(cmd, argv); } } } diff --git a/configure.ac b/configure.ac index 15c8f12b89..d098671e90 100644 --- a/configure.ac +++ b/configure.ac @@ -454,7 +454,7 @@ PKG_CHECK_MODULES(ZLIB, [zlib]) PKG_STATIC(ZLIB_LIBS_STATIC, [zlib]) AC_ARG_ENABLE([zstd], - AS_HELP_STRING([--disable-zstd], [build without zstd support for restore and receive (default: enabled)]), + AS_HELP_STRING([--disable-zstd], [build without zstd compression support (btrfs-restore, btrfs-receive, mkfs.btrfs) (default: enabled)]), [], [enable_zstd=yes] ) @@ -466,6 +466,27 @@ fi AS_IF([test "x$enable_zstd" = xyes], [COMPRESSION_ZSTD=1], [COMPRESSION_ZSTD=0]) AC_SUBST(COMPRESSION_ZSTD) +AC_ARG_ENABLE([lzo], + AS_HELP_STRING([--disable-lzo], [build without lzo compression support (btrfs-restore, btrfs-receive, mkfs.btrfs) (default: enabled)]), + [], [enable_lzo=yes] +) + +if test "x$enable_lzo" = xyes; then + dnl lzo library does not provide pkg-config, use classic way + AC_CHECK_LIB([lzo2], [lzo_version], [ + LZO2_LIBS="-llzo2" + LZO2_CFLAGS="" + LZO2_LIBS_STATIC="-llzo2"],[ + AC_MSG_ERROR([cannot find lzo2 library]) + ]) + AC_SUBST([LZO2_LIBS]) + AC_SUBST([LZO2_LIBS_STATIC]) + AC_SUBST([LZO2_CFLAGS]) +fi + +AS_IF([test "x$enable_lzo" = xyes], [COMPRESSION_LZO=1], [COMPRESSION_LZO=0]) +AC_SUBST(COMPRESSION_LZO) + AC_ARG_ENABLE([libudev], AS_HELP_STRING([--disable-libudev], [build without libudev support (for multipath)]), [], [enable_libudev=yes] @@ -513,27 +534,6 @@ if ${PKG_CONFIG} udev --atleast-version 190; then fi AC_SUBST(UDEVDIR) -AC_ARG_ENABLE([lzo], - AS_HELP_STRING([--disable-lzo], [build without lzo support for restore and receive (default: enabled)]), - [], [enable_lzo=yes] -) - -if test "x$enable_lzo" = xyes; then - dnl lzo library does not provide pkg-config, use classic way - AC_CHECK_LIB([lzo2], [lzo_version], [ - LZO2_LIBS="-llzo2" - LZO2_CFLAGS="" - LZO2_LIBS_STATIC="-llzo2"],[ - AC_MSG_ERROR([cannot find lzo2 library]) - ]) - AC_SUBST([LZO2_LIBS]) - AC_SUBST([LZO2_LIBS_STATIC]) - AC_SUBST([LZO2_CFLAGS]) -fi - -AS_IF([test "x$enable_lzo" = xyes], [COMPRESSION_LZO=1], [COMPRESSION_LZO=0]) -AC_SUBST(COMPRESSION_LZO) - dnl call PKG_INSTALLDIR from pkg.m4 to set pkgconfigdir m4_ifdef([PKG_INSTALLDIR], [PKG_INSTALLDIR], [AC_MSG_ERROR([please install pkgconf])]) diff --git a/convert/main.c b/convert/main.c index bf31bb3a94..98b96b5f04 100644 --- a/convert/main.c +++ b/convert/main.c @@ -1868,6 +1868,10 @@ static const char * const convert_usage[] = { OPTLINE("-O|--features LIST", "comma separated list of filesystem features"), OPTLINE("--no-progress", "show only overview, not the detailed progress"), "", + "General:", + OPTLINE("--version", "print the btrfs-convert version and exit"), + OPTLINE("--help", "print this help and exit"), + "", "Supported filesystems:", "\text2/3/4: " #if BTRFSCONVERT_EXT2 @@ -1910,11 +1914,10 @@ int BOX_MAIN(convert)(int argc, char *argv[]) cpu_detect_flags(); hash_init_accel(); btrfs_assert_feature_buf_size(); - printf("btrfs-convert from %s\n\n", PACKAGE_STRING); while(1) { enum { GETOPT_VAL_NO_PROGRESS = GETOPT_VAL_FIRST, GETOPT_VAL_CHECKSUM, - GETOPT_VAL_UUID }; + GETOPT_VAL_UUID, GETOPT_VAL_VERSION }; static const struct option long_options[] = { { "no-progress", no_argument, NULL, GETOPT_VAL_NO_PROGRESS }, @@ -1932,7 +1935,8 @@ int BOX_MAIN(convert)(int argc, char *argv[]) { "copy-label", no_argument, NULL, 'L' }, { "uuid", required_argument, NULL, GETOPT_VAL_UUID }, { "nodesize", required_argument, NULL, 'N' }, - { "help", no_argument, NULL, GETOPT_VAL_HELP}, + { "help", no_argument, NULL, GETOPT_VAL_HELP }, + { "version", no_argument, NULL, GETOPT_VAL_VERSION }, { NULL, 0, NULL, 0 } }; int c = getopt_long(argc, argv, "dinN:rl:LpO:", long_options, NULL); @@ -2024,11 +2028,18 @@ int BOX_MAIN(convert)(int argc, char *argv[]) strncpy_null(fsid, optarg, sizeof(fsid)); } break; + case GETOPT_VAL_VERSION: + printf("btrfs-convert, part of %s\n", PACKAGE_STRING); + ret = 0; + goto success; case GETOPT_VAL_HELP: default: usage(&convert_cmd, c != GETOPT_VAL_HELP); } } + + printf("btrfs-convert from %s\n\n", PACKAGE_STRING); + set_argv0(argv); if (check_argc_exact(argc - optind, 1)) { usage(&convert_cmd, 1); @@ -2072,5 +2083,6 @@ int BOX_MAIN(convert)(int argc, char *argv[]) } if (ret) return 1; +success: return 0; } diff --git a/image/main.c b/image/main.c index 51cf80ad24..3d61074a93 100644 --- a/image/main.c +++ b/image/main.c @@ -56,6 +56,10 @@ static const char * const image_usage[] = { OPTLINE("-m", "restore for multiple devices"), OPTLINE("-d", "also dump data, conflicts with -w"), "", + "General:", + OPTLINE("--version", "print the btrfs-image version and exit"), + OPTLINE("--help", "print this help and exit"), + "", "In the dump mode, source is the btrfs device and target is the output file (use '-' for stdout).", "In the restore mode, source is the dumped image and target is the btrfs device/file.", NULL @@ -86,8 +90,10 @@ int BOX_MAIN(image)(int argc, char *argv[]) hash_init_accel(); while (1) { + enum { GETOPT_VAL_VERSION = GETOPT_VAL_FIRST }; static const struct option long_options[] = { { "help", no_argument, NULL, GETOPT_VAL_HELP}, + { "version", no_argument, NULL, GETOPT_VAL_VERSION }, { NULL, 0, NULL, 0 } }; int c = getopt_long(argc, argv, "rc:t:oswmd", long_options, NULL); @@ -133,6 +139,10 @@ int BOX_MAIN(image)(int argc, char *argv[]) btrfs_warn_experimental("Feature: dump image with data"); dump_data = true; break; + case GETOPT_VAL_VERSION: + printf("btrfs-image, part of %s\n", PACKAGE_STRING); + ret = 0; + goto success; case GETOPT_VAL_HELP: default: usage(&image_cmd, c != GETOPT_VAL_HELP); @@ -293,5 +303,6 @@ int BOX_MAIN(image)(int argc, char *argv[]) btrfs_close_all_devices(); +success: return !!ret; } diff --git a/mkfs/main.c b/mkfs/main.c index 08d1e1f749..e0c419eaf9 100644 --- a/mkfs/main.c +++ b/mkfs/main.c @@ -431,20 +431,37 @@ static const char * const mkfs_usage[] = { "Features:", OPTLINE("--csum TYPE", ""), OPTLINE("--checksum TYPE", "checksum algorithm to use, crc32c (default), xxhash, sha256, blake2"), - OPTLINE("-n|--nodesize SIZE", "size of btree nodes"), - OPTLINE("-s|--sectorsize SIZE", "data block size (may not be mountable by current kernel)"), + OPTLINE("-n|--nodesize SIZE", "size of btree nodes (default 16K)"), + OPTLINE("-s|--sectorsize SIZE", "data block size (default 4K, may not be mountable by current kernel if CPU page size is different)"), OPTLINE("-O|--features LIST", "comma separated list of filesystem features (use '-O list-all' to list features)"), - OPTLINE("-L|--label LABEL", "set the filesystem label"), + OPTLINE("-L|--label LABEL", "set the filesystem label (maximum length 255)"), OPTLINE("-U|--uuid UUID", "specify the filesystem UUID (must be unique for a filesystem with multiple devices)"), - OPTLINE("--device-uuid UUID", "Specify the filesystem device UUID (a.k.a sub-uuid) (for single device filesystem only)"), + OPTLINE("--device-uuid UUID", "specify the filesystem device UUID (a.k.a sub-uuid) (for single device filesystem only)"), "", "Creation:", OPTLINE("-b|--byte-count SIZE", "set size of each device to SIZE (filesystem size is sum of all device sizes)"), - OPTLINE("-r|--rootdir DIR", "copy files from DIR to the image root directory"), + OPTLINE("-r|--rootdir DIR", "copy files from DIR to the image root directory, can be combined with --subvol"), + OPTLINE("--compress ALGO[:LEVEL]", "compress files by algorithm and level, ALGO can be 'no' (default), zstd, lzo, zlib"), + OPTLINE("", "Built-in:"), +#if COMPRESSION_ZSTD + OPTLINE("", "- ZSTD: yes (levels 1..15)"), +#else + OPTLINE("", "- ZSTD: no"), +#endif +#if COMPRESSION_LZO + OPTLINE("", "- LZO: yes"), +#else + OPTLINE("", "- LZO: no"), +#endif + OPTLINE("", "- ZLIB: yes (levels 1..9)"), OPTLINE("-u|--subvol TYPE:SUBDIR", "create SUBDIR as subvolume rather than normal directory, can be specified multiple times"), + OPTLINE("", "TYPE is one of:"), + OPTLINE("", "- rw - (default) create a writeable subvolume in SUBDIR"), + OPTLINE("", "- ro - create the subvolume as read-only"), + OPTLINE("", "- default - the SUBDIR will be a subvolume and also set as default (can be specified only once)"), + OPTLINE("", "- defalut-ro - like 'default' and is created as read-only subvolume (can be specified only once)"), OPTLINE("--shrink", "(with --rootdir) shrink the filled filesystem to minimal size"), OPTLINE("-K|--nodiscard", "do not perform whole device TRIM"), - OPTLINE("--compress ALGO[:LEVEL]", "compression algorithm and level to use; ALGO can be no (default), zlib, lzo, zstd"), OPTLINE("-f|--force", "force overwrite of existing filesystem"), "", "General:", @@ -1019,6 +1036,72 @@ static void *prepare_one_device(void *ctx) return NULL; } +static int parse_compression(const char *str, enum btrfs_compression_type *type, + unsigned int *level) +{ + const char *colon; + size_t type_size; + unsigned int default_level, max_level; + + *level = 0; + if (strcmp(str, "no") == 0) { + *type = BTRFS_COMPRESS_NONE; + return 0; + } + + colon = strstr(str, ":"); + + if (colon) + type_size = colon - str; + else + type_size = strlen(str); + + if (strncmp(str, "zlib", type_size) == 0) { + *type = BTRFS_COMPRESS_ZLIB; + max_level = ZLIB_BTRFS_MAX_LEVEL; + default_level = ZLIB_BTRFS_DEFAULT_LEVEL; + } else if (strncmp(str, "lzo", type_size) == 0) { +#if COMPRESSION_LZO + *type = BTRFS_COMPRESS_LZO; + max_level = 1; + default_level = 1; +#else + error("lzo support not compiled in"); + return 1; +#endif + } else if (strncmp(str, "zstd", type_size) == 0) { +#if COMPRESSION_ZSTD + *type = BTRFS_COMPRESS_ZSTD; + max_level = ZSTD_BTRFS_MAX_LEVEL; + default_level = ZSTD_BTRFS_DEFAULT_LEVEL; +#else + error("zstd support not compiled in"); + return 1; +#endif + } else { + return 1; + } + + if (colon) { + u64 tmplevel = arg_strtou64(colon + 1); + + if (tmplevel > UINT_MAX) + return 1; + + if (tmplevel == 0) + *level = default_level; + else if (tmplevel > max_level) { + error("compression level %llu out of range [1..%u]", + tmplevel, max_level); + return 1; + } else { + *level = tmplevel; + } + } + + return 0; +} + int BOX_MAIN(mkfs)(int argc, char **argv) { char *file; @@ -1063,7 +1146,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv) struct rootdir_subvol *rds; bool has_default_subvol = false; enum btrfs_compression_type compression = BTRFS_COMPRESS_NONE; - u64 compression_level = 0; + unsigned int compression_level = 0; LIST_HEAD(subvols); cpu_detect_flags(); @@ -1281,42 +1364,12 @@ int BOX_MAIN(mkfs)(int argc, char **argv) case 'q': bconf_be_quiet(); break; - case GETOPT_VAL_COMPRESS: { - char *colon; - size_t type_size; - - if (!strcmp(optarg, "no")) { - compression = BTRFS_COMPRESS_NONE; - break; - } - - colon = strstr(optarg, ":"); - - if (colon) - type_size = colon - optarg; - else - type_size = strlen(optarg); - - if (!strncmp(optarg, "zlib", type_size)) { - compression = BTRFS_COMPRESS_ZLIB; - } else if (!strncmp(optarg, "lzo", type_size)) { - compression = BTRFS_COMPRESS_LZO; - } else if (!strncmp(optarg, "zstd", type_size)) { - compression = BTRFS_COMPRESS_ZSTD; - } else { - error("unrecognized compression type %s", - optarg); + case GETOPT_VAL_COMPRESS: + if (parse_compression(optarg, &compression, &compression_level)) { ret = 1; goto error; } - - if (colon) - compression_level = arg_strtou64(colon + 1); - else - compression_level = 0; - break; - } case GETOPT_VAL_DEVICE_UUID: strncpy_null(dev_uuid, optarg, BTRFS_UUID_UNPARSED_SIZE); break; @@ -1389,6 +1442,12 @@ int BOX_MAIN(mkfs)(int argc, char **argv) free(source_dir); source_dir = canonical; + } else { + if (compression != BTRFS_COMPRESS_NONE) { + error("--compression must be used with --rootdir"); + ret = 1; + goto error; + } } list_for_each_entry(rds, &subvols, list) { @@ -1997,6 +2056,15 @@ int BOX_MAIN(mkfs)(int argc, char **argv) goto out; } + pr_verbose(LOG_DEFAULT, " Compress: %s%s%s\n", + compression == BTRFS_COMPRESS_ZSTD ? "zstd" : + compression == BTRFS_COMPRESS_LZO ? "lzo" : + compression == BTRFS_COMPRESS_ZLIB ? "zlib" : "no", + compression_level > 0 ? ":" : "", + compression_level > 0 ? + pretty_size_mode(compression_level, UNITS_RAW) : + ""); + ret = btrfs_mkfs_fill_dir(trans, source_dir, root, &subvols, compression, compression_level); diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c index a3ec75459c..735c30d399 100644 --- a/mkfs/rootdir.c +++ b/mkfs/rootdir.c @@ -54,12 +54,6 @@ #include "common/rbtree-utils.h" #include "mkfs/rootdir.h" -#define ZLIB_BTRFS_DEFAULT_LEVEL 3 -#define ZLIB_BTRFS_MAX_LEVEL 9 - -#define ZSTD_BTRFS_DEFAULT_LEVEL 3 -#define ZSTD_BTRFS_MAX_LEVEL 15 - #define LZO_LEN 4 static u32 fs_block_size; @@ -1637,7 +1631,7 @@ static int set_default_subvolume(struct btrfs_trans_handle *trans) int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir, struct btrfs_root *root, struct list_head *subvols, enum btrfs_compression_type compression, - u64 compression_level) + unsigned int compression_level) { int ret; struct stat root_st; diff --git a/mkfs/rootdir.h b/mkfs/rootdir.h index f0f231619f..b32fda5bfd 100644 --- a/mkfs/rootdir.h +++ b/mkfs/rootdir.h @@ -28,6 +28,12 @@ #include "kernel-lib/list.h" #include "kernel-shared/compression.h" +#define ZLIB_BTRFS_DEFAULT_LEVEL 3 +#define ZLIB_BTRFS_MAX_LEVEL 9 + +#define ZSTD_BTRFS_DEFAULT_LEVEL 3 +#define ZSTD_BTRFS_MAX_LEVEL 15 + struct btrfs_fs_info; struct btrfs_root; @@ -42,7 +48,7 @@ struct rootdir_subvol { int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir, struct btrfs_root *root, struct list_head *subvols, enum btrfs_compression_type compression, - u64 compression_level); + unsigned int compression_level); u64 btrfs_mkfs_size_dir(const char *dir_name, u32 sectorsize, u64 min_dev_size, u64 meta_profile, u64 data_profile); int btrfs_mkfs_shrink_fs(struct btrfs_fs_info *fs_info, u64 *new_size_ret, diff --git a/tests/cli-tests/001-btrfs/test.sh b/tests/cli-tests/001-btrfs/test.sh index 85213fccf0..ef828f80f8 100755 --- a/tests/cli-tests/001-btrfs/test.sh +++ b/tests/cli-tests/001-btrfs/test.sh @@ -28,3 +28,17 @@ run_check "$TOP/btrfs" -vvvv help run_check "$TOP/btrfs" --log=quiet help run_check "$TOP/btrfs" -q help run_mustfail "invalid log level accepted" "$TOP/btrfs" --log=invalid help + +# Combine help with other options +run_mustfail "unrecognized option accepted" "$TOP/btrfs" filesystem df -v +run_mustfail "unrecognized option accepted" "$TOP/btrfs" filesystem df -v / +run_mustfail "unrecognized option accepted" "$TOP/btrfs" filesystem df -v --help / +if ! run_check_stdout "$TOP/btrfs" filesystem df --help / | grep -q 'usage.*filesystem df'; then + _fail "standalone option --help" +fi +if ! run_check_stdout "$TOP/btrfs" filesystem df -H --help / | grep -q 'usage.*filesystem df'; then + _fail "option --help with valid option (1)" +fi +if ! run_check_stdout "$TOP/btrfs" filesystem df --help -H / | grep -q 'usage.*filesystem df'; then + _fail "option --help with valid option (2)" +fi diff --git a/tune/main.c b/tune/main.c index 91e70eeb62..c4cafa290b 100644 --- a/tune/main.c +++ b/tune/main.c @@ -123,7 +123,8 @@ static const char * const tune_usage[] = { "", "General:", OPTLINE("-f", "allow dangerous operations, make sure that you are aware of the dangers"), - OPTLINE("--help", "print this help"), + OPTLINE("--version", "print the btrfstune version and exit"), + OPTLINE("--help", "print this help and exit"), #if EXPERIMENTAL "", "EXPERIMENTAL FEATURES:", @@ -212,9 +213,11 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[]) GETOPT_VAL_ENABLE_FREE_SPACE_TREE, GETOPT_VAL_ENABLE_SIMPLE_QUOTA, GETOPT_VAL_REMOVE_SIMPLE_QUOTA, + GETOPT_VAL_VERSION, }; static const struct option long_options[] = { { "help", no_argument, NULL, GETOPT_VAL_HELP}, + { "version", no_argument, NULL, GETOPT_VAL_VERSION }, { "convert-to-block-group-tree", no_argument, NULL, GETOPT_VAL_ENABLE_BLOCK_GROUP_TREE}, { "convert-from-block-group-tree", no_argument, NULL, @@ -305,6 +308,10 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[]) btrfstune_cmd_groups[CSUM_CHANGE] = true; break; #endif + case GETOPT_VAL_VERSION: + printf("btrfstune, part of %s\n", PACKAGE_STRING); + ret = 0; + goto free_out; case GETOPT_VAL_HELP: default: usage(&tune_cmd, c != GETOPT_VAL_HELP);