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