diff options
-rw-r--r-- | ls.c | 298 |
1 files changed, 240 insertions, 58 deletions
@@ -24,8 +24,12 @@ #define _XOPEN_SOURCE 700 #include <errno.h> +#include <fcntl.h> /* included because GNU only defines AT_FDCWD here */ +#include <grp.h> +#include <inttypes.h> #include <limits.h> #include <locale.h> +#include <pwd.h> #include <stdio.h> #include <stdint.h> #include <sys/stat.h> @@ -35,11 +39,6 @@ #include <string.h> #include <ftw.h> #include <time.h> -#include <sys/types.h> -#include <pwd.h> -#include <grp.h> -#include <limits.h> -#include <libgen.h> #ifndef PATH_MAX #ifdef _XOPEN_PATH_MAX @@ -49,9 +48,7 @@ #endif #endif -#define NONE 0 -#define ALL 1 -#define ALMOST 2 +#define SIZEWIDTH (8) static enum { ALPHA, @@ -73,18 +70,15 @@ static enum { #define DEFAULT_BLOCK_SIZE 512 -static int all = NONE; -static int format = NONE; -static int links = NONE; +static enum { HIDE_ALL, HIDE_SOME, HIDE_NONE } ls_hidden = HIDE_ALL; +static enum { GROUP_NAME, GROUP_ID, GROUP_NONE } ls_group = GROUP_NAME; +static enum { OWNER_NAME, OWNER_ID, OWNER_NONE } ls_owner = OWNER_NAME; + +static int format = 0; static int recurse = 0; -static int listdirs = 0; -static int noowner = 0; static int blocksize = DEFAULT_BLOCK_SIZE; -static int numeric = 0; -static int nogroup = 0; static int reverse = 0; -//static int totaldirs = 0; -static int columns = 80; +static int stat_flags = AT_SYMLINK_NOFOLLOW; struct file_info { char *path; @@ -93,11 +87,26 @@ struct file_info { static void (*ls_print)(size_t n, struct file_info[static n]); -static char * ls_filename(struct file_info *fi) +static char * ls_filename(struct file_info *f) { - static char name[PATH_MAX]; - /* TODO: decorate with /@|* if needed */ - snprintf(name, sizeof(name), "%s", fi->path); + static char name[PATH_MAX + 2]; + char *suffix = ""; + + /* TODO: only if requested */ + + if (S_ISDIR(f->st.st_mode)) { + suffix = "/"; + } else if (S_ISFIFO(f->st.st_mode)) { + suffix = "|"; + } else if (S_ISLNK(f->st.st_mode)) { + suffix = "@"; + } else if (S_ISREG(f->st.st_mode)) { + if ((S_IXUSR & f->st.st_mode) || (S_IXGRP & f->st.st_mode) || (S_IXOTH & f->st.st_mode)) { + suffix = "*"; + } + } + + snprintf(name, sizeof(name), "%s%s", f->path, suffix); return name; } @@ -140,28 +149,63 @@ static int ls_compare_files(const void *op_a, const void *op_b) return (reverse ? -1 : 1) * ret; } -/* static int ls_dir(char *dir) { + size_t nfiles = 0; + struct file_info *files = NULL; + int ret = 0; + DIR *d = opendir(dir); - struct dirent *de; - int blocks = 0; - char filename[PATH_MAX]; + if (d == NULL) { + fprintf(stderr, "ls: %s: %s\n", dir, strerror(errno)); + return 1; + } + int dfd = dirfd(d); + struct dirent *de; while ((de = readdir(d)) != NULL) { - if (de->d_name[0] != '.' || (de->d_name[0] == '.' && all == ALL) - || (strcmp(".", de->d_name) && strcmp("..", de->d_name) - && all == ALMOST)) { - strcpy(filename, dir); - strcat(filename, "/"); - strcat(filename, de->d_name); - //blocks += ls_add(filename, 1); + if (de->d_name[0] == '.') { + if (ls_hidden != HIDE_NONE) { + continue; + } + + if (ls_hidden == HIDE_SOME && (!strcmp(".", de->d_name) || !strcmp("..", de->d_name))) { + continue; + } + } + + struct file_info *tmp = realloc(files, sizeof(*tmp) * (nfiles + 1)); + if (tmp == NULL) { + perror("ls: out of memory"); + return 1; + } + files = tmp; + + files[nfiles].path = strdup(de->d_name); + if (files[nfiles].path == NULL) { + perror("ls: out of memory"); + return 1; + } + + /* TODO: maybe don't need stat */ + if (fstatat(dfd, de->d_name, &files[nfiles].st, stat_flags) == -1) { + fprintf(stderr, "ls: %s: %s\n", de->d_name, strerror(errno)); + free(files[nfiles].path); + files[nfiles].path = NULL; + ret = 1; + } else { + nfiles++; } } closedir(d); - return blocks * DEFAULT_BLOCK_SIZE / blocksize; + + qsort(files, nfiles, sizeof(*files), ls_compare_files); + ls_print(nfiles, files); + + free(files); + + return ret; } -*/ static size_t ls_find_widest(size_t n, struct file_info files[static n]) { @@ -175,6 +219,110 @@ static size_t ls_find_widest(size_t n, struct file_info files[static n]) return ret + 1; } +static char *ls_file_mode(struct stat st) +{ + mode_t m = st.st_mode; + static char mode[] = "----------"; + + if (S_ISDIR(m)) { + mode[0] = 'd'; + } else if (S_ISBLK(m)) { + mode[0] = 'b'; + } else if (S_ISCHR(m)) { + mode[0] = 'c'; + } else if (S_ISLNK(m)) { + mode[0] = 'l'; + } else if (S_ISFIFO(m)) { + mode[0] = 'p'; + } else { + mode[0] = '-'; + } + + mode[1] = m & S_IRUSR ? 'r' : '-'; + mode[2] = m & S_IWUSR ? 'w' : '-'; + if (m & S_ISUID) { + mode[3] = m & S_IXUSR ? 's' : 'S'; + } else { + mode[3] = m & S_IXUSR ? 'x' : '-'; + } + + mode[4] = m & S_IRGRP ? 'r' : '-'; + mode[5] = m & S_IWGRP ? 'w' : '-'; + if (m & S_ISGID) { + mode[6] = m & S_IXGRP ? 's' : 'S'; + } else { + mode[6] = m & S_IXGRP ? 'x' : '-'; + } + + mode[7] = m & S_IROTH ? 'r' : '-'; + mode[8] = m & S_IWOTH ? 'w' : '-'; + if (m & S_ISVTX) { + mode[9] = m & S_IXOTH ? 't' : 'T'; + } else { + mode[9] = m & S_IXOTH ? 'x' : '-'; + } + + return mode; +} + +static char *ls_file_owner(uid_t uid) +{ + static char owner[32]; + if (ls_owner == OWNER_NAME) { + struct passwd *pwd = getpwuid(uid); + if (pwd) { + return pwd->pw_name; + } + } + snprintf(owner, sizeof(owner), "%ju", (uintmax_t)uid); + return owner; +} + +static char *ls_file_group(gid_t gid) +{ + static char group[32]; + if (ls_group == GROUP_NAME) { + struct group *grp = getgrgid(gid); + if (grp) { + return grp->gr_name; + } + } + snprintf(group, sizeof(group), "%ju", (uintmax_t)gid); + return group; +} + +static void ls_print_long(size_t n, struct file_info files[static n]) +{ + for (size_t i = 0; i < n; i++) { + struct stat st = files[i].st; + printf("%s ", ls_file_mode(st)); + printf("%ju ", (uintmax_t)st.st_nlink); + + if (ls_owner != OWNER_NONE) { + printf("%*s ", - _POSIX_LOGIN_NAME_MAX, + ls_file_owner(st.st_uid)); + } + + if (ls_group != GROUP_NONE) { + printf("%*s ", - _POSIX_LOGIN_NAME_MAX, + ls_file_group(st.st_gid)); + } + + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { + printf("%s ", "DEVICE INFO"); + } else { + printf("%*ju ", SIZEWIDTH, (uintmax_t)st.st_size); + } + + printf("%s ", "DATE/TIME"); + printf("%s", ls_filename(files + i)); + if (S_ISLNK(st.st_mode)) { + printf(" -> %s\n", "LINK_DESTINATION"); + } + printf("\n"); + } +} + static void ls_print_single(size_t n, struct file_info files[static n]) { for (size_t i = 0; i < n; i++) { @@ -195,11 +343,27 @@ static void ls_print_serial(size_t n, struct file_info files[static n]) printf("%s\n", ls_filename(files + n - 1)); } +static size_t ls_get_columns(void) +{ + static size_t columns = 0; + if (columns == 0) { + char *env = getenv("COLUMNS"); + if (env) { + columns = atoi(env); + } + if (columns == 0) { + columns = 80; + } + } + return columns; +} + static void ls_print_columns(size_t n, struct file_info files[static n]) { /* TODO: partial columns */ size_t widest = ls_find_widest(n, files); + size_t columns = ls_get_columns(); size_t ncolumns = columns / widest; size_t nrows = n / ncolumns; char format[32] = ""; @@ -215,6 +379,7 @@ static void ls_print_columns(size_t n, struct file_info files[static n]) static void ls_print_rows(size_t n, struct file_info files[static n]) { size_t widest = ls_find_widest(n, files); + size_t columns = ls_get_columns(); size_t ncolumns = columns / widest; char format[32] = ""; snprintf(format, sizeof(format), "%%-%zds", widest); @@ -224,6 +389,7 @@ static void ls_print_rows(size_t n, struct file_info files[static n]) printf("\n"); } } + printf("\n"); } static int ls_compare_operands(const void *op_a, const void *op_b) @@ -258,19 +424,16 @@ int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); - char *cols = getenv("COLUMNS"); - if (cols != NULL) { - columns = atoi(cols); - } - - /* TODO: default to ls_print_columns */ ls_print = isatty(STDOUT_FILENO) ? ls_print_columns : ls_print_single; + enum { NONE, OPERANDS, ALL } links = NONE; + int list_dirs_separately = 1; + int c; while ((c = getopt(argc, argv, ":ACFRSacdgfiklmnoprstux1HL")) != -1) { switch (c) { case 'A': /** all but . and .. **/ - all = ALMOST; + ls_hidden = HIDE_SOME; break; case 'C': /** sort in columns **/ @@ -282,11 +445,11 @@ int main(int argc, char *argv[]) break; case 'H': /** follow symbolic links on command line **/ - links = FOLLOW; + links = OPERANDS; break; case 'L': /** follow all symbolic links **/ - links = LLONG; + links = ALL; break; case 'R': /** run recursively **/ @@ -298,7 +461,7 @@ int main(int argc, char *argv[]) break; case 'a': /** output all files **/ - all = ALL; + ls_hidden = HIDE_NONE; break; case 'c': /** sort by change time **/ @@ -306,20 +469,20 @@ int main(int argc, char *argv[]) break; case 'd': /** do not enter directories **/ - listdirs = 1; + list_dirs_separately = 0; break; case 'f': /** do not sort **/ sort = DIRECTORY; - all = ALL; + ls_hidden = HIDE_NONE; //display = ROWS; format ^= BLOCKS; recurse = 0; break; case 'g': /** like -l, but without owner **/ - //display = LONG; - noowner = 1; + ls_print = ls_print_long; + ls_owner = OWNER_NONE; break; case 'i': /** include file serial numbers **/ @@ -331,7 +494,7 @@ int main(int argc, char *argv[]) break; case 'l': /** output full details **/ - //display = LONG; + ls_print = ls_print_long; break; case 'm': /** stream output separated by commas **/ @@ -339,13 +502,14 @@ int main(int argc, char *argv[]) break; case 'n': /** like -l, but with numeric UID and GID **/ - //display = LONG; - numeric = 1; + ls_print = ls_print_long; + ls_owner = OWNER_ID; + ls_group = GROUP_ID; break; case 'o': /** like -l, but without group **/ - //display = LONG; - nogroup = 1; + ls_print = ls_print_long; + ls_group = GROUP_NONE; break; case 'p': /** append / after directory names **/ @@ -389,11 +553,16 @@ int main(int argc, char *argv[]) /* TODO: deal with -L/-H */ argv += optind; argc -= optind; + + if (links != NONE) { + stat_flags = AT_SYMLINK_NOFOLLOW; + } + struct file_info operands[argc + 1]; int ret = 0; for (int i = 0; i < argc; i++) { operands[i].path = argv[i]; - if (stat(argv[i], &operands[i].st) == -1) { + if (fstatat(AT_FDCWD, argv[i], &operands[i].st, stat_flags) == -1) { operands[i].path = NULL; fprintf(stderr, "ls: %s: %s\n", argv[i], strerror(errno)); ret = 1; @@ -402,10 +571,17 @@ int main(int argc, char *argv[]) operands[argc].path = NULL; qsort(operands, argc, sizeof(operands[0]), ls_compare_operands); + if (links != ALL) { + stat_flags = AT_SYMLINK_NOFOLLOW; + } + /* list files */ size_t nfiles = 0; struct file_info *op = operands; - while (op->path != NULL && !S_ISDIR(op->st.st_mode)) { + while (op->path != NULL) { + if (list_dirs_separately && !S_ISDIR(op->st.st_mode)) { + break; + } op++; nfiles++; } @@ -415,12 +591,18 @@ int main(int argc, char *argv[]) /* list directories */ - /* TODO: now always necessary */ - printf("\n"); + if (nfiles > 0) { + printf("\n"); + } while (op->path != NULL) { - printf("%s: (directory)\n", op->path); + ls_dir(op->path); op++; } + + if (argc == 0) { + return ls_dir("."); + } + return ret; } |