busybox/e2fsprogs/chattr.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * chattr.c             - Change file attributes on an ext2 file system
   4 *
   5 * Copyright (C) 1993, 1994  Remy Card <card@masi.ibp.fr>
   6 *                           Laboratoire MASI, Institut Blaise Pascal
   7 *                           Universite Pierre et Marie Curie (Paris VI)
   8 *
   9 * This file can be redistributed under the terms of the GNU General
  10 * Public License
  11 */
  12//config:config CHATTR
  13//config:       bool "chattr (3.8 kb)"
  14//config:       default y
  15//config:       help
  16//config:       chattr changes the file attributes on a second extended file system.
  17
  18//applet:IF_CHATTR(APPLET_NOEXEC(chattr, chattr, BB_DIR_BIN, BB_SUID_DROP, chattr))
  19
  20//kbuild:lib-$(CONFIG_CHATTR) += chattr.o e2fs_lib.o
  21
  22//usage:#define chattr_trivial_usage
  23//usage:       "[-R] [-v VERSION] [-p PROJID] [-+=AacDdijsStTu] FILE..."
  24//usage:#define chattr_full_usage "\n\n"
  25//usage:       "Change ext2 file attributes\n"
  26//usage:     "\n        -R      Recurse"
  27//usage:     "\n        -v NUM  Set version/generation number"
  28//usage:     "\n        -p NUM  Set project number"
  29//-V, -f accepted but ignored
  30//usage:     "\nModifiers:"
  31//usage:     "\n        -,+,=   Remove/add/set attributes"
  32//usage:     "\nAttributes:"
  33//usage:     "\n        A       No atime"
  34//usage:     "\n        a       Append only"
  35//usage:     "\n        C       No copy-on-write"
  36//usage:     "\n        c       Compressed"
  37//usage:     "\n        D       Synchronous dir updates"
  38//usage:     "\n        d       Don't backup with dump"
  39//usage:     "\n        E       Encrypted"
  40//usage:     "\n        e       File uses extents"
  41//usage:     "\n        F       Case-insensitive dir"
  42//usage:     "\n        I       Indexed dir"
  43//usage:     "\n        i       Immutable"
  44//usage:     "\n        j       Write data to journal first"
  45//usage:     "\n        N       File is stored in inode"
  46//usage:     "\n        P       Hierarchical project ID dir"
  47//usage:     "\n        S       Synchronous file updates"
  48//usage:     "\n        s       Zero storage when deleted"
  49//usage:     "\n        T       Top of dir hierarchy"
  50//usage:     "\n        t       Don't tail-merge with other files"
  51//usage:     "\n        u       Allow undelete"
  52//usage:     "\n        V       Verity"
  53
  54#include "libbb.h"
  55#include "e2fs_lib.h"
  56
  57#define OPT_ADD      (1 << 0)
  58#define OPT_REM      (1 << 1)
  59#define OPT_SET      (1 << 2)
  60#define OPT_SET_VER  (1 << 3)
  61#define OPT_SET_PROJ (1 << 4)
  62
  63struct globals {
  64        unsigned version;
  65        unsigned af;
  66        unsigned rf;
  67        int flags;
  68        uint32_t projid;
  69        smallint recursive;
  70};
  71
  72static unsigned long get_flag(char c)
  73{
  74        const char *fp = strchr(e2attr_flags_sname_chattr, c);
  75        if (fp)
  76                return e2attr_flags_value_chattr[fp - e2attr_flags_sname_chattr];
  77        bb_show_usage();
  78}
  79
  80static char** decode_arg(char **argv, struct globals *gp)
  81{
  82        unsigned *fl;
  83        const char *arg = *argv;
  84        char opt = *arg;
  85
  86        fl = &gp->af;
  87        if (opt == '-') {
  88                /* gp->flags |= OPT_REM; - WRONG, it can be an option */
  89                /* testcase: chattr =ae -R FILE should not complain "= is incompatible with - and +" */
  90                /* (and should not read flags, with =FLAGS they can be just set directly) */
  91                fl = &gp->rf;
  92        } else if (opt == '+') {
  93                gp->flags |= OPT_ADD;
  94        } else { /* if (opt == '=') */
  95                gp->flags |= OPT_SET;
  96        }
  97
  98        while (*++arg) {
  99                if (opt == '-') {
 100//e2fsprogs-1.43.1 accepts:
 101// "-RRR", "-RRRv VER" and even "-ARRRva VER" and "-vvv V1 V2 V3"
 102// but not "-vVER".
 103// IOW: options are parsed as part of "remove attrs" strings,
 104// if "v" is seen, next argv[] is VER, even if more opts/attrs follow in this argv[]!
 105                        if (*arg == 'R') {
 106                                gp->recursive = 1;
 107                                continue;
 108                        }
 109                        if (*arg == 'V') {
 110                                /*"verbose and print program version" (nop for now) */;
 111                                continue;
 112                        }
 113                        if (*arg == 'f') {
 114                                /*"suppress most error messages" (nop) */;
 115                                continue;
 116                        }
 117                        if (*arg == 'v') {
 118                                if (!*++argv)
 119                                        bb_show_usage();
 120                                gp->version = xatou(*argv);
 121                                gp->flags |= OPT_SET_VER;
 122                                continue;
 123                        }
 124                        if (*arg == 'p') {
 125                                if (!*++argv)
 126                                        bb_show_usage();
 127                                gp->projid = xatou32(*argv);
 128                                gp->flags |= OPT_SET_PROJ;
 129                                continue;
 130                        }
 131                        /* not a known option, try as an attribute */
 132                        gp->flags |= OPT_REM;
 133                }
 134                *fl |= get_flag(*arg); /* aborts on bad flag letter */
 135        }
 136
 137        return argv;
 138}
 139
 140static void change_attributes(const char *name, struct globals *gp);
 141
 142static int FAST_FUNC chattr_dir_proc(const char *dir_name, struct dirent *de, void *gp)
 143{
 144//TODO: use de->d_type (if it's not DT_UNKNOWN) to skip !(REG || DIR || LNK) entries without lstat?
 145
 146        char *path = concat_subpath_file(dir_name, de->d_name);
 147        /* path is NULL if de->d_name is "." or "..", else... */
 148        if (path) {
 149                change_attributes(path, gp);
 150                free(path);
 151        }
 152        return 0;
 153}
 154
 155static void change_attributes(const char *name, struct globals *gp)
 156{
 157        unsigned fsflags;
 158        int fd;
 159        struct stat st;
 160
 161        if (lstat(name, &st) != 0) {
 162                bb_perror_msg("can't stat '%s'", name);
 163                return;
 164        }
 165
 166        /* Don't try to open device files, fifos etc.  We probably
 167         * ought to display an error if the file was explicitly given
 168         * on the command line (whether or not recursive was
 169         * requested).  */
 170        if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode) && !S_ISDIR(st.st_mode))
 171                return;
 172
 173        /* There is no way to run needed ioctls on a symlink.
 174         * open(O_PATH | O_NOFOLLOW) _can_ be used to get a fd referring to the symlink,
 175         * but ioctls fail on such a fd (tried on 4.12.0 kernel).
 176         * e2fsprogs-1.46.2 uses open(O_NOFOLLOW), it fails on symlinks.
 177         */
 178        fd = open_or_warn(name, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_NOFOLLOW);
 179        if (fd >= 0) {
 180                int r;
 181
 182                if (gp->flags & OPT_SET_VER) {
 183                        r = ioctl(fd, EXT2_IOC_SETVERSION, &gp->version);
 184                        if (r != 0)
 185                                bb_perror_msg("setting %s on %s", "version", name);
 186                }
 187
 188                if (gp->flags & OPT_SET_PROJ) {
 189                        struct ext2_fsxattr fsxattr;
 190                        r = ioctl(fd, EXT2_IOC_FSGETXATTR, &fsxattr);
 191                        /* note: ^^^ may fail in 32-bit userspace on 64-bit kernel (seen on 4.12.0) */
 192                        if (r != 0) {
 193                                bb_perror_msg("getting %s on %s", "project ID", name);
 194                        } else {
 195                                fsxattr.fsx_projid = gp->projid;
 196                                r = ioctl(fd, EXT2_IOC_FSSETXATTR, &fsxattr);
 197                                if (r != 0)
 198                                        bb_perror_msg("setting %s on %s", "project ID", name);
 199                        }
 200                }
 201
 202                if (gp->flags & OPT_SET) {
 203                        fsflags = gp->af;
 204                } else {
 205                        r = ioctl(fd, EXT2_IOC_GETFLAGS, &fsflags);
 206                        if (r != 0) {
 207                                bb_perror_msg("getting %s on %s", "flags", name);
 208                                goto skip_setflags;
 209                        }
 210                        /*if (gp->flags & OPT_REM) - not needed, rf is zero otherwise */
 211                                fsflags &= ~gp->rf;
 212                        /*if (gp->flags & OPT_ADD) - not needed, af is zero otherwise */
 213                                fsflags |= gp->af;
 214// What is this? And why it's not done for SET case?
 215                        if (!S_ISDIR(st.st_mode))
 216                                fsflags &= ~EXT2_DIRSYNC_FL;
 217                }
 218                r = ioctl(fd, EXT2_IOC_SETFLAGS, &fsflags);
 219                if (r != 0)
 220                        bb_perror_msg("setting %s on %s", "flags", name);
 221 skip_setflags:
 222                close(fd);
 223        }
 224
 225        if (gp->recursive && S_ISDIR(st.st_mode))
 226                iterate_on_dir(name, chattr_dir_proc, gp);
 227}
 228
 229int chattr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 230int chattr_main(int argc UNUSED_PARAM, char **argv)
 231{
 232        struct globals g;
 233
 234        memset(&g, 0, sizeof(g));
 235
 236        /* parse the args */
 237        for (;;) {
 238                char *arg = *++argv;
 239                if (!arg)
 240                        bb_show_usage();
 241                if (arg[0] != '-' && arg[0] != '+' && arg[0] != '=')
 242                        break;
 243
 244                argv = decode_arg(argv, &g);
 245        }
 246        /* note: on loop exit, remaining argv[] is never empty */
 247
 248        /* run sanity checks on all the arguments given us */
 249        if ((g.flags & OPT_SET) && (g.flags & (OPT_ADD|OPT_REM)))
 250                bb_simple_error_msg_and_die("= is incompatible with - and +");
 251        if (g.rf & g.af)
 252                bb_simple_error_msg_and_die("can't set and unset a flag");
 253        if (!g.flags)
 254                bb_simple_error_msg_and_die("must use -v, -p, =, - or +");
 255
 256        /* now run chattr on all the files passed to us */
 257        do change_attributes(*argv, &g); while (*++argv);
 258
 259        return EXIT_SUCCESS;
 260}
 261