#include "../command.h" #include #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; }; struct FileInfo { char mode[11]; int links; struct passwd* usr; struct group* grp; char size[5]; char date[13]; struct dirent* file; bool set_uid; bool set_gid; bool exec; }; struct FileListInfo { int max_link; int max_usr; int max_grp; int max_size; int max_name; int total_len; }; static DIR* get_directory(char* path) { DIR* d = opendir(path); if (d == NULL) { if (errno == ENOTDIR) { printf("\x1b[0m%s is a a file\n", path); } else { 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 bool get_file_info(struct dirent* file, const char* dir_path, struct FileInfo* 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 false; } info->set_uid = false; info->set_gid = false; info->exec = false; if (file->d_type == DT_DIR) info->mode[0] = 'd'; else if (file->d_type == DT_LNK) info->mode[0] = 'l'; else info->mode[0] = '-'; info->mode[1] = (s.st_mode & S_IRUSR) ? 'r' : '-'; info->mode[2] = (s.st_mode & S_IWUSR) ? 'w' : '-'; if (s.st_mode & S_IXUSR) { if (s.st_mode & S_ISUID) { info->mode[3] = 's'; info->set_uid = true; } else { info->mode[3] = 'x'; } if (!info->exec) info->exec = s.st_uid == uid; } else { info->mode[3] = '-'; } info->mode[4] = (s.st_mode & S_IRGRP) ? 'r' : '-'; info->mode[5] = (s.st_mode & S_IWGRP) ? 'w' : '-'; if (s.st_mode & S_IXGRP) { if (s.st_mode & S_ISGID) { info->mode[6] = 's'; info->set_gid = true; } else { info->mode[6] = 'x'; } if (!info->exec) info->exec = s.st_gid == gid; } else { info->mode[6] = '-'; } info->mode[7] = (s.st_mode & S_IROTH) ? 'r' : '-'; info->mode[8] = (s.st_mode & S_IWOTH) ? 'w' : '-'; if (s.st_mode & S_IXOTH) { info->mode[9] = 'x'; info->exec = true; } else { info->mode[9] = '-'; } info->mode[10] = '\0'; info->usr = getpwuid(s.st_uid); info->grp = getgrgid(s.st_gid); info->file = file; info->links = s.st_nlink; print_file_size(s.st_size, info->size); print_date_time(s.st_mtim.tv_sec + s.st_mtim.tv_nsec / 1000000, info->date); return true; } static char* get_file_color(struct FileInfo* info) { char* color; if (info->file->d_type == DT_DIR) { color = DIR_COLOR; } else if (info->file->d_type == DT_LNK) { color = LINK_COLOR; } else { if (info->set_uid) { color = SET_UID_COLOR; } else if (info->set_gid) { color = SET_GID_COLOR; } else if (info->exec) { color = EXEC_COLOR; } else { color = FILE_COLOR; } } return color; } static void list_files(struct FileInfo* files, int file_len, struct FileListInfo info, struct Flags* flags, const char* dir_path) { struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); if (!isatty(1)) { flags->one_column = true; } char* color; const int column_width = info.max_name + 1; const int row_count = w.ws_col / column_width; for (int i = 0; i < file_len; i++) { struct FileInfo finfo = files[i]; if (strlen(finfo.file->d_name) < 1) continue; color = get_file_color(&finfo); if (flags->more_info) { printf("%s %*d %*s %*s %*s %s %s%s\x1b[0m", finfo.mode, info.max_link, finfo.links, info.max_usr, finfo.usr->pw_name, info.max_grp, finfo.grp->gr_name, info.max_size, finfo.size, finfo.date, color, finfo.file->d_name ); if (finfo.file->d_type == DT_LNK) { char path[PATH_MAX]; append_path(path, dir_path, finfo.file); char lnk[PATH_MAX]; if (readlink(path, lnk, PATH_MAX) < 0) { putchar('\n'); } else { printf(" -> %s\n", lnk); } } else { putchar('\n'); } } else if (flags->one_column) { printf("%s%s\x1b[0m\n", color, finfo.file->d_name); } else { if (info.total_len > w.ws_col) { if (i != 0 && i % row_count == 0) putchar('\n'); printf("%s%*s\x1b[0m", color, column_width, finfo.file->d_name); } else { printf("%s%s\x1b[0m ", color, finfo.file->d_name); } } } if (info.total_len <= w.ws_col) printf("\n"); } static bool is_dot_dir(const char* path) { return streql(path, ".") || streql(path, ".."); } static int num_places (int n) { int r = 1; if (n < 0) n = (n == INT_MIN) ? INT_MAX: -n; while (n > 9) { n /= 10; r++; } return r; } static void push_file( struct FileInfo** files, struct FileListInfo* info, int* size, int* capacity, struct dirent* file, const char* dir_path ) { struct FileInfo finfo; if (!get_file_info(file, dir_path, &finfo)) return; if (*size == *capacity) { *capacity *= 2; *files = realloc(*files, sizeof(struct FileInfo) * *capacity); } int user_len = strlen(finfo.usr->pw_name); if (user_len > info->max_usr) info->max_usr = user_len; int group_len = strlen(finfo.grp->gr_name); if (group_len > info->max_grp) info->max_grp = group_len; int name_len = strlen(file->d_name); if (name_len > info->max_name) info->max_name = name_len; int size_len = strlen(finfo.size); if (size_len > info->max_size) info->max_size = size_len; int link_len = num_places(finfo.links); if (link_len > info->max_link) info->max_link = link_len; info->total_len += name_len + 2; (*files)[*size] = finfo; (*size)++; } static bool recurse_directory(char* path, struct Flags* flags) { DIR* d; struct dirent* file; bool first = true; d = get_directory(path); if (d == NULL) return false; int capacity = 8; int size = 0; struct FileInfo* files = malloc(sizeof(struct FileInfo) * capacity); struct FileListInfo info; memset(&info, 0, sizeof(struct FileListInfo)); 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; } push_file(&files, &info, &size, &capacity, file, path); } list_files(files, size, info, flags, path); free(files); if (!flags->more_info) printf("\n"); closedir(d); d = get_directory(path); if (d == NULL) return false; 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); return true; } static bool list_directory(char* path, struct Flags* flags) { if (flags->recurse) { return recurse_directory(path, flags); } DIR* d = get_directory(path); if (d == NULL) return false; int capacity = 8; int size = 0; struct FileInfo* files = malloc(sizeof(struct FileInfo) * capacity); struct FileListInfo info; memset(&info, 0, sizeof(struct FileListInfo)); 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; push_file(&files, &info, &size, &capacity, file, path); } list_files(files, size, info, flags, path); free(files); closedir(d); return true; } 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); } if (list_directory(argv[i], &flags) && i + 1 != argc) if (titled && !flags.recurse) printf("\n"); } return EXIT_SUCCESS; }