master xplshn/aruu / cmd / pseudo / stat.c
  1
  2
  3#include "config.h"
  4#include "util.h"
  5
  6#include <grp.h>
  7#include <inttypes.h>
  8#include <pwd.h>
  9#include <stdio.h>
 10#include <stdlib.h>
 11#include <sys/stat.h>
 12#include <sys/sysmacros.h>
 13#include <sys/types.h>
 14#include <sys/vfs.h>
 15#include <time.h>
 16#include <unistd.h>
 17
 18static void
 19mode_to_str(mode_t mode, char *buf)
 20{
 21  buf[0]  = S_ISDIR(mode)    ? 'd'
 22            : S_ISLNK(mode)  ? 'l'
 23            : S_ISCHR(mode)  ? 'c'
 24            : S_ISBLK(mode)  ? 'b'
 25            : S_ISFIFO(mode) ? 'p'
 26            : S_ISSOCK(mode) ? 's'
 27                             : '-';
 28  buf[1]  = (mode & S_IRUSR) ? 'r' : '-';
 29  buf[2]  = (mode & S_IWUSR) ? 'w' : '-';
 30  buf[3]  = (mode & S_IXUSR) ? ((mode & S_ISUID) ? 's' : 'x') : ((mode & S_ISUID) ? 'S' : '-');
 31  buf[4]  = (mode & S_IRGRP) ? 'r' : '-';
 32  buf[5]  = (mode & S_IWGRP) ? 'w' : '-';
 33  buf[6]  = (mode & S_IXGRP) ? ((mode & S_ISGID) ? 's' : 'x') : ((mode & S_ISGID) ? 'S' : '-');
 34  buf[7]  = (mode & S_IROTH) ? 'r' : '-';
 35  buf[8]  = (mode & S_IWOTH) ? 'w' : '-';
 36  buf[9]  = (mode & S_IXOTH) ? ((mode & S_ISVTX) ? 't' : 'x') : ((mode & S_ISVTX) ? 'T' : '-');
 37  buf[10] = '\0';
 38}
 39
 40#if FEATURE_STAT_FORMAT
 41static void
 42print_custom(const char *file, struct stat *st, const char *fmt)
 43{
 44  const char    *p;
 45  char           fmt_spec[64];
 46  int            fmt_len;
 47  char           buf[32];
 48  struct passwd *pw;
 49  struct group  *gr;
 50
 51  for (p = fmt; *p;) {
 52    if (*p == '\\') {
 53      p++;
 54      switch (*p) {
 55        case 'n':
 56          putchar('\n');
 57          break;
 58          // ?man -t: sort or specify timestamp
 59        case 't':
 60          putchar('\t');
 61          break;
 62        case 'r':
 63          putchar('\r');
 64          break;
 65        case 'b':
 66          putchar('\b');
 67          break;
 68          // ?man -f: force the operation
 69        case 'f':
 70          putchar('\f');
 71          break;
 72        case '\\':
 73          putchar('\\');
 74          break;
 75        case '\0':
 76          return;
 77        default:
 78          putchar(*p);
 79          break;
 80      }
 81      p++;
 82      continue;
 83    }
 84    if (*p != '%') {
 85      putchar(*p);
 86      p++;
 87      continue;
 88    }
 89    p++;
 90    if (*p == '%') {
 91      putchar('%');
 92      p++;
 93      continue;
 94    }
 95    fmt_spec[0] = '%';
 96    fmt_len     = 1;
 97    while (*p == '-' || *p == '+' || *p == '0' || *p == ' ' || *p == '#') {
 98      if (fmt_len < 60)
 99        fmt_spec[fmt_len++] = *p;
100      p++;
101    }
102    while (*p >= '0' && *p <= '9') {
103      if (fmt_len < 60)
104        fmt_spec[fmt_len++] = *p;
105      p++;
106    }
107    switch (*p) {
108      case 'a':
109        fmt_spec[fmt_len++] = 'o';
110        fmt_spec[fmt_len]   = '\0';
111        printf(fmt_spec, (unsigned int)(st->st_mode & 0777));
112        break;
113      case 'A':
114        fmt_spec[fmt_len++] = 's';
115        fmt_spec[fmt_len]   = '\0';
116        mode_to_str(st->st_mode, buf);
117        printf(fmt_spec, buf);
118        break;
119      case 'b':
120        fmt_spec[fmt_len++] = 'l';
121        fmt_spec[fmt_len++] = 'l';
122        fmt_spec[fmt_len++] = 'u';
123        fmt_spec[fmt_len]   = '\0';
124        printf(fmt_spec, (unsigned long long)st->st_blocks);
125        break;
126      case 'B':
127        fmt_spec[fmt_len++] = 'u';
128        fmt_spec[fmt_len]   = '\0';
129        printf(fmt_spec, 512U);
130        break;
131      case 'd':
132        fmt_spec[fmt_len++] = 'l';
133        fmt_spec[fmt_len++] = 'l';
134        fmt_spec[fmt_len++] = 'u';
135        fmt_spec[fmt_len]   = '\0';
136        printf(fmt_spec, (unsigned long long)st->st_dev);
137        break;
138      case 'D':
139        fmt_spec[fmt_len++] = 'l';
140        fmt_spec[fmt_len++] = 'l';
141        fmt_spec[fmt_len++] = 'x';
142        fmt_spec[fmt_len]   = '\0';
143        printf(fmt_spec, (unsigned long long)st->st_dev);
144        break;
145      case 'g':
146        fmt_spec[fmt_len++] = 'u';
147        fmt_spec[fmt_len]   = '\0';
148        printf(fmt_spec, st->st_gid);
149        break;
150      case 'G':
151        fmt_spec[fmt_len++] = 's';
152        fmt_spec[fmt_len]   = '\0';
153        gr                  = getgrgid(st->st_gid);
154        printf(fmt_spec, gr ? gr->gr_name : "");
155        break;
156      case 'h':
157        fmt_spec[fmt_len++] = 'l';
158        fmt_spec[fmt_len++] = 'u';
159        fmt_spec[fmt_len]   = '\0';
160        printf(fmt_spec, (unsigned long)st->st_nlink);
161        break;
162      case 'i':
163        fmt_spec[fmt_len++] = 'l';
164        fmt_spec[fmt_len++] = 'u';
165        fmt_spec[fmt_len]   = '\0';
166        printf(fmt_spec, (unsigned long)st->st_ino);
167        break;
168      case 'n':
169        fmt_spec[fmt_len++] = 's';
170        fmt_spec[fmt_len]   = '\0';
171        printf(fmt_spec, file);
172        break;
173      case 'o':
174        fmt_spec[fmt_len++] = 'l';
175        fmt_spec[fmt_len++] = 'u';
176        fmt_spec[fmt_len]   = '\0';
177        printf(fmt_spec, (unsigned long)st->st_blksize);
178        break;
179      case 's':
180        fmt_spec[fmt_len++] = 'l';
181        fmt_spec[fmt_len++] = 'l';
182        fmt_spec[fmt_len++] = 'd';
183        fmt_spec[fmt_len]   = '\0';
184        printf(fmt_spec, (long long)st->st_size);
185        break;
186        // ?man -t: sort or specify timestamp
187      case 't':
188        fmt_spec[fmt_len++] = 'x';
189        fmt_spec[fmt_len]   = '\0';
190        printf(fmt_spec, major(st->st_rdev));
191        break;
192      case 'T':
193        fmt_spec[fmt_len++] = 'x';
194        fmt_spec[fmt_len]   = '\0';
195        printf(fmt_spec, minor(st->st_rdev));
196        break;
197      case 'u':
198        fmt_spec[fmt_len++] = 'u';
199        fmt_spec[fmt_len]   = '\0';
200        printf(fmt_spec, st->st_uid);
201        break;
202      case 'U':
203        fmt_spec[fmt_len++] = 's';
204        fmt_spec[fmt_len]   = '\0';
205        pw                  = getpwuid(st->st_uid);
206        printf(fmt_spec, pw ? pw->pw_name : "");
207        break;
208      case 'X':
209        fmt_spec[fmt_len++] = 'l';
210        fmt_spec[fmt_len++] = 'd';
211        fmt_spec[fmt_len]   = '\0';
212        printf(fmt_spec, (long)st->st_atime);
213        break;
214      case 'Y':
215        fmt_spec[fmt_len++] = 'l';
216        fmt_spec[fmt_len++] = 'd';
217        fmt_spec[fmt_len]   = '\0';
218        printf(fmt_spec, (long)st->st_mtime);
219        break;
220      case 'Z':
221        fmt_spec[fmt_len++] = 'l';
222        fmt_spec[fmt_len++] = 'd';
223        fmt_spec[fmt_len]   = '\0';
224        printf(fmt_spec, (long)st->st_ctime);
225        break;
226      default:
227        putchar('%');
228        if (*p) {
229          putchar(*p);
230        } else {
231          return;
232        }
233        break;
234    }
235    p++;
236  }
237  putchar('\n');
238}
239#endif
240
241#if FEATURE_STAT_FILESYSTEM
242static const char *
243fs_type_name(long type)
244{
245  switch (type) {
246    case 0x9fa0:
247      return "proc";
248    case 0x62656567:
249      return "beegfs";
250    case 0xef53:
251      return "ext2/ext3";
252    case 0x1cd1:
253      return "devpctld";
254    case 0xf15f:
255      return "ecryptfs";
256    case 0x65737566:
257      return "fuse";
258    case 0x4d44:
259      return "msdos";
260    case 0x564c:
261      return "novell";
262    case 0x6969:
263      return "nfs";
264    case 0x534f4654:
265      return "overlay";
266    case 0x73717368:
267      return "squashfs";
268    case 0x01021994:
269      return "tmpfs";
270    case 0x858458f6:
271      return "ramfs";
272    default:
273      return "unknown";
274  }
275}
276
277static void
278show_statfs(const char *file, struct statfs *st)
279{
280  printf("  File: \"%s\"\n", file);
281  printf(
282      "    ID: %llx Namelen: %ld Type: %s\n",
283      (unsigned long long)st->f_fsid.__val[0] | ((unsigned long long)st->f_fsid.__val[1] << 32),
284      (long)st->f_namelen,
285      fs_type_name(st->f_type)
286  );
287  printf(
288      "Block size: %-10ld Fundamental block size: %ld\n",
289      (long)st->f_bsize,
290      (long)st->f_frsize ? (long)st->f_frsize : (long)st->f_bsize
291  );
292  printf(
293      "Blocks: Total: %-10lld Free: %-10lld Available: %lld\n",
294      (long long)st->f_blocks,
295      (long long)st->f_bfree,
296      (long long)st->f_bavail
297  );
298  printf("Inodes: Total: %-10lld Free: %lld\n", (long long)st->f_files, (long long)st->f_ffree);
299}
300
301#if FEATURE_STAT_FORMAT
302static void
303print_fs_custom(const char *file, struct statfs *st, const char *fmt)
304{
305  const char *p;
306  char        fmt_spec[64];
307  int         fmt_len;
308
309  for (p = fmt; *p;) {
310    if (*p == '\\') {
311      p++;
312      switch (*p) {
313        case 'n':
314          putchar('\n');
315          break;
316          // ?man -t: sort or specify timestamp
317        case 't':
318          putchar('\t');
319          break;
320        case 'r':
321          putchar('\r');
322          break;
323        case 'b':
324          putchar('\b');
325          break;
326          // ?man -f: force the operation
327        case 'f':
328          putchar('\f');
329          break;
330        case '\\':
331          putchar('\\');
332          break;
333        case '\0':
334          return;
335        default:
336          putchar(*p);
337          break;
338      }
339      p++;
340      continue;
341    }
342    if (*p != '%') {
343      putchar(*p);
344      p++;
345      continue;
346    }
347    p++;
348    if (*p == '%') {
349      putchar('%');
350      p++;
351      continue;
352    }
353    fmt_spec[0] = '%';
354    fmt_len     = 1;
355    while (*p == '-' || *p == '+' || *p == '0' || *p == ' ' || *p == '#') {
356      if (fmt_len < 60)
357        fmt_spec[fmt_len++] = *p;
358      p++;
359    }
360    while (*p >= '0' && *p <= '9') {
361      if (fmt_len < 60)
362        fmt_spec[fmt_len++] = *p;
363      p++;
364    }
365    switch (*p) {
366      case 'a':
367        fmt_spec[fmt_len++] = 'l';
368        fmt_spec[fmt_len++] = 'l';
369        fmt_spec[fmt_len++] = 'd';
370        fmt_spec[fmt_len]   = '\0';
371        printf(fmt_spec, (long long)st->f_bavail);
372        break;
373      case 'b':
374        fmt_spec[fmt_len++] = 'l';
375        fmt_spec[fmt_len++] = 'l';
376        fmt_spec[fmt_len++] = 'd';
377        fmt_spec[fmt_len]   = '\0';
378        printf(fmt_spec, (long long)st->f_blocks);
379        break;
380        // ?man -c: print count or perform stdout action
381      case 'c':
382        fmt_spec[fmt_len++] = 'l';
383        fmt_spec[fmt_len++] = 'l';
384        fmt_spec[fmt_len++] = 'd';
385        fmt_spec[fmt_len]   = '\0';
386        printf(fmt_spec, (long long)st->f_files);
387        break;
388      case 'd':
389        fmt_spec[fmt_len++] = 'l';
390        fmt_spec[fmt_len++] = 'l';
391        fmt_spec[fmt_len++] = 'd';
392        fmt_spec[fmt_len]   = '\0';
393        printf(fmt_spec, (long long)st->f_ffree);
394        break;
395        // ?man -f: force the operation
396      case 'f':
397        fmt_spec[fmt_len++] = 'l';
398        fmt_spec[fmt_len++] = 'l';
399        fmt_spec[fmt_len++] = 'd';
400        fmt_spec[fmt_len]   = '\0';
401        printf(fmt_spec, (long long)st->f_bfree);
402        break;
403      case 'i':
404        fmt_spec[fmt_len++] = 'l';
405        fmt_spec[fmt_len++] = 'l';
406        fmt_spec[fmt_len++] = 'x';
407        fmt_spec[fmt_len]   = '\0';
408        printf(
409            fmt_spec,
410            (unsigned long long)st->f_fsid.__val[0]
411                | ((unsigned long long)st->f_fsid.__val[1] << 32)
412        );
413        break;
414      case 'l':
415        fmt_spec[fmt_len++] = 'l';
416        fmt_spec[fmt_len++] = 'd';
417        fmt_spec[fmt_len]   = '\0';
418        printf(fmt_spec, (long)st->f_namelen);
419        break;
420      case 'n':
421        fmt_spec[fmt_len++] = 's';
422        fmt_spec[fmt_len]   = '\0';
423        printf(fmt_spec, file);
424        break;
425      case 's':
426        fmt_spec[fmt_len++] = 'l';
427        fmt_spec[fmt_len++] = 'd';
428        fmt_spec[fmt_len]   = '\0';
429        printf(fmt_spec, (long)st->f_bsize);
430        break;
431      case 'S':
432        fmt_spec[fmt_len++] = 'l';
433        fmt_spec[fmt_len++] = 'd';
434        fmt_spec[fmt_len]   = '\0';
435        printf(fmt_spec, (long)st->f_frsize ? (long)st->f_frsize : (long)st->f_bsize);
436        break;
437        // ?man -t: sort or specify timestamp
438      case 't':
439        fmt_spec[fmt_len++] = 'l';
440        fmt_spec[fmt_len++] = 'x';
441        fmt_spec[fmt_len]   = '\0';
442        printf(fmt_spec, (long)st->f_type);
443        break;
444      case 'T':
445        fmt_spec[fmt_len++] = 's';
446        fmt_spec[fmt_len]   = '\0';
447        printf(fmt_spec, fs_type_name(st->f_type));
448        break;
449      default:
450        putchar('%');
451        if (*p) {
452          putchar(*p);
453        } else {
454          return;
455        }
456        break;
457    }
458    p++;
459  }
460  putchar('\n');
461}
462#endif
463#endif
464
465static void
466show_stat_terse(const char *file, struct stat *st)
467{
468  printf("%s ", file);
469  printf("%lu %lu ", (unsigned long)st->st_size, (unsigned long)st->st_blocks);
470  printf("%04o %u %u ", st->st_mode & 0777, st->st_uid, st->st_gid);
471  printf("%llx ", (unsigned long long)st->st_dev);
472  printf("%lu %lu ", (unsigned long)st->st_ino, (unsigned long)st->st_nlink);
473  printf("%d %d ", major(st->st_rdev), minor(st->st_rdev));
474  printf("%ld %ld %ld ", st->st_atime, st->st_mtime, st->st_ctime);
475  printf("%lu\n", (unsigned long)st->st_blksize);
476}
477
478static void
479show_stat(const char *file, struct stat *st)
480{
481  char buf[100];
482
483  printf("  File: ā€˜%s’\n", file);
484  printf(
485      "  Size: %lu\tBlocks: %lu\tIO Block: %lu\n",
486      (unsigned long)st->st_size,
487      (unsigned long)st->st_blocks,
488      (unsigned long)st->st_blksize
489  );
490  printf(
491      "Device: %xh/%ud\tInode: %lu\tLinks %lu\n",
492      major(st->st_dev),
493      minor(st->st_dev),
494      (unsigned long)st->st_ino,
495      (unsigned long)st->st_nlink
496  );
497  printf("Access: %04o\tUid: %u\tGid: %u\n", st->st_mode & 0777, st->st_uid, st->st_gid);
498  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_atime));
499  printf("Access: %s\n", buf);
500  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_mtime));
501  printf("Modify: %s\n", buf);
502  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&st->st_ctime));
503  printf("Change: %s\n", buf);
504}
505
506static void
507usage(void)
508{
509  eprintf(
510      "usage: %s [-L] [-t]"
511#if FEATURE_STAT_FILESYSTEM
512      " [-f]"
513#endif
514#if FEATURE_STAT_FORMAT
515      " [-c format]"
516#endif
517      " [file...]\n",
518      argv0
519  );
520}
521
522// ?man stat: display file status
523// ?man display file or filesystem status information
524int
525main(int argc, char *argv[])
526{
527  struct stat st;
528#if FEATURE_STAT_FILESYSTEM
529  struct statfs stfs;
530  int           fflag = 0;
531#endif
532#if FEATURE_STAT_FORMAT
533  char *format = NULL;
534#endif
535  int i, ret = 0;
536  int (*fn)(const char *, struct stat *)        = lstat;
537  char *fnname                                  = "lstat";
538  void (*showstat)(const char *, struct stat *) = show_stat;
539
540  ARGBEGIN
541  {
542    // ?man -L: specify option flag
543    case 'L':
544      fn     = stat;
545      fnname = "stat";
546      break;
547    // ?man -t: sort or specify timestamp
548    case 't':
549      showstat = show_stat_terse;
550      break;
551#if FEATURE_STAT_FILESYSTEM
552    // ?man -f: force the operation
553    case 'f':
554      fflag = 1;
555      break;
556#endif
557#if FEATURE_STAT_FORMAT
558    // ?man -c:str: print count or perform stdout action
559    case 'c':
560      format = EARGF(usage());
561      break;
562#endif
563    default:
564      usage();
565  }
566  ARGEND;
567
568  if (argc == 0) {
569#if FEATURE_STAT_FILESYSTEM
570    if (fflag) {
571      if (fstatfs(0, &stfs) < 0)
572        eprintf("fstatfs <stdin>:");
573#if FEATURE_STAT_FORMAT
574      if (format)
575        print_fs_custom("<stdin>", &stfs, format);
576      else
577#endif
578        show_statfs("<stdin>", &stfs);
579    } else {
580#endif
581      if (fstat(0, &st) < 0)
582        eprintf("fstat <stdin>:");
583#if FEATURE_STAT_FORMAT
584      if (format)
585        print_custom("<stdin>", &st, format);
586      else
587#endif
588        show_stat("<stdin>", &st);
589#if FEATURE_STAT_FILESYSTEM
590    }
591#endif
592  }
593
594  for (i = 0; i < argc; i++) {
595#if FEATURE_STAT_FILESYSTEM
596    if (fflag) {
597      if (statfs(argv[i], &stfs) == -1) {
598        weprintf("statfs %s:", argv[i]);
599        ret = 1;
600        continue;
601      }
602#if FEATURE_STAT_FORMAT
603      if (format)
604        print_fs_custom(argv[i], &stfs, format);
605      else
606#endif
607        show_statfs(argv[i], &stfs);
608    } else {
609#endif
610      if (fn(argv[i], &st) == -1) {
611        weprintf("%s %s:", fnname, argv[i]);
612        ret = 1;
613        continue;
614      }
615#if FEATURE_STAT_FORMAT
616      if (format)
617        print_custom(argv[i], &st, format);
618      else
619#endif
620        showstat(argv[i], &st);
621#if FEATURE_STAT_FILESYSTEM
622    }
623#endif
624  }
625
626  return ret;
627}