| ls.c - sbase - suckless unix tools | |
| git clone git://git.suckless.org/sbase | |
| Log | |
| Files | |
| Refs | |
| README | |
| 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_IX… | |
| 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 mont… | |
| 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… | |
| 202 printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname,… | |
| 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)) < … | |
| 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) &&… | |
| 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->n… | |
| 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; ++… | |
| 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, sizeo… | |
| 468 memcpy(&dents[ds - 1], &ent, sizeof(ent)… | |
| 469 } else { | |
| 470 fents = ereallocarray(fents, ++fs, sizeo… | |
| 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 } |