| /* lsattr.c - List file attributes on a Linux second extended file system. |
| * |
| * Copyright 2013 Ranjan Kumar <ranjankumar.bth@gmail.com> |
| * Copyright 2013 Kyungwan Han <asura321@gmail.com> |
| * |
| * 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 <linux/fs.h> |
| |
| 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); |
| } |