| // 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); | 
 | } |