/* lsr.c PUBLIC DOMAIN 2016 */ /* part of lsr */ /* I, Tom Vajzovic, am the author of this software and its documentation. I permanently abandon all intellectual property rights in them, including copyright, trademarks, design rights, database right, patents, and the right to be identified as the author. I am fairly certain that the software does what the documentation says it does, but I do not guarantee that it does, or that it does what you think it should. I do not guarantee that it will not have undesirable side effects. You are free to use, modify and distribute this software as you please, but you do so at your own risk. If you do not pass on this warning then you may be responsible for any problems encountered by those who obtain the software through you. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "output_buffer.h" #define TYPE_REG WHITE // non-executable regular file #define TYPE_REG_X GREEN // executable regular file #define TYPE_SYM CYAN // non-dangling symlink #define TYPE_SYM_NO MAGENTA // dangling symlink #define TYPE_DIR BLUE // readable directory #define TYPE_DIR_NO RED // unreadable directory #define TYPE_SPEC YELLOW // something that cannot be statted, character device, block device, FIFO, socket, or something else struct type_name { unsigned char type; char name[]; }; struct type_name_list { struct type_name **entries; size_t size; size_t count; }; static char *name_buffer = NULL; static size_t name_buffer_size = 0; static size_t name_buffer_len = 0; static size_t round_up (size_t s) {{{ return ((s <= 2) ? s : ((SIZE_MAX >> __builtin_clzll (s - 1)) + 1)); }}} static void allocate_name (size_t size) {{{ if (size > name_buffer_size) { name_buffer_size = size; name_buffer = realloc (name_buffer, size); } }}} static void set_name (const char *name) {{{ size_t len = strlen (name); allocate_name (round_up (len + 3000)); if (len > 0) { memcpy (name_buffer, name, len); if (name[len - 1] != '/') { name_buffer[len] = '/'; len++; } } name_buffer_len = len; }}} static void add_to_name (const char *basename) {{{ size_t dirname_len = name_buffer_len; size_t basename_len = strlen (basename); if ((dirname_len + basename_len + 1) >= name_buffer_size) { allocate_name (round_up (dirname_len + basename_len + 1000)); } memcpy (&name_buffer[dirname_len], basename, basename_len); name_buffer[dirname_len + basename_len] = '/'; name_buffer_len = (dirname_len + basename_len + 1); }}} static void print_name (const struct type_name *entry) {{{ size_t i; for (i = 0; i < name_buffer_len; i++) { if ((name_buffer[i] < 0x20) || (name_buffer[i] > 0x7E)) { output_mode (WHITE | ON_BLACK | INVERSE); char code[4] = "\\x"; code[2] = "0123456789ABCDEF"[(name_buffer[i] & 0xF0u) >> 4]; code[3] = "0123456789ABCDEF"[(name_buffer[i] & 0x0Fu) ]; output (code, 4); } else { if (name_buffer[i] == '/') { output_mode (BOLD | BLACK | ON_BLACK); } else { output_mode (WHITE | ON_BLACK); } output (&name_buffer[i], 1); } } for (i = 0; entry->name[i]; i++) { if ((entry->name[i] < 0x20) || (entry->name[i] > 0x7E)) { output_mode (BOLD | entry->type | INVERSE); char code[4] = "\\x"; code[2] = "0123456789ABCDEF"[(entry->name[i] & 0xF0u) >> 4]; code[3] = "0123456789ABCDEF"[(entry->name[i] & 0x0Fu) ]; output (code, 4); } else { if ((entry->name[i] == 0x20) && !entry->name[i + 1]) { output_mode (BOLD | entry->type | INVERSE); } else { output_mode (BOLD | entry->type); } output (&entry->name[i], 1); } } output_mode (WHITE | ON_BLACK); output ("\n", 1); }}} static DIR *opendirat (int dir_fd_parent, const char *name) {{{ DIR *dir_ptr_child = NULL; int dir_fd_child = openat (dir_fd_parent, name, (O_RDONLY | O_DIRECTORY | O_NOFOLLOW)); if (dir_fd_child >= 0) { dir_ptr_child = fdopendir (dir_fd_child); if (!dir_ptr_child) { int save_errno = errno; (void)close (dir_fd_child); errno = save_errno; } } return dir_ptr_child; }}} static void setup_list (struct type_name_list *list) {{{ list->count = 0; list->size = 1024; list->entries = malloc (list->size * sizeof list->entries[0]); }}} static void add_to_list (struct type_name_list *list, unsigned char type, const char *name) {{{ size_t len = strlen (name); struct type_name *new_entry = malloc (len + 2); new_entry->type = type; memcpy (new_entry->name, name, (len + 1)); if (list->count >= list->size) { list->size *= 2; list->entries = realloc (list->entries, (list->size * sizeof list->entries[0])); } list->entries[list->count] = new_entry; list->count++; }}} static int sort_callback (const void *pa, const void *pb) {{{ const char *a = (*(const struct type_name **)pa)->name; const char *b = (*(const struct type_name **)pb)->name; return strcoll (a, b); }}} static void recurse (DIR *dirp) {{{ struct type_name_list list_others; struct type_name_list list_subdirs; setup_list (&list_others); setup_list (&list_subdirs); struct dirent *dent; while ((dent = readdir (dirp))) { if (strcmp (dent->d_name, ".") && strcmp (dent->d_name, "..")) { struct stat sb; if (dent->d_type == DT_DIR) { add_to_list (&list_subdirs, TYPE_DIR, dent->d_name); } else if (dent->d_type == DT_LNK) { add_to_list (&list_others, TYPE_SYM, dent->d_name); } else if ((dent->d_type == DT_CHR) || (dent->d_type == DT_FIFO) || (dent->d_type == DT_BLK) || (dent->d_type == DT_SOCK)) { add_to_list (&list_others, TYPE_SPEC, dent->d_name); } else if (fstatat (dirfd (dirp), dent->d_name, &sb, AT_SYMLINK_NOFOLLOW)) { add_to_list (&list_others, TYPE_SPEC, dent->d_name); } else if (S_ISDIR (sb.st_mode)) { add_to_list (&list_subdirs, TYPE_DIR, dent->d_name); } else if (S_ISLNK (sb.st_mode)) { add_to_list (&list_others, TYPE_SYM, dent->d_name); } else if (!S_ISREG (sb.st_mode)) { add_to_list (&list_others, TYPE_SPEC, dent->d_name); } else if ((sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0) { add_to_list (&list_others, TYPE_REG_X, dent->d_name); } else { add_to_list (&list_others, TYPE_REG, dent->d_name); } } } qsort (list_others .entries, list_others .count, sizeof list_others .entries[0], sort_callback); qsort (list_subdirs.entries, list_subdirs.count, sizeof list_subdirs.entries[0], sort_callback); size_t i; for (i = 0; i < list_others.count; i++) { if (list_others.entries[i]->type == TYPE_SYM) { if (faccessat (dirfd (dirp), list_others.entries[i]->name, F_OK, 0)) { list_others.entries[i]->type = TYPE_SYM_NO; } } print_name (list_others.entries[i]); free (list_others.entries[i]); } free (list_others.entries); for (i = 0; i < list_subdirs.count; i++) { DIR *subdir = opendirat (dirfd (dirp), list_subdirs.entries[i]->name); if (subdir) { list_subdirs.entries[i]->type = TYPE_DIR; print_name (list_subdirs.entries[i]); size_t dirname_len = name_buffer_len; add_to_name (list_subdirs.entries[i]->name); recurse (subdir); name_buffer_len = dirname_len; } else { list_subdirs.entries[i]->type = TYPE_DIR_NO; print_name (list_subdirs.entries[i]); } free (list_subdirs.entries[i]); } free (list_subdirs.entries); closedir (dirp); }}} static void lsr (const char *name) {{{ const char *name_to_open = (*name ? name : "."); DIR *dirp = opendir (name_to_open); if (dirp) { set_name (name); recurse (dirp); } else { dprintf (STDERR_FILENO, "%s: %s: %m\n", program_invocation_short_name, name_to_open); exit (EXIT_FAILURE); } }}} int main (int argc, char **argv) {{{ if (argc < 2) { lsr (""); } else { int i; for (i = 1; i < argc; i++) { lsr (argv[i]); } } output_flush (); return 0; }}} // TODO : own sort order // . then 0-9 AaBb etc then punc in ASCII order then non-printable in byte order // a100b after a99b