diff options
Diffstat (limited to 'src/sash/cmd_ls.c')
-rw-r--r-- | src/sash/cmd_ls.c | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/src/sash/cmd_ls.c b/src/sash/cmd_ls.c new file mode 100644 index 0000000..41f31cc --- /dev/null +++ b/src/sash/cmd_ls.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2014 by David I. Bell + * Permission is granted to use, distribute, or modify this source, + * provided that this copyright notice remains intact. + * + * The "ls" built-in command. + */ + +#include "sash.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include "dirent.h" +#include <pwd.h> +#include <grp.h> + + +#define LISTSIZE 8192 + + +#ifdef S_ISLNK +#define LSTAT lstat +#else +#define LSTAT stat +#endif + + +/* + * Flags for the LS command. + */ +#define LSF_LONG 0x01 +#define LSF_DIR 0x02 +#define LSF_INODE 0x04 +#define LSF_MULT 0x08 +#define LSF_FLAG 0x10 +#define LSF_COLUMN 0x20 +#define LSF_NUMERIC 0x40 + + +/* + * Data holding list of files. + */ +static char ** list; +static int listSize; +static int listUsed; + +/* + * Cached user and group name data. + */ +static char userName[12]; +static int userId; +static BOOL userIdKnown; +static char groupName[12]; +static int groupId; +static BOOL groupIdKnown; + + +/* + * Local procedures. + */ +static void listFile( + const char * name, + const struct stat * statBuf, + int flags, + int width +); + +static BOOL addListName(const char * fileName); +static void listAllFiles(int flags, int displayWidth); +static void clearListNames(void); + + +int +do_ls(int argc, const char ** argv) +{ + const char * cp; + const char * name; + int flags; + int i; + int displayWidth; + BOOL endSlash; + DIR * dirp; + struct dirent * dp; + char fullName[PATH_LEN]; + struct stat statBuf; + int r; + + static const char * def[] = {"."}; + + /* + * Reset for a new listing run. + */ + clearListNames(); + + userIdKnown = FALSE; + groupIdKnown = FALSE; + + displayWidth = 0; + flags = 0; + + /* + * Handle options. + */ + argc--; + argv++; + + while ((argc > 0) && (**argv == '-')) + { + cp = *argv++ + 1; + argc--; + + while (*cp) switch (*cp++) + { + case 'l': flags |= LSF_LONG; break; + case 'n': flags |= LSF_NUMERIC; break; + case 'd': flags |= LSF_DIR; break; + case 'i': flags |= LSF_INODE; break; + case 'F': flags |= LSF_FLAG; break; + case 'C': flags |= LSF_COLUMN; break; + + default: + fprintf(stderr, "Unknown option -%c\n", cp[-1]); + + return 1; + } + } + + /* + * If long or numeric listing is specified then turn off column listing. + */ + if (flags & (LSF_LONG | LSF_NUMERIC)) + flags &= ~LSF_COLUMN; + + /* + * If column listing is specified then calculate the maximum + * width available for the columns of file names. + * This is settable using the COLS environment variable. + */ + if (flags & LSF_COLUMN) + { + name = getenv("COLS"); + + if (name) + displayWidth = atoi(name); + + if (displayWidth <= 0) + displayWidth = 80; + } + + /* + * If no arguments are given set up to show the current directory. + */ + if (argc <= 0) + { + argc = 1; + argv = def; + } + + if (argc > 1) + flags |= LSF_MULT; + + /* + * Make one pass over the file names to collect together + * all of the files which are not directories. + * We will process them all as one list. + */ + for (i = 0; i < argc; i++) + { + if ((flags & LSF_DIR) || !isDirectory(argv[i])) + { + if (!addListName(argv[i])) + return 1; + } + } + + /* + * List those file names, and then clear the list. + */ + listAllFiles(flags, displayWidth); + clearListNames(); + + /* + * If directories were being listed as themselves, then we are done. + */ + if (flags & LSF_DIR) + return r; + + /* + * Now iterate over the file names processing the directories. + */ + while (!intFlag && (argc-- > 0)) + { + name = *argv++; + endSlash = (*name && (name[strlen(name) - 1] == '/')); + + if (LSTAT(name, &statBuf) < 0) + { + perror(name); + r = 1; + + continue; + } + + /* + * If this file name is not a directory, then ignore it. + */ + if (!S_ISDIR(statBuf.st_mode)) + continue; + + /* + * Collect all the files in the directory. + */ + dirp = opendir(name); + + if (dirp == NULL) + { + perror(name); + + continue; + } + + if (flags & LSF_MULT) + printf("\n%s:\n", name); + + while (!intFlag && ((dp = readdir(dirp)) != NULL)) + { + fullName[0] = '\0'; + + if ((*name != '.') || (name[1] != '\0')) + { + strcpy(fullName, name); + + if (!endSlash) + strcat(fullName, "/"); + } + + strcat(fullName, dp->d_name); + + /* + * Save the file name in the list. + */ + if (!addListName(fullName)) + { + closedir(dirp); + + return 1; + } + } + + closedir(dirp); + + /* + * List the files we collected in this directory, + * and then clear the list. + */ + listAllFiles(flags, displayWidth); + clearListNames(); + } + + return r; +} + + +/* + * List all of the files in the current list of files. + * The files are displayed according to the specified flags, + * in the specified display width. + */ +static void +listAllFiles(int flags, int displayWidth) +{ + const char * name; + const char * cp; + int fileWidth; + int column; + int len; + int i; + struct stat statBuf; + + /* + * Initialise width data until we need it. + */ + fileWidth = 0; + column = 0; + + /* + * Sort the files in the list. + */ + qsort((void *) list, listUsed, sizeof(char *), nameSort); + + /* + * If we are showing the files in columns then calculate the + * maximum width of all of the file names, taking into account + * various factors. + */ + if (flags & LSF_COLUMN) + { + for (i = 0; i < listUsed; i++) + { + len = strlen(list[i]); + + if (fileWidth < len) + fileWidth = len; + } + + if (flags & LSF_FLAG) + fileWidth++; + + if (flags & LSF_INODE) + fileWidth += 8; + + fileWidth += 2; + } + + /* + * Now list the fileNames. + */ + for (i = 0; i < listUsed; i++) + { + name = list[i]; + + if (LSTAT(name, &statBuf) < 0) + { + perror(name); + + continue; + } + + cp = strrchr(name, '/'); + + if (cp) + cp++; + else + cp = name; + + /* + * List the file in the next column or at the end + * of a line depending on the width left. + */ + if (column + fileWidth * 2 >= displayWidth) + { + listFile(cp, &statBuf, flags, 0); + column = 0; + } + else + { + listFile(cp, &statBuf, flags, fileWidth); + column += fileWidth; + } + } + + /* + * Terminate the last file name if necessary. + */ + if (column > 0) + fputc('\n', stdout); +} + + +/* + * Do a listing of a particular file name according to the flags. + * The output is shown within the specified width if it is nonzero, + * or on its own line if the width is zero. + */ +static void +listFile( + const char * name, + const struct stat * statBuf, + int flags, + int width +) +{ + char * cp; + struct passwd * pwd; + struct group * grp; + int len; + int mode; + int flagChar; + int usedWidth; + char buf[PATH_LEN]; + + mode = statBuf->st_mode; + + /* + * Initialise buffers for use. + */ + cp = buf; + buf[0] = '\0'; + flagChar = '\0'; + + /* + * Show the inode number if requested. + */ + if (flags & LSF_INODE) + { + sprintf(cp, "%7ld ", statBuf->st_ino); + cp += strlen(cp); + } + + /* + * Create the long or numeric status line if requested. + */ + if (flags & (LSF_LONG | LSF_NUMERIC)) + { + strcpy(cp, modeString(mode)); + cp += strlen(cp); + + sprintf(cp, "%3ld ", (long) statBuf->st_nlink); + cp += strlen(cp); + + if (!userIdKnown || (statBuf->st_uid != userId)) + { + if (flags & LSF_NUMERIC) + pwd = 0; + else + pwd = getpwuid(statBuf->st_uid); + + if (pwd) + strcpy(userName, pwd->pw_name); + else + sprintf(userName, "%d", statBuf->st_uid); + + userId = statBuf->st_uid; + userIdKnown = TRUE; + } + + sprintf(cp, "%-8s ", userName); + cp += strlen(cp); + + if (!groupIdKnown || (statBuf->st_gid != groupId)) + { + if (flags & LSF_NUMERIC) + grp = 0; + else + grp = getgrgid(statBuf->st_gid); + + if (grp) + strcpy(groupName, grp->gr_name); + else + sprintf(groupName, "%d", statBuf->st_gid); + + groupId = statBuf->st_gid; + groupIdKnown = TRUE; + } + + sprintf(cp, "%-8s ", groupName); + cp += strlen(cp); + + if (S_ISBLK(mode) || S_ISCHR(mode)) + { + sprintf(cp, "%3lu, %3lu ", + ((unsigned long) statBuf->st_rdev) >> 8, + ((unsigned long) statBuf->st_rdev) & 0xff); + } + else + sprintf(cp, "%8ld ", statBuf->st_size); + + cp += strlen(cp); + + sprintf(cp, " %-12s ", timeString(statBuf->st_mtime)); + } + + /* + * Set the special character if the file is a directory or + * symbolic link or executable and the display was requested. + */ + if (flags & LSF_FLAG) + { + if (S_ISDIR(mode)) + flagChar = '/'; +#ifdef S_ISLNK + else if (S_ISLNK(mode)) + flagChar = '@'; +#endif + else if ((mode & 0111) != 0) + flagChar = '*'; + } + + /* + * Print the status info followed by the file name. + */ + fputs(buf, stdout); + fputs(name, stdout); + + if (flagChar) + fputc(flagChar, stdout); + + /* + * Calculate the width used so far. + */ + usedWidth = strlen(buf) + strlen(name); + + if (flagChar) + usedWidth++; + + /* + * Show where a symbolic link points. + */ +#ifdef S_ISLNK + if ((flags & LSF_LONG) && S_ISLNK(mode)) + { + len = readlink(name, buf, PATH_LEN - 1); + + if (len >= 0) + { + buf[len] = '\0'; + printf(" -> %s", buf); + } + + usedWidth += strlen(buf) + 4; + } +#endif + + /* + * If no width was given then just end the line with a newline. + */ + if (width == 0) + { + fputc('\n', stdout); + + return; + } + + /* + * There is a width given. + * Print as many spaces as it takes to reach that width. + */ + while (usedWidth++ < width) + fputc(' ', stdout); +} + + +/* + * Save a file name to the end of the static list, reallocating if necessary. + * The file name is copied into allocated memory owned by the list. + * Returns TRUE on success. + */ +static BOOL +addListName(const char * fileName) +{ + char ** newList; + + /* + * Reallocate the list if necessary. + */ + if (listUsed >= listSize) + { + newList = realloc(list, + ((sizeof(char **)) * (listSize + LISTSIZE))); + + if (newList == NULL) + { + fprintf(stderr, "No memory for file name buffer\n"); + + return FALSE; + } + + list = newList; + listSize += LISTSIZE; + } + + /* + * Copy the file name into the next entry. + */ + list[listUsed] = strdup(fileName); + + if (list[listUsed] == NULL) + { + fprintf(stderr, "No memory for file name\n"); + + return FALSE; + } + + /* + * Increment the amount of space used. + */ + listUsed++; + + return TRUE; +} + + +/* + * Free all of the names from the list of file names. + */ +static void +clearListNames(void) +{ + while (listUsed > 0) + { + listUsed--; + + free(list[listUsed]); + } +} + +/* END CODE */ |