diff --git a/src/ls/Makefile b/src/ls/Makefile index 411fc95..b0065b9 100644 --- a/src/ls/Makefile +++ b/src/ls/Makefile @@ -1,5 +1,8 @@ OUT := ls -SRC := ls.c +SRC := ls_main.c +SRC += ls_list_directories.c +SRC += ls_print_info.c +SRC += ls_recursive_walk.c include ../shared.mk diff --git a/src/ls/ls.1 b/src/ls/ls.1 index fca0bf6..c7b82a3 100644 --- a/src/ls/ls.1 +++ b/src/ls/ls.1 @@ -1 +1,41 @@ -\" TODO \ No newline at end of file +.Dt %N 1 +.Dd April 19, 2024 +.Dt ls 1 +.Os +.Sh NAME +.Nm ls +.Nd list directory contents (Canoutils implementation) +.Sh SYNOPSIS +.Nm +.Op Fl h +.TP +. +.Sh DESCRIPTION +.Nm +List information about the FILEs (the current directory by default). +. +.Sh OPTIONS +\fBls\fR interprets the following options when it is invoked: +.Pp +.Bl -tag -width Ds +. +.It Fl a +do not ignore entries starting with `.` +.It Fl l +use a long listing format +.It Fl R +list subdirectories recursively +.It Fl d +list directories themselves, not their contents +.It Fl r +reverse order while sorting +.It Fl t +sort by time, newest first +. +.El +. +.Sh Copyright +This project is dedicated to the public domain, which means you're free to use it however you like. While mentioning us is optional, we'd greatly appreciate it if you would consider acknowledging our contribution. +.Sh AUTHOR +Canoutils `ls` was written by \fBSigmanificient\fB. +.br diff --git a/src/ls/ls.c b/src/ls/ls.c deleted file mode 100644 index 6e6d777..0000000 --- a/src/ls/ls.c +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main(void) { - printf("Hello, World!\n"); - return 0; -} \ No newline at end of file diff --git a/src/ls/ls.h b/src/ls/ls.h new file mode 100644 index 0000000..6696ce1 --- /dev/null +++ b/src/ls/ls.h @@ -0,0 +1,60 @@ +#ifndef MY_LS_H +#define MY_LS_H + +#include +#include + +#define ZERO_OR(expr, default) ((!!(expr)) * default) + +#define __USE_XOPEN2K8 +#define __USE_MISC + +#include +#include + +#define MIN_ALLOCATED_ENTRY (1024) + +#if !defined(__clang__) || !defined(__GNUC__) +#define __attribute__(x) // omitted +#endif + +enum { + F_ALL_FILES = 1 << 0, + F_LONG_FORM = 1 << 1, + F_RECURSIVE = 1 << 2, + F_DIRECTORY = 1 << 3, + F_REV_ORDER = 1 << 4, + F_SORT_TIME = 1 << 5, + F_SHOW_DIRS = 1 << 6, +}; + +typedef struct { + struct stat stat; + struct passwd *passwd; + struct group *group; + char name[NAME_MAX + 1]; +} entry_t; + +typedef struct { + char *name; + entry_t *entries; + size_t size; + bool is_file; +} dirbuff_t; + +inline __attribute__((const)) int stridx(const char *str, char c) { + for (const char *p = str; *p != '\0'; p++) + if (*p == c) + return (int)(p - str); + return -1; +} + +char *strdup(char const *s); + +int list_dir(dirbuff_t *db, char flags); +size_t recurse(dirbuff_t *db, size_t count, char flags); + +void print_entries(entry_t *entry, size_t count, char flags); +char *path_concat(char *dest, char *basepath, char *suffix); + +#endif diff --git a/src/ls/ls_list_directories.c b/src/ls/ls_list_directories.c new file mode 100644 index 0000000..210098c --- /dev/null +++ b/src/ls/ls_list_directories.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ls.h" + +static void get_file_info(const char *path, entry_t *entry) { + if (stat(path, &entry->stat) < 0) + return; + entry->passwd = getpwuid(entry->stat.st_uid); + entry->group = getgrgid(entry->stat.st_gid); +} + +static __attribute__((nonnull)) size_t read_directory(dirbuff_t *db, DIR *dir, + char flags) { + static char path[PATH_MAX]; + size_t i = 0; + + for (struct dirent *dirent = readdir(dir); dirent != NULL; + dirent = readdir(dir)) { + if (dirent->d_name[0] == '.' && ~flags & F_ALL_FILES) + continue; + if (i == db->size) { + db->size <<= 1; + db->entries = realloc(db->entries, db->size * sizeof(*db->entries)); + } + strcpy(db->entries[i].name, dirent->d_name); + if (flags & (F_LONG_FORM | F_SORT_TIME | F_RECURSIVE)) + get_file_info(path_concat(path, db->name, db->entries[i].name), + &db->entries[i]); + i++; + } + return i; +} + +static void print_error(char *dirname) { + char const *err = strerror(errno); + + switch (errno) { + case ENOENT: + fprintf(stderr, "ls: cannot access '%s': %s\n", dirname, err); + return; + case EACCES: + fprintf(stderr, "ls: cannot open directory '%s': %s\n", dirname, err); + return; + default: + fprintf(stderr, "ls: %s\n", err); + } +} + +static int compare_names(entry_t const *leftp, entry_t const *rightp) { + return strcoll(leftp->name, rightp->name); +} + +static int compare_times(entry_t const *leftp, entry_t const *rightp) { + return (int)(rightp->stat.st_mtim.tv_sec - leftp->stat.st_mtim.tv_sec); +} + +int list_dir(dirbuff_t *db, char flags) { + struct stat fi; + size_t count = 1; + DIR *dir; + + if (stat(db->name, &fi) < 0) + return print_error(db->name), -1; + db->is_file = S_ISDIR(fi.st_mode) && ~flags & F_DIRECTORY; + if (db->is_file) { + dir = opendir(db->name); + if (dir == NULL) + return print_error(db->name), -1; + count = read_directory(db, dir, flags); + closedir(dir); + } else { + strcpy(db->entries[0].name, db->name); + get_file_info(db->name, &db->entries[0]); + } + + qsort(db->entries, count, sizeof *db->entries, (__compar_fn_t)&compare_names); + if (flags & F_SORT_TIME) + qsort(db->entries, count, sizeof *db->entries, + (__compar_fn_t)&compare_times); + if (flags & (F_SHOW_DIRS | F_RECURSIVE) && !db->is_file) + printf("%s:\n", db->name); + print_entries(db->entries, count, flags); + if (flags & F_RECURSIVE && !db->is_file) + recurse(db, count, flags); + return 0; +} diff --git a/src/ls/ls_main.c b/src/ls/ls_main.c new file mode 100644 index 0000000..50b8ceb --- /dev/null +++ b/src/ls/ls_main.c @@ -0,0 +1,76 @@ +#include +#include +#include + +#include "ls.h" + +#define NAME "ls (canoutils)" +#define VERSION "1.0.0" +#define AUTHOR "Yohann Boniface (Sigmanificient)" + +#define print_version() \ + do { \ + printf("%s\nversion: %s\nby: %s\n", NAME, VERSION, AUTHOR); \ + } while (0) + +static const char FLAGLIST[] = "alRdrt"; +static char DEFAULT_LOCATION[] = "."; + +static char compose_flaglist(int argc, char **argv) { + int flags = 0; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] != '-' || argv[i][1] == '\0') + continue; + for (int j = 1; argv[i][j] != '\0'; j++) + flags |= 1 << (stridx(FLAGLIST, argv[i][j]) + 1); + } + return (char)(flags >> 1); +} + +static size_t count_targets(int argc, char **argv) { + int count = 0; + + for (int i = 1; i < argc; i++) + if (argv[i][0] != '-' || argv[i][1] == '\0') + count++; + return count; +} + +static bool list_dirs(dirbuff_t *db, int argc, char **argv, char flags) { + int err = 0; + size_t count = count_targets(argc, argv); + + if (count == 0) { + db->name = DEFAULT_LOCATION; + err |= list_dir(db, flags); + } + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] != '\0') + continue; + db->name = argv[i]; + if (count > 1) + flags |= F_SHOW_DIRS; + err |= list_dir(db, flags); + } + return err; +} + +int main(int argc, char **argv) { + dirbuff_t db = {.size = MIN_ALLOCATED_ENTRY}; + char flags; + int err = 0; + + for (int i = 0; argv[i] != NULL; i++) + if (!strcmp(argv[i], "--version")) + return printf(VERSION), EXIT_SUCCESS; + flags = compose_flaglist(argc, argv); + db.entries = malloc(db.size * sizeof(*db.entries)); + if (db.entries == NULL) + return EXIT_FAILURE; + if (flags & F_DIRECTORY) + flags &= ~F_RECURSIVE; + err |= !list_dirs(&db, argc, argv, flags); + free(db.entries); + return err ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/ls/ls_print_info.c b/src/ls/ls_print_info.c new file mode 100644 index 0000000..993062d --- /dev/null +++ b/src/ls/ls_print_info.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ls.h" + +typedef unsigned char uchar; + +static void get_file_right(char bits[static sizeof("rwxrwxrwx")], + entry_t *entry) { + char *bitsp = bits; + mode_t mode = entry->stat.st_mode; + static const char *s = "-rwx"; + + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IRUSR, 1)]; + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IWUSR, 2)]; + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IXUSR, 3)]; + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IRGRP, 1)]; + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IWGRP, 2)]; + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IXGRP, 3)]; + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IROTH, 1)]; + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IWOTH, 2)]; + *bitsp++ = s[(uchar)ZERO_OR(mode & S_IXOTH, 3)]; + if (mode & S_ISUID) + bits[1] = (mode & S_IXUSR) ? 's' : 'S'; + if (mode & S_ISGID) + bits[3] = (mode & S_IXGRP) ? 's' : 'l'; + if (mode & S_ISVTX) + bits[8] = (mode & S_IXOTH) ? 't' : 'T'; +} + +static char get_file_type(entry_t *entry) { + const char typ[] = { + [S_IFBLK] = 'b', [S_IFCHR] = 'c', [S_IFDIR] = 'd', [S_IFIFO] = 'p', + [S_IFLNK] = 'l', [S_IFREG] = '-', [S_IFSOCK] = 's', [0] = '?'}; + + return typ[(entry->stat.st_mode & S_IFMT)]; +} + +static char *get_creation_time(entry_t *entry) { + static char fmt[sizeof("Jan 01 00:00")]; + char *ct = ctime(&entry->stat.st_mtim.tv_sec); + time_t now = time(NULL); + const int six_month_sec = 6 * 24 * 3600 * 31; + + if (strlen(ct) < 24) + return NULL; + if (entry->stat.st_mtim.tv_sec + six_month_sec < now) { + strncpy(fmt, ct + 4, 7); + strncpy(fmt + 7, ct + 19, 5); + } else + strncpy(fmt, ct + 4, 12); + return fmt; +} + +static void print_file_infos(entry_t *entry) { + struct stat *fi = &entry->stat; + char perms[sizeof("-rwxr-xr-x")] = {[0] = get_file_type(entry), '\0'}; + char const *owner = (entry->passwd == NULL) ? "?" : entry->passwd->pw_name; + char const *grp = (entry->group == NULL) ? "?" : entry->group->gr_name; + char *time = get_creation_time(entry); + + get_file_right(perms + 1, entry); + printf("%.10s %ld %s %s ", perms, fi->st_nlink, owner, grp); + if (stridx("bc", perms[0]) != -1) + printf("%d, %d", major(fi->st_rdev), minor(fi->st_rdev)); + else + printf("%ld", fi->st_size); + printf(" %.12s ", time); +} + +void print_entries(entry_t *entry, size_t count, char flags) { + int d; + + if (flags & F_REV_ORDER) { + d = -1; + entry += (count - 1); + } else + d = 1; + for (size_t i = 0; i < count; i++) { + if (flags & F_LONG_FORM) + print_file_infos(entry); + printf("%s", entry->name); + printf(((i + 1) == count || flags & F_LONG_FORM) ? "\n" : " "); + entry += d; + } +} diff --git a/src/ls/ls_recursive_walk.c b/src/ls/ls_recursive_walk.c new file mode 100644 index 0000000..78c9164 --- /dev/null +++ b/src/ls/ls_recursive_walk.c @@ -0,0 +1,51 @@ +#include +#include + +#include "ls.h" + +char *path_concat(char *dest, char *basepath, char *suffix) { + unsigned long written = 0; + + strcpy(dest, basepath); + written = strlen(basepath); + if (dest[written - 1] != '/') { + dest[written] = '/'; + written++; + } + strcpy(dest + written, suffix); + written += strlen(suffix); + dest[written] = '\0'; + return dest; +} + +static __attribute__((nonnull)) size_t +find_directories(char **dirs, dirbuff_t *db, size_t count) { + size_t found = 0; + + for (size_t i = 0; i < count; i++) { + if (!strcmp(db->entries[i].name, ".") || !strcmp(db->entries[i].name, "..")) + continue; + if (S_ISDIR(db->entries[i].stat.st_mode)) { + dirs[found] = strdup(db->entries[i].name); + found++; + } + } + return found; +} + +size_t recurse(dirbuff_t *db, size_t count, char flags) { + static char path[PATH_MAX]; + size_t dirsize = strlen(db->name); + char **dirs = malloc(count * sizeof(char *)); + size_t j = find_directories(dirs, db, count); + + for (size_t i = 0; i < j; i++) { + db->name = path_concat(path, db->name, dirs[i]); + list_dir(db, flags); + db->name[dirsize] = '\0'; + } + for (size_t i = 0; i < j; i++) + free(dirs[i]); + free(dirs); + return 0; +} diff --git a/src/ls/ls_strdup.c b/src/ls/ls_strdup.c new file mode 100644 index 0000000..c67be60 --- /dev/null +++ b/src/ls/ls_strdup.c @@ -0,0 +1,13 @@ +#include +#include + +#include "ls.h" + +char *strdup(char const *s) { + size_t len = strlen(s); + char *dupped = malloc(len + 1); + + strcpy(dupped, s); + dupped[len] = '\0'; + return dupped; +}