ls.c - sbase - suckless unix tools
 (HTM) git clone git://git.suckless.org/sbase
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       ls.c (9612B)
       ---
            1 /* See LICENSE file for copyright and license details. */
            2 #include <sys/stat.h>
            3 #include <sys/types.h>
            4 #ifndef major
            5 #include <sys/sysmacros.h>
            6 #endif
            7 
            8 #include <dirent.h>
            9 #include <grp.h>
           10 #include <pwd.h>
           11 #include <stdio.h>
           12 #include <stdlib.h>
           13 #include <string.h>
           14 #include <time.h>
           15 #include <unistd.h>
           16 
           17 #include "utf.h"
           18 #include "util.h"
           19 
           20 struct entry {
           21         char   *name;
           22         mode_t  mode, tmode;
           23         nlink_t nlink;
           24         uid_t   uid;
           25         gid_t   gid;
           26         off_t   size;
           27         struct timespec t;
           28         dev_t   dev;
           29         dev_t   rdev;
           30         ino_t   ino, tino;
           31 };
           32 
           33 static struct {
           34         dev_t dev;
           35         ino_t ino;
           36 } *tree;
           37 
           38 static int ret   = 0;
           39 static int Aflag = 0;
           40 static int aflag = 0;
           41 static int cflag = 0;
           42 static int dflag = 0;
           43 static int Fflag = 0;
           44 static int fflag = 0;
           45 static int Hflag = 0;
           46 static int hflag = 0;
           47 static int iflag = 0;
           48 static int Lflag = 0;
           49 static int lflag = 0;
           50 static int nflag = 0;
           51 static int pflag = 0;
           52 static int qflag = 0;
           53 static int Rflag = 0;
           54 static int rflag = 0;
           55 static int Uflag = 0;
           56 static int uflag = 0;
           57 static int first = 1;
           58 static char sort = 0;
           59 static int showdirs;
           60 
           61 static void ls(const char *, const struct entry *, int);
           62 
           63 static void
           64 mkent(struct entry *ent, char *path, int dostat, int follow)
           65 {
           66         struct stat st;
           67 
           68         ent->name = path;
           69         if (!dostat)
           70                 return;
           71         if ((follow ? stat : lstat)(path, &st) < 0)
           72                 eprintf("%s %s:", follow ? "stat" : "lstat", path);
           73         ent->mode  = st.st_mode;
           74         ent->nlink = st.st_nlink;
           75         ent->uid   = st.st_uid;
           76         ent->gid   = st.st_gid;
           77         ent->size  = st.st_size;
           78         if (cflag)
           79                 ent->t = st.st_ctim;
           80         else if (uflag)
           81                 ent->t = st.st_atim;
           82         else
           83                 ent->t = st.st_mtim;
           84         ent->dev   = st.st_dev;
           85         ent->rdev  = st.st_rdev;
           86         ent->ino   = st.st_ino;
           87         if (S_ISLNK(ent->mode)) {
           88                 if (stat(path, &st) == 0) {
           89                         ent->tmode = st.st_mode;
           90                         ent->dev   = st.st_dev;
           91                         ent->tino  = st.st_ino;
           92                 } else {
           93                         ent->tmode = ent->tino = 0;
           94                 }
           95         }
           96 }
           97 
           98 static char *
           99 indicator(mode_t mode)
          100 {
          101         if (pflag || Fflag)
          102                 if (S_ISDIR(mode))
          103                         return "/";
          104 
          105         if (Fflag) {
          106                 if (S_ISLNK(mode))
          107                         return "@";
          108                 else if (S_ISFIFO(mode))
          109                         return "|";
          110                 else if (S_ISSOCK(mode))
          111                         return "=";
          112                 else if (mode & S_IXUSR || mode & S_IXGRP || mode & S_IXOTH)
          113                         return "*";
          114         }
          115 
          116         return "";
          117 }
          118 
          119 static void
          120 printname(const char *name)
          121 {
          122         const char *c;
          123         Rune r;
          124         size_t l;
          125 
          126         for (c = name; *c; c += l) {
          127                 l = chartorune(&r, c);
          128                 if (!qflag || isprintrune(r))
          129                         fwrite(c, 1, l, stdout);
          130                 else
          131                         putchar('?');
          132         }
          133 }
          134 
          135 static void
          136 output(const struct entry *ent)
          137 {
          138         struct group *gr;
          139         struct passwd *pw;
          140         struct tm *tm;
          141         ssize_t len;
          142         char *fmt, buf[BUFSIZ], pwname[_SC_LOGIN_NAME_MAX],
          143              grname[_SC_LOGIN_NAME_MAX], mode[] = "----------";
          144 
          145         if (iflag)
          146                 printf("%lu ", (unsigned long)ent->ino);
          147         if (!lflag) {
          148                 printname(ent->name);
          149                 puts(indicator(ent->mode));
          150                 return;
          151         }
          152         if (S_ISREG(ent->mode))
          153                 mode[0] = '-';
          154         else if (S_ISBLK(ent->mode))
          155                 mode[0] = 'b';
          156         else if (S_ISCHR(ent->mode))
          157                 mode[0] = 'c';
          158         else if (S_ISDIR(ent->mode))
          159                 mode[0] = 'd';
          160         else if (S_ISFIFO(ent->mode))
          161                 mode[0] = 'p';
          162         else if (S_ISLNK(ent->mode))
          163                 mode[0] = 'l';
          164         else if (S_ISSOCK(ent->mode))
          165                 mode[0] = 's';
          166         else
          167                 mode[0] = '?';
          168 
          169         if (ent->mode & S_IRUSR) mode[1] = 'r';
          170         if (ent->mode & S_IWUSR) mode[2] = 'w';
          171         if (ent->mode & S_IXUSR) mode[3] = 'x';
          172         if (ent->mode & S_IRGRP) mode[4] = 'r';
          173         if (ent->mode & S_IWGRP) mode[5] = 'w';
          174         if (ent->mode & S_IXGRP) mode[6] = 'x';
          175         if (ent->mode & S_IROTH) mode[7] = 'r';
          176         if (ent->mode & S_IWOTH) mode[8] = 'w';
          177         if (ent->mode & S_IXOTH) mode[9] = 'x';
          178 
          179         if (ent->mode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
          180         if (ent->mode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
          181         if (ent->mode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
          182 
          183         if (!nflag && (pw = getpwuid(ent->uid)))
          184                 snprintf(pwname, sizeof(pwname), "%s", pw->pw_name);
          185         else
          186                 snprintf(pwname, sizeof(pwname), "%d", ent->uid);
          187 
          188         if (!nflag && (gr = getgrgid(ent->gid)))
          189                 snprintf(grname, sizeof(grname), "%s", gr->gr_name);
          190         else
          191                 snprintf(grname, sizeof(grname), "%d", ent->gid);
          192 
          193         if (time(NULL) > ent->t.tv_sec + (180 * 24 * 60 * 60)) /* 6 months ago? */
          194                 fmt = "%b %d  %Y";
          195         else
          196                 fmt = "%b %d %H:%M";
          197 
          198         if ((tm = localtime(&ent->t.tv_sec)))
          199                 strftime(buf, sizeof(buf), fmt, tm);
          200         else
          201                 snprintf(buf, sizeof(buf), "%lld", (long long)(ent->t.tv_sec));
          202         printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname, grname);
          203 
          204         if (S_ISBLK(ent->mode) || S_ISCHR(ent->mode))
          205                 printf("%4u, %4u ", major(ent->rdev), minor(ent->rdev));
          206         else if (hflag)
          207                 printf("%10s ", humansize(ent->size));
          208         else
          209                 printf("%10lu ", (unsigned long)ent->size);
          210         printf("%s ", buf);
          211         printname(ent->name);
          212         fputs(indicator(ent->mode), stdout);
          213         if (S_ISLNK(ent->mode)) {
          214                 if ((len = readlink(ent->name, buf, sizeof(buf) - 1)) < 0)
          215                         eprintf("readlink %s:", ent->name);
          216                 buf[len] = '\0';
          217                 printf(" -> %s%s", buf, indicator(ent->tmode));
          218         }
          219         putchar('\n');
          220 }
          221 
          222 static int
          223 entcmp(const void *va, const void *vb)
          224 {
          225         int cmp = 0;
          226         const struct entry *a = va, *b = vb;
          227 
          228         switch (sort) {
          229         case 'S':
          230                 cmp = b->size - a->size;
          231                 break;
          232         case 't':
          233                 if (!(cmp = b->t.tv_sec - a->t.tv_sec))
          234                         cmp = b->t.tv_nsec - a->t.tv_nsec;
          235                 break;
          236         }
          237 
          238         if (!cmp)
          239                 cmp = strcmp(a->name, b->name);
          240 
          241         return rflag ? 0 - cmp : cmp;
          242 }
          243 
          244 static void
          245 lsdir(const char *path, const struct entry *dir)
          246 {
          247         DIR *dp;
          248         struct entry *ent, *ents = NULL;
          249         struct dirent *d;
          250         size_t i, n = 0;
          251         char prefix[PATH_MAX];
          252 
          253         if (!(dp = opendir(dir->name))) {
          254                 ret = 1;
          255                 weprintf("opendir %s%s:", path, dir->name);
          256                 return;
          257         }
          258         if (chdir(dir->name) < 0)
          259                 eprintf("chdir %s:", dir->name);
          260 
          261         while ((d = readdir(dp))) {
          262                 if (d->d_name[0] == '.' && !aflag && !Aflag)
          263                         continue;
          264                 else if (Aflag)
          265                         if (strcmp(d->d_name, ".") == 0 ||
          266                             strcmp(d->d_name, "..") == 0)
          267                                 continue;
          268 
          269                 ents = ereallocarray(ents, ++n, sizeof(*ents));
          270                 mkent(&ents[n - 1], estrdup(d->d_name), Fflag || iflag ||
          271                     lflag || pflag || Rflag || sort, Lflag);
          272         }
          273 
          274         closedir(dp);
          275 
          276         if (!Uflag)
          277                 qsort(ents, n, sizeof(*ents), entcmp);
          278 
          279         if (path[0] || showdirs) {
          280                 fputs(path, stdout);
          281                 printname(dir->name);
          282                 puts(":");
          283         }
          284         for (i = 0; i < n; i++)
          285                 output(&ents[i]);
          286 
          287         if (Rflag) {
          288                 if (snprintf(prefix, PATH_MAX, "%s%s/", path, dir->name) >=
          289                     PATH_MAX)
          290                         eprintf("path too long: %s%s\n", path, dir->name);
          291 
          292                 for (i = 0; i < n; i++) {
          293                         ent = &ents[i];
          294                         if (strcmp(ent->name, ".") == 0 ||
          295                             strcmp(ent->name, "..") == 0)
          296                                 continue;
          297                         if (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Lflag)
          298                                 continue;
          299 
          300                         ls(prefix, ent, 1);
          301                 }
          302         }
          303 
          304         for (i = 0; i < n; ++i)
          305                 free(ents[i].name);
          306         free(ents);
          307 }
          308 
          309 static int
          310 visit(const struct entry *ent)
          311 {
          312         dev_t dev;
          313         ino_t ino;
          314         int i;
          315 
          316         dev = ent->dev;
          317         ino = S_ISLNK(ent->mode) ? ent->tino : ent->ino;
          318 
          319         for (i = 0; i < PATH_MAX && tree[i].ino; ++i) {
          320                 if (ino == tree[i].ino && dev == tree[i].dev)
          321                         return -1;
          322         }
          323 
          324         tree[i].ino = ino;
          325         tree[i].dev = dev;
          326 
          327         return i;
          328 }
          329 
          330 static void
          331 ls(const char *path, const struct entry *ent, int listdir)
          332 {
          333         int treeind;
          334         char cwd[PATH_MAX];
          335 
          336         if (!listdir) {
          337                 output(ent);
          338         } else if (S_ISDIR(ent->mode) ||
          339             (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode))) {
          340                 if ((treeind = visit(ent)) < 0) {
          341                         ret = 1;
          342                         weprintf("%s%s: Already visited\n", path, ent->name);
          343                         return;
          344                 }
          345 
          346                 if (!getcwd(cwd, PATH_MAX))
          347                         eprintf("getcwd:");
          348 
          349                 if (first)
          350                         first = 0;
          351                 else
          352                         putchar('\n');
          353 
          354                 lsdir(path, ent);
          355                 tree[treeind].ino = 0;
          356 
          357                 if (chdir(cwd) < 0)
          358                         eprintf("chdir %s:", cwd);
          359         }
          360 }
          361 
          362 static void
          363 usage(void)
          364 {
          365         eprintf("usage: %s [-1AacdFfHhiLlnpqRrtUu] [file ...]\n", argv0);
          366 }
          367 
          368 int
          369 main(int argc, char *argv[])
          370 {
          371         struct entry ent, *dents, *fents;
          372         size_t i, ds, fs;
          373 
          374         tree = ereallocarray(NULL, PATH_MAX, sizeof(*tree));
          375 
          376         ARGBEGIN {
          377         case '1':
          378                 /* force output to 1 entry per line */
          379                 qflag = 1;
          380                 break;
          381         case 'A':
          382                 Aflag = 1;
          383                 break;
          384         case 'a':
          385                 aflag = 1;
          386                 break;
          387         case 'c':
          388                 cflag = 1;
          389                 uflag = 0;
          390                 break;
          391         case 'd':
          392                 dflag = 1;
          393                 break;
          394         case 'f':
          395                 aflag = 1;
          396                 fflag = 1;
          397                 Uflag = 1;
          398                 break;
          399         case 'F':
          400                 Fflag = 1;
          401                 break;
          402         case 'H':
          403                 Hflag = 1;
          404                 break;
          405         case 'h':
          406                 hflag = 1;
          407                 break;
          408         case 'i':
          409                 iflag = 1;
          410                 break;
          411         case 'L':
          412                 Lflag = 1;
          413                 break;
          414         case 'l':
          415                 lflag = 1;
          416                 break;
          417         case 'n':
          418                 lflag = 1;
          419                 nflag = 1;
          420                 break;
          421         case 'p':
          422                 pflag = 1;
          423                 break;
          424         case 'q':
          425                 qflag = 1;
          426                 break;
          427         case 'R':
          428                 Rflag = 1;
          429                 break;
          430         case 'r':
          431                 rflag = 1;
          432                 break;
          433         case 'S':
          434                 sort = 'S';
          435                 break;
          436         case 't':
          437                 sort = 't';
          438                 break;
          439         case 'U':
          440                 Uflag = 1;
          441                 break;
          442         case 'u':
          443                 uflag = 1;
          444                 cflag = 0;
          445                 break;
          446         default:
          447                 usage();
          448         } ARGEND
          449 
          450         switch (argc) {
          451         case 0: /* fallthrough */
          452                 *--argv = ".", ++argc;
          453         case 1:
          454                 mkent(&ent, argv[0], 1, Hflag || Lflag);
          455                 ls("", &ent, (!dflag && S_ISDIR(ent.mode)) ||
          456                     (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
          457                      !(dflag || Fflag || lflag)));
          458 
          459                 break;
          460         default:
          461                 for (i = ds = fs = 0, fents = dents = NULL; i < argc; ++i) {
          462                         mkent(&ent, argv[i], 1, Hflag || Lflag);
          463 
          464                         if ((!dflag && S_ISDIR(ent.mode)) ||
          465                             (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
          466                              !(dflag || Fflag || lflag))) {
          467                                 dents = ereallocarray(dents, ++ds, sizeof(*dents));
          468                                 memcpy(&dents[ds - 1], &ent, sizeof(ent));
          469                         } else {
          470                                 fents = ereallocarray(fents, ++fs, sizeof(*fents));
          471                                 memcpy(&fents[fs - 1], &ent, sizeof(ent));
          472                         }
          473                 }
          474 
          475                 showdirs = ds > 1 || (ds && fs);
          476 
          477                 qsort(fents, fs, sizeof(ent), entcmp);
          478                 qsort(dents, ds, sizeof(ent), entcmp);
          479 
          480                 for (i = 0; i < fs; ++i)
          481                         ls("", &fents[i], 0);
          482                 free(fents);
          483                 if (fs && ds)
          484                         putchar('\n');
          485                 for (i = 0; i < ds; ++i)
          486                         ls("", &dents[i], 1);
          487                 free(dents);
          488         }
          489 
          490         return (fshut(stdout, "<stdout>") | ret);
          491 }