From 3b53b4c96d05cfadcf275d24f021159471343048 Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Fri, 28 Apr 2023 12:31:49 -0400 Subject: [PATCH] improve ls command --- src/commands/ls.c | 329 ++++++++++++++++++++++++++++++++-------------- src/shared.c | 29 ++-- src/shared.h | 4 +- 3 files changed, 244 insertions(+), 118 deletions(-) diff --git a/src/commands/ls.c b/src/commands/ls.c index bcc042c..e9fec8c 100644 --- a/src/commands/ls.c +++ b/src/commands/ls.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,28 @@ struct Flags { 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) { @@ -44,114 +67,152 @@ static void append_path(char buf[PATH_MAX], const char* dir_path, const struct d 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) { +static bool get_file_info(struct dirent* file, const char* dir_path, struct FileInfo* info) { - char* color; + uid_t uid = getuid(); + gid_t gid = getgid(); - if (file->d_type == DT_DIR) { - color = DIR_COLOR; - } else if (file->d_type == DT_LNK) { - color = LINK_COLOR; - } else { - color = FILE_COLOR; + 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; } - if (flags->more_info) { + info->set_uid = false; + info->set_gid = false; + info->exec = false; - uid_t uid = getuid(); - gid_t gid = getgid(); + 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] = '-'; - 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; + 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 { - putchar('-'); + info->mode[3] = 'x'; } - - 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"); - } - + if (!info->exec) info->exec = s.st_uid == uid; } else { - printf("%s%s", color, file->d_name); - flags->one_column ? putchar('\n') : putchar(' '); + 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); + + 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); + } + } } } @@ -159,6 +220,53 @@ 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 void recurse_directory(char* path, struct Flags* flags) { DIR* d; struct dirent* file; @@ -167,6 +275,12 @@ static void recurse_directory(char* path, struct Flags* flags) { d = get_directory(path); if (d == NULL) return; + 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; @@ -175,8 +289,11 @@ static void recurse_directory(char* path, struct Flags* flags) { printf("\n%s%s:%s\n", DIR_COLOR, path, FILE_COLOR); first = false; } - list_file(file, flags, path); + push_file(&files, &info, &size, &capacity, file, path); } + + list_files(files, size, info, flags, path); + free(files); if (!flags->more_info) printf("\n"); @@ -206,14 +323,22 @@ static void list_directory(char* path, struct Flags* flags) { DIR* d = get_directory(path); if (d == NULL) return; + 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; - list_file(file, flags, path); + push_file(&files, &info, &size, &capacity, file, path); } + list_files(files, size, info, flags, path); + free(files); + closedir(d); } diff --git a/src/shared.c b/src/shared.c index a8837c4..b167e6f 100644 --- a/src/shared.c +++ b/src/shared.c @@ -47,7 +47,7 @@ bool prefix(const char* pre, const char* str) { } static char fs_types[5] = {'K','M','G','T','P'}; -void print_file_size(size_t bytes) { +void print_file_size(size_t bytes, char buf[5]) { int index = 0; float next = bytes; while (true) { @@ -61,25 +61,26 @@ void print_file_size(size_t bytes) { next /= 1024; index++; } - - if (next/100 < 1) putchar(' '); - if (next/10 < 1) putchar(' '); - if (index == 0) putchar(' '); - - printf("%u", (int)(next+.5)); + + int n = snprintf(buf, 4, "%u", (int)(next+.5)); if (index > 0) { - putchar(fs_types[index - 1]); + buf[n] = (fs_types[index - 1]); + n++; } - putchar(' '); + buf[n] = '\0'; } static char* months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; -void print_date_time(time_t mills) { +void print_date_time(time_t mills, char buf[13]) { struct tm* info; info = localtime(&mills); - printf("%s ", months[info->tm_mon]); - if (info->tm_mday < 10) - printf(" "); - printf("%d %02d:%02d ", info->tm_mday, info->tm_hour, info->tm_sec); + int n = snprintf(buf, 5, "%s ", months[info->tm_mon]); + + if (info->tm_mday < 10) { + buf[n] = ' '; + n++; + } + + snprintf(buf + n, 13 - n, "%d %02d:%02d ", info->tm_mday, info->tm_hour, info->tm_sec); } diff --git a/src/shared.h b/src/shared.h index 56f0a70..4556d5d 100644 --- a/src/shared.h +++ b/src/shared.h @@ -13,5 +13,5 @@ long int get_number(const char* text); bool streql(const char* a, const char* b); bool prefix(const char* pre, const char* str); -void print_file_size(size_t bytes); -void print_date_time(time_t mills); +void print_file_size(size_t bytes, char buf[5]); +void print_date_time(time_t mills, char buf[13]);