summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ls.c298
1 files changed, 240 insertions, 58 deletions
diff --git a/ls.c b/ls.c
index 4a4f34f..8a0c53f 100644
--- a/ls.c
+++ b/ls.c
@@ -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;
}