/* lsattr.c - List file attributes on a Linux second extended file system. * * Copyright 2013 Ranjan Kumar * Copyright 2013 Kyungwan Han * * No Standard. * * TODO cleanup USE_LSATTR(NEWTOY(lsattr, "ldapvR", TOYFLAG_BIN)) USE_CHATTR(NEWTOY(chattr, "?p#v#R", TOYFLAG_BIN)) config LSATTR bool "lsattr" default y help usage: lsattr [-Radlpv] [FILE...] List file attributes on a Linux file system. Flag letters are defined in chattr help. -R Recursively list attributes of directories and their contents -a List all files in directories, including files that start with '.' -d List directories like other files, rather than listing their contents -l List long flag names -p List the file's project number -v List the file's version/generation number config CHATTR bool "chattr" default y help usage: chattr [-R] [-+=AacDdijsStTu] [-p PROJID] [-v VERSION] [FILE...] Change file attributes on a Linux file system. -R Recurse -p Set the file's project number -v Set the file's version/generation number Operators: '-' Remove attributes '+' Add attributes '=' Set attributes Attributes: A No atime a Append only C No COW c Compression D Synchronous dir updates d No dump E Encrypted e Extents F Case-insensitive (casefold) I Indexed directory i Immutable j Journal data N Inline data in inode P Project hierarchy S Synchronous file updates s Secure delete T Top of dir hierarchy t No tail-merging u Allow undelete V Verity */ #define FOR_lsattr #include "toys.h" #include GLOBALS( long v; long p; long add, rm, set; // !add and !rm tell us whether they were used, but `chattr =` is meaningful. int have_set; ) #define FS_PROJINHERT_FL 0x20000000 // Linux 4.5 #define FS_CASEFOLD_FL 0x40000000 // Linux 5.4 #define FS_VERITY_FL 0x00100000 // Linux 5.4 // Linux 4.5 struct fsxattr_4_5 { unsigned fsx_xflags; unsigned fsx_extsize; unsigned fsx_nextents; unsigned fsx_projid; unsigned fsx_cowextsize; char fsx_pad[8]; }; #define FS_IOC_FSGETXATTR_4_5 _IOR('X', 31, struct fsxattr_4_5) #define FS_IOC_FSSETXATTR_4_5 _IOW('X', 32, struct fsxattr_4_5) static struct ext2_attr { char *name; unsigned long flag; char opt; } e2attrs[] = { // Do not sort! These are in the order that lsattr outputs them. {"Secure_Deletion", FS_SECRM_FL, 's'}, {"Undelete", FS_UNRM_FL, 'u'}, {"Synchronous_Updates", FS_SYNC_FL, 'S'}, {"Synchronous_Directory_Updates", FS_DIRSYNC_FL, 'D'}, {"Immutable", FS_IMMUTABLE_FL, 'i'}, {"Append_Only", FS_APPEND_FL, 'a'}, {"No_Dump", FS_NODUMP_FL, 'd'}, {"No_Atime", FS_NOATIME_FL, 'A'}, {"Compression_Requested", FS_COMPR_FL, 'c'}, // FS_ENCRYPT_FL added to linux 4.5 march 2016, +y7 = 2023 {"Encrypted", 0x800, 'E'}, {"Journaled_Data", FS_JOURNAL_DATA_FL, 'j'}, {"Indexed_directory", FS_INDEX_FL, 'I'}, {"No_Tailmerging", FS_NOTAIL_FL, 't'}, {"Top_of_Directory_Hierarchies", FS_TOPDIR_FL, 'T'}, {"Extents", FS_EXTENT_FL, 'e'}, {"No_COW", FS_NOCOW_FL, 'C'}, {"Casefold", FS_CASEFOLD_FL, 'F'}, {"Inline_Data", FS_INLINE_DATA_FL, 'N'}, {"Project_Hierarchy", FS_PROJINHERIT_FL, 'P'}, {"Verity", FS_VERITY_FL, 'V'}, {NULL, 0, 0}, }; // Get file flags on a Linux second extended file system. static int ext2_getflag(int fd, struct stat *sb, unsigned long *flag) { if(!S_ISREG(sb->st_mode) && !S_ISDIR(sb->st_mode)) { errno = EOPNOTSUPP; return -1; } return (ioctl(fd, FS_IOC_GETFLAGS, (void*)flag)); } static char *attrstr(unsigned long attrs, int full) { struct ext2_attr *a = e2attrs; char *s = toybuf; for (; a->name; a++) if (attrs & a->flag) *s++ = a->opt; else if (full) *s++ = '-'; *s = '\0'; return toybuf; } static void print_file_attr(char *path) { unsigned long flag = 0, version = 0; int fd; struct stat sb; if (!stat(path, &sb) && !S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) { errno = EOPNOTSUPP; goto LABEL1; } if (-1 == (fd=open(path, O_RDONLY | O_NONBLOCK))) goto LABEL1; if (FLAG(p)) { struct fsxattr_4_5 fsx; if (ioctl(fd, FS_IOC_FSGETXATTR_4_5, &fsx)) goto LABEL2; xprintf("%5u ", fsx.fsx_projid); } if (FLAG(v)) { if (ioctl(fd, FS_IOC_GETVERSION, (void*)&version) < 0) goto LABEL2; xprintf("%-10lu ", version); } if (ext2_getflag(fd, &sb, &flag) < 0) perror_msg("reading flags '%s'", path); else { struct ext2_attr *ptr = e2attrs; if (FLAG(l)) { int name_found = 0; xprintf("%-50s ", path); for (; ptr->name; ptr++) { if (flag & ptr->flag) { if (name_found) xprintf(", "); //for formatting. xprintf("%s", ptr->name); name_found = 1; } } if (!name_found) xprintf("---"); xputc('\n'); } else xprintf("%s %s\n", attrstr(flag, 1), path); } xclose(fd); return; LABEL2: xclose(fd); LABEL1: perror_msg("reading '%s'", path); } // Get directory information. static int retell_dir(struct dirtree *root) { char *fpath = NULL; if (root->again) { xputc('\n'); return 0; } if (S_ISDIR(root->st.st_mode) && !root->parent) return (DIRTREE_RECURSE | DIRTREE_COMEAGAIN); fpath = dirtree_path(root, NULL); //Special case: with '-a' option and '.'/'..' also included in printing list. if ((root->name[0] != '.') || FLAG(a)) { print_file_attr(fpath); if (S_ISDIR(root->st.st_mode) && FLAG(R) && dirtree_notdotdot(root)) { xprintf("\n%s:\n", fpath); free(fpath); return (DIRTREE_RECURSE | DIRTREE_COMEAGAIN); } } free(fpath); return 0; } void lsattr_main(void) { if (!*toys.optargs) dirtree_read(".", retell_dir); else for (; *toys.optargs; toys.optargs++) { struct stat sb; if (lstat(*toys.optargs, &sb)) perror_msg("stat '%s'", *toys.optargs); else if (S_ISDIR(sb.st_mode) && !FLAG(d)) dirtree_read(*toys.optargs, retell_dir); else print_file_attr(*toys.optargs);// to handle "./Filename" or "./Dir" } } // Switch gears from lsattr to chattr. #define CLEANUP_lsattr #define FOR_chattr #include "generated/flags.h" // Set file flags on a Linux second extended file system. static inline int ext2_setflag(int fd, struct stat *sb, unsigned long flag) { if (!S_ISREG(sb->st_mode) && !S_ISDIR(sb->st_mode)) { errno = EOPNOTSUPP; return -1; } return (ioctl(fd, FS_IOC_SETFLAGS, (void*)&flag)); } static unsigned long get_flag_val(char ch) { struct ext2_attr *ptr = e2attrs; for (; ptr->name; ptr++) if (ptr->opt == ch) return ptr->flag; help_exit("bad '%c'", ch); } // Parse command line argument and fill the chattr structure. static void parse_cmdline_arg(char ***argv) { char *arg = **argv, *ptr; while (arg) { switch (arg[0]) { case '-': for (ptr = ++arg; *ptr; ptr++) TT.rm |= get_flag_val(*ptr); break; case '+': for (ptr = ++arg; *ptr; ptr++) TT.add |= get_flag_val(*ptr); break; case '=': TT.have_set = 1; for (ptr = ++arg; *ptr; ptr++) TT.set |= get_flag_val(*ptr); break; default: return; } arg = *(*argv += 1); } } // Update attribute of given file. static int update_attr(struct dirtree *root) { char *fpath = NULL; int v = TT.v, fd; if (!dirtree_notdotdot(root)) return 0; /* * if file is a link and recursive is set or file is not regular+link+dir * (like fifo or dev file) then escape the file. */ if ((S_ISLNK(root->st.st_mode) && FLAG(R)) || (!S_ISREG(root->st.st_mode) && !S_ISLNK(root->st.st_mode) && !S_ISDIR(root->st.st_mode))) return 0; fpath = dirtree_path(root, NULL); if (-1 == (fd=open(fpath, O_RDONLY | O_NONBLOCK))) { free(fpath); return DIRTREE_ABORT; } // Any potential flag changes? if (TT.have_set | TT.add | TT.rm) { unsigned long orig, new; // Read current flags. if (ext2_getflag(fd, &(root->st), &orig) < 0) { perror_msg("read flags of '%s'", fpath); free(fpath); xclose(fd); return DIRTREE_ABORT; } // Apply the requested changes. if (TT.have_set) new = TT.set; // '='. else { // '-' and/or '+'. new = orig; new &= ~(TT.rm); new |= TT.add; if (!S_ISDIR(root->st.st_mode)) new &= ~FS_DIRSYNC_FL; } // Write them back if there was any change. if (orig != new && ext2_setflag(fd, &(root->st), new)<0) perror_msg("%s: setting flags to =%s failed", fpath, attrstr(new, 0)); } // (FS_IOC_SETVERSION works all the way back to 2.6, but FS_IOC_FSSETXATTR // isn't available until 4.5.) if (FLAG(v) && (ioctl(fd, FS_IOC_SETVERSION, &v)<0)) perror_msg("%s: setting version to %d failed", fpath, v); if (FLAG(p)) { struct fsxattr_4_5 fsx; int fail = ioctl(fd, FS_IOC_FSGETXATTR_4_5, &fsx); fsx.fsx_projid = TT.p; if (fail || ioctl(fd, FS_IOC_FSSETXATTR_4_5, &fsx)) perror_msg("%s: setting projid to %u failed", fpath, fsx.fsx_projid); } free(fpath); xclose(fd); return (FLAG(R) && S_ISDIR(root->st.st_mode)) ? DIRTREE_RECURSE : 0; } void chattr_main(void) { char **argv = toys.optargs; parse_cmdline_arg(&argv); if (TT.p < 0 || TT.p > UINT_MAX) error_exit("bad projid %lu", TT.p); if (TT.v < 0 || TT.v > UINT_MAX) error_exit("bad version %ld", TT.v); if (!*argv) help_exit("no file"); if (TT.have_set && (TT.add || TT.rm)) error_exit("no '=' with '-' or '+'"); if (TT.rm & TT.add) error_exit("set/unset same flag"); if (!(TT.add || TT.rm || TT.have_set || FLAG(p) || FLAG(v))) error_exit("need '-p', '-v', '=', '-', or '+'"); for (; *argv; argv++) dirtree_read(*argv, update_attr); }