blob: f436cd91e431ecd66119207fe247e6b58e9478d8 [file] [log] [blame]
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +00001/* 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 */
Denys Vlasenko000eda42015-10-18 22:40:23 +020012//config:config CHATTR
Denys Vlasenkob097a842018-12-28 03:20:17 +010013//config: bool "chattr (3.8 kb)"
Denys Vlasenko000eda42015-10-18 22:40:23 +020014//config: default y
15//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020016//config: chattr changes the file attributes on a second extended file system.
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000017
Denys Vlasenko99125c02017-08-05 20:38:04 +020018//applet:IF_CHATTR(APPLET_NOEXEC(chattr, chattr, BB_DIR_BIN, BB_SUID_DROP, chattr))
Denys Vlasenko000eda42015-10-18 22:40:23 +020019
20//kbuild:lib-$(CONFIG_CHATTR) += chattr.o e2fs_lib.o
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000021
Pere Orga6a3e01d2011-04-01 22:56:30 +020022//usage:#define chattr_trivial_usage
Denys Vlasenko526b8342021-06-20 10:57:24 +020023//usage: "[-R] [-v VERSION] [-p PROJID] [-+=AacDdijsStTu] FILE..."
Pere Orga6a3e01d2011-04-01 22:56:30 +020024//usage:#define chattr_full_usage "\n\n"
Denys Vlasenko36647ab2015-10-19 01:29:20 +020025//usage: "Change ext2 file attributes\n"
Denys Vlasenkod3147cd2017-08-05 20:33:48 +020026//usage: "\n -R Recurse"
Denys Vlasenkoe2b92152021-06-14 20:47:20 +020027//usage: "\n -v NUM Set version/generation number"
Denys Vlasenko526b8342021-06-20 10:57:24 +020028//usage: "\n -p NUM Set project number"
Denys Vlasenkod3147cd2017-08-05 20:33:48 +020029//-V, -f accepted but ignored
Pere Orga6a3e01d2011-04-01 22:56:30 +020030//usage: "\nModifiers:"
Denys Vlasenko36647ab2015-10-19 01:29:20 +020031//usage: "\n -,+,= Remove/add/set attributes"
Pere Orga6a3e01d2011-04-01 22:56:30 +020032//usage: "\nAttributes:"
Denys Vlasenkoe2b92152021-06-14 20:47:20 +020033//usage: "\n A No atime"
34//usage: "\n a Append only"
Denys Vlasenkoe7ff0172021-06-23 09:42:01 +020035//usage: "\n C No copy-on-write"
Denys Vlasenkoe2b92152021-06-14 20:47:20 +020036//usage: "\n c Compressed"
37//usage: "\n D Synchronous dir updates"
Pere Orga6a3e01d2011-04-01 22:56:30 +020038//usage: "\n d Don't backup with dump"
Denys Vlasenkoe7ff0172021-06-23 09:42:01 +020039//usage: "\n E Encrypted"
40//usage: "\n e File uses extents"
41//usage: "\n F Case-insensitive dir"
42//usage: "\n I Indexed dir"
Denys Vlasenkoe2b92152021-06-14 20:47:20 +020043//usage: "\n i Immutable"
44//usage: "\n j Write data to journal first"
Denys Vlasenkoe7ff0172021-06-23 09:42:01 +020045//usage: "\n N File is stored in inode"
46//usage: "\n P Hierarchical project ID dir"
Denys Vlasenkoe2b92152021-06-14 20:47:20 +020047//usage: "\n S Synchronous file updates"
Denys Vlasenkoe7ff0172021-06-23 09:42:01 +020048//usage: "\n s Zero storage when deleted"
49//usage: "\n T Top of dir hierarchy"
Denys Vlasenkoe2b92152021-06-14 20:47:20 +020050//usage: "\n t Don't tail-merge with other files"
51//usage: "\n u Allow undelete"
Denys Vlasenkoe7ff0172021-06-23 09:42:01 +020052//usage: "\n V Verity"
Pere Orga6a3e01d2011-04-01 22:56:30 +020053
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000054#include "libbb.h"
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000055#include "e2fs_lib.h"
56
Denys Vlasenko526b8342021-06-20 10:57:24 +020057#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)
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000062
Denis Vlasenko8acf5212007-04-15 11:48:27 +000063struct globals {
Denys Vlasenko96436fb2021-06-23 12:45:51 +020064 unsigned version;
65 unsigned af;
66 unsigned rf;
Denys Vlasenkod3147cd2017-08-05 20:33:48 +020067 int flags;
Denys Vlasenko526b8342021-06-20 10:57:24 +020068 uint32_t projid;
Denis Vlasenko8acf5212007-04-15 11:48:27 +000069 smallint recursive;
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000070};
71
72static unsigned long get_flag(char c)
73{
Denis Vlasenkod059ddc2007-10-30 19:36:07 +000074 const char *fp = strchr(e2attr_flags_sname_chattr, c);
75 if (fp)
76 return e2attr_flags_value_chattr[fp - e2attr_flags_sname_chattr];
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000077 bb_show_usage();
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000078}
79
Denys Vlasenkod3147cd2017-08-05 20:33:48 +020080static char** decode_arg(char **argv, struct globals *gp)
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000081{
Denys Vlasenko96436fb2021-06-23 12:45:51 +020082 unsigned *fl;
Denys Vlasenkod3147cd2017-08-05 20:33:48 +020083 const char *arg = *argv;
84 char opt = *arg;
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000085
Denis Vlasenko8acf5212007-04-15 11:48:27 +000086 fl = &gp->af;
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000087 if (opt == '-') {
Denys Vlasenko5709b512021-06-23 18:57:00 +020088 /* 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) */
Denis Vlasenko8acf5212007-04-15 11:48:27 +000091 fl = &gp->rf;
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000092 } else if (opt == '+') {
Denis Vlasenko8acf5212007-04-15 11:48:27 +000093 gp->flags |= OPT_ADD;
Denys Vlasenkod3147cd2017-08-05 20:33:48 +020094 } else { /* if (opt == '=') */
Denis Vlasenko8acf5212007-04-15 11:48:27 +000095 gp->flags |= OPT_SET;
Denys Vlasenkod3147cd2017-08-05 20:33:48 +020096 }
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +000097
Denys Vlasenkod3147cd2017-08-05 20:33:48 +020098 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();
Denys Vlasenko5709b512021-06-23 18:57:00 +0200120 gp->version = xatou(*argv);
Denys Vlasenkod3147cd2017-08-05 20:33:48 +0200121 gp->flags |= OPT_SET_VER;
122 continue;
123 }
Denys Vlasenko526b8342021-06-20 10:57:24 +0200124 if (*arg == 'p') {
125 if (!*++argv)
126 bb_show_usage();
127 gp->projid = xatou32(*argv);
128 gp->flags |= OPT_SET_PROJ;
129 continue;
130 }
Denys Vlasenkod3147cd2017-08-05 20:33:48 +0200131 /* not a known option, try as an attribute */
Denys Vlasenko5709b512021-06-23 18:57:00 +0200132 gp->flags |= OPT_REM;
Denys Vlasenkod3147cd2017-08-05 20:33:48 +0200133 }
Denys Vlasenko5709b512021-06-23 18:57:00 +0200134 *fl |= get_flag(*arg); /* aborts on bad flag letter */
Denys Vlasenkod3147cd2017-08-05 20:33:48 +0200135 }
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000136
Denys Vlasenkod3147cd2017-08-05 20:33:48 +0200137 return argv;
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000138}
139
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000140static void change_attributes(const char *name, struct globals *gp);
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000141
Denys Vlasenkod5f1b1b2009-06-05 12:06:05 +0200142static int FAST_FUNC chattr_dir_proc(const char *dir_name, struct dirent *de, void *gp)
Denis Vlasenko5dd7ef02006-12-26 03:36:28 +0000143{
Denys Vlasenko0e55af62021-06-23 19:44:15 +0200144//TODO: use de->d_type (if it's not DT_UNKNOWN) to skip !(REG || DIR || LNK) entries without lstat?
145
Denis Vlasenko5dd7ef02006-12-26 03:36:28 +0000146 char *path = concat_subpath_file(dir_name, de->d_name);
147 /* path is NULL if de->d_name is "." or "..", else... */
148 if (path) {
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000149 change_attributes(path, gp);
Denis Vlasenko5dd7ef02006-12-26 03:36:28 +0000150 free(path);
151 }
152 return 0;
153}
154
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000155static void change_attributes(const char *name, struct globals *gp)
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000156{
Denys Vlasenko96436fb2021-06-23 12:45:51 +0200157 unsigned fsflags;
158 int fd;
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000159 struct stat st;
160
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000161 if (lstat(name, &st) != 0) {
Denys Vlasenko85a5bc92021-06-23 15:33:22 +0200162 bb_perror_msg("can't stat '%s'", name);
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000163 return;
164 }
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000165
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
Denys Vlasenko9468ea02021-06-24 01:07:56 +0200173 /* 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);
Denys Vlasenko96436fb2021-06-23 12:45:51 +0200179 if (fd >= 0) {
180 int r;
Denys Vlasenko526b8342021-06-20 10:57:24 +0200181
Denys Vlasenko96436fb2021-06-23 12:45:51 +0200182 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);
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000186 }
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000187
Denys Vlasenko96436fb2021-06-23 12:45:51 +0200188 if (gp->flags & OPT_SET_PROJ) {
189 struct ext2_fsxattr fsxattr;
190 r = ioctl(fd, EXT2_IOC_FSGETXATTR, &fsxattr);
Denys Vlasenko85a5bc92021-06-23 15:33:22 +0200191 /* note: ^^^ may fail in 32-bit userspace on 64-bit kernel (seen on 4.12.0) */
Denys Vlasenko0e55af62021-06-23 19:44:15 +0200192 if (r != 0) {
Denys Vlasenko96436fb2021-06-23 12:45:51 +0200193 bb_perror_msg("getting %s on %s", "project ID", name);
Denys Vlasenko0e55af62021-06-23 19:44:15 +0200194 } 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 }
Denys Vlasenko96436fb2021-06-23 12:45:51 +0200200 }
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);
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000221 skip_setflags:
Denys Vlasenko96436fb2021-06-23 12:45:51 +0200222 close(fd);
223 }
224
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000225 if (gp->recursive && S_ISDIR(st.st_mode))
226 iterate_on_dir(name, chattr_dir_proc, gp);
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000227}
228
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000229int chattr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000230int chattr_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000231{
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000232 struct globals g;
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000233
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000234 memset(&g, 0, sizeof(g));
235
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000236 /* parse the args */
Denys Vlasenkod3147cd2017-08-05 20:33:48 +0200237 for (;;) {
238 char *arg = *++argv;
239 if (!arg)
240 bb_show_usage();
241 if (arg[0] != '-' && arg[0] != '+' && arg[0] != '=')
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000242 break;
Denys Vlasenkod3147cd2017-08-05 20:33:48 +0200243
244 argv = decode_arg(argv, &g);
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000245 }
Denys Vlasenkod3147cd2017-08-05 20:33:48 +0200246 /* note: on loop exit, remaining argv[] is never empty */
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000247
248 /* run sanity checks on all the arguments given us */
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000249 if ((g.flags & OPT_SET) && (g.flags & (OPT_ADD|OPT_REM)))
James Byrne69374872019-07-02 11:35:03 +0200250 bb_simple_error_msg_and_die("= is incompatible with - and +");
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000251 if (g.rf & g.af)
James Byrne69374872019-07-02 11:35:03 +0200252 bb_simple_error_msg_and_die("can't set and unset a flag");
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000253 if (!g.flags)
Denys Vlasenko5709b512021-06-23 18:57:00 +0200254 bb_simple_error_msg_and_die("must use -v, -p, =, - or +");
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000255
256 /* now run chattr on all the files passed to us */
Denis Vlasenko8acf5212007-04-15 11:48:27 +0000257 do change_attributes(*argv, &g); while (*++argv);
Denis Vlasenkoc4f623e2006-12-26 01:30:59 +0000258
259 return EXIT_SUCCESS;
260}