1 /*
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.
5 *
6 * The "ls" built-in command.
7 */
8
9 #include "sash.h"
10
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <dirent.h>
14 #include <pwd.h>
15 #include <grp.h>
16
17
18 #define LISTSIZE 8192
19
20
21 #ifdef S_ISLNK
22 #define LSTAT lstat
23 #else
24 #define LSTAT stat
25 #endif
26
27
28 /*
29 * Flags for the LS command.
30 */
31 #define LSF_LONG 0x01
32 #define LSF_DIR 0x02
33 #define LSF_INODE 0x04
34 #define LSF_MULT 0x08
35 #define LSF_FLAG 0x10
36 #define LSF_COLUMN 0x20
37 #define LSF_NUMERIC 0x40
38
39
40 /*
41 * Data holding list of files.
42 */
43 static char ** list;
44 static int listSize;
45 static int listUsed;
46
47 /*
48 * Cached user and group name data.
49 */
50 static char userName[12];
51 static int userId;
52 static BOOL userIdKnown;
53 static char groupName[12];
54 static int groupId;
55 static BOOL groupIdKnown;
56
57
58 /*
59 * Local procedures.
60 */
61 static void listFile(
62 const char * name,
63 const struct stat * statBuf,
64 int flags,
65 int width
66 );
67
68 static BOOL addListName(const char * fileName);
69 static void listAllFiles(int flags, int displayWidth);
70 static void clearListNames(void);
71
72
73 int
do_ls(int argc,const char ** argv)74 do_ls(int argc, const char ** argv)
75 {
76 const char * cp;
77 const char * name;
78 int flags;
79 int i;
80 int displayWidth;
81 BOOL endSlash;
82 DIR * dirp;
83 struct dirent * dp;
84 char fullName[PATH_LEN];
85 struct stat statBuf;
86 int r;
87
88 static const char * def[] = {"."};
89
90 /*
91 * Reset for a new listing run.
92 */
93 clearListNames();
94
95 userIdKnown = FALSE;
96 groupIdKnown = FALSE;
97
98 displayWidth = 0;
99 flags = 0;
100
101 /*
102 * Handle options.
103 */
104 argc--;
105 argv++;
106
107 while ((argc > 0) && (**argv == '-'))
108 {
109 cp = *argv++ + 1;
110 argc--;
111
112 while (*cp) switch (*cp++)
113 {
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;
120
121 default:
122 fprintf(stderr, "Unknown option -%c\n", cp[-1]);
123
124 return 1;
125 }
126 }
127
128 /*
129 * If long or numeric listing is specified then turn off column listing.
130 */
131 if (flags & (LSF_LONG | LSF_NUMERIC))
132 flags &= ~LSF_COLUMN;
133
134 /*
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.
138 */
139 if (flags & LSF_COLUMN)
140 {
141 name = getenv("COLS");
142
143 if (name)
144 displayWidth = atoi(name);
145
146 if (displayWidth <= 0)
147 displayWidth = 80;
148 }
149
150 /*
151 * If no arguments are given set up to show the current directory.
152 */
153 if (argc <= 0)
154 {
155 argc = 1;
156 argv = def;
157 }
158
159 if (argc > 1)
160 flags |= LSF_MULT;
161
162 /*
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.
166 */
167 for (i = 0; i < argc; i++)
168 {
169 if ((flags & LSF_DIR) || !isDirectory(argv[i]))
170 {
171 if (!addListName(argv[i]))
172 return 1;
173 }
174 }
175
176 /*
177 * List those file names, and then clear the list.
178 */
179 listAllFiles(flags, displayWidth);
180 clearListNames();
181
182 /*
183 * If directories were being listed as themselves, then we are done.
184 */
185 if (flags & LSF_DIR)
186 return r;
187
188 /*
189 * Now iterate over the file names processing the directories.
190 */
191 while (!intFlag && (argc-- > 0))
192 {
193 name = *argv++;
194 endSlash = (*name && (name[strlen(name) - 1] == '/'));
195
196 if (LSTAT(name, &statBuf) < 0)
197 {
198 perror(name);
199 r = 1;
200
201 continue;
202 }
203
204 /*
205 * If this file name is not a directory, then ignore it.
206 */
207 if (!S_ISDIR(statBuf.st_mode))
208 continue;
209
210 /*
211 * Collect all the files in the directory.
212 */
213 dirp = opendir(name);
214
215 if (dirp == NULL)
216 {
217 perror(name);
218
219 continue;
220 }
221
222 if (flags & LSF_MULT)
223 printf("\n%s:\n", name);
224
225 while (!intFlag && ((dp = readdir(dirp)) != NULL))
226 {
227 fullName[0] = '\0';
228
229 if ((*name != '.') || (name[1] != '\0'))
230 {
231 strcpy(fullName, name);
232
233 if (!endSlash)
234 strcat(fullName, "/");
235 }
236
237 strcat(fullName, dp->d_name);
238
239 /*
240 * Save the file name in the list.
241 */
242 if (!addListName(fullName))
243 {
244 closedir(dirp);
245
246 return 1;
247 }
248 }
249
250 closedir(dirp);
251
252 /*
253 * List the files we collected in this directory,
254 * and then clear the list.
255 */
256 listAllFiles(flags, displayWidth);
257 clearListNames();
258 }
259
260 return r;
261 }
262
263
264 /*
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.
268 */
269 static void
listAllFiles(int flags,int displayWidth)270 listAllFiles(int flags, int displayWidth)
271 {
272 const char * name;
273 const char * cp;
274 int fileWidth;
275 int column;
276 int len;
277 int i;
278 struct stat statBuf;
279
280 /*
281 * Initialise width data until we need it.
282 */
283 fileWidth = 0;
284 column = 0;
285
286 /*
287 * Sort the files in the list.
288 */
289 qsort((void *) list, listUsed, sizeof(char *), nameSort);
290
291 /*
292 * If we are showing the files in columns then calculate the
293 * maximum width of all of the file names, taking into account
294 * various factors.
295 */
296 if (flags & LSF_COLUMN)
297 {
298 for (i = 0; i < listUsed; i++)
299 {
300 len = strlen(list[i]);
301
302 if (fileWidth < len)
303 fileWidth = len;
304 }
305
306 if (flags & LSF_FLAG)
307 fileWidth++;
308
309 if (flags & LSF_INODE)
310 fileWidth += 8;
311
312 fileWidth += 2;
313 }
314
315 /*
316 * Now list the fileNames.
317 */
318 for (i = 0; i < listUsed; i++)
319 {
320 name = list[i];
321
322 if (LSTAT(name, &statBuf) < 0)
323 {
324 perror(name);
325
326 continue;
327 }
328
329 cp = strrchr(name, '/');
330
331 if (cp)
332 cp++;
333 else
334 cp = name;
335
336 /*
337 * List the file in the next column or at the end
338 * of a line depending on the width left.
339 */
340 if (column + fileWidth * 2 >= displayWidth)
341 {
342 listFile(cp, &statBuf, flags, 0);
343 column = 0;
344 }
345 else
346 {
347 listFile(cp, &statBuf, flags, fileWidth);
348 column += fileWidth;
349 }
350 }
351
352 /*
353 * Terminate the last file name if necessary.
354 */
355 if (column > 0)
356 fputc('\n', stdout);
357 }
358
359
360 /*
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.
364 */
365 static void
listFile(const char * name,const struct stat * statBuf,int flags,int width)366 listFile(
367 const char * name,
368 const struct stat * statBuf,
369 int flags,
370 int width
371 )
372 {
373 char * cp;
374 struct passwd * pwd;
375 struct group * grp;
376 int len;
377 int mode;
378 int flagChar;
379 int usedWidth;
380 char buf[PATH_LEN];
381
382 mode = statBuf->st_mode;
383
384 /*
385 * Initialise buffers for use.
386 */
387 cp = buf;
388 buf[0] = '\0';
389 flagChar = '\0';
390
391 /*
392 * Show the inode number if requested.
393 */
394 if (flags & LSF_INODE)
395 {
396 sprintf(cp, "%7d ", statBuf->st_ino);
397 cp += strlen(cp);
398 }
399
400 /*
401 * Create the long or numeric status line if requested.
402 */
403 if (flags & (LSF_LONG | LSF_NUMERIC))
404 {
405 strcpy(cp, modeString(mode));
406 cp += strlen(cp);
407
408 sprintf(cp, "%3ld ", (long) statBuf->st_nlink);
409 cp += strlen(cp);
410
411 if (!userIdKnown || (statBuf->st_uid != userId))
412 {
413 if (flags & LSF_NUMERIC)
414 pwd = 0;
415 else
416 pwd = getpwuid(statBuf->st_uid);
417
418 if (pwd)
419 strcpy(userName, pwd->pw_name);
420 else
421 sprintf(userName, "%d", statBuf->st_uid);
422
423 userId = statBuf->st_uid;
424 userIdKnown = TRUE;
425 }
426
427 sprintf(cp, "%-8s ", userName);
428 cp += strlen(cp);
429
430 if (!groupIdKnown || (statBuf->st_gid != groupId))
431 {
432 if (flags & LSF_NUMERIC)
433 grp = 0;
434 else
435 grp = getgrgid(statBuf->st_gid);
436
437 if (grp)
438 strcpy(groupName, grp->gr_name);
439 else
440 sprintf(groupName, "%d", statBuf->st_gid);
441
442 groupId = statBuf->st_gid;
443 groupIdKnown = TRUE;
444 }
445
446 sprintf(cp, "%-8s ", groupName);
447 cp += strlen(cp);
448
449 if (S_ISBLK(mode) || S_ISCHR(mode))
450 {
451 sprintf(cp, "%3lu, %3lu ",
452 ((unsigned long) statBuf->st_rdev) >> 8,
453 ((unsigned long) statBuf->st_rdev) & 0xff);
454 }
455 else
456 sprintf(cp, "%8lld ", statBuf->st_size);
457
458 cp += strlen(cp);
459
460 sprintf(cp, " %-12s ", timeString(statBuf->st_mtime));
461 }
462
463 /*
464 * Set the special character if the file is a directory or
465 * symbolic link or executable and the display was requested.
466 */
467 if (flags & LSF_FLAG)
468 {
469 if (S_ISDIR(mode))
470 flagChar = '/';
471 #ifdef S_ISLNK
472 else if (S_ISLNK(mode))
473 flagChar = '@';
474 #endif
475 else if ((mode & 0111) != 0)
476 flagChar = '*';
477 }
478
479 /*
480 * Print the status info followed by the file name.
481 */
482 fputs(buf, stdout);
483 fputs(name, stdout);
484
485 if (flagChar)
486 fputc(flagChar, stdout);
487
488 /*
489 * Calculate the width used so far.
490 */
491 usedWidth = strlen(buf) + strlen(name);
492
493 if (flagChar)
494 usedWidth++;
495
496 /*
497 * Show where a symbolic link points.
498 */
499 #ifdef S_ISLNK
500 if ((flags & LSF_LONG) && S_ISLNK(mode))
501 {
502 len = readlink(name, buf, PATH_LEN - 1);
503
504 if (len >= 0)
505 {
506 buf[len] = '\0';
507 printf(" -> %s", buf);
508 }
509
510 usedWidth += strlen(buf) + 4;
511 }
512 #endif
513
514 /*
515 * If no width was given then just end the line with a newline.
516 */
517 if (width == 0)
518 {
519 fputc('\n', stdout);
520
521 return;
522 }
523
524 /*
525 * There is a width given.
526 * Print as many spaces as it takes to reach that width.
527 */
528 while (usedWidth++ < width)
529 fputc(' ', stdout);
530 }
531
532
533 /*
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.
537 */
538 static BOOL
addListName(const char * fileName)539 addListName(const char * fileName)
540 {
541 char ** newList;
542
543 /*
544 * Reallocate the list if necessary.
545 */
546 if (listUsed >= listSize)
547 {
548 newList = realloc(list,
549 ((sizeof(char **)) * (listSize + LISTSIZE)));
550
551 if (newList == NULL)
552 {
553 fprintf(stderr, "No memory for file name buffer\n");
554
555 return FALSE;
556 }
557
558 list = newList;
559 listSize += LISTSIZE;
560 }
561
562 /*
563 * Copy the file name into the next entry.
564 */
565 list[listUsed] = strdup(fileName);
566
567 if (list[listUsed] == NULL)
568 {
569 fprintf(stderr, "No memory for file name\n");
570
571 return FALSE;
572 }
573
574 /*
575 * Increment the amount of space used.
576 */
577 listUsed++;
578
579 return TRUE;
580 }
581
582
583 /*
584 * Free all of the names from the list of file names.
585 */
586 static void
clearListNames(void)587 clearListNames(void)
588 {
589 while (listUsed > 0)
590 {
591 listUsed--;
592
593 free(list[listUsed]);
594 }
595 }
596
597 /* END CODE */
598