#include "../command.h" #include #include #include #include #include #include #include #include #include #include #define FILE_COLOR "\x1b[0m" #define DIR_COLOR "\x1b[1;34m" #define SET_UID_COLOR "\x1b[41m" #define SET_GID_COLOR "\x1b[43m" #define EXEC_COLOR "\x1b[1;92m" #define LINK_COLOR "\x1b[1;96m" struct Flags { bool hidden; bool hide_dot; bool more_info; bool one_column; bool recurse; }; static DIR* get_directory(char* path) { DIR* d = opendir(path); if (d == NULL) { printf("\x1b[0merror: failed to open directory '%s': %s\n", path, strerror(errno)); } return d; } static void append_path(char buf[PATH_MAX], const char* dir_path, const struct dirent* file) { size_t dir_len = strlen(dir_path); memcpy(buf, dir_path, dir_len); if (buf[dir_len-1] != '/') { buf[dir_len] = '/'; dir_len++; } memcpy(buf + dir_len, file->d_name, strlen(file->d_name) + 1); } static void list_file(struct dirent* file, struct Flags* flags, const char* dir_path) { char* color; if (file->d_type == DT_DIR) { color = DIR_COLOR; } else if (file->d_type == DT_LNK) { color = LINK_COLOR; } else { color = FILE_COLOR; } if (flags->more_info) { uid_t uid = getuid(); gid_t gid = getgid(); char buf[PATH_MAX]; append_path(buf, dir_path, file); struct stat s; if (stat(buf, &s) < 0) { printf("\x1b[0merror: failed to read file '%s': %s\n", buf, strerror(errno)); return; } if (file->d_type == DT_DIR) putchar('d'); else if (file->d_type == DT_LNK) putchar('l'); else putchar('-'); bool group_sticky = false; bool user_sticky = false; bool exectuable = false; putchar((s.st_mode & S_IRUSR) ? 'r' : '-'); putchar((s.st_mode & S_IWUSR) ? 'w' : '-'); if (s.st_mode & S_IXUSR) { if (putchar(s.st_mode & S_ISUID)) { putchar('s'); user_sticky = true; } else { putchar('x'); } if (!exectuable) exectuable = s.st_uid == uid; } else { putchar('-'); } putchar((s.st_mode & S_IRGRP) ? 'r' : '-'); putchar((s.st_mode & S_IWGRP) ? 'w' : '-'); if (s.st_mode & S_IXGRP) { if (putchar(s.st_mode & S_ISGID)) { putchar('s'); group_sticky = true; } else { putchar('x'); } if (!exectuable) exectuable = s.st_gid == gid; } else { putchar('-'); } putchar((s.st_mode & S_IROTH) ? 'r' : '-'); putchar((s.st_mode & S_IWOTH) ? 'w' : '-'); if (s.st_mode & S_IXOTH) { putchar('x'); exectuable = true; } else { putchar('-'); } printf(" %ld\t", s.st_nlink); struct passwd* passwd = getpwuid(s.st_uid); struct group* group = getgrgid(s.st_gid); printf("%s ", passwd->pw_name); printf("%s ", group->gr_name); print_file_size(s.st_size); print_date_time(s.st_mtim.tv_sec + s.st_mtim.tv_nsec / 1000000); if (file->d_type == DT_REG) { if (user_sticky) { color = SET_UID_COLOR; } else if (group_sticky) { color = SET_GID_COLOR; } else if (exectuable) { color = EXEC_COLOR; } } printf("%s%s\x1b[0m", color, file->d_name); if (file->d_type == DT_LNK) { char link[PATH_MAX]; if (readlink(buf, link, PATH_MAX) < 0) { printf("\n"); } else { printf(" -> %s\n", link); } } else { printf("\n"); } } else { printf("%s%s", color, file->d_name); flags->one_column ? putchar('\n') : putchar(' '); } } static bool is_dot_dir(const char* path) { return streql(path, ".") || streql(path, ".."); } static void recurse_directory(char* path, struct Flags* flags) { DIR* d; struct dirent* file; bool first = true; d = get_directory(path); if (d == NULL) return; while((file = readdir(d)) != NULL) { if (file->d_type == DT_DIR) continue; if (!flags->hidden && prefix(".", file->d_name)) continue; if (is_dot_dir(file->d_name)) continue; if (first) { printf("\n%s%s:%s\n", DIR_COLOR, path, FILE_COLOR); first = false; } list_file(file, flags, path); } if (!flags->more_info) printf("\n"); closedir(d); d = get_directory(path); if (d == NULL) return; while((file = readdir(d)) != NULL) { if (file->d_type != DT_DIR) continue; if (!flags->hidden && prefix(".", file->d_name)) continue; if (is_dot_dir(file->d_name)) continue; char buf[PATH_MAX]; append_path(buf, path, file); recurse_directory(buf, flags); } closedir(d); } static void list_directory(char* path, struct Flags* flags) { if (flags->recurse) { recurse_directory(path, flags); return; } DIR* d = get_directory(path); if (d == NULL) return; struct dirent* file; while ((file = readdir(d)) != NULL) { if (!flags->hidden && prefix(".", file->d_name)) continue; if (flags->hide_dot && is_dot_dir(file->d_name)) continue; list_file(file, flags, path); } closedir(d); } static void help() { printf("Usage: ls [FILE]...\n\n"); printf("List directory contents\n\n"); printf("\t-1\tOne column output\n"); printf("\t-l\tLong format\n"); printf("\t-a\tInclude names starting with .\n"); printf("\t-A\tLike -a but without . and ..\n"); printf("\t-R\tRecurse\n"); exit(EXIT_SUCCESS); } COMMAND(ls) { struct Flags flags; flags.hidden = false; flags.more_info = false; flags.hide_dot = false; flags.one_column = false; flags.recurse = false; int start = 0; for (int i = 0; i < argc; i++) { if (!prefix("-", argv[i])) break; if (streql("--help", argv[i])) help(); start++; for (size_t j = 1; j < strlen(argv[i]); j++) { char c = argv[i][j]; switch (c) { case 'R': flags.recurse = true; break; case '1': flags.one_column = true; break; case 'A': flags.hide_dot = true; flags.hidden = true; break; case 'a': flags.hidden = true; break; case 'l': flags.more_info = true; break; default: error("error: unkown option -%c", c); } } } if (argc - start == 0) { list_directory(".", &flags); if (!flags.more_info) printf("\n"); return EXIT_SUCCESS; } bool titled = argc - start > 1; for (int i = start; i < argc; i++) { if (titled && !flags.recurse) { printf("\n%s%s:%s\n", DIR_COLOR, argv[i], FILE_COLOR); } list_directory(argv[i], &flags); if (titled && !flags.recurse) printf("\n"); } if (!flags.more_info) printf("\n"); return EXIT_SUCCESS; }