ls: Rewrite whole program for cleaner output
ls -l now sorts elements and aligns them by columns.
This commit is contained in:
parent
9d8c9baa3f
commit
a6bfbbf655
|
@ -8,7 +8,7 @@ set(SOURCES
|
||||||
|
|
||||||
add_executable(ls ${SOURCES})
|
add_executable(ls ${SOURCES})
|
||||||
target_compile_options(ls PUBLIC -O2 -g)
|
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
|
add_custom_target(ls-install
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/ls ${BANAN_BIN}/
|
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/ls ${BANAN_BIN}/
|
||||||
|
|
|
@ -1,165 +1,311 @@
|
||||||
|
#include <BAN/Sort.h>
|
||||||
|
#include <BAN/String.h>
|
||||||
|
#include <BAN/Time.h>
|
||||||
|
#include <BAN/Vector.h>
|
||||||
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <pwd.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
bool all { false };
|
struct config_t
|
||||||
bool list { false };
|
|
||||||
|
|
||||||
const char* mode_string(mode_t mode)
|
|
||||||
{
|
{
|
||||||
static char buffer[11];
|
bool list = false;
|
||||||
buffer[0] = (mode & 0770000) == S_IFLNK ? 'l' :
|
bool all = false;
|
||||||
(mode & 0770000) == S_IFDIR ? 'd' :
|
bool directory = false;
|
||||||
(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';
|
|
||||||
|
|
||||||
return buffer;
|
struct simple_entry_t
|
||||||
}
|
|
||||||
|
|
||||||
const char* color_string(mode_t mode)
|
|
||||||
{
|
{
|
||||||
if ((mode & 0770000) == S_IFLNK)
|
BAN::String name;
|
||||||
return "\e[36m";
|
struct stat st;
|
||||||
if ((mode & 0770000) == S_IFDIR)
|
};
|
||||||
|
|
||||||
|
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";
|
return "\e[34m";
|
||||||
if ((mode & 0770000) == S_IFCHR)
|
if (S_ISSOCK(mode))
|
||||||
return "\e[33m";
|
return "\e[35m";
|
||||||
if ((mode & 0770000) == S_IFBLK)
|
if (S_ISLNK(mode))
|
||||||
return "\e[33m";
|
return "\e[36m";
|
||||||
if ((mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
|
if (mode & (S_IXUSR | S_IXGRP | S_IXOTH))
|
||||||
return "\e[32m";
|
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);
|
BAN::String access;
|
||||||
if (dirp == nullptr)
|
MUST(access.resize(10));
|
||||||
return perror("opendir");
|
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;
|
BAN::String build_owner_name_string(uid_t uid)
|
||||||
while (auto* dirent = readdir(dirp))
|
{
|
||||||
{
|
struct passwd* passwd = getpwuid(uid);
|
||||||
if (!all && dirent->d_name[0] == '.')
|
if (passwd == nullptr)
|
||||||
continue;
|
return BAN::String::formatted("{}", uid);
|
||||||
|
return BAN::String(BAN::StringView(passwd->pw_name));
|
||||||
|
}
|
||||||
|
|
||||||
if (!first)
|
BAN::String build_owner_group_string(gid_t gid)
|
||||||
printf(list ? "\n" : " ");
|
{
|
||||||
|
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<simple_entry_t> entries;
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstatat(dirfd(dirp), dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1)
|
|
||||||
|
auto stat_func = config.directory ? lstat : stat;
|
||||||
|
if (stat_func(path.data(), &st) == -1)
|
||||||
{
|
{
|
||||||
perror("stat");
|
perror("stat");
|
||||||
if (list)
|
return 2;
|
||||||
printf("?????????? ???? ???? ?????? %s", dirent->d_name);
|
|
||||||
else
|
|
||||||
printf("%s", dirent->d_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list)
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!S_ISDIR(st.st_mode))
|
||||||
|
MUST(entries.emplace_back(path, st));
|
||||||
|
else
|
||||||
{
|
{
|
||||||
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);
|
DIR* dirp = opendir(path.data());
|
||||||
if (S_ISLNK(st.st_mode))
|
if (dirp == NULL)
|
||||||
{
|
{
|
||||||
char link_buffer[128];
|
perror("opendir");
|
||||||
ssize_t ret = readlinkat(dirfd(dirp), dirent->d_name, link_buffer, sizeof(link_buffer));
|
return 2;
|
||||||
if (ret >= 0)
|
|
||||||
printf(" -> %.*s", ret, link_buffer);
|
|
||||||
else
|
|
||||||
perror("readlink");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
printf("%s%s\e[m", color_string(st.st_mode), dirent->d_name);
|
|
||||||
|
|
||||||
first = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errno != 0)
|
struct dirent* dirent;
|
||||||
perror("readdir");
|
while (dirent = readdir(dirp))
|
||||||
|
{
|
||||||
|
if (!config.all && dirent->d_name[0] == '.')
|
||||||
|
continue;
|
||||||
|
|
||||||
printf("\n");
|
if (fstatat(dirfd(dirp), dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1)
|
||||||
|
{
|
||||||
|
perror("fstatat");
|
||||||
|
ret = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MUST(entries.emplace_back(BAN::StringView(dirent->d_name), st));
|
||||||
|
}
|
||||||
|
|
||||||
closedir(dirp);
|
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++)
|
|
||||||
{
|
|
||||||
char c = argv[arg][i];
|
|
||||||
if (c == 'a')
|
|
||||||
all = true;
|
|
||||||
else if (c == 'l')
|
|
||||||
list = true;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
print_usage();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
arg++;
|
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;
|
||||||
|
|
||||||
if (arg == argc)
|
// sort by name
|
||||||
{
|
for (size_t i = 0; i < BAN::Math::min(lhs.name.size(), rhs.name.size()); i++)
|
||||||
list_directory(".");
|
if (lhs.name[i] != rhs.name[i])
|
||||||
|
return lhs.name[i] < rhs.name[i];
|
||||||
|
return lhs.name.size() < rhs.name.size();
|
||||||
}
|
}
|
||||||
else if (arg + 1 == argc)
|
);
|
||||||
|
|
||||||
|
if (!config.list)
|
||||||
{
|
{
|
||||||
list_directory(argv[arg]);
|
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());
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = arg; i < argc; i++)
|
|
||||||
{
|
|
||||||
if (i > arg)
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("%s:\n", argv[i]);
|
return ret;
|
||||||
list_directory(argv[i]);
|
}
|
||||||
|
|
||||||
|
BAN::Vector<full_entry_t> 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
|
||||||
|
{
|
||||||
|
for (size_t j = 1; argv[i][j]; j++)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
fprintf(stderr, "unrecognized option '%c'\n", argv[i][j]);
|
||||||
|
return usage(argv[0], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
BAN::Vector<BAN::String> 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++)
|
||||||
|
{
|
||||||
|
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 ret;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue