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