toybox/toys/other/lsattr.c
<<
>>
Prefs
   1/* lsattr.c - List file attributes on a Linux second extended file system.
   2 *
   3 * Copyright 2013 Ranjan Kumar <ranjankumar.bth@gmail.com>
   4 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
   5 *
   6 * No Standard.
   7 *
   8 * TODO cleanup
   9
  10USE_LSATTR(NEWTOY(lsattr, "vldaR", TOYFLAG_BIN))
  11USE_CHATTR(NEWTOY(chattr, NULL, TOYFLAG_BIN))
  12
  13config LSATTR
  14  bool "lsattr"
  15  default y
  16  help
  17    usage: lsattr [-Radlv] [Files...]
  18
  19    List file attributes on a Linux second extended file system.
  20    (AacDdijsStu defined in chattr --help)
  21
  22    -R  Recursively list attributes of directories and their contents
  23    -a  List all files in directories, including files that start with '.'
  24    -d  List directories like other files, rather than listing their contents
  25    -l  List long flag names
  26    -v  List the file's version/generation number
  27
  28config CHATTR
  29  bool "chattr"
  30  default y
  31  help
  32    usage: chattr [-R] [-+=AacDdijsStTu] [-v version] [File...]
  33
  34    Change file attributes on a Linux second extended file system.
  35
  36    -R  Recurse
  37    -v  Set the file's version/generation number
  38
  39    Operators:
  40      '-' Remove attributes
  41      '+' Add attributes
  42      '=' Set attributes
  43
  44    Attributes:
  45      A  Don't track atime
  46      a  Append mode only
  47      c  Enable compress
  48      D  Write dir contents synchronously
  49      d  Don't backup with dump
  50      i  Cannot be modified (immutable)
  51      j  Write all data to journal first
  52      s  Zero disk storage when deleted
  53      S  Write file contents synchronously
  54      t  Disable tail-merging of partial blocks with other files
  55      u  Allow file to be undeleted
  56*/
  57#define FOR_lsattr
  58#include "toys.h"
  59#include <linux/fs.h>
  60
  61static struct ext2_attr {
  62  char *name;
  63  unsigned long flag;
  64  char opt;
  65} e2attrs[] = {
  66  {"Secure_Deletion",               FS_SECRM_FL,        's'}, // Secure deletion
  67  {"Undelete",                      FS_UNRM_FL,         'u'}, // Undelete
  68  {"Compression_Requested",         FS_COMPR_FL,        'c'}, // Compress file
  69  {"Synchronous_Updates",           FS_SYNC_FL,         'S'}, // Synchronous updates
  70  {"Immutable",                     FS_IMMUTABLE_FL,    'i'}, // Immutable file
  71  {"Append_Only",                   FS_APPEND_FL,       'a'}, // writes to file may only append
  72  {"No_Dump",                       FS_NODUMP_FL,       'd'}, // do not dump file
  73  {"No_Atime",                      FS_NOATIME_FL,      'A'}, // do not update atime
  74  {"Indexed_directory",             FS_INDEX_FL,        'I'}, // hash-indexed directory
  75  {"Journaled_Data",                FS_JOURNAL_DATA_FL, 'j'}, // file data should be journaled
  76  {"No_Tailmerging",                FS_NOTAIL_FL,       't'}, // file tail should not be merged
  77  {"Synchronous_Directory_Updates", FS_DIRSYNC_FL,      'D'}, // dirsync behaviour (directories only)
  78  {"Top_of_Directory_Hierarchies",  FS_TOPDIR_FL,       'T'}, // Top of directory hierarchies
  79  {NULL,                            -1,                   0},
  80};
  81
  82// Get file flags on a Linux second extended file system.
  83static int ext2_getflag(int fd, struct stat *sb, unsigned long *flag)
  84{
  85  if(!S_ISREG(sb->st_mode) && !S_ISDIR(sb->st_mode)) {
  86    errno = EOPNOTSUPP;
  87    return -1;
  88  }
  89  return (ioctl(fd, FS_IOC_GETFLAGS, (void*)flag));
  90}
  91
  92static void print_file_attr(char *path)
  93{
  94  unsigned long flag = 0, version = 0;
  95  int fd;
  96  struct stat sb;
  97
  98  if (!stat(path, &sb) && !S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) {
  99    errno = EOPNOTSUPP;
 100    goto LABEL1;
 101  }
 102  if (-1 == (fd=open(path, O_RDONLY | O_NONBLOCK))) goto LABEL1;
 103
 104  if (toys.optflags & FLAG_v) { 
 105    if (ioctl(fd, FS_IOC_GETVERSION, (void*)&version) < 0) goto LABEL2;
 106    xprintf("%5lu ", version);
 107  }
 108
 109  if (ext2_getflag(fd, &sb, &flag) < 0) perror_msg("reading flags '%s'", path);
 110  else {
 111    struct ext2_attr *ptr = e2attrs;
 112
 113    if (toys.optflags & FLAG_l) {
 114      int name_found = 0;
 115
 116      xprintf("%-50s ", path);
 117      for (; ptr->name; ptr++) {
 118        if (flag & ptr->flag) {
 119          if (name_found) xprintf(", "); //for formatting.
 120          xprintf("%s", ptr->name);
 121          name_found = 1;
 122        }
 123      }
 124      if (!name_found) xprintf("---");
 125      xputc('\n');
 126    } else {
 127      int index = 0;
 128
 129      for (; ptr->name; ptr++)
 130        toybuf[index++] = (flag & ptr->flag) ? ptr->opt : '-';
 131      toybuf[index] = '\0';
 132      xprintf("%s %s\n", toybuf, path);
 133    }
 134  }
 135  xclose(fd);
 136  return;
 137LABEL2: xclose(fd);
 138LABEL1: perror_msg("reading '%s'", path);
 139}
 140
 141// Get directory information.
 142static int retell_dir(struct dirtree *root)
 143{
 144  char *fpath = NULL;
 145  
 146  if (root->again) {
 147    xputc('\n');
 148    return 0;
 149  }
 150  if (S_ISDIR(root->st.st_mode) && !root->parent) 
 151    return (DIRTREE_RECURSE | DIRTREE_COMEAGAIN);
 152
 153  fpath = dirtree_path(root, NULL);
 154  //Special case: with '-a' option and '.'/'..' also included in printing list.
 155  if ((root->name[0] != '.') || (toys.optflags & FLAG_a)) {
 156    print_file_attr(fpath);
 157    if (S_ISDIR(root->st.st_mode) && (toys.optflags & FLAG_R)
 158        && dirtree_notdotdot(root)) {
 159      xprintf("\n%s:\n", fpath);
 160      free(fpath);
 161      return (DIRTREE_RECURSE | DIRTREE_COMEAGAIN);
 162    }
 163  }
 164  free(fpath);
 165  return 0;
 166}
 167
 168void lsattr_main(void)
 169{
 170  if (!*toys.optargs) dirtree_read(".", retell_dir);
 171  else
 172    for (; *toys.optargs;  toys.optargs++) {
 173      struct stat sb;
 174
 175      if (lstat(*toys.optargs, &sb)) perror_msg("stat '%s'", *toys.optargs);
 176      else if (S_ISDIR(sb.st_mode) && !(toys.optflags & FLAG_d))
 177        dirtree_read(*toys.optargs, retell_dir);
 178      else print_file_attr(*toys.optargs);// to handle "./Filename" or "./Dir"
 179    }
 180}
 181
 182// Switch gears from lsattr to chattr.
 183#define CLEANUP_lsattr
 184#define FOR_chattr
 185#include "generated/flags.h"
 186
 187static struct _chattr {
 188  unsigned long add, rm, set, version;
 189  unsigned char vflag, recursive;
 190} chattr;
 191
 192// Set file flags on a Linux second extended file system.
 193static inline int ext2_setflag(int fd, struct stat *sb, unsigned long flag)
 194{
 195  if (!S_ISREG(sb->st_mode) && !S_ISDIR(sb->st_mode)) {
 196    errno = EOPNOTSUPP;
 197    return -1;
 198  }
 199  return (ioctl(fd, FS_IOC_SETFLAGS, (void*)&flag));
 200}
 201
 202static unsigned long get_flag_val(char ch)
 203{
 204  struct ext2_attr *ptr = e2attrs;
 205
 206  for (; ptr->name; ptr++)
 207    if (ptr->opt == ch) return ptr->flag;
 208  help_exit("bad '%c'", ch);
 209}
 210
 211// Parse command line argument and fill the chattr structure.
 212static void parse_cmdline_arg(char ***argv)
 213{
 214  char *arg = **argv, *ptr = NULL;
 215
 216  while (arg) {
 217    switch (arg[0]) {
 218      case '-':
 219        for (ptr = ++arg; *ptr; ptr++) {
 220          if (*ptr == 'R') {
 221            chattr.recursive = 1;
 222            continue;
 223          } else if (*ptr == 'v') {// get version from next argv.
 224            char *endptr;
 225
 226            errno = 0;
 227            arg = *(*argv += 1);
 228            if (!arg) help_exit("bad -v");
 229            if (*arg == '-') perror_exit("Invalid Number '%s'", arg);
 230            chattr.version = strtoul(arg, &endptr, 0);
 231            if (errno || *endptr) perror_exit("bad version '%s'", arg);
 232            chattr.vflag = 1;
 233            continue;
 234          } else chattr.rm |= get_flag_val(*ptr);
 235        }
 236        break;
 237      case '+':
 238        for (ptr = ++arg; *ptr; ptr++)
 239          chattr.add |= get_flag_val(*ptr);
 240        break;
 241      case '=':
 242        for (ptr = ++arg; *ptr; ptr++)
 243          chattr.set |= get_flag_val(*ptr);
 244        break;
 245      default: return;
 246    }
 247    arg = *(*argv += 1);
 248  }
 249}
 250
 251// Update attribute of given file.
 252static int update_attr(struct dirtree *root)
 253{
 254  unsigned long fval = 0;
 255  char *fpath = NULL;
 256  int fd;
 257
 258  if (!dirtree_notdotdot(root)) return 0;
 259
 260  /*
 261   * if file is a link and recursive is set or file is not regular+link+dir
 262   * (like fifo or dev file) then escape the file.
 263   */
 264  if ((S_ISLNK(root->st.st_mode) && chattr.recursive)
 265    || (!S_ISREG(root->st.st_mode) && !S_ISLNK(root->st.st_mode)
 266      && !S_ISDIR(root->st.st_mode)))
 267    return 0;
 268
 269  fpath = dirtree_path(root, NULL);
 270  if (-1 == (fd=open(fpath, O_RDONLY | O_NONBLOCK))) {
 271    free(fpath);
 272    return DIRTREE_ABORT;
 273  }
 274  // Get current attr of file.
 275  if (ext2_getflag(fd, &(root->st), &fval) < 0) {
 276    perror_msg("read flags of '%s'", fpath);
 277    free(fpath);
 278    xclose(fd);
 279    return DIRTREE_ABORT;
 280  }
 281  if (chattr.set) { // for '=' operator.
 282    if (ext2_setflag(fd, &(root->st), chattr.set) < 0)
 283      perror_msg("setting flags '%s'", fpath);
 284  } else { // for '-' / '+' operator.
 285    fval &= ~(chattr.rm);
 286    fval |= chattr.add;
 287    if (!S_ISDIR(root->st.st_mode)) fval &= ~FS_DIRSYNC_FL;
 288    if (ext2_setflag(fd, &(root->st), fval) < 0)
 289      perror_msg("setting flags '%s'", fpath);
 290  }
 291  // set file version
 292  if (chattr.vflag && (ioctl(fd, FS_IOC_SETVERSION, &chattr.version)<0))
 293    perror_msg("while setting version on '%s'", fpath);
 294  free(fpath);
 295  xclose(fd);
 296
 297  return (S_ISDIR(root->st.st_mode) && chattr.recursive) ? DIRTREE_RECURSE : 0;
 298}
 299
 300void chattr_main(void)
 301{
 302  char **argv = toys.optargs;
 303
 304  memset(&chattr, 0, sizeof(struct _chattr));
 305  parse_cmdline_arg(&argv);
 306  if (!*argv) help_exit("no file");
 307  if (chattr.set && (chattr.add || chattr.rm))
 308    error_exit("no '=' with '-' or '+'");
 309  if (chattr.rm & chattr.add) error_exit("set/unset same flag");
 310  if (!(chattr.add || chattr.rm || chattr.set || chattr.vflag))
 311    error_exit("need '-v', '=', '-' or '+'");
 312  for (; *argv; argv++) dirtree_read(*argv, update_attr);
 313  toys.exitval = 0; //always set success at this point.
 314}
 315