0adc35de18ee76608a1250057584e99422f5ac24
[oweals/busybox.git] / coreutils / ls.c
1 /*
2  * Mini ls implementation for busybox
3  *
4  * Copyright (C) 1998 by Erik Andersen <andersee@debian.org>
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 GNU
14  * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19  *
20  */
21
22 // I started writing a newer small one, but it isn't done yet....
23 //  -Erik
24 #if fooBar
25
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <dirent.h>
29 #include "internal.h"
30
31
32 static const char ls_usage[] = "ls [OPTION]... [FILE]...\n"
33 "List information about the FILEs (the current directory by default).\n";
34
35 int oneFlag=FALSE;
36 int allFlag=FALSE;
37 int directoryFlag=FALSE;
38 int longFlag=FALSE;
39 int typeFlag=FALSE;
40 int dereferenceFlag=FALSE;
41 int recursiveFlag=FALSE;
42
43 static int fileAction(const char *fileName)
44 {
45     if ( allFlag==FALSE && ((strcmp(fileName, "..") == 0) 
46                 || (strcmp(fileName, ".") == 0)) ) {
47         return( TRUE);
48     }
49     //struct stat statBuf;
50     //if (stat(fileName, &statBuf) > 0) {
51         fprintf(stdout, "%s\n", fileName);
52         return( TRUE);
53     //}
54     //else {
55 //      perror(fileName);
56 //      return( FALSE);
57 //    }
58 }
59
60 static int dirAction(const char *fileName)
61 {
62     DIR *dir;
63     struct dirent *entry;
64     
65     fprintf(stdout, "%s\n", fileName);
66
67     dir = opendir( fileName);
68     if (!dir) {
69         perror("Can't open directory");
70         exit(FALSE);
71     }
72     while ((entry = readdir(dir)) != NULL) {
73         recursiveAction( entry->d_name, recursiveFlag, dereferenceFlag, FALSE, fileAction, dirAction);
74     }
75     return( TRUE);
76 }
77
78 int ls_main(int argc, char **argv)
79 {
80     if (argc <= 1) {
81         char buf[NAME_MAX];
82         getcwd( buf, NAME_MAX);
83         dirAction( buf); 
84     }
85
86     /* peel of the "ls" */
87     argc--;
88     argv++;
89
90     /* Parse any options */
91     while (**argv == '-') {
92         while (*++(*argv)) switch (**argv) {
93             case '1':
94                 oneFlag = TRUE;
95                 break;
96             case 'a':
97                 allFlag = TRUE;
98                 break;
99             case 'd':
100                 directoryFlag = TRUE;
101                 break;
102             case 'l':
103                 longFlag = TRUE;
104                 break;
105             case 'F':
106                 typeFlag = TRUE;
107                 break;
108             case 'L':
109                 dereferenceFlag = TRUE;
110                 break;
111             case 'R':
112                 recursiveFlag = TRUE;
113                 break;
114             default:
115                 usage (ls_usage);
116         }
117         argc--;
118         argv++;
119     }
120     
121     /* Ok, ready to do the deed now */
122     fprintf(stderr, "B\n");
123     while (argc-- > 1) {
124         fprintf(stderr, "C\n");
125         recursiveAction( *argv, recursiveFlag, dereferenceFlag, fileAction, dirAction);
126     }
127     exit(TRUE);
128 }
129
130
131
132 #else
133
134
135 #include "internal.h"
136 /*
137  * tiny-ls.c version 0.1.0: A minimalist 'ls'
138  * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
139  * 
140  *  This program is free software; you can redistribute it and/or modify
141  *  it under the terms of the GNU General Public License as published by
142  *  the Free Software Foundation; either version 2 of the License, or
143  *  (at your option) any later version.
144  *
145  *  This program is distributed in the hope that it will be useful,
146  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
147  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
148  *  GNU General Public License for more details.
149  *
150  *  You should have received a copy of the GNU General Public License
151  *  along with this program; if not, write to the Free Software
152  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
153  */
154
155 /*
156  * To achieve a small memory footprint, this version of 'ls' doesn't do any
157  * file sorting, and only has the most essential command line switches
158  * (i.e. the ones I couldn't live without :-) All features which involve
159  * linking in substantial chunks of libc can be disabled.
160  *
161  * Although I don't really want to add new features to this program to
162  * keep it small, I *am* interested to receive bug fixes and ways to make
163  * it more portable.
164  *
165  * KNOWN BUGS:
166  * 1. messy output if you mix files and directories on the command line
167  * 2. ls -l of a directory doesn't give "total <blocks>" header
168  * 3. ls of a symlink to a directory doesn't list directory contents
169  * 4. hidden files can make column width too large
170  * NON-OPTIMAL BEHAVIOUR:
171  * 1. autowidth reads directories twice
172  * 2. if you do a short directory listing without filetype characters
173  *    appended, there's no need to stat each one
174  * PORTABILITY:
175  * 1. requires lstat (BSD) - how do you do it without?
176  */
177
178 #define FEATURE_USERNAME        /* show username/groupnames (libc6 uses NSS) */
179 #define FEATURE_TIMESTAMPS      /* show file timestamps */
180 #define FEATURE_AUTOWIDTH       /* calculate terminal & column widths */
181 #define FEATURE_FILETYPECHAR    /* enable -p and -F */
182
183 #define TERMINAL_WIDTH  80      /* use 79 if your terminal has linefold bug */
184 #define COLUMN_WIDTH    14      /* default if AUTOWIDTH not defined */
185 #define COLUMN_GAP      2       /* includes the file type char, if present */
186 #define HAS_REWINDDIR
187
188 /************************************************************************/
189
190
191 #if 1 /* FIXME libc 6 */
192 # include <linux/types.h> 
193 #else
194 # include <sys/types.h> 
195 #endif
196 #include <sys/stat.h>
197 #include <stdio.h>
198 #include <unistd.h>
199 #include <dirent.h>
200 #include <errno.h>
201 #include <stdio.h>
202 #ifdef FEATURE_USERNAME
203 #include <pwd.h>
204 #include <grp.h>
205 #endif
206 #ifdef FEATURE_TIMESTAMPS
207 #include <time.h>
208 #endif
209
210 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
211 #define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
212 #ifdef FEATURE_FILETYPECHAR
213 #define APPCHAR(mode)   ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
214 #endif
215
216 #ifndef MAJOR
217 #define MAJOR(dev) (((dev)>>8)&0xff)
218 #define MINOR(dev) ((dev)&0xff)
219 #endif
220
221 #define MODE1  "rwxrwxrwx"
222 #define MODE0  "---------"
223 #define SMODE1 "..s..s..t"
224 #define SMODE0 "..S..S..T"
225
226 /* The 9 mode bits to test */
227
228 static const umode_t MBIT[] = {
229   S_IRUSR, S_IWUSR, S_IXUSR,
230   S_IRGRP, S_IWGRP, S_IXGRP,
231   S_IROTH, S_IWOTH, S_IXOTH
232 };
233
234 /* The special bits. If set, display SMODE0/1 instead of MODE0/1 */
235
236 static const umode_t SBIT[] = {
237   0, 0, S_ISUID,
238   0, 0, S_ISGID,
239   0, 0, S_ISVTX
240 };
241
242 #define FMT_AUTO        0
243 #define FMT_LONG        1       /* one record per line, extended info */
244 #define FMT_SINGLE      2       /* one record per line */
245 #define FMT_ROWS        3       /* print across rows */
246 #define FMT_COLUMNS     3       /* fill columns (same, since we don't sort) */
247
248 #define TIME_MOD        0
249 #define TIME_CHANGE     1
250 #define TIME_ACCESS     2
251
252 #define DISP_FTYPE      1       /* show character for file type */
253 #define DISP_EXEC       2       /* show '*' if regular executable file */
254 #define DISP_HIDDEN     4       /* show files starting . (except . and ..) */
255 #define DISP_DOT        8       /* show . and .. */
256 #define DISP_NUMERIC    16      /* numeric uid and gid */
257 #define DISP_FULLTIME   32      /* show extended time display */
258 #define DIR_NOLIST      64      /* show directory as itself, not contents */
259 #define DISP_DIRNAME    128     /* show directory name (for internal use) */
260 #define DIR_RECURSE     256     /* -R (not yet implemented) */
261
262 static unsigned char    display_fmt = FMT_AUTO;
263 static unsigned short   opts = 0;
264 static unsigned short   column = 0;
265
266 #ifdef FEATURE_AUTOWIDTH
267 static unsigned short terminal_width = 0, column_width = 0;
268 #else
269 #define terminal_width  TERMINAL_WIDTH
270 #define column_width    COLUMN_WIDTH
271 #endif
272
273 #ifdef FEATURE_TIMESTAMPS
274 static unsigned char time_fmt = TIME_MOD;
275 #endif
276
277 #define wr(data,len) fwrite(data, 1, len, stdout)
278
279 static void writenum(long val, short minwidth)
280 {
281         char    scratch[20];
282
283         char *p = scratch + sizeof(scratch);
284         short len = 0;
285         short neg = (val < 0);
286         
287         if (neg) val = -val;
288         do
289                 *--p = (val % 10) + '0', len++, val /= 10;
290         while (val);
291         if (neg)
292                 *--p = '-', len++;
293         while (len < minwidth)
294                 *--p = ' ', len++;
295         wr(p, len);
296         column += len;
297 }
298
299 static void newline(void)
300 {
301         if (column > 0) {
302                 wr("\n", 1);
303                 column = 0;
304         }
305 }
306
307 static void tab(short col)
308 {
309         static const char spaces[] = "                ";
310         #define nspaces ((sizeof spaces)-1)     /* null terminator! */
311         
312         short n = col - column;
313
314         if (n > 0) {
315                 column = col;
316                 while (n > nspaces) {
317                         wr(spaces, nspaces);
318                         n -= nspaces;
319                 }
320                 /* must be 1...(sizeof spaces) left */
321                 wr(spaces, n);
322         }
323         #undef nspaces
324 }
325
326 #ifdef FEATURE_FILETYPECHAR
327 static char append_char(umode_t mode)
328 {
329         if (!(opts & DISP_FTYPE))
330                 return '\0';
331         if ((opts & DISP_EXEC) && S_ISREG(mode) && (mode & (S_IXUSR|S_IXGRP|S_IXOTH)))
332                 return '*';
333         return APPCHAR(mode);
334 }
335 #endif
336
337 /**
338  **
339  ** Display a file or directory as a single item
340  ** (in either long or short format)
341  **
342  **/
343
344 static void list_single(const char *name, struct stat *info)
345 {
346         char scratch[20];
347         short len = strlen(name);
348 #ifdef FEATURE_FILETYPECHAR
349         char append = append_char(info->st_mode);
350 #endif
351         
352         if (display_fmt == FMT_LONG) {
353                 umode_t mode = info->st_mode; 
354                 int i;
355                 
356                 scratch[0] = TYPECHAR(mode);
357                 for (i=0; i<9; i++)
358                         if (mode & SBIT[i])
359                                 scratch[i+1] = (mode & MBIT[i])
360                                                 ? SMODE1[i]
361                                                 : SMODE0[i];
362                         else
363                                 scratch[i+1] = (mode & MBIT[i])
364                                                 ? MODE1[i]
365                                                 : MODE0[i];
366                 newline();
367                 wr(scratch, 10);
368                 column=10;
369                 writenum((long)info->st_nlink,(short)4);
370                 fputs(" ", stdout);
371 #ifdef FEATURE_USERNAME
372                 if (!(opts & DISP_NUMERIC)) {
373                         struct passwd *pw = getpwuid(info->st_uid);
374                         if (pw)
375                                 fputs(pw->pw_name, stdout);
376                         else
377                                 writenum((long)info->st_uid,(short)0);
378                 } else
379 #endif
380                 writenum((long)info->st_uid,(short)0);
381                 tab(24);
382 #ifdef FEATURE_USERNAME
383                 if (!(opts & DISP_NUMERIC)) {
384                         struct group *gr = getgrgid(info->st_gid);
385                         if (gr)
386                                 fputs(gr->gr_name, stdout);
387                         else
388                                 writenum((long)info->st_gid,(short)0);
389                 } else
390 #endif
391                 writenum((long)info->st_gid,(short)0);
392                 tab(33);
393                 if (S_ISBLK(mode) || S_ISCHR(mode)) {
394                         writenum((long)MAJOR(info->st_rdev),(short)3);
395                         fputs(", ", stdout);
396                         writenum((long)MINOR(info->st_rdev),(short)3);
397                 }
398                 else
399                         writenum((long)info->st_size,(short)8);
400                 fputs(" ", stdout);
401 #ifdef FEATURE_TIMESTAMPS
402                 {
403                         time_t cal;
404                         char *string;
405                         
406                         switch(time_fmt) {
407                         case TIME_CHANGE:
408                                 cal=info->st_ctime; break;
409                         case TIME_ACCESS:
410                                 cal=info->st_atime; break;
411                         default:
412                                 cal=info->st_mtime; break;
413                         }
414                         string=ctime(&cal);
415                         if (opts & DISP_FULLTIME)
416                                 wr(string,24);
417                         else {
418                                 time_t age = time(NULL) - cal;
419                                 wr(string+4,7); /* mmm_dd_ */
420                                 if(age < 3600L*24*365/2 && age > -15*60)
421                                         /* hh:mm if less than 6 months old */
422                                         wr(string+11,5);
423                                 else
424                                         /* _yyyy otherwise */
425                                         wr(string+19,5);
426                         }
427                         wr(" ", 1);
428                 }
429 #else
430                 fputs("--- -- ----- ", stdout);
431 #endif
432                 wr(name, len);
433                 if (S_ISLNK(mode)) {
434                         wr(" -> ", 4);
435                         len = readlink(name, scratch, sizeof scratch);
436                         if (len > 0) fwrite(scratch, 1, len, stdout);
437 #ifdef FEATURE_FILETYPECHAR
438                         /* show type of destination */
439                         if (opts & DISP_FTYPE) {
440                                 if (!stat(name, info)) {
441                                         append = append_char(info->st_mode);
442                                         if (append)
443                                                 fputc(append, stdout);
444                                 }
445                         }
446 #endif
447                 }
448 #ifdef FEATURE_FILETYPECHAR
449                 else if (append)
450                         wr(&append, 1);
451 #endif
452         } else {
453                 static short nexttab = 0;
454                 
455                 /* sort out column alignment */
456                 if (column == 0)
457                         ; /* nothing to do */
458                 else if (display_fmt == FMT_SINGLE)
459                         newline();
460                 else {
461                         if (nexttab + column_width > terminal_width
462 #ifndef FEATURE_AUTOWIDTH
463                         || nexttab + len >= terminal_width
464 #endif
465                         )
466                                 newline();
467                         else
468                                 tab(nexttab);
469                 }
470                 /* work out where next column starts */
471 #ifdef FEATURE_AUTOWIDTH
472                 /* we know the calculated width is big enough */
473                 nexttab = column + column_width + COLUMN_GAP;
474 #else
475                 /* might cover more than one fixed-width column */
476                 nexttab = column;
477                 do
478                         nexttab += column_width + COLUMN_GAP;
479                 while (nexttab < (column + len + COLUMN_GAP));
480 #endif
481                 /* now write the data */
482                 wr(name, len);
483                 column = column + len;
484 #ifdef FEATURE_FILETYPECHAR
485                 if (append)
486                         wr(&append, 1), column++;
487 #endif
488         }
489 }
490
491 /**
492  **
493  ** List the given file or directory, expanding a directory
494  ** to show its contents if required
495  **
496  **/
497
498 static int list_item(const char *name)
499 {
500         struct stat info;
501         DIR *dir;
502         struct dirent *entry;
503         char fullname[MAXNAMLEN+1], *fnend;
504         
505         if (lstat(name, &info))
506                 goto listerr;
507         
508         if (!S_ISDIR(info.st_mode) || 
509             (opts & DIR_NOLIST)) {
510                 list_single(name, &info);
511                 return 0;
512         }
513
514         /* Otherwise, it's a directory we want to list the contents of */
515
516         if (opts & DISP_DIRNAME) {   /* identify the directory */
517                 if (column)
518                         wr("\n\n", 2), column = 0;
519                 wr(name, strlen(name));
520                 wr(":\n", 2);
521         }
522         
523         dir = opendir(name);
524         if (!dir) goto listerr;
525 #ifdef FEATURE_AUTOWIDTH
526         column_width = 0;
527         while ((entry = readdir(dir)) != NULL) {
528                 short w = strlen(entry->d_name);
529                 if (column_width < w)
530                         column_width = w;
531         }
532 #ifdef HAS_REWINDDIR
533         rewinddir(dir);
534 #else
535         closedir(dir);
536         dir = opendir(name);
537         if (!dir) goto listerr;
538 #endif
539 #endif
540
541         /* List the contents */
542         
543         strcpy(fullname,name);  /* *** ignore '.' by itself */
544         fnend=fullname+strlen(fullname);
545         if (fnend[-1] != '/')
546                 *fnend++ = '/';
547         
548         while ((entry = readdir(dir)) != NULL) {
549                 const char *en=entry->d_name;
550                 if (en[0] == '.') {
551                         if (!en[1] || (en[1] == '.' && !en[2])) { /* . or .. */
552                                 if (!(opts & DISP_DOT))
553                                         continue;
554                         }
555                         else if (!(opts & DISP_HIDDEN))
556                                 continue;
557                 }
558                 /* FIXME: avoid stat if not required */
559                 strcpy(fnend, entry->d_name);
560                 if (lstat(fullname, &info))
561                         goto direrr; /* (shouldn't fail) */
562                 list_single(entry->d_name, &info);
563         }
564         closedir(dir);
565         return 0;
566
567 direrr:
568         closedir(dir);  
569 listerr:
570         newline();
571         perror(name);
572         return 1;
573 }
574
575 const char      ls_usage[] = "ls [-1a"
576 #ifdef FEATURE_TIMESTAMPS
577         "c"
578 #endif
579         "d"
580 #ifdef FEATURE_TIMESTAMPS
581         "e"
582 #endif
583         "ln"
584 #ifdef FEATURE_FILETYPECHAR
585         "p"
586 #endif
587 #ifdef FEATURE_TIMESTAMPS
588         "u"
589 #endif
590         "xAC"
591 #ifdef FEATURE_FILETYPECHAR
592         "F"
593 #endif
594 #ifdef FEATURE_RECURSIVE
595         "R"
596 #endif
597         "] [filenames...]\n";
598
599 extern int
600 ls_main(int argc, char * * argv)
601 {
602         int argi=1, i;
603         
604         /* process options */
605         while (argi < argc && argv[argi][0] == '-') {
606                 const char *p = &argv[argi][1];
607                 
608                 if (!*p) goto print_usage_message;      /* "-" by itself not allowed */
609                 if (*p == '-') {
610                         if (!p[1]) {    /* "--" forces end of options */
611                                 argi++;
612                                 break;
613                         }
614                         /* it's a long option name - we don't support them */
615                         goto print_usage_message;
616                 }
617                 
618                 while (*p)
619                         switch (*p++) {
620                         case 'l':       display_fmt = FMT_LONG; break;
621                         case '1':       display_fmt = FMT_SINGLE; break;
622                         case 'x':       display_fmt = FMT_ROWS; break;
623                         case 'C':       display_fmt = FMT_COLUMNS; break;
624 #ifdef FEATURE_FILETYPECHAR
625                         case 'p':       opts |= DISP_FTYPE; break;
626                         case 'F':       opts |= DISP_FTYPE|DISP_EXEC; break;
627 #endif
628                         case 'A':       opts |= DISP_HIDDEN; break;
629                         case 'a':       opts |= DISP_HIDDEN|DISP_DOT; break;
630                         case 'n':       opts |= DISP_NUMERIC; break;
631                         case 'd':       opts |= DIR_NOLIST; break;
632 #ifdef FEATURE_RECURSIVE
633                         case 'R':       opts |= DIR_RECURSE; break;
634 #endif
635 #ifdef FEATURE_TIMESTAMPS
636                         case 'u':       time_fmt = TIME_ACCESS; break;
637                         case 'c':       time_fmt = TIME_CHANGE; break;
638                         case 'e':       opts |= DISP_FULLTIME; break;
639 #endif
640                         default:        goto print_usage_message;
641                         }
642                 
643                 argi++;
644         }
645
646         /* choose a display format */
647         if (display_fmt == FMT_AUTO)
648                 display_fmt = isatty(STDOUT_FILENO) ? FMT_COLUMNS : FMT_SINGLE;
649         if (argi < argc - 1)
650                 opts |= DISP_DIRNAME; /* 2 or more items? label directories */
651 #ifdef FEATURE_AUTOWIDTH
652         /* could add a -w option and/or TIOCGWINSZ call */
653         if (terminal_width < 1) terminal_width = TERMINAL_WIDTH;
654         
655         for (i = argi; i < argc; i++) {
656                 int len = strlen(argv[i]);
657                 if (column_width < len)
658                         column_width = len;
659         }
660 #endif
661
662         /* process files specified, or current directory if none */
663         i=0;
664         if (argi == argc)
665                 i = list_item(".");
666         while (argi < argc)
667                 i |= list_item(argv[argi++]);
668         newline();
669         exit( i);
670
671 print_usage_message:
672         usage (ls_usage);
673         exit( FALSE);
674 }
675
676 #endif