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