From a6bfbbf655668ca47fc8623d8d4e3ced6e8ef8fc Mon Sep 17 00:00:00 2001 From: Bananymous Date: Sun, 10 Dec 2023 01:05:12 +0200 Subject: [PATCH] ls: Rewrite whole program for cleaner output ls -l now sorts elements and aligns them by columns. --- userspace/ls/CMakeLists.txt | 2 +- userspace/ls/main.cpp | 398 ++++++++++++++++++++++++------------ 2 files changed, 273 insertions(+), 127 deletions(-) diff --git a/userspace/ls/CMakeLists.txt b/userspace/ls/CMakeLists.txt index 4f0bcbef..0bb3e557 100644 --- a/userspace/ls/CMakeLists.txt +++ b/userspace/ls/CMakeLists.txt @@ -8,7 +8,7 @@ set(SOURCES add_executable(ls ${SOURCES}) target_compile_options(ls PUBLIC -O2 -g) -target_link_libraries(ls PUBLIC libc) +target_link_libraries(ls PUBLIC libc ban) add_custom_target(ls-install COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/ls ${BANAN_BIN}/ diff --git a/userspace/ls/main.cpp b/userspace/ls/main.cpp index bdaf01c4..9a3c42ec 100644 --- a/userspace/ls/main.cpp +++ b/userspace/ls/main.cpp @@ -1,165 +1,311 @@ +#include +#include +#include +#include + #include -#include #include +#include #include -#include -#include #include -bool all { false }; -bool list { false }; - -const char* mode_string(mode_t mode) +struct config_t { - static char buffer[11]; - buffer[0] = (mode & 0770000) == S_IFLNK ? 'l' : - (mode & 0770000) == S_IFDIR ? 'd' : - (mode & 0770000) == S_IFBLK ? 'b' : - (mode & 0770000) == S_IFCHR ? 'c' : - '-'; - buffer[1] = (mode & S_IRUSR) ? 'r' : '-'; - buffer[2] = (mode & S_IWUSR) ? 'w' : '-'; - buffer[3] = (mode & S_ISUID) ? ((mode & S_IXUSR) ? 's' : 'S') : (mode & S_IXUSR) ? 'x' : '-'; - buffer[4] = (mode & S_IRGRP) ? 'r' : '-'; - buffer[5] = (mode & S_IWGRP) ? 'w' : '-'; - buffer[6] = (mode & S_ISGID) ? ((mode & S_IXGRP) ? 's' : 'S') : (mode & S_IXGRP) ? 'x' : '-'; - buffer[7] = (mode & S_IROTH) ? 'r' : '-'; - buffer[8] = (mode & S_IWOTH) ? 'w' : '-'; - buffer[9] = (mode & S_ISVTX) ? ((mode & S_IXOTH) ? 't' : 'T') : (mode & S_IXOTH) ? 'x' : '-'; - buffer[10] = '\0'; + bool list = false; + bool all = false; + bool directory = false; +}; - return buffer; -} - -const char* color_string(mode_t mode) +struct simple_entry_t { - if ((mode & 0770000) == S_IFLNK) - return "\e[36m"; - if ((mode & 0770000) == S_IFDIR) + BAN::String name; + struct stat st; +}; + +struct full_entry_t +{ + BAN::String access; + BAN::String hard_links; + BAN::String owner_name; + BAN::String owner_group; + BAN::String size; + BAN::String month; + BAN::String day; + BAN::String time; + BAN::String full_name; +}; + +const char* entry_color(mode_t mode) +{ + // TODO: handle suid, sgid, sticky + + if (S_ISFIFO(mode) || S_ISCHR(mode) || S_ISBLK(mode)) + return "\e[33m"; + if (S_ISDIR(mode)) return "\e[34m"; - if ((mode & 0770000) == S_IFCHR) - return "\e[33m"; - if ((mode & 0770000) == S_IFBLK) - return "\e[33m"; - if ((mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + if (S_ISSOCK(mode)) + return "\e[35m"; + if (S_ISLNK(mode)) + return "\e[36m"; + if (mode & (S_IXUSR | S_IXGRP | S_IXOTH)) return "\e[32m"; - return ""; + return "\e[0m"; } -void list_directory(const char* path) +BAN::String build_access_string(mode_t mode) { - DIR* dirp = opendir(path); - if (dirp == nullptr) - return perror("opendir"); + BAN::String access; + MUST(access.resize(10)); + access[0] = S_ISBLK(mode) ? 'b' : S_ISCHR(mode) ? 'c' : S_ISDIR(mode) ? 'd' : S_ISFIFO(mode) ? 'f' : S_ISLNK(mode) ? 'l' : S_ISSOCK(mode) ? 's' : '-'; + access[1] = (mode & S_IRUSR) ? 'r' : '-'; + access[2] = (mode & S_IWUSR) ? 'w' : '-'; + access[3] = (mode & S_ISUID) ? ((mode & S_IXUSR) ? 's' : 'S') : (mode & S_IXUSR) ? 'x' : '-'; + access[4] = (mode & S_IRGRP) ? 'r' : '-'; + access[5] = (mode & S_IWGRP) ? 'w' : '-'; + access[6] = (mode & S_ISGID) ? ((mode & S_IXGRP) ? 's' : 'S') : (mode & S_IXGRP) ? 'x' : '-'; + access[7] = (mode & S_IROTH) ? 'r' : '-'; + access[8] = (mode & S_IWOTH) ? 'w' : '-'; + access[9] = (mode & S_ISVTX) ? ((mode & S_IXOTH) ? 't' : 'T') : (mode & S_IXOTH) ? 'x' : '-'; + return access; +} - errno = 0; +BAN::String build_hard_links_string(nlink_t links) +{ + return BAN::String::formatted("{}", links); +} - bool first = true; - while (auto* dirent = readdir(dirp)) +BAN::String build_owner_name_string(uid_t uid) +{ + struct passwd* passwd = getpwuid(uid); + if (passwd == nullptr) + return BAN::String::formatted("{}", uid); + return BAN::String(BAN::StringView(passwd->pw_name)); +} + +BAN::String build_owner_group_string(gid_t gid) +{ + return BAN::String::formatted("{}", gid); +} + +BAN::String build_size_string(off_t size) +{ + return BAN::String::formatted("{}", size); +} + +BAN::String build_month_string(BAN::Time time) +{ + static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + return BAN::String(BAN::StringView(months[(time.month - 1) % 12])); +} + +BAN::String build_day_string(BAN::Time time) +{ + return BAN::String::formatted("{}", time.day); +} + +BAN::String build_time_string(BAN::Time time) +{ + static uint32_t current_year = ({ timespec real_time; clock_gettime(CLOCK_REALTIME, &real_time); BAN::from_unix_time(real_time.tv_sec).year; }); + if (time.year != current_year) + return BAN::String::formatted("{}", time.year); + return BAN::String::formatted("{2}:{2}", time.hour, time.minute); +} + +int list_directory(const BAN::String& path, config_t config) +{ + BAN::Vector entries; + + struct stat st; + + auto stat_func = config.directory ? lstat : stat; + if (stat_func(path.data(), &st) == -1) { - if (!all && dirent->d_name[0] == '.') - continue; + perror("stat"); + return 2; + } - if (!first) - printf(list ? "\n" : " "); + int ret = 0; - struct stat st; - if (fstatat(dirfd(dirp), dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) + if (!S_ISDIR(st.st_mode)) + MUST(entries.emplace_back(path, st)); + else + { + DIR* dirp = opendir(path.data()); + if (dirp == NULL) { - perror("stat"); - if (list) - printf("?????????? ???? ???? ?????? %s", dirent->d_name); - else - printf("%s", dirent->d_name); + perror("opendir"); + return 2; } - if (list) + struct dirent* dirent; + while (dirent = readdir(dirp)) { - printf("%s %4d %4d %6d %s%s\e[m", mode_string(st.st_mode), st.st_uid, st.st_gid, st.st_size, color_string(st.st_mode), dirent->d_name); - if (S_ISLNK(st.st_mode)) + if (!config.all && dirent->d_name[0] == '.') + continue; + + if (fstatat(dirfd(dirp), dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) { - char link_buffer[128]; - ssize_t ret = readlinkat(dirfd(dirp), dirent->d_name, link_buffer, sizeof(link_buffer)); - if (ret >= 0) - printf(" -> %.*s", ret, link_buffer); - else - perror("readlink"); + perror("fstatat"); + ret = 1; + continue; + } + + MUST(entries.emplace_back(BAN::StringView(dirent->d_name), st)); + } + + closedir(dirp); + } + + BAN::sort::sort(entries.begin(), entries.end(), + [](const simple_entry_t& lhs, const simple_entry_t& rhs) + { + // sort directories first + bool lhs_isdir = S_ISDIR(lhs.st.st_mode); + bool rhs_isdir = S_ISDIR(rhs.st.st_mode); + if (lhs_isdir != rhs_isdir) + return lhs_isdir; + + // sort by name + for (size_t i = 0; i < BAN::Math::min(lhs.name.size(), rhs.name.size()); i++) + if (lhs.name[i] != rhs.name[i]) + return lhs.name[i] < rhs.name[i]; + return lhs.name.size() < rhs.name.size(); + } + ); + + if (!config.list) + { + for (size_t i = 0; i < entries.size(); i++) + { + if (i > 0) + printf(" "); + printf("%s%s\e[m", entry_color(entries[i].st.st_mode), entries[i].name.data()); + } + printf("\n"); + return ret; + } + + BAN::Vector full_entries; + MUST(full_entries.reserve(entries.size())); + + full_entry_t max_entry; + for (const simple_entry_t& entry : entries) + { + full_entry_t full_entry; + +#define GET_ENTRY_STRING(property, input) \ + full_entry.property = build_ ## property ## _string(input); \ + if (full_entry.property.size() > max_entry.property.size()) \ + max_entry.property = full_entry.property; + + GET_ENTRY_STRING(access, entry.st.st_mode); + GET_ENTRY_STRING(hard_links, entry.st.st_nlink); + GET_ENTRY_STRING(owner_name, entry.st.st_uid); + GET_ENTRY_STRING(owner_group, entry.st.st_gid); + GET_ENTRY_STRING(size, entry.st.st_size); + + BAN::Time time = BAN::from_unix_time(entry.st.st_mtim.tv_sec); + GET_ENTRY_STRING(month, time); + GET_ENTRY_STRING(day, time); + GET_ENTRY_STRING(time, time); + + full_entry.full_name = BAN::String::formatted("{}{}\e[m", entry_color(entry.st.st_mode), entry.name); + + MUST(full_entries.push_back(BAN::move(full_entry))); + } + + for (const auto& full_entry : full_entries) + printf("%*s %*s %*s %*s %*s %*s %*s %*s %s\n", + max_entry.access.size(), full_entry.access.data(), + max_entry.hard_links.size(), full_entry.hard_links.data(), + max_entry.owner_name.size(), full_entry.owner_name.data(), + max_entry.owner_group.size(), full_entry.owner_group.data(), + max_entry.size.size(), full_entry.size.data(), + max_entry.month.size(), full_entry.month.data(), + max_entry.day.size(), full_entry.day.data(), + max_entry.time.size(), full_entry.time.data(), + full_entry.full_name.data() + ); + + return ret; +} + +int usage(const char* argv0, int ret) +{ + FILE* fout = ret ? stderr : stdout; + fprintf(fout, "usage: %s [OPTION]... [FILE]...\n", argv0); + fprintf(fout, " -a, --all show hidden files\n"); + fprintf(fout, " -l, --list use list format\n"); + fprintf(fout, " -d, --directory show directories as directories, don't list their contents\n"); + fprintf(fout, " -h, --help show this message and exit\n"); + return ret; +} + +int main(int argc, const char* argv[]) +{ + config_t config; + + int i = 1; + for (; i < argc; i++) + { + if (argv[i][0] != '-') + break; + if (argv[i][1] == '\0') + break; + + if (argv[i][1] == '-') + { + if (strcmp(argv[i], "--help") == 0) + return usage(argv[0], 0); + else if (strcmp(argv[i], "--all") == 0) + config.all = true; + else if (strcmp(argv[i], "--list") == 0) + config.list = true; + else if (strcmp(argv[i], "--directory") == 0) + config.directory = true; + else + { + fprintf(stderr, "unrecognized option '%s'\n", argv[i]); + return usage(argv[0], 2); } } else - printf("%s%s\e[m", color_string(st.st_mode), dirent->d_name); - - first = false; - } - - if (errno != 0) - perror("readdir"); - - printf("\n"); - - closedir(dirp); -} - -void print_usage() -{ - printf("Usage: ls [OPTION]... [FILE]...\n"); - printf(" -a, --all show all files\n"); - printf(" -l, --list print files on separate lines\n"); -} - -int main(int argc, char** argv) -{ - int arg = 1; - - while (arg < argc && argv[arg][0] == '-') - { - if (strcmp(argv[arg], "--all") == 0) - all = true; - else if (strcmp(argv[arg], "--list") == 0) - list = true; - else if (strcmp(argv[arg], "--help") == 0) { - print_usage(); - return 0; - } - else - { - for (int i = 1; argv[arg][i]; i++) + for (size_t j = 1; argv[i][j]; j++) { - char c = argv[arg][i]; - if (c == 'a') - all = true; - else if (c == 'l') - list = true; + if (argv[i][j] == 'h') + return usage(argv[0], 0); + else if (argv[i][j] == 'a') + config.all = true; + else if (argv[i][j] == 'l') + config.list = true; + else if (argv[i][j] == 'd') + config.directory = true; else { - print_usage(); - return 1; + fprintf(stderr, "unrecognized option '%c'\n", argv[i][j]); + return usage(argv[0], 2); } } } - - arg++; } - if (arg == argc) + BAN::Vector files; + + if (i == argc) + MUST(files.emplace_back("."sv)); + else for (; i < argc; i++) + MUST(files.emplace_back(BAN::StringView(argv[i]))); + + int ret = 0; + for (size_t i = 0; i < files.size(); i++) { - list_directory("."); - } - else if (arg + 1 == argc) - { - list_directory(argv[arg]); - } - else - { - for (int i = arg; i < argc; i++) - { - if (i > arg) - printf("\n"); - printf("%s:\n", argv[i]); - list_directory(argv[i]); - } + if (i > 0) + printf("\n"); + if (files.size() > 1) + printf("%s:\n", files[i].data()); + ret = BAN::Math::max(ret, list_directory(files[i], config)); } - return 0; + return ret; }