2 * Copyright (c) 2014 by David I. Bell
3 * Permission is granted to use, distribute, or modify this source,
4 * provided that this copyright notice remains intact.
6 * The "ls" built-in command.
11 #include <sys/types.h>
29 * Flags for the LS command.
33 #define LSF_INODE 0x04
36 #define LSF_COLUMN 0x20
37 #define LSF_NUMERIC 0x40
41 * Data holding list of files.
48 * Cached user and group name data.
50 static char userName[12];
52 static BOOL userIdKnown;
53 static char groupName[12];
55 static BOOL groupIdKnown;
63 const struct stat * statBuf,
68 static BOOL addListName(const char * fileName);
69 static void listAllFiles(int flags, int displayWidth);
70 static void clearListNames(void);
74 do_ls(int argc, const char ** argv)
84 char fullName[PATH_LEN];
88 static const char * def[] = {"."};
91 * Reset for a new listing run.
107 while ((argc > 0) && (**argv == '-'))
112 while (*cp) switch (*cp++)
114 case 'l': flags |= LSF_LONG; break;
115 case 'n': flags |= LSF_NUMERIC; break;
116 case 'd': flags |= LSF_DIR; break;
117 case 'i': flags |= LSF_INODE; break;
118 case 'F': flags |= LSF_FLAG; break;
119 case 'C': flags |= LSF_COLUMN; break;
122 fprintf(stderr, "Unknown option -%c\n", cp[-1]);
129 * If long or numeric listing is specified then turn off column listing.
131 if (flags & (LSF_LONG | LSF_NUMERIC))
132 flags &= ~LSF_COLUMN;
135 * If column listing is specified then calculate the maximum
136 * width available for the columns of file names.
137 * This is settable using the COLS environment variable.
139 if (flags & LSF_COLUMN)
141 name = getenv("COLS");
144 displayWidth = atoi(name);
146 if (displayWidth <= 0)
151 * If no arguments are given set up to show the current directory.
163 * Make one pass over the file names to collect together
164 * all of the files which are not directories.
165 * We will process them all as one list.
167 for (i = 0; i < argc; i++)
169 if ((flags & LSF_DIR) || !isDirectory(argv[i]))
171 if (!addListName(argv[i]))
177 * List those file names, and then clear the list.
179 listAllFiles(flags, displayWidth);
183 * If directories were being listed as themselves, then we are done.
189 * Now iterate over the file names processing the directories.
191 while (!intFlag && (argc-- > 0))
194 endSlash = (*name && (name[strlen(name) - 1] == '/'));
196 if (LSTAT(name, &statBuf) < 0)
205 * If this file name is not a directory, then ignore it.
207 if (!S_ISDIR(statBuf.st_mode))
211 * Collect all the files in the directory.
213 dirp = opendir(name);
222 if (flags & LSF_MULT)
223 printf("\n%s:\n", name);
225 while (!intFlag && ((dp = readdir(dirp)) != NULL))
229 if ((*name != '.') || (name[1] != '\0'))
231 strcpy(fullName, name);
234 strcat(fullName, "/");
237 strcat(fullName, dp->d_name);
240 * Save the file name in the list.
242 if (!addListName(fullName))
253 * List the files we collected in this directory,
254 * and then clear the list.
256 listAllFiles(flags, displayWidth);
265 * List all of the files in the current list of files.
266 * The files are displayed according to the specified flags,
267 * in the specified display width.
270 listAllFiles(int flags, int displayWidth)
281 * Initialise width data until we need it.
287 * Sort the files in the list.
289 qsort((void *) list, listUsed, sizeof(char *), nameSort);
292 * If we are showing the files in columns then calculate the
293 * maximum width of all of the file names, taking into account
296 if (flags & LSF_COLUMN)
298 for (i = 0; i < listUsed; i++)
300 len = strlen(list[i]);
306 if (flags & LSF_FLAG)
309 if (flags & LSF_INODE)
316 * Now list the fileNames.
318 for (i = 0; i < listUsed; i++)
322 if (LSTAT(name, &statBuf) < 0)
329 cp = strrchr(name, '/');
337 * List the file in the next column or at the end
338 * of a line depending on the width left.
340 if (column + fileWidth * 2 >= displayWidth)
342 listFile(cp, &statBuf, flags, 0);
347 listFile(cp, &statBuf, flags, fileWidth);
353 * Terminate the last file name if necessary.
361 * Do a listing of a particular file name according to the flags.
362 * The output is shown within the specified width if it is nonzero,
363 * or on its own line if the width is zero.
368 const struct stat * statBuf,
382 mode = statBuf->st_mode;
385 * Initialise buffers for use.
392 * Show the inode number if requested.
394 if (flags & LSF_INODE)
396 sprintf(cp, "%7ld ", statBuf->st_ino);
401 * Create the long or numeric status line if requested.
403 if (flags & (LSF_LONG | LSF_NUMERIC))
405 strcpy(cp, modeString(mode));
408 sprintf(cp, "%3ld ", (long) statBuf->st_nlink);
411 if (!userIdKnown || (statBuf->st_uid != userId))
413 if (flags & LSF_NUMERIC)
416 pwd = getpwuid(statBuf->st_uid);
419 strcpy(userName, pwd->pw_name);
421 sprintf(userName, "%d", statBuf->st_uid);
423 userId = statBuf->st_uid;
427 sprintf(cp, "%-8s ", userName);
430 if (!groupIdKnown || (statBuf->st_gid != groupId))
432 if (flags & LSF_NUMERIC)
435 grp = getgrgid(statBuf->st_gid);
438 strcpy(groupName, grp->gr_name);
440 sprintf(groupName, "%d", statBuf->st_gid);
442 groupId = statBuf->st_gid;
446 sprintf(cp, "%-8s ", groupName);
449 if (S_ISBLK(mode) || S_ISCHR(mode))
451 sprintf(cp, "%3lu, %3lu ",
452 ((unsigned long) statBuf->st_rdev) >> 8,
453 ((unsigned long) statBuf->st_rdev) & 0xff);
456 sprintf(cp, "%8ld ", statBuf->st_size);
460 sprintf(cp, " %-12s ", timeString(statBuf->st_mtime));
464 * Set the special character if the file is a directory or
465 * symbolic link or executable and the display was requested.
467 if (flags & LSF_FLAG)
472 else if (S_ISLNK(mode))
475 else if ((mode & 0111) != 0)
480 * Print the status info followed by the file name.
486 fputc(flagChar, stdout);
489 * Calculate the width used so far.
491 usedWidth = strlen(buf) + strlen(name);
497 * Show where a symbolic link points.
500 if ((flags & LSF_LONG) && S_ISLNK(mode))
502 len = readlink(name, buf, PATH_LEN - 1);
507 printf(" -> %s", buf);
510 usedWidth += strlen(buf) + 4;
515 * If no width was given then just end the line with a newline.
525 * There is a width given.
526 * Print as many spaces as it takes to reach that width.
528 while (usedWidth++ < width)
534 * Save a file name to the end of the static list, reallocating if necessary.
535 * The file name is copied into allocated memory owned by the list.
536 * Returns TRUE on success.
539 addListName(const char * fileName)
544 * Reallocate the list if necessary.
546 if (listUsed >= listSize)
548 newList = realloc(list,
549 ((sizeof(char **)) * (listSize + LISTSIZE)));
553 fprintf(stderr, "No memory for file name buffer\n");
559 listSize += LISTSIZE;
563 * Copy the file name into the next entry.
565 list[listUsed] = strdup(fileName);
567 if (list[listUsed] == NULL)
569 fprintf(stderr, "No memory for file name\n");
575 * Increment the amount of space used.
584 * Free all of the names from the list of file names.
593 free(list[listUsed]);