]> err.no Git - sash/blob - cmd_ls.c
Stop stripping during build. Also thanks to Helmut Grohne. Closes: #852771
[sash] / cmd_ls.c
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
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
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
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, "%7ld ", 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, "%8ld ", 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
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
587 clearListNames(void)
588 {
589         while (listUsed > 0)
590         {
591                 listUsed--;
592
593                 free(list[listUsed]);
594         }
595 }
596
597 /* END CODE */