Implementations of common Unix command-line utilities written in C.
unix-tools / ls.c
| 1 | #define _XOPEN_SOURCE 700 |
| 2 | #define _POSIX_C_SOURCE 200809L |
| 3 | #include <sys/stat.h> |
| 4 | #include <dirent.h> |
| 5 | #include <fcntl.h> |
| 6 | #include <unistd.h> |
| 7 | #include <errno.h> |
| 8 | #include <inttypes.h> |
| 9 | #include <stdint.h> |
| 10 | #include <stdio.h> |
| 11 | #include <stdlib.h> |
| 12 | #include <string.h> |
| 13 | #include <pwd.h> |
| 14 | #include <grp.h> |
| 15 | #include <time.h> |
| 16 | |
| 17 | struct entry { |
| 18 | char *name; |
| 19 | struct stat st; |
| 20 | }; |
| 21 | |
| 22 | static int dflag, lflag, nflag, pflag, rflag, sflag, tflag, uflag, Fflag; |
| 23 | |
| 24 | static int |
| 25 | compar(const void *a, const void *b) |
| 26 | { |
| 27 | const struct entry *left = a, *right = b; |
| 28 | int result; |
| 29 | if (tflag) { |
| 30 | time_t x = uflag ? left->st.st_atime : left->st.st_mtime; |
| 31 | time_t y = uflag ? right->st.st_atime : right->st.st_mtime; |
| 32 | result = x > y ? -1 : x < y ? 1 : strcmp(left->name, right->name); |
| 33 | } else result = strcmp(left->name, right->name); |
| 34 | return rflag ? -result : result; |
| 35 | } |
| 36 | |
| 37 | static void |
| 38 | format(const struct stat *st, const char *name, const char *prefix) |
| 39 | { |
| 40 | if (sflag) printf("%ju ", (uintmax_t)st->st_size / 1024 + (st->st_size % 1024 != 0)); |
| 41 | if (lflag) { |
| 42 | char mode[] = "----------", date[64] = "?"; |
| 43 | const char *bits = "rwxrwxrwx"; |
| 44 | struct passwd *user = getpwuid(st->st_uid); |
| 45 | struct group *group = getgrgid(st->st_gid); |
| 46 | time_t stamp = uflag ? st->st_atime : st->st_mtime; |
| 47 | struct tm *tm = localtime(&stamp); |
| 48 | int i; |
| 49 | if (S_ISDIR(st->st_mode)) mode[0] = 'd'; |
| 50 | else if (S_ISLNK(st->st_mode)) mode[0] = 'l'; |
| 51 | else if (S_ISFIFO(st->st_mode)) mode[0] = 'p'; |
| 52 | else if (S_ISCHR(st->st_mode)) mode[0] = 'c'; |
| 53 | else if (S_ISBLK(st->st_mode)) mode[0] = 'b'; |
| 54 | else if (S_ISSOCK(st->st_mode)) mode[0] = 's'; |
| 55 | for (i = 0; i < 9; ++i) if (st->st_mode & (0400 >> i)) mode[i+1] = bits[i]; |
| 56 | if (st->st_mode & S_ISUID) mode[3] = mode[3] == 'x' ? 's' : 'S'; |
| 57 | if (st->st_mode & S_ISGID) mode[6] = mode[6] == 'x' ? 's' : 'S'; |
| 58 | if (st->st_mode & S_ISVTX) mode[9] = mode[9] == 'x' ? 't' : 'T'; |
| 59 | if (tm) (void)strftime(date, sizeof(date), "%b %e %H:%M", tm); |
| 60 | printf("%s %ju ", mode, (uintmax_t)st->st_nlink); |
| 61 | if (user) printf("%s ", user->pw_name); else printf("%ju ", (uintmax_t)st->st_uid); |
| 62 | if (group) printf("%s ", group->gr_name); else printf("%ju ", (uintmax_t)st->st_gid); |
| 63 | printf("%jd %s ", (intmax_t)st->st_size, date); |
| 64 | } |
| 65 | if (prefix) printf("%s%s", prefix, prefix[strlen(prefix)-1] == '/' ? "" : "/"); |
| 66 | printf("%s%s\n", name, Fflag ? (S_ISDIR(st->st_mode) ? "/" : |
| 67 | (st->st_mode & 0111) ? "*" : "") : ""); |
| 68 | } |
| 69 | |
| 70 | static int |
| 71 | ls(const char *path, int prefix) |
| 72 | { |
| 73 | struct stat st; |
| 74 | struct entry *entries = NULL; |
| 75 | struct dirent *entry; |
| 76 | DIR *dir; |
| 77 | size_t count = 0, i; |
| 78 | int status = 0; |
| 79 | if (lstat(path, &st) < 0) { perror(path); return 1; } |
| 80 | if (!S_ISDIR(st.st_mode) || dflag) { format(&st, path, NULL); return 0; } |
| 81 | dir = opendir(path); |
| 82 | if (dir == NULL) { perror(path); return 1; } |
| 83 | for (;;) { |
| 84 | struct entry *newentries; |
| 85 | errno = 0; |
| 86 | entry = readdir(dir); |
| 87 | if (entry == NULL) { if (errno) { perror(path); status = 1; } break; } |
| 88 | if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; |
| 89 | if (fstatat(dirfd(dir), entry->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { |
| 90 | perror(entry->d_name); status = 1; continue; |
| 91 | } |
| 92 | if (count >= SIZE_MAX / sizeof(*entries)) { errno = ENOMEM; perror("ls"); status = 1; break; } |
| 93 | newentries = realloc(entries, (count + 1) * sizeof(*entries)); |
| 94 | if (newentries == NULL) { perror("ls: realloc"); status = 1; break; } |
| 95 | entries = newentries; |
| 96 | entries[count].name = strdup(entry->d_name); |
| 97 | if (entries[count].name == NULL) { perror("ls: strdup"); status = 1; break; } |
| 98 | entries[count].st = st; |
| 99 | ++count; |
| 100 | } |
| 101 | if (!nflag && count > 1) qsort(entries, count, sizeof(*entries), compar); |
| 102 | for (i = 0; i < count; ++i) { |
| 103 | format(&entries[i].st, entries[i].name, prefix && !pflag ? path : NULL); |
| 104 | free(entries[i].name); |
| 105 | } |
| 106 | free(entries); |
| 107 | if (closedir(dir) < 0) { perror(path); status = 1; } |
| 108 | return status; |
| 109 | } |
| 110 | |
| 111 | int |
| 112 | main(int argc, char **argv) |
| 113 | { |
| 114 | int option, status = 0; |
| 115 | while ((option = getopt(argc, argv, "dlnprstuF")) != -1) { |
| 116 | switch (option) { |
| 117 | case 'd': dflag = 1; break; |
| 118 | case 'l': lflag = 1; break; |
| 119 | case 'n': nflag = 1; break; |
| 120 | case 'p': pflag = 1; break; |
| 121 | case 'r': rflag = 1; break; |
| 122 | case 's': sflag = 1; break; |
| 123 | case 't': tflag = 1; break; |
| 124 | case 'u': uflag = 1; break; |
| 125 | case 'F': Fflag = 1; break; |
| 126 | default: fprintf(stderr, "usage: %s [-dlnprstuF] [file...]\n", argv[0]); return 1; |
| 127 | } |
| 128 | } |
| 129 | if (optind == argc) status = ls(".", 0); |
| 130 | for (; optind < argc; ++optind) status |= ls(argv[optind], 1); |
| 131 | if (fflush(stdout) == EOF || ferror(stdout)) { perror("ls: stdout"); status = 1; } |
| 132 | return status; |
| 133 | } |