Test applets containing numbers in their name. Thanks to Larry Doolittle.
[oweals/busybox.git] / ls.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * tiny-ls.c version 0.1.0: A minimalist 'ls'
4  * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 /*
22  * To achieve a small memory footprint, this version of 'ls' doesn't do any
23  * file sorting, and only has the most essential command line switches
24  * (i.e. the ones I couldn't live without :-) All features which involve
25  * linking in substantial chunks of libc can be disabled.
26  *
27  * Although I don't really want to add new features to this program to
28  * keep it small, I *am* interested to receive bug fixes and ways to make
29  * it more portable.
30  *
31  * KNOWN BUGS:
32  * 1. ls -l of a directory doesn't give "total <blocks>" header
33  * 2. ls of a symlink to a directory doesn't list directory contents
34  * 3. hidden files can make column width too large
35  *
36  * NON-OPTIMAL BEHAVIOUR:
37  * 1. autowidth reads directories twice
38  * 2. if you do a short directory listing without filetype characters
39  *    appended, there's no need to stat each one
40  * PORTABILITY:
41  * 1. requires lstat (BSD) - how do you do it without?
42  */
43
44 static const int TERMINAL_WIDTH = 80;           /* use 79 if your terminal has linefold bug */
45 static const int COLUMN_WIDTH = 14;             /* default if AUTOWIDTH not defined */
46 static const int COLUMN_GAP = 2;                        /* includes the file type char, if present */
47
48 /************************************************************************/
49
50 #include "busybox.h"
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 #include <stdio.h>
54 #include <unistd.h>
55 #include <dirent.h>
56 #include <errno.h>
57 #include <stdio.h>
58 #ifdef BB_FEATURE_LS_TIMESTAMPS
59 #include <time.h>
60 #endif
61 #include <string.h>
62 #include <stdlib.h>
63
64 #include <fcntl.h>
65 #include <signal.h>
66 #include <sys/ioctl.h>
67
68 #ifndef NAJOR
69 #define MAJOR(dev) (((dev)>>8)&0xff)
70 #define MINOR(dev) ((dev)&0xff)
71 #endif
72
73 /* what is the overall style of the listing */
74 enum {
75 STYLE_AUTO = 0,
76 STYLE_LONG = 1,         /* one record per line, extended info */
77 STYLE_SINGLE = 2,               /* one record per line */
78 STYLE_COLUMNS = 3               /* fill columns */
79 };
80
81 /* 51306 lrwxrwxrwx  1 root     root         2 May 11 01:43 /bin/view -> vi* */
82 /* what file information will be listed */
83 #define LIST_INO                (1<<0)
84 #define LIST_BLOCKS             (1<<1)
85 #define LIST_MODEBITS   (1<<2)
86 #define LIST_NLINKS             (1<<3)
87 #define LIST_ID_NAME    (1<<4)
88 #define LIST_ID_NUMERIC (1<<5)
89 #define LIST_SIZE               (1<<6)
90 #define LIST_DEV                (1<<7)
91 #define LIST_DATE_TIME  (1<<8)
92 #define LIST_FULLTIME   (1<<9)
93 #define LIST_FILENAME   (1<<10)
94 #define LIST_SYMLINK    (1<<11)
95 #define LIST_FILETYPE   (1<<12)
96 #define LIST_EXEC               (1<<13)
97
98 /* what files will be displayed */
99 #define DISP_NORMAL             (0)             /* show normal filenames */
100 #define DISP_DIRNAME    (1<<0)  /* 2 or more items? label directories */
101 #define DISP_HIDDEN             (1<<1)  /* show filenames starting with .  */
102 #define DISP_DOT                (1<<2)  /* show . and .. */
103 #define DISP_NOLIST             (1<<3)  /* show directory as itself, not contents */
104 #define DISP_RECURSIVE  (1<<4)  /* show directory and everything below it */
105 #define DISP_ROWS               (1<<5)  /* print across rows */
106
107 #ifdef BB_FEATURE_LS_SORTFILES
108 /* how will the files be sorted */
109 static const int SORT_FORWARD = 0;              /* sort in reverse order */
110 static const int SORT_REVERSE = 1;              /* sort in reverse order */
111 static const int SORT_NAME = 2;         /* sort by file name */
112 static const int SORT_SIZE = 3;         /* sort by file size */
113 static const int SORT_ATIME = 4;                /* sort by last access time */
114 static const int SORT_CTIME = 5;                /* sort by last change time */
115 static const int SORT_MTIME = 6;                /* sort by last modification time */
116 static const int SORT_VERSION = 7;              /* sort by version */
117 static const int SORT_EXT = 8;          /* sort by file name extension */
118 static const int SORT_DIR = 9;          /* sort by file or directory */
119 #endif
120
121 #ifdef BB_FEATURE_LS_TIMESTAMPS
122 /* which of the three times will be used */
123 static const int TIME_MOD = 0;
124 static const int TIME_CHANGE = 1;
125 static const int TIME_ACCESS = 2;
126 #endif
127
128 #define LIST_SHORT              (LIST_FILENAME)
129 #define LIST_ISHORT             (LIST_INO | LIST_FILENAME)
130 #define LIST_LONG               (LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | \
131                                                 LIST_SIZE | LIST_DATE_TIME | LIST_FILENAME | \
132                                                 LIST_SYMLINK)
133 #define LIST_ILONG              (LIST_INO | LIST_LONG)
134
135 static const int SPLIT_DIR = 0;
136 static const int SPLIT_FILE = 1;
137 static const int SPLIT_SUBDIR = 2;
138
139 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
140 #define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
141 #ifdef BB_FEATURE_LS_FILETYPES
142 #define APPCHAR(mode)   ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
143 #endif
144
145 /*
146  * a directory entry and its stat info are stored here
147  */
148 struct dnode {                          /* the basic node */
149     char *name;                         /* the dir entry name */
150     char *fullname;                     /* the dir entry name */
151     struct stat dstat;          /* the file stat info */
152     struct dnode *next;         /* point at the next node */
153 };
154 typedef struct dnode dnode_t;
155
156 struct dnode **list_dir(char *);
157 struct dnode **dnalloc(int);
158 int list_single(struct dnode *);
159
160 static unsigned int disp_opts;
161 static unsigned int style_fmt;
162 static unsigned int list_fmt;
163 #ifdef BB_FEATURE_LS_SORTFILES
164 static unsigned int sort_opts;
165 static unsigned int sort_order;
166 #endif
167 #ifdef BB_FEATURE_LS_TIMESTAMPS
168 static unsigned int time_fmt;
169 #endif
170 #ifdef BB_FEATURE_LS_FOLLOWLINKS
171 static unsigned int follow_links=FALSE;
172 #endif
173
174 static unsigned short column = 0;
175 #ifdef BB_FEATURE_AUTOWIDTH
176 static unsigned short terminal_width;
177 static unsigned short column_width;
178 static unsigned short tabstops;
179 #else
180 static unsigned short column_width = COLUMN_WIDTH;
181 #endif
182
183 static int status = EXIT_SUCCESS;
184
185 #ifdef BB_FEATURE_HUMAN_READABLE
186 unsigned long ls_disp_hr = KILOBYTE;
187 #endif
188
189 static int my_stat(struct dnode *cur)
190 {
191 #ifdef BB_FEATURE_LS_FOLLOWLINKS
192         if (follow_links == TRUE) {
193                 if (stat(cur->fullname, &cur->dstat)) {
194                         perror_msg("%s", cur->fullname);
195                         status = EXIT_FAILURE;
196                         free(cur->fullname);
197                         free(cur);
198                         return -1;
199                 }
200         } else
201 #endif
202         if (lstat(cur->fullname, &cur->dstat)) {
203                 perror_msg("%s", cur->fullname);
204                 status = EXIT_FAILURE;
205                 free(cur->fullname);
206                 free(cur);
207                 return -1;
208         }
209         return 0;
210 }
211
212 static void newline(void)
213 {
214     if (column > 0) {
215         putchar('\n');
216         column = 0;
217     }
218 }
219
220 /*----------------------------------------------------------------------*/
221 #ifdef BB_FEATURE_LS_FILETYPES
222 static char append_char(mode_t mode)
223 {
224         if ( !(list_fmt & LIST_FILETYPE))
225                 return '\0';
226         if ((list_fmt & LIST_EXEC) && S_ISREG(mode)
227             && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return '*';
228                 return APPCHAR(mode);
229 }
230 #endif
231
232 /*----------------------------------------------------------------------*/
233 static void nexttabstop( void )
234 {
235         static short nexttab= 0;
236         int n=0;
237
238         if (column > 0) {
239                 n= nexttab - column;
240                 if (n < 1) n= 1;
241                 while (n--) {
242                         putchar(' ');
243                         column++;
244                 }
245         }
246         nexttab= column + column_width + COLUMN_GAP; 
247 }
248
249 /*----------------------------------------------------------------------*/
250 static int is_subdir(struct dnode *dn)
251 {
252         return (S_ISDIR(dn->dstat.st_mode) && strcmp(dn->name, ".") != 0 &&
253                         strcmp(dn->name, "..") != 0);
254 }
255
256 int countdirs(struct dnode **dn, int nfiles)
257 {
258         int i, dirs;
259
260         if (dn==NULL || nfiles < 1) return(0);
261         dirs= 0;
262         for (i=0; i<nfiles; i++) {
263                 if (S_ISDIR(dn[i]->dstat.st_mode)) dirs++;
264         }
265         return(dirs);
266 }
267
268 int countsubdirs(struct dnode **dn, int nfiles)
269 {
270         int i, subdirs;
271
272         if (dn == NULL || nfiles < 1) return 0;
273         subdirs = 0;
274         for (i = 0; i < nfiles; i++)
275                 if (is_subdir(dn[i]))
276                         subdirs++;
277         return subdirs;
278 }
279
280 int countfiles(struct dnode **dnp)
281 {
282         int nfiles;
283         struct dnode *cur;
284
285         if (dnp == NULL) return(0);
286         nfiles= 0;
287         for (cur= dnp[0];  cur->next != NULL ; cur= cur->next) nfiles++;
288         nfiles++;
289         return(nfiles);
290 }
291
292 /* get memory to hold an array of pointers */
293 struct dnode **dnalloc(int num)
294 {
295         struct dnode **p;
296
297         if (num < 1) return(NULL);
298
299         p= (struct dnode **)xcalloc((size_t)num, (size_t)(sizeof(struct dnode *)));
300         return(p);
301 }
302
303 void dfree(struct dnode **dnp)
304 {
305         struct dnode *cur, *next;
306
307         if(dnp == NULL) return;
308
309         cur=dnp[0];
310         while (cur != NULL) {
311                 if (cur->fullname != NULL) free(cur->fullname); /* free the filename */
312                 next= cur->next;
313                 free(cur);                              /* free the dnode */
314                 cur= next;
315         }
316         free(dnp);      /* free the array holding the dnode pointers */
317 }
318
319 struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
320 {
321         int dncnt, i, d;
322         struct dnode **dnp;
323
324         if (dn==NULL || nfiles < 1) return(NULL);
325
326         /* count how many dirs and regular files there are */
327         if (which == SPLIT_SUBDIR)
328                 dncnt = countsubdirs(dn, nfiles);
329         else {
330                 dncnt= countdirs(dn, nfiles); /* assume we are looking for dirs */
331                 if (which == SPLIT_FILE)
332                         dncnt= nfiles - dncnt;  /* looking for files */
333         }
334
335         /* allocate a file array and a dir array */
336         dnp= dnalloc(dncnt);
337
338         /* copy the entrys into the file or dir array */
339         for (d= i=0; i<nfiles; i++) {
340                 if (which == SPLIT_DIR) {
341                         if (S_ISDIR(dn[i]->dstat.st_mode)) {
342                                 dnp[d++]= dn[i];
343                         }  /* else skip the file */
344                 } else if (which == SPLIT_SUBDIR) {
345                         if (is_subdir(dn[i])) {
346                                 dnp[d++]= dn[i];
347                         }  /* else skip the file or dir */
348                 } else {
349                         if (!(S_ISDIR(dn[i]->dstat.st_mode))) {
350                                 dnp[d++]= dn[i];
351                         }  /* else skip the dir */
352                 }
353         }
354         return(dnp);
355 }
356
357 /*----------------------------------------------------------------------*/
358 #ifdef BB_FEATURE_LS_SORTFILES
359 int sortcmp(struct dnode *d1, struct dnode *d2)
360 {
361         int cmp, dif;
362
363         cmp= 0;
364         if (sort_opts == SORT_SIZE) {
365                 dif= (int)(d1->dstat.st_size - d2->dstat.st_size);
366         } else if (sort_opts == SORT_ATIME) {
367                 dif= (int)(d1->dstat.st_atime - d2->dstat.st_atime);
368         } else if (sort_opts == SORT_CTIME) {
369                 dif= (int)(d1->dstat.st_ctime - d2->dstat.st_ctime);
370         } else if (sort_opts == SORT_MTIME) {
371                 dif= (int)(d1->dstat.st_mtime - d2->dstat.st_mtime);
372         } else if (sort_opts == SORT_DIR) {
373                 dif= S_ISDIR(d1->dstat.st_mode) - S_ISDIR(d2->dstat.st_mode);
374         /* } else if (sort_opts == SORT_VERSION) { */
375         /* } else if (sort_opts == SORT_EXT) { */
376         } else {    /* assume SORT_NAME */
377                 dif= 0;
378         }
379
380         if (dif > 0) cmp= -1;
381         if (dif < 0) cmp=  1;
382         if (dif == 0) {
383                 /* sort by name- may be a tie_breaker for time or size cmp */
384                 dif= strcmp(d1->name, d2->name);
385                 if (dif > 0) cmp=  1;
386                 if (dif < 0) cmp= -1;
387         }
388
389         if (sort_order == SORT_REVERSE) {
390                 cmp=  -1 * cmp;
391         }
392         return(cmp);
393 }
394
395 /*----------------------------------------------------------------------*/
396 void shellsort(struct dnode **dn, int size)
397 {
398         struct dnode *temp;
399         int gap, i, j;
400
401         /* shell short the array */
402         if(dn==NULL || size < 2) return;
403
404         for (gap= size/2; gap>0; gap /=2) {
405                 for (i=gap; i<size; i++) {
406                         for (j= i-gap; j>=0; j-=gap) {
407                                 if (sortcmp(dn[j], dn[j+gap]) <= 0)
408                                         break;
409                                 /* they are out of order, swap them */
410                                 temp= dn[j];
411                                 dn[j]= dn[j+gap];
412                                 dn[j+gap]= temp;
413                         }
414                 }
415         }
416 }
417 #endif
418
419 /*----------------------------------------------------------------------*/
420 void showfiles(struct dnode **dn, int nfiles)
421 {
422         int i, ncols, nrows, row, nc;
423 #ifdef BB_FEATURE_AUTOWIDTH
424         int len;
425 #endif
426
427         if(dn==NULL || nfiles < 1) return;
428
429 #ifdef BB_FEATURE_AUTOWIDTH
430         /* find the longest file name-  use that as the column width */
431         column_width= 0;
432         for (i=0; i<nfiles; i++) {
433                 len= strlen(dn[i]->name) +
434                         ((list_fmt & LIST_INO) ? 8 : 0) +
435                         ((list_fmt & LIST_BLOCKS) ? 5 : 0)
436                         ;
437                 if (column_width < len) column_width= len;
438         }
439         ncols= (int)(terminal_width / (column_width + COLUMN_GAP));
440 #else
441         ncols= TERMINAL_WIDTH;
442 #endif
443         switch (style_fmt) {
444                 case STYLE_LONG:        /* one record per line, extended info */
445                 case STYLE_SINGLE:      /* one record per line */
446                         ncols= 1;
447                         break;
448         }
449
450         nrows= nfiles / ncols;
451         if ((nrows * ncols) < nfiles) nrows++; /* round up fractionals */
452
453         if (nrows > nfiles) nrows= nfiles;
454         for (row=0; row<nrows; row++) {
455                 for (nc=0; nc<ncols; nc++) {
456                         /* reach into the array based on the column and row */
457                         i= (nc * nrows) + row;          /* assume display by column */
458                         if (disp_opts & DISP_ROWS)
459                                 i= (row * ncols) + nc;  /* display across row */
460                         if (i < nfiles) {
461                                 nexttabstop();
462                                 list_single(dn[i]);
463                         }
464                 }
465                 newline();
466         }
467 }
468
469 /*----------------------------------------------------------------------*/
470 void showdirs(struct dnode **dn, int ndirs)
471 {
472         int i, nfiles;
473         struct dnode **subdnp;
474 #ifdef BB_FEATURE_LS_RECURSIVE
475         int dndirs;
476         struct dnode **dnd;
477 #endif
478
479         if (dn==NULL || ndirs < 1) return;
480
481         for (i=0; i<ndirs; i++) {
482                 if (disp_opts & (DISP_DIRNAME | DISP_RECURSIVE)) {
483                         printf("\n%s:\n", dn[i]->fullname);
484                 }
485                 subdnp= list_dir(dn[i]->fullname);
486                 nfiles= countfiles(subdnp);
487                 if (nfiles > 0) {
488                         /* list all files at this level */
489 #ifdef BB_FEATURE_LS_SORTFILES
490                         shellsort(subdnp, nfiles);
491 #endif
492                         showfiles(subdnp, nfiles);
493 #ifdef BB_FEATURE_LS_RECURSIVE
494                         if (disp_opts & DISP_RECURSIVE) {
495                                 /* recursive- list the sub-dirs */
496                                 dnd= splitdnarray(subdnp, nfiles, SPLIT_SUBDIR);
497                                 dndirs= countsubdirs(subdnp, nfiles);
498                                 if (dndirs > 0) {
499 #ifdef BB_FEATURE_LS_SORTFILES
500                                         shellsort(dnd, dndirs);
501 #endif
502                                         showdirs(dnd, dndirs);
503                                         free(dnd);  /* free the array of dnode pointers to the dirs */
504                                 }
505                         }
506                         dfree(subdnp);  /* free the dnodes and the fullname mem */
507 #endif
508                 }
509         }
510 }
511
512 /*----------------------------------------------------------------------*/
513 struct dnode **list_dir(char *path)
514 {
515         struct dnode *dn, *cur, **dnp;
516         struct dirent *entry;
517         DIR *dir;
518         int i, nfiles;
519
520         if (path==NULL) return(NULL);
521
522         dn= NULL;
523         nfiles= 0;
524         dir = opendir(path);
525         if (dir == NULL) {
526                 perror_msg("%s", path);
527                 status = EXIT_FAILURE;
528                 return(NULL);   /* could not open the dir */
529         }
530         while ((entry = readdir(dir)) != NULL) {
531                 /* are we going to list the file- it may be . or .. or a hidden file */
532                 if ((strcmp(entry->d_name, ".")==0) && !(disp_opts & DISP_DOT)) continue;
533                 if ((strcmp(entry->d_name, "..")==0) && !(disp_opts & DISP_DOT)) continue;
534                 if ((entry->d_name[0] ==  '.') && !(disp_opts & DISP_HIDDEN)) continue;
535                 cur= (struct dnode *)xmalloc(sizeof(struct dnode));
536                 cur->fullname = xmalloc(strlen(path)+1+strlen(entry->d_name)+1);
537                 strcpy(cur->fullname, path);
538                 if (cur->fullname[strlen(cur->fullname)-1] != '/')
539                         strcat(cur->fullname, "/");
540                 cur->name= cur->fullname + strlen(cur->fullname);
541                 strcat(cur->fullname, entry->d_name);
542                 if (my_stat(cur))
543                         continue;
544                 cur->next= dn;
545                 dn= cur;
546                 nfiles++;
547         }
548         closedir(dir);
549
550         /* now that we know how many files there are
551         ** allocate memory for an array to hold dnode pointers
552         */
553         if (nfiles < 1) return(NULL);
554         dnp= dnalloc(nfiles);
555         for (i=0, cur=dn; i<nfiles; i++) {
556                 dnp[i]= cur;   /* save pointer to node in array */
557                 cur= cur->next;
558         }
559
560         return(dnp);
561 }
562
563 /*----------------------------------------------------------------------*/
564 int list_single(struct dnode *dn)
565 {
566         int i, len;
567         char scratch[BUFSIZ + 1];
568 #ifdef BB_FEATURE_LS_TIMESTAMPS
569         char *filetime;
570         time_t ttime, age;
571 #endif
572 #if defined (BB_FEATURE_LS_FILETYPES)
573         struct stat info;
574 #endif
575 #ifdef BB_FEATURE_LS_FILETYPES
576         char append;
577 #endif
578
579         if (dn==NULL || dn->fullname==NULL) return(0);
580
581 #ifdef BB_FEATURE_LS_TIMESTAMPS
582         ttime= dn->dstat.st_mtime;      /* the default time */
583         if (time_fmt & TIME_ACCESS) ttime= dn->dstat.st_atime;
584         if (time_fmt & TIME_CHANGE) ttime= dn->dstat.st_ctime;
585         filetime= ctime(&ttime);
586 #endif
587 #ifdef BB_FEATURE_LS_FILETYPES
588         append = append_char(dn->dstat.st_mode);
589 #endif
590
591         for (i=0; i<=31; i++) {
592                 switch (list_fmt & (1<<i)) {
593                         case LIST_INO:
594                                 printf("%7ld ", dn->dstat.st_ino);
595                                 column += 8;
596                                 break;
597                         case LIST_BLOCKS:
598 #ifdef BB_FEATURE_HUMAN_READABLE
599                                 fprintf(stdout, "%5s ", format(dn->dstat.st_size, ls_disp_hr));
600 #else
601 #if _FILE_OFFSET_BITS == 64
602                                 printf("%4lld ", dn->dstat.st_blocks>>1);
603 #else
604                                 printf("%4ld ", dn->dstat.st_blocks>>1);
605 #endif
606 #endif
607                                 column += 5;
608                                 break;
609                         case LIST_MODEBITS:
610                                 printf("%10s", (char *)mode_string(dn->dstat.st_mode));
611                                 column += 10;
612                                 break;
613                         case LIST_NLINKS:
614                                 printf("%4d ", dn->dstat.st_nlink);
615                                 column += 10;
616                                 break;
617                         case LIST_ID_NAME:
618 #ifdef BB_FEATURE_LS_USERNAME
619                                 my_getpwuid(scratch, dn->dstat.st_uid);
620                                 if (*scratch)
621                                         printf("%-8.8s ", scratch);
622                                 else
623                                         printf("%-8d ", dn->dstat.st_uid);
624                                 my_getgrgid(scratch, dn->dstat.st_gid);
625                                 if (*scratch)
626                                         printf("%-8.8s", scratch);
627                                 else
628                                         printf("%-8d", dn->dstat.st_gid);
629                                 column += 17;
630                                 break;
631 #endif
632                         case LIST_ID_NUMERIC:
633                                 printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid);
634                                 column += 17;
635                                 break;
636                         case LIST_SIZE:
637                         case LIST_DEV:
638                                 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
639                                         printf("%4d, %3d ", (int)MAJOR(dn->dstat.st_rdev), (int)MINOR(dn->dstat.st_rdev));
640                                 } else {
641 #ifdef BB_FEATURE_HUMAN_READABLE
642                                         fprintf(stdout, "%9s ", format(dn->dstat.st_size, ls_disp_hr));
643 #else
644 #if _FILE_OFFSET_BITS == 64
645                                         printf("%9lld ", dn->dstat.st_size);
646 #else
647                                         printf("%9ld ", dn->dstat.st_size);
648 #endif
649 #endif
650                                 }
651                                 column += 10;
652                                 break;
653 #ifdef BB_FEATURE_LS_TIMESTAMPS
654                         case LIST_FULLTIME:
655                         case LIST_DATE_TIME:
656                                 if (list_fmt & LIST_FULLTIME) {
657                                         printf("%24.24s ", filetime);
658                                         column += 25;
659                                         break;
660                                 }
661                                 age = time(NULL) - ttime;
662                                 printf("%6.6s ", filetime+4);
663                                 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
664                                         /* hh:mm if less than 6 months old */
665                                         printf("%5.5s ", filetime+11);
666                                 } else {
667                                         printf(" %4.4s ", filetime+20);
668                                 }
669                                 column += 13;
670                                 break;
671 #endif
672                         case LIST_FILENAME:
673                                 printf("%s", dn->name);
674                                 column += strlen(dn->name);
675                                 break;
676                         case LIST_SYMLINK:
677                                 if (S_ISLNK(dn->dstat.st_mode)) {
678                                         len= readlink(dn->fullname, scratch, (sizeof scratch)-1);
679                                         if (len > 0) {
680                                                 scratch[len]= '\0';
681                                                 printf(" -> %s", scratch);
682 #ifdef BB_FEATURE_LS_FILETYPES
683                                                 if (!stat(dn->fullname, &info)) {
684                                                         append = append_char(info.st_mode);
685                                                 }
686 #endif
687                                                 column += len+4;
688                                         }
689                                 }
690                                 break;
691 #ifdef BB_FEATURE_LS_FILETYPES
692                         case LIST_FILETYPE:
693                                 if (append != '\0') {
694                                         printf("%1c", append);
695                                         column++;
696                                 }
697                                 break;
698 #endif
699                 }
700         }
701
702         return(0);
703 }
704
705 /*----------------------------------------------------------------------*/
706 extern int ls_main(int argc, char **argv)
707 {
708         struct dnode **dnf, **dnd;
709         int dnfiles, dndirs;
710         struct dnode *dn, *cur, **dnp;
711         int i, nfiles;
712         int opt;
713         int oi, ac;
714         char **av;
715 #ifdef BB_FEATURE_AUTOWIDTH
716         struct winsize win = { 0, 0, 0, 0 };
717 #endif
718
719         disp_opts= DISP_NORMAL;
720         style_fmt= STYLE_AUTO;
721         list_fmt=  LIST_SHORT;
722 #ifdef BB_FEATURE_LS_SORTFILES
723         sort_opts= SORT_NAME;
724         sort_order=     SORT_FORWARD;
725 #endif
726 #ifdef BB_FEATURE_LS_TIMESTAMPS
727         time_fmt= TIME_MOD;
728 #endif
729 #ifdef BB_FEATURE_AUTOWIDTH
730                 ioctl(fileno(stdout), TIOCGWINSZ, &win);
731                 if (win.ws_row > 4)
732                         column_width = win.ws_row - 2;
733                 if (win.ws_col > 0)
734                         terminal_width = win.ws_col - 1;
735 #endif
736         tabstops = 8;
737         nfiles=0;
738
739         /* process options */
740         while ((opt = getopt(argc, argv, "1AaCdgilnsx"
741 #ifdef BB_FEATURE_AUTOWIDTH
742 "T:w:"
743 #endif
744 #ifdef BB_FEATURE_LS_FILETYPES
745 "Fp"
746 #endif
747 #ifdef BB_FEATURE_LS_RECURSIVE
748 "R"
749 #endif
750 #ifdef BB_FEATURE_LS_SORTFILES
751 "rSvX"
752 #endif
753 #ifdef BB_FEATURE_LS_TIMESTAMPS
754 "cetu"
755 #endif
756 #ifdef BB_FEATURE_LS_FOLLOWLINKS
757 "L"
758 #endif
759 #ifdef BB_FEATURE_HUMAN_READABLE
760 "h"
761 #endif
762 "k")) > 0) {
763                 switch (opt) {
764                         case '1': style_fmt = STYLE_SINGLE; break;
765                         case 'A': disp_opts |= DISP_HIDDEN; break;
766                         case 'a': disp_opts |= DISP_HIDDEN | DISP_DOT; break;
767                         case 'C': style_fmt = STYLE_COLUMNS; break;
768                         case 'd': disp_opts |= DISP_NOLIST; break;
769                         case 'g': /* ignore -- for ftp servers */ break;
770                         case 'i': list_fmt |= LIST_INO; break;
771                         case 'l':
772                                 style_fmt = STYLE_LONG;
773                                 list_fmt |= LIST_LONG;
774 #ifdef BB_FEATURE_HUMAN_READABLE
775                                 ls_disp_hr = 1;
776 #endif
777                         break;
778                         case 'n': list_fmt |= LIST_ID_NUMERIC; break;
779                         case 's': list_fmt |= LIST_BLOCKS; break;
780                         case 'x': disp_opts = DISP_ROWS; break;
781 #ifdef BB_FEATURE_LS_FILETYPES
782                         case 'F': list_fmt |= LIST_FILETYPE | LIST_EXEC; break;
783                         case 'p': list_fmt |= LIST_FILETYPE; break;
784 #endif
785 #ifdef BB_FEATURE_LS_RECURSIVE
786                         case 'R': disp_opts |= DISP_RECURSIVE; break;
787 #endif
788 #ifdef BB_FEATURE_LS_SORTFILES
789                         case 'r': sort_order |= SORT_REVERSE; break;
790                         case 'S': sort_opts= SORT_SIZE; break;
791                         case 'v': sort_opts= SORT_VERSION; break;
792                         case 'X': sort_opts= SORT_EXT; break;
793 #endif
794 #ifdef BB_FEATURE_LS_TIMESTAMPS
795                         case 'e': list_fmt |= LIST_FULLTIME; break;
796                         case 'c':
797                                 time_fmt = TIME_CHANGE;
798 #ifdef BB_FEATURE_LS_SORTFILES
799                                 sort_opts= SORT_CTIME;
800 #endif
801                                 break;
802                         case 'u':
803                                 time_fmt = TIME_ACCESS;
804 #ifdef BB_FEATURE_LS_SORTFILES
805                                 sort_opts= SORT_ATIME;
806 #endif
807                                 break;
808                         case 't':
809 #ifdef BB_FEATURE_LS_SORTFILES
810                                 sort_opts= SORT_MTIME;
811 #endif
812                                 break;
813 #endif
814 #ifdef BB_FEATURE_LS_FOLLOWLINKS
815                         case 'L': follow_links= TRUE; break;
816 #endif
817 #ifdef BB_FEATURE_AUTOWIDTH
818                         case 'T': tabstops= atoi(optarg); break;
819                         case 'w': terminal_width= atoi(optarg); break;
820 #endif
821 #ifdef BB_FEATURE_HUMAN_READABLE
822                         case 'h': ls_disp_hr = 0; break;
823                         case 'k': ls_disp_hr = KILOBYTE; break;
824 #else
825                         case 'k': break;
826 #endif
827                         default:
828                                 goto print_usage_message;
829                 }
830         }
831
832         /* sort out which command line options take precedence */
833 #ifdef BB_FEATURE_LS_RECURSIVE
834         if (disp_opts & DISP_NOLIST)
835                 disp_opts &= ~DISP_RECURSIVE;   /* no recurse if listing only dir */
836 #endif
837 #if defined (BB_FEATURE_LS_TIMESTAMPS) && defined (BB_FEATURE_LS_SORTFILES)
838         if (time_fmt & TIME_CHANGE) sort_opts= SORT_CTIME;
839         if (time_fmt & TIME_ACCESS) sort_opts= SORT_ATIME;
840 #endif
841         if (style_fmt != STYLE_LONG)
842                         list_fmt &= ~LIST_ID_NUMERIC;  /* numeric uid only for long list */
843 #ifdef BB_FEATURE_LS_USERNAME
844         if (style_fmt == STYLE_LONG && (list_fmt & LIST_ID_NUMERIC))
845                         list_fmt &= ~LIST_ID_NAME;  /* don't list names if numeric uid */
846 #endif
847
848         /* choose a display format */
849         if (style_fmt == STYLE_AUTO)
850                 style_fmt = isatty(fileno(stdout)) ? STYLE_COLUMNS : STYLE_SINGLE;
851
852         /*
853          * when there are no cmd line args we have to supply a default "." arg.
854          * we will create a second argv array, "av" that will hold either
855          * our created "." arg, or the real cmd line args.  The av array
856          * just holds the pointers- we don't move the date the pointers
857          * point to.
858          */
859         ac= argc - optind;   /* how many cmd line args are left */
860         if (ac < 1) {
861                 av= (char **)xcalloc((size_t)1, (size_t)(sizeof(char *)));
862                 av[0]= xstrdup(".");
863                 ac=1;
864         } else {
865                 av= (char **)xcalloc((size_t)ac, (size_t)(sizeof(char *)));
866                 for (oi=0 ; oi < ac; oi++) {
867                         av[oi]= argv[optind++];  /* copy pointer to real cmd line arg */
868                 }
869         }
870
871         /* now, everything is in the av array */
872         if (ac > 1)
873                 disp_opts |= DISP_DIRNAME;   /* 2 or more items? label directories */
874
875         /* stuff the command line file names into an dnode array */
876         dn=NULL;
877         for (oi=0 ; oi < ac; oi++) {
878                 cur= (struct dnode *)xmalloc(sizeof(struct dnode));
879                 cur->fullname= xstrdup(av[oi]);
880                 cur->name= cur->fullname;
881                 if (my_stat(cur))
882                         continue;
883                 cur->next= dn;
884                 dn= cur;
885                 nfiles++;
886         }
887
888         /* now that we know how many files there are
889         ** allocate memory for an array to hold dnode pointers
890         */
891         dnp= dnalloc(nfiles);
892         for (i=0, cur=dn; i<nfiles; i++) {
893                 dnp[i]= cur;   /* save pointer to node in array */
894                 cur= cur->next;
895         }
896
897
898         if (disp_opts & DISP_NOLIST) {
899 #ifdef BB_FEATURE_LS_SORTFILES
900                 shellsort(dnp, nfiles);
901 #endif
902                 if (nfiles > 0) showfiles(dnp, nfiles);
903         } else {
904                 dnd= splitdnarray(dnp, nfiles, SPLIT_DIR);
905                 dnf= splitdnarray(dnp, nfiles, SPLIT_FILE);
906                 dndirs= countdirs(dnp, nfiles);
907                 dnfiles= nfiles - dndirs;
908                 if (dnfiles > 0) {
909 #ifdef BB_FEATURE_LS_SORTFILES
910                         shellsort(dnf, dnfiles);
911 #endif
912                         showfiles(dnf, dnfiles);
913                 }
914                 if (dndirs > 0) {
915 #ifdef BB_FEATURE_LS_SORTFILES
916                         shellsort(dnd, dndirs);
917 #endif
918                         showdirs(dnd, dndirs);
919                 }
920         }
921         return(status);
922
923   print_usage_message:
924         usage(ls_usage);
925 }