ls.c revision 1.13 1 /*
2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Michael Fischbein.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37 #ifndef lint
38 static char copyright[] =
39 "@(#) Copyright (c) 1989, 1993, 1994\n\
40 The Regents of the University of California. All rights reserved.\n";
41 #endif /* not lint */
42
43 #ifndef lint
44 /*static char sccsid[] = "from: @(#)ls.c 8.7 (Berkeley) 8/5/94";*/
45 static char *rcsid = "$Id: ls.c,v 1.13 1994/12/27 23:14:49 mycroft Exp $";
46 #endif /* not lint */
47
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <sys/ioctl.h>
51
52 #include <dirent.h>
53 #include <err.h>
54 #include <errno.h>
55 #include <fts.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60
61 #include "ls.h"
62 #include "extern.h"
63
64 char *group_from_gid __P((u_int, int));
65 char *user_from_uid __P((u_int, int));
66
67 static void display __P((FTSENT *, FTSENT *));
68 static int mastercmp __P((const FTSENT **, const FTSENT **));
69 static void traverse __P((int, char **, int));
70
71 static void (*printfcn) __P((DISPLAY *));
72 static int (*sortfcn) __P((const FTSENT *, const FTSENT *));
73
74 #define BY_NAME 0
75 #define BY_SIZE 1
76 #define BY_TIME 2
77
78 long blocksize; /* block size units */
79 int termwidth = 80; /* default terminal width */
80 int sortkey = BY_NAME;
81
82 /* flags */
83 int f_accesstime; /* use time of last access */
84 int f_column; /* columnated format */
85 int f_flags; /* show flags associated with a file */
86 int f_inode; /* print inode */
87 int f_listdir; /* list actual directory, not contents */
88 int f_listdot; /* list files beginning with . */
89 int f_longform; /* long listing format */
90 int f_newline; /* if precede with newline */
91 int f_nonprint; /* show unprintables as ? */
92 int f_nosort; /* don't sort output */
93 int f_recursive; /* ls subdirectories also */
94 int f_reversesort; /* reverse whatever sort is used */
95 int f_sectime; /* print the real time for all files */
96 int f_singlecol; /* use single column output */
97 int f_size; /* list size in short listing */
98 int f_statustime; /* use time of last mode change */
99 int f_dirname; /* if precede with directory name */
100 int f_type; /* add type character for non-regular files */
101 int f_whiteout; /* show whiteout entries */
102
103 int
104 main(argc, argv)
105 int argc;
106 char *argv[];
107 {
108 static char dot[] = ".", *dotav[] = { dot, NULL };
109 struct winsize win;
110 int ch, fts_options, notused;
111 int kflag = 0;
112 char *p;
113
114 /* Terminal defaults to -Cq, non-terminal defaults to -1. */
115 if (isatty(STDOUT_FILENO)) {
116 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == -1 ||
117 !win.ws_col) {
118 if ((p = getenv("COLUMNS")) != NULL)
119 termwidth = atoi(p);
120 }
121 else
122 termwidth = win.ws_col;
123 f_column = f_nonprint = 1;
124 } else
125 f_singlecol = 1;
126
127 /* Root is -A automatically. */
128 if (!getuid())
129 f_listdot = 1;
130
131 fts_options = FTS_PHYSICAL;
132 while ((ch = getopt(argc, argv, "1ACFLRSTWacdfgikloqrstu")) != -1) {
133 switch (ch) {
134 /*
135 * The -1, -C and -l options all override each other so shell
136 * aliasing works right.
137 */
138 case '1':
139 f_singlecol = 1;
140 f_column = f_longform = 0;
141 break;
142 case 'C':
143 f_column = 1;
144 f_longform = f_singlecol = 0;
145 break;
146 case 'l':
147 f_longform = 1;
148 f_column = f_singlecol = 0;
149 break;
150 /* The -c and -u options override each other. */
151 case 'c':
152 f_statustime = 1;
153 f_accesstime = 0;
154 break;
155 case 'u':
156 f_accesstime = 1;
157 f_statustime = 0;
158 break;
159 case 'F':
160 f_type = 1;
161 break;
162 case 'L':
163 fts_options &= ~FTS_PHYSICAL;
164 fts_options |= FTS_LOGICAL;
165 break;
166 case 'R':
167 f_recursive = 1;
168 break;
169 case 'a':
170 fts_options |= FTS_SEEDOT;
171 /* FALLTHROUGH */
172 case 'A':
173 f_listdot = 1;
174 break;
175 /* The -d option turns off the -R option. */
176 case 'd':
177 f_listdir = 1;
178 f_recursive = 0;
179 break;
180 case 'f':
181 f_nosort = 1;
182 break;
183 case 'g': /* Compatibility with 4.3BSD. */
184 break;
185 case 'i':
186 f_inode = 1;
187 break;
188 case 'k':
189 blocksize = 1024;
190 kflag = 1;
191 break;
192 case 'o':
193 f_flags = 1;
194 break;
195 case 'q':
196 f_nonprint = 1;
197 break;
198 case 'r':
199 f_reversesort = 1;
200 break;
201 case 'S':
202 sortkey = BY_SIZE;
203 break;
204 case 's':
205 f_size = 1;
206 break;
207 case 'T':
208 f_sectime = 1;
209 break;
210 case 't':
211 sortkey = BY_TIME;
212 break;
213 case 'W':
214 f_whiteout = 1;
215 break;
216 default:
217 case '?':
218 usage();
219 }
220 }
221 argc -= optind;
222 argv += optind;
223
224 /*
225 * If not -F, -i, -l, -S, -s or -t options, don't require stat
226 * information.
227 */
228 if (!f_inode && !f_longform && !f_size && !f_type &&
229 sortkey == BY_NAME)
230 fts_options |= FTS_NOSTAT;
231
232 /*
233 * If not -F, -d or -l options, follow any symbolic links listed on
234 * the command line.
235 */
236 if (!f_longform && !f_listdir && !f_type)
237 fts_options |= FTS_COMFOLLOW;
238
239 /*
240 * If -W, show whiteout entries.
241 */
242 #ifdef FTS_WHITEOUT
243 if (f_whiteout)
244 fts_options |= FTS_WHITEOUT;
245 #endif
246
247 /* If -l or -s, figure out block size. */
248 if (f_longform || f_size) {
249 if (!kflag)
250 (void)getbsize(¬used, &blocksize);
251 blocksize /= 512;
252 }
253
254 /* Select a sort function. */
255 if (f_reversesort) {
256 switch (sortkey) {
257 case BY_NAME:
258 sortfcn = revnamecmp;
259 break;
260 case BY_SIZE:
261 sortfcn = revsizecmp;
262 break;
263 case BY_TIME:
264 if (f_accesstime)
265 sortfcn = revacccmp;
266 else if (f_statustime)
267 sortfcn = revstatcmp;
268 else /* Use modification time. */
269 sortfcn = revmodcmp;
270 break;
271 }
272 } else {
273 switch (sortkey) {
274 case BY_NAME:
275 sortfcn = namecmp;
276 break;
277 case BY_SIZE:
278 sortfcn = sizecmp;
279 break;
280 case BY_TIME:
281 if (f_accesstime)
282 sortfcn = acccmp;
283 else if (f_statustime)
284 sortfcn = statcmp;
285 else /* Use modification time. */
286 sortfcn = modcmp;
287 break;
288 }
289 }
290
291 /* Select a print function. */
292 if (f_singlecol)
293 printfcn = printscol;
294 else if (f_longform)
295 printfcn = printlong;
296 else
297 printfcn = printcol;
298
299 if (argc)
300 traverse(argc, argv, fts_options);
301 else
302 traverse(1, dotav, fts_options);
303 exit(0);
304 }
305
306 static int output; /* If anything output. */
307
308 /*
309 * Traverse() walks the logical directory structure specified by the argv list
310 * in the order specified by the mastercmp() comparison function. During the
311 * traversal it passes linked lists of structures to display() which represent
312 * a superset (may be exact set) of the files to be displayed.
313 */
314 static void
315 traverse(argc, argv, options)
316 int argc, options;
317 char *argv[];
318 {
319 FTS *ftsp;
320 FTSENT *p, *chp;
321 int ch_options;
322
323 if ((ftsp =
324 fts_open(argv, options, f_nosort ? NULL : mastercmp)) == NULL)
325 err(1, NULL);
326
327 display(NULL, fts_children(ftsp, 0));
328 if (f_listdir)
329 return;
330
331 /*
332 * If not recursing down this tree and don't need stat info, just get
333 * the names.
334 */
335 ch_options = !f_recursive && options & FTS_NOSTAT ? FTS_NAMEONLY : 0;
336
337 while ((p = fts_read(ftsp)) != NULL)
338 switch (p->fts_info) {
339 case FTS_DC:
340 warnx("%s: directory causes a cycle", p->fts_name);
341 break;
342 case FTS_DNR:
343 case FTS_ERR:
344 warnx("%s: %s", p->fts_name, strerror(p->fts_errno));
345 break;
346 case FTS_D:
347 if (p->fts_level != FTS_ROOTLEVEL &&
348 p->fts_name[0] == '.' && !f_listdot)
349 break;
350
351 /*
352 * If already output something, put out a newline as
353 * a separator. If multiple arguments, precede each
354 * directory with its name.
355 */
356 if (output)
357 (void)printf("\n%s:\n", p->fts_path);
358 else if (argc > 1) {
359 (void)printf("%s:\n", p->fts_path);
360 output = 1;
361 }
362
363 chp = fts_children(ftsp, ch_options);
364 display(p, chp);
365
366 if (!f_recursive && chp != NULL)
367 (void)fts_set(ftsp, p, FTS_SKIP);
368 break;
369 }
370 if (errno)
371 err(1, "fts_read");
372 }
373
374 /*
375 * Display() takes a linked list of FTSENT structures and passes the list
376 * along with any other necessary information to the print function. P
377 * points to the parent directory of the display list.
378 */
379 static void
380 display(p, list)
381 FTSENT *p, *list;
382 {
383 struct stat *sp;
384 DISPLAY d;
385 FTSENT *cur;
386 NAMES *np;
387 u_quad_t maxsize;
388 u_long btotal, maxblock, maxinode, maxlen, maxnlink;
389 int bcfile, flen, glen, ulen, maxflags, maxgroup, maxuser;
390 int entries, needstats;
391 char *user, *group, *flags, buf[20]; /* 32 bits == 10 digits */
392
393 /*
394 * If list is NULL there are two possibilities: that the parent
395 * directory p has no children, or that fts_children() returned an
396 * error. We ignore the error case since it will be replicated
397 * on the next call to fts_read() on the post-order visit to the
398 * directory p, and will be signalled in traverse().
399 */
400 if (list == NULL)
401 return;
402
403 needstats = f_inode || f_longform || f_size;
404 flen = 0;
405 btotal = maxblock = maxinode = maxlen = maxnlink = 0;
406 bcfile = 0;
407 maxuser = maxgroup = maxflags = 0;
408 maxsize = 0;
409 for (cur = list, entries = 0; cur; cur = cur->fts_link) {
410 if (cur->fts_info == FTS_ERR || cur->fts_info == FTS_NS) {
411 warnx("%s: %s",
412 cur->fts_name, strerror(cur->fts_errno));
413 cur->fts_number = NO_PRINT;
414 continue;
415 }
416
417 /*
418 * P is NULL if list is the argv list, to which different rules
419 * apply.
420 */
421 if (p == NULL) {
422 /* Directories will be displayed later. */
423 if (cur->fts_info == FTS_D && !f_listdir) {
424 cur->fts_number = NO_PRINT;
425 continue;
426 }
427 } else {
428 /* Only display dot file if -a/-A set. */
429 if (cur->fts_name[0] == '.' && !f_listdot) {
430 cur->fts_number = NO_PRINT;
431 continue;
432 }
433 }
434 if (f_nonprint)
435 prcopy(cur->fts_name, cur->fts_name, cur->fts_namelen);
436 if (cur->fts_namelen > maxlen)
437 maxlen = cur->fts_namelen;
438 if (needstats) {
439 sp = cur->fts_statp;
440 if (sp->st_blocks > maxblock)
441 maxblock = sp->st_blocks;
442 if (sp->st_ino > maxinode)
443 maxinode = sp->st_ino;
444 if (sp->st_nlink > maxnlink)
445 maxnlink = sp->st_nlink;
446 if (sp->st_size > maxsize)
447 maxsize = sp->st_size;
448
449 btotal += sp->st_blocks;
450 if (f_longform) {
451 user = user_from_uid(sp->st_uid, 0);
452 if ((ulen = strlen(user)) > maxuser)
453 maxuser = ulen;
454 group = group_from_gid(sp->st_gid, 0);
455 if ((glen = strlen(group)) > maxgroup)
456 maxgroup = glen;
457 if (f_flags) {
458 flags =
459 flags_to_string(sp->st_flags, "-");
460 if ((flen = strlen(flags)) > maxflags)
461 maxflags = flen;
462 } else
463 flen = 0;
464
465 if ((np = malloc(sizeof(NAMES) +
466 ulen + glen + flen + 3)) == NULL)
467 err(1, NULL);
468
469 np->user = &np->data[0];
470 (void)strcpy(np->user, user);
471 np->group = &np->data[ulen + 1];
472 (void)strcpy(np->group, group);
473
474 if (S_ISCHR(sp->st_mode) ||
475 S_ISBLK(sp->st_mode))
476 bcfile = 1;
477
478 if (f_flags) {
479 np->flags = &np->data[ulen + glen + 2];
480 (void)strcpy(np->flags, flags);
481 }
482 cur->fts_pointer = np;
483 }
484 }
485 ++entries;
486 }
487
488 if (!entries)
489 return;
490
491 d.list = list;
492 d.entries = entries;
493 d.maxlen = maxlen;
494 if (needstats) {
495 d.bcfile = bcfile;
496 d.btotal = btotal;
497 (void)snprintf(buf, sizeof(buf), "%lu", maxblock);
498 d.s_block = strlen(buf);
499 d.s_flags = maxflags;
500 d.s_group = maxgroup;
501 (void)snprintf(buf, sizeof(buf), "%lu", maxinode);
502 d.s_inode = strlen(buf);
503 (void)snprintf(buf, sizeof(buf), "%lu", maxnlink);
504 d.s_nlink = strlen(buf);
505 (void)snprintf(buf, sizeof(buf), "%qu", maxsize);
506 d.s_size = strlen(buf);
507 d.s_user = maxuser;
508 }
509
510 printfcn(&d);
511 output = 1;
512
513 if (f_longform)
514 for (cur = list; cur; cur = cur->fts_link)
515 free(cur->fts_pointer);
516 }
517
518 /*
519 * Ordering for mastercmp:
520 * If ordering the argv (fts_level = FTS_ROOTLEVEL) return non-directories
521 * as larger than directories. Within either group, use the sort function.
522 * All other levels use the sort function. Error entries remain unsorted.
523 */
524 static int
525 mastercmp(a, b)
526 const FTSENT **a, **b;
527 {
528 int a_info, b_info;
529
530 a_info = (*a)->fts_info;
531 if (a_info == FTS_ERR)
532 return (0);
533 b_info = (*b)->fts_info;
534 if (b_info == FTS_ERR)
535 return (0);
536
537 if (a_info == FTS_NS || b_info == FTS_NS)
538 return (namecmp(*a, *b));
539
540 if (a_info == b_info)
541 return (sortfcn(*a, *b));
542
543 if ((*a)->fts_level == FTS_ROOTLEVEL)
544 if (a_info == FTS_D)
545 return (1);
546 else if (b_info == FTS_D)
547 return (-1);
548 else
549 return (sortfcn(*a, *b));
550 else
551 return (sortfcn(*a, *b));
552 }
553