| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2003-2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include "command.h" |
| #include "input.h" |
| #include "init.h" |
| #include "io.h" |
| |
| static cmdinfo_t chattr_cmd; |
| static cmdinfo_t lsattr_cmd; |
| static unsigned int orflags; |
| static unsigned int andflags; |
| unsigned int recurse_all; |
| unsigned int recurse_dir; |
| |
| static struct xflags { |
| uint flag; |
| char *shortname; |
| char *longname; |
| } xflags[] = { |
| { FS_XFLAG_REALTIME, "r", "realtime" }, |
| { FS_XFLAG_PREALLOC, "p", "prealloc" }, |
| { FS_XFLAG_IMMUTABLE, "i", "immutable" }, |
| { FS_XFLAG_APPEND, "a", "append-only" }, |
| { FS_XFLAG_SYNC, "s", "sync" }, |
| { FS_XFLAG_NOATIME, "A", "no-atime" }, |
| { FS_XFLAG_NODUMP, "d", "no-dump" }, |
| { FS_XFLAG_RTINHERIT, "t", "rt-inherit" }, |
| { FS_XFLAG_PROJINHERIT, "P", "proj-inherit" }, |
| { FS_XFLAG_NOSYMLINKS, "n", "nosymlinks" }, |
| { FS_XFLAG_EXTSIZE, "e", "extsize" }, |
| { FS_XFLAG_EXTSZINHERIT, "E", "extsz-inherit" }, |
| { FS_XFLAG_NODEFRAG, "f", "no-defrag" }, |
| { FS_XFLAG_FILESTREAM, "S", "filestream" }, |
| { FS_XFLAG_DAX, "x", "dax" }, |
| { FS_XFLAG_COWEXTSIZE, "C", "cowextsize" }, |
| { FS_XFLAG_HASATTR, "X", "has-xattr" }, |
| { 0, NULL, NULL } |
| }; |
| #define CHATTR_XFLAG_LIST "r"/*p*/"iasAdtPneEfSxC"/*X*/ |
| |
| static void |
| lsattr_help(void) |
| { |
| printf(_( |
| "\n" |
| " displays the set of extended inode flags associated with the current file\n" |
| "\n" |
| " Each individual flag is displayed as a single character, in this order:\n" |
| " r -- file data is stored in the realtime section\n" |
| " p -- file has preallocated extents (cannot be changed using chattr)\n" |
| " i -- immutable, file cannot be modified\n" |
| " a -- append-only, file can only be appended to\n" |
| " s -- all updates are synchronous\n" |
| " A -- the access time is not updated for this inode\n" |
| " d -- do not include this file in a dump of the filesystem\n" |
| " t -- child created in this directory has realtime bit set by default\n" |
| " P -- child created in this directory has parents project ID by default\n" |
| " n -- symbolic links cannot be created in this directory\n" |
| " e -- for non-realtime files, observe the inode extent size value\n" |
| " E -- children created in this directory inherit the extent size value\n" |
| " f -- do not include this file when defragmenting the filesystem\n" |
| " S -- enable filestreams allocator for this directory\n" |
| " x -- Use direct access (DAX) for data in this file\n" |
| " C -- for files with shared blocks, observe the inode CoW extent size value\n" |
| " X -- file has extended attributes (cannot be changed using chattr)\n" |
| "\n" |
| " Options:\n" |
| " -R -- recursively descend (useful when current file is a directory)\n" |
| " -D -- recursively descend, but only list attributes on directories\n" |
| " -a -- show all flags which can be set alongside those which are set\n" |
| " -v -- verbose mode; show long names of flags, not single characters\n" |
| "\n")); |
| } |
| |
| static void |
| chattr_help(void) |
| { |
| printf(_( |
| "\n" |
| " modifies the set of extended inode flags associated with the current file\n" |
| "\n" |
| " Examples:\n" |
| " 'chattr +a' - sets the append-only flag\n" |
| " 'chattr -a' - clears the append-only flag\n" |
| "\n" |
| " -R -- recursively descend (useful when current file is a directory)\n" |
| " -D -- recursively descend, only modifying attributes on directories\n" |
| " +/-r -- set/clear the realtime flag\n" |
| " +/-i -- set/clear the immutable flag\n" |
| " +/-a -- set/clear the append-only flag\n" |
| " +/-s -- set/clear the sync flag\n" |
| " +/-A -- set/clear the no-atime flag\n" |
| " +/-d -- set/clear the no-dump flag\n" |
| " +/-t -- set/clear the realtime inheritance flag\n" |
| " +/-P -- set/clear the project ID inheritance flag\n" |
| " +/-n -- set/clear the no-symbolic-links flag\n" |
| " +/-e -- set/clear the extent-size flag\n" |
| " +/-E -- set/clear the extent-size inheritance flag\n" |
| " +/-f -- set/clear the no-defrag flag\n" |
| " +/-S -- set/clear the filestreams allocator flag\n" |
| " +/-x -- set/clear the direct access (DAX) flag\n" |
| " +/-C -- set/clear the CoW extent-size flag\n" |
| " Note1: user must have certain capabilities to modify immutable/append-only.\n" |
| " Note2: immutable/append-only files cannot be deleted; removing these files\n" |
| " requires the immutable/append-only flag to be cleared first.\n" |
| " Note3: the realtime flag can only be set if the filesystem has a realtime\n" |
| " section, and the (regular) file must be empty when the flag is set.\n" |
| "\n")); |
| } |
| |
| void |
| printxattr( |
| uint flags, |
| int verbose, |
| int dofname, |
| const char *fname, |
| int dobraces, |
| int doeol) |
| { |
| struct xflags *p; |
| int first = 1; |
| |
| if (dobraces) |
| fputs("[", stdout); |
| for (p = xflags; p->flag; p++) { |
| if (flags & p->flag) { |
| if (verbose) { |
| if (first) |
| first = 0; |
| else |
| fputs(", ", stdout); |
| fputs(p->longname, stdout); |
| } else { |
| fputs(p->shortname, stdout); |
| } |
| } else if (!verbose) { |
| fputs("-", stdout); |
| } |
| } |
| if (dobraces) |
| fputs("]", stdout); |
| if (dofname) |
| printf(" %s ", fname); |
| if (doeol) |
| fputs("\n", stdout); |
| } |
| |
| static int |
| lsattr_callback( |
| const char *path, |
| const struct stat *stat, |
| int status, |
| struct FTW *data) |
| { |
| struct fsxattr fsx; |
| int fd; |
| |
| if (recurse_dir && !S_ISDIR(stat->st_mode)) |
| return 0; |
| |
| if ((fd = open(path, O_RDONLY)) == -1) { |
| fprintf(stderr, _("%s: cannot open %s: %s\n"), |
| progname, path, strerror(errno)); |
| exitcode = 1; |
| } else if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) { |
| fprintf(stderr, _("%s: cannot get flags on %s: %s\n"), |
| progname, path, strerror(errno)); |
| exitcode = 1; |
| } else |
| printxattr(fsx.fsx_xflags, 0, 1, path, 0, 1); |
| |
| if (fd != -1) |
| close(fd); |
| return 0; |
| } |
| |
| static int |
| lsattr_f( |
| int argc, |
| char **argv) |
| { |
| struct fsxattr fsx; |
| char *name = file->name; |
| int c, aflag = 0, vflag = 0; |
| |
| recurse_all = recurse_dir = 0; |
| while ((c = getopt(argc, argv, "DRav")) != EOF) { |
| switch (c) { |
| case 'D': |
| recurse_dir = 1; |
| break; |
| case 'R': |
| recurse_all = 1; |
| break; |
| case 'a': |
| aflag = 1; |
| break; |
| case 'v': |
| vflag = 1; |
| break; |
| default: |
| exitcode = 1; |
| return command_usage(&lsattr_cmd); |
| } |
| } |
| |
| if (recurse_all || recurse_dir) { |
| nftw(name, lsattr_callback, |
| 100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH); |
| } else if ((xfsctl(name, file->fd, FS_IOC_FSGETXATTR, &fsx)) < 0) { |
| fprintf(stderr, _("%s: cannot get flags on %s: %s\n"), |
| progname, name, strerror(errno)); |
| exitcode = 1; |
| } else { |
| printxattr(fsx.fsx_xflags, vflag, !aflag, name, vflag, !aflag); |
| if (aflag) { |
| fputs("/", stdout); |
| printxattr(-1, 0, 1, name, 0, 1); |
| } |
| } |
| return 0; |
| } |
| |
| static int |
| chattr_callback( |
| const char *path, |
| const struct stat *stat, |
| int status, |
| struct FTW *data) |
| { |
| struct fsxattr attr; |
| int fd; |
| |
| if (recurse_dir && !S_ISDIR(stat->st_mode)) |
| return 0; |
| |
| if ((fd = open(path, O_RDONLY)) == -1) { |
| fprintf(stderr, _("%s: cannot open %s: %s\n"), |
| progname, path, strerror(errno)); |
| exitcode = 1; |
| } else if (xfsctl(path, fd, FS_IOC_FSGETXATTR, &attr) < 0) { |
| fprintf(stderr, _("%s: cannot get flags on %s: %s\n"), |
| progname, path, strerror(errno)); |
| exitcode = 1; |
| } else { |
| attr.fsx_xflags |= orflags; |
| attr.fsx_xflags &= ~andflags; |
| if (xfsctl(path, fd, FS_IOC_FSSETXATTR, &attr) < 0) { |
| fprintf(stderr, _("%s: cannot set flags on %s: %s\n"), |
| progname, path, strerror(errno)); |
| exitcode = 1; |
| } |
| } |
| |
| if (fd != -1) |
| close(fd); |
| return 0; |
| } |
| |
| static int |
| chattr_f( |
| int argc, |
| char **argv) |
| { |
| struct fsxattr attr; |
| struct xflags *p; |
| unsigned int i = 0; |
| char *c, *name = file->name; |
| |
| orflags = andflags = 0; |
| recurse_all = recurse_dir = 0; |
| while (++i < argc) { |
| if (argv[i][0] == '-' && argv[i][1] == 'R') { |
| recurse_all = 1; |
| } else if (argv[i][0] == '-' && argv[i][1] == 'D') { |
| recurse_dir = 1; |
| } else if (argv[i][0] == '+') { |
| for (c = &argv[i][1]; *c; c++) { |
| for (p = xflags; p->flag; p++) { |
| if (strncmp(p->shortname, c, 1) == 0) { |
| orflags |= p->flag; |
| break; |
| } |
| } |
| if (!p->flag) { |
| fprintf(stderr, _("%s: unknown flag\n"), |
| progname); |
| exitcode = 1; |
| return 0; |
| } |
| } |
| } else if (argv[i][0] == '-') { |
| for (c = &argv[i][1]; *c; c++) { |
| for (p = xflags; p->flag; p++) { |
| if (strncmp(p->shortname, c, 1) == 0) { |
| andflags |= p->flag; |
| break; |
| } |
| } |
| if (!p->flag) { |
| fprintf(stderr, _("%s: unknown flag\n"), |
| progname); |
| exitcode = 1; |
| return 0; |
| } |
| } |
| } else { |
| fprintf(stderr, _("%s: bad chattr command, not +/-X\n"), |
| progname); |
| exitcode = 1; |
| return 0; |
| } |
| } |
| |
| if (recurse_all && recurse_dir) { |
| fprintf(stderr, _("%s: -R and -D options are mutually exclusive\n"), |
| progname); |
| exitcode = 1; |
| return 0; |
| } |
| |
| if (recurse_all || recurse_dir) { |
| nftw(name, chattr_callback, |
| 100, FTW_PHYS | FTW_MOUNT | FTW_DEPTH); |
| } else if (xfsctl(name, file->fd, FS_IOC_FSGETXATTR, &attr) < 0) { |
| fprintf(stderr, _("%s: cannot get flags on %s: %s\n"), |
| progname, name, strerror(errno)); |
| exitcode = 1; |
| } else { |
| attr.fsx_xflags |= orflags; |
| attr.fsx_xflags &= ~andflags; |
| if (xfsctl(name, file->fd, FS_IOC_FSSETXATTR, &attr) < 0) { |
| fprintf(stderr, _("%s: cannot set flags on %s: %s\n"), |
| progname, name, strerror(errno)); |
| exitcode = 1; |
| } |
| } |
| return 0; |
| } |
| |
| void |
| attr_init(void) |
| { |
| chattr_cmd.name = "chattr"; |
| chattr_cmd.cfunc = chattr_f; |
| chattr_cmd.args = _("[-R|-D] [+/-"CHATTR_XFLAG_LIST"]"); |
| chattr_cmd.argmin = 1; |
| chattr_cmd.argmax = -1; |
| chattr_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK; |
| chattr_cmd.oneline = |
| _("change extended inode flags on the currently open file"); |
| chattr_cmd.help = chattr_help; |
| |
| lsattr_cmd.name = "lsattr"; |
| lsattr_cmd.cfunc = lsattr_f; |
| lsattr_cmd.args = _("[-R|-D|-a|-v]"); |
| lsattr_cmd.argmin = 0; |
| lsattr_cmd.argmax = 1; |
| lsattr_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK; |
| lsattr_cmd.oneline = |
| _("list extended inode flags set on the currently open file"); |
| lsattr_cmd.help = lsattr_help; |
| |
| add_command(&chattr_cmd); |
| add_command(&lsattr_cmd); |
| } |