aboutsummaryrefslogtreecommitdiff
path: root/coreutils/ls.c
diff options
context:
space:
mode:
authorEric Andersen <andersen@codepoet.org>2000-09-07 17:24:47 +0000
committerEric Andersen <andersen@codepoet.org>2000-09-07 17:24:47 +0000
commit11c655295c78970d67bc5ee661076743957d8ffb (patch)
treee5afeaa3728657f54799a54221f43a8f8c73f612 /coreutils/ls.c
parent44e384034380bde803ec444418532a315e8589e8 (diff)
downloadbusybox-w32-11c655295c78970d67bc5ee661076743957d8ffb.tar.gz
busybox-w32-11c655295c78970d67bc5ee661076743957d8ffb.tar.bz2
busybox-w32-11c655295c78970d67bc5ee661076743957d8ffb.zip
New ls sorting patch, as written by Sterling Huxley, and then updated
by kent robotti. I then updated it to use my_getpwuid and my_getgrgid (per busybox policy). -Erik
Diffstat (limited to 'coreutils/ls.c')
-rw-r--r--coreutils/ls.c1114
1 files changed, 642 insertions, 472 deletions
diff --git a/coreutils/ls.c b/coreutils/ls.c
index 207c61763..03fed5478 100644
--- a/coreutils/ls.c
+++ b/coreutils/ls.c
@@ -2,7 +2,7 @@
2/* 2/*
3 * tiny-ls.c version 0.1.0: A minimalist 'ls' 3 * tiny-ls.c version 0.1.0: A minimalist 'ls'
4 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com> 4 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
5 * 5 *
6 * This program is free software; you can redistribute it and/or modify 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 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 8 * the Free Software Foundation; either version 2 of the License, or
@@ -42,14 +42,14 @@
42 */ 42 */
43 43
44#define TERMINAL_WIDTH 80 /* use 79 if your terminal has linefold bug */ 44#define TERMINAL_WIDTH 80 /* use 79 if your terminal has linefold bug */
45#define COLUMN_WIDTH 14 /* default if AUTOWIDTH not defined */ 45#define COLUMN_WIDTH 14 /* default if AUTOWIDTH not defined */
46#define COLUMN_GAP 2 /* includes the file type char, if present */ 46#define COLUMN_GAP 2 /* includes the file type char, if present */
47#define HAS_REWINDDIR
48 47
49/************************************************************************/ 48/************************************************************************/
50 49
51#include "internal.h" 50#include "internal.h"
52# include <sys/types.h> 51#include <sys/types.h>
52#include <sys/stat.h>
53#include <stdio.h> 53#include <stdio.h>
54#include <unistd.h> 54#include <unistd.h>
55#include <dirent.h> 55#include <dirent.h>
@@ -58,590 +58,760 @@
58#ifdef BB_FEATURE_LS_TIMESTAMPS 58#ifdef BB_FEATURE_LS_TIMESTAMPS
59#include <time.h> 59#include <time.h>
60#endif 60#endif
61#include <string.h>
61 62
62#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) 63#ifndef NAJOR
63#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
64#ifdef BB_FEATURE_LS_FILETYPES
65#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
66#endif
67
68#define FMT_AUTO 0
69#define FMT_LONG 1 /* one record per line, extended info */
70#define FMT_SINGLE 2 /* one record per line */
71#define FMT_ROWS 3 /* print across rows */
72#define FMT_COLUMNS 3 /* fill columns (same, since we don't sort) */
73
74#define TIME_MOD 0
75#define TIME_CHANGE 1
76#define TIME_ACCESS 2
77
78#define DISP_FTYPE 1 /* show character for file type */
79#define DISP_EXEC 2 /* show '*' if regular executable file */
80#define DISP_HIDDEN 4 /* show files starting . (except . and ..) */
81#define DISP_DOT 8 /* show . and .. */
82#define DISP_NUMERIC 16 /* numeric uid and gid */
83#define DISP_FULLTIME 32 /* show extended time display */
84#define DIR_NOLIST 64 /* show directory as itself, not contents */
85#define DISP_DIRNAME 128 /* show directory name (for internal use) */
86#define DISP_RECURSIVE 256 /* Do a recursive listing */
87
88#ifndef MAJOR
89#define MAJOR(dev) (((dev)>>8)&0xff) 64#define MAJOR(dev) (((dev)>>8)&0xff)
90#define MINOR(dev) ((dev)&0xff) 65#define MINOR(dev) ((dev)&0xff)
91#endif 66#endif
92 67
68/* what is the overall style of the listing */
69#define STYLE_AUTO 0
70#define STYLE_LONG 1 /* one record per line, extended info */
71#define STYLE_SINGLE 2 /* one record per line */
72#define STYLE_COLUMNS 3 /* fill columns */
73
74/* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
75/* what file information will be listed */
76#define LIST_INO (1<<0)
77#define LIST_BLOCKS (1<<1)
78#define LIST_MODEBITS (1<<2)
79#define LIST_NLINKS (1<<3)
80#define LIST_ID_NAME (1<<4)
81#define LIST_ID_NUMERIC (1<<5)
82#define LIST_SIZE (1<<6)
83#define LIST_DEV (1<<7)
84#define LIST_DATE_TIME (1<<8)
85#define LIST_FULLTIME (1<<9)
86#define LIST_FILENAME (1<<10)
87#define LIST_SYMLINK (1<<11)
88#define LIST_FILETYPE (1<<12)
89#define LIST_EXEC (1<<13)
90
91/* what files will be displayed */
92#define DISP_NORMAL (0) /* show normal filenames */
93#define DISP_DIRNAME (1<<0) /* 2 or more items? label directories */
94#define DISP_HIDDEN (1<<1) /* show filenames starting with . */
95#define DISP_DOT (1<<2) /* show . and .. */
96#define DISP_NOLIST (1<<3) /* show directory as itself, not contents */
97#define DISP_RECURSIVE (1<<4) /* show directory and everything below it */
98#define DISP_ROWS (1<<5) /* print across rows */
99
93#ifdef BB_FEATURE_LS_SORTFILES 100#ifdef BB_FEATURE_LS_SORTFILES
101/* how will the files be sorted */
102#define SORT_FORWARD 0 /* sort in reverse order */
103#define SORT_REVERSE 1 /* sort in reverse order */
104#define SORT_NAME 2 /* sort by file name */
105#define SORT_SIZE 3 /* sort by file size */
106#define SORT_ATIME 4 /* sort by last access time */
107#define SORT_CTIME 5 /* sort by last change time */
108#define SORT_MTIME 6 /* sort by last modification time */
109#define SORT_VERSION 7 /* sort by version */
110#define SORT_EXT 8 /* sort by file name extension */
111#define SORT_DIR 9 /* sort by file or directory */
112#endif
113
114#ifdef BB_FEATURE_LS_TIMESTAMPS
115/* which of the three times will be used */
116#define TIME_MOD 0
117#define TIME_CHANGE 1
118#define TIME_ACCESS 2
119#endif
120
121#define LIST_SHORT (LIST_FILENAME)
122#define LIST_ISHORT (LIST_INO | LIST_FILENAME)
123#define LIST_LONG (LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | \
124 LIST_SIZE | LIST_DATE_TIME | LIST_FILENAME | \
125 LIST_SYMLINK)
126#define LIST_ILONG (LIST_INO | LIST_LONG)
127
128#define SPLIT_DIR 0
129#define SPLIT_FILE 1
130
131#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
132#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
133#ifdef BB_FEATURE_LS_FILETYPES
134#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
135#endif
136
137/*
138 * a directory entry and its stat info are stored here
139 */
94struct dnode { /* the basic node */ 140struct dnode { /* the basic node */
95 char *name; /* the dir entry name */ 141 char *name; /* the dir entry name */
96 char *fullname; /* the dir entry name */ 142 char *fullname; /* the dir entry name */
97 struct stat dstat; /* the file stat info */ 143 struct stat dstat; /* the file stat info */
144 struct dnode *next; /* point at the next node */
98}; 145};
99typedef struct dnode dnode_t; 146typedef struct dnode dnode_t;
100#endif
101static unsigned char display_fmt = FMT_AUTO;
102static unsigned short opts = 0;
103static unsigned short column = 0;
104 147
105#ifdef BB_FEATURE_AUTOWIDTH 148struct dnode **list_dir(char *);
106static unsigned short terminal_width = 0; 149struct dnode **dnalloc(int);
107static unsigned short column_width = 0; 150int list_single(struct dnode *);
108static unsigned short toplevel_column_width = 0;
109#else
110#define terminal_width TERMINAL_WIDTH
111#define column_width COLUMN_WIDTH
112#endif
113 151
152static unsigned int disp_opts= DISP_NORMAL;
153static unsigned int style_fmt= STYLE_AUTO ;
154static unsigned int list_fmt= LIST_SHORT ;
155#ifdef BB_FEATURE_LS_SORTFILES
156static unsigned int sort_opts= SORT_FORWARD;
157static unsigned int sort_order= SORT_FORWARD;
158#endif
114#ifdef BB_FEATURE_LS_TIMESTAMPS 159#ifdef BB_FEATURE_LS_TIMESTAMPS
115static unsigned char time_fmt = TIME_MOD; 160static unsigned int time_fmt= TIME_MOD;
116#endif 161#endif
117 162
118#define wr(data,len) fwrite(data, 1, len, stdout) 163static unsigned short column = 0;
164#ifdef BB_FEATURE_AUTOWIDTH
165static unsigned short terminal_width = TERMINAL_WIDTH;
166static unsigned short column_width = COLUMN_WIDTH;
167static unsigned short tabstops = 8;
168#else
169#define terminal_width TERMINAL_WIDTH
170#define column_width COLUMN_WIDTH
171#endif
119 172
120static void writenum(long val, short minwidth) 173static void newline(void)
121{ 174{
122 char scratch[128]; 175 if (column > 0) {
123 176 fprintf(stdout, "\n");
124 char *p = scratch + sizeof(scratch); 177 column = 0;
125 short len = 0; 178 }
126 short neg = (val < 0);
127
128 if (neg)
129 val = -val;
130 do
131 *--p = (val % 10) + '0', len++, val /= 10;
132 while (val);
133 if (neg)
134 *--p = '-', len++;
135 while (len < minwidth)
136 *--p = ' ', len++;
137 wr(p, len);
138 column += len;
139} 179}
140 180
141static void newline(void) 181/*----------------------------------------------------------------------*/
182#ifdef BB_FEATURE_LS_FILETYPES
183static char append_char(mode_t mode)
142{ 184{
143 if (column > 0) { 185 if ( !(list_fmt & LIST_FILETYPE))
144 wr("\n", 1); 186 return '\0';
145 column = 0; 187 if ((list_fmt & LIST_EXEC) && S_ISREG(mode)
146 } 188 && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return '*';
189 return APPCHAR(mode);
147} 190}
191#endif
148 192
149static void tab(short col) 193/*----------------------------------------------------------------------*/
194static void nexttabstop( void )
150{ 195{
151 static const char spaces[] = " "; 196 static short nexttab= 0;
197 int n=0;
152 198
153#define nspaces ((sizeof spaces)-1) /* null terminator! */ 199 if (column > 0) {
200 n= nexttab - column;
201 if (n < 1) n= 1;
202 while (n--) {
203 fprintf(stdout, " ");
204 column++;
205 }
206 }
207 nexttab= column + column_width + COLUMN_GAP ;
208}
154 209
155 short n = col - column; 210/*----------------------------------------------------------------------*/
211int countdirs(struct dnode **dn, int nfiles)
212{
213 int i, dirs;
156 214
157 if (n > 0) { 215 /* count how many dirs and regular files there are */
158 column = col; 216 if (dn==NULL || nfiles < 1) return(0);
159 while (n > nspaces) { 217 dirs= 0;
160 wr(spaces, nspaces); 218 for (i=0; i<nfiles; i++) {
161 n -= nspaces; 219 if (S_ISDIR(dn[i]->dstat.st_mode)) dirs++;
162 }
163 /* must be 1...(sizeof spaces) left */
164 wr(spaces, n);
165 } 220 }
166#undef nspaces 221 return(dirs);
167} 222}
168 223
169#ifdef BB_FEATURE_LS_FILETYPES 224int countfiles(struct dnode **dnp)
170static char append_char(mode_t mode)
171{ 225{
172 if (!(opts & DISP_FTYPE)) 226 int nfiles;
173 return '\0'; 227 struct dnode *cur;
174 if ((opts & DISP_EXEC) && S_ISREG(mode) 228
175 && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return '*'; 229 if (dnp == NULL) return(0);
176 return APPCHAR(mode); 230 nfiles= 0;
231 for (cur= dnp[0]; cur->next != NULL ; cur= cur->next) nfiles++;
232 nfiles++;
233 return(nfiles);
177} 234}
178#endif
179 235
180/** 236/* get memory to hold an array of pointers */
181 ** 237struct dnode **dnalloc(int num)
182 ** Display a file or directory as a single item 238{
183 ** (in either long or short format) 239 struct dnode **p;
184 ** 240
185 **/ 241 if (num < 1) return(NULL);
242
243 p= (struct dnode **)xcalloc((size_t)num, (size_t)(sizeof(struct dnode *)));
244 return(p);
245}
186 246
187static void list_single(const char *name, struct stat *info, 247void dfree(struct dnode **dnp)
188 const char *fullname)
189{ 248{
190 char scratch[BUFSIZ + 1]; 249 struct dnode *cur, *next;
191 short len = strlen(name);
192 250
193#ifdef BB_FEATURE_LS_FILETYPES 251 if(dnp == NULL) return;
194 char append = append_char(info->st_mode);
195#endif
196 252
197 if (display_fmt == FMT_LONG) { 253 cur=dnp[0];
198 mode_t mode = info->st_mode; 254 while (cur != NULL) {
255 if (cur->fullname != NULL) free(cur->fullname); /* free the filename */
256 next= cur->next;
257 free(cur); /* free the dnode */
258 cur= next;
259 }
260 free(dnp); /* free the array holding the dnode pointers */
261}
199 262
200 newline(); 263struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
201 wr(modeString(mode), 10); 264{
202 column = 10; 265 int dncnt, i, d;
203 writenum((long) info->st_nlink, (short) 5); 266 struct dnode **dnp;
204 fputs(" ", stdout);
205#ifdef BB_FEATURE_LS_USERNAME
206 if (!(opts & DISP_NUMERIC)) {
207 memset(scratch, 0, sizeof(scratch));
208 my_getpwuid(scratch, info->st_uid);
209 if (*scratch) {
210 fputs(scratch, stdout);
211 if (strlen(scratch) <= 8)
212 wr(" ", 9 - strlen(scratch));
213 } else {
214 writenum((long) info->st_uid, (short) 8);
215 fputs(" ", stdout);
216 }
217 } else
218#endif
219 {
220 writenum((long) info->st_uid, (short) 8);
221 fputs(" ", stdout);
222 }
223#ifdef BB_FEATURE_LS_USERNAME
224 if (!(opts & DISP_NUMERIC)) {
225 memset(scratch, 0, sizeof(scratch));
226 my_getgrgid(scratch, info->st_gid);
227 if (*scratch) {
228 fputs(scratch, stdout);
229 if (strlen(scratch) <= 8)
230 wr(" ", 8 - strlen(scratch));
231 } else
232 writenum((long) info->st_gid, (short) 8);
233 } else
234#endif
235 writenum((long) info->st_gid, (short) 8);
236 //tab(26);
237 if (S_ISBLK(mode) || S_ISCHR(mode)) {
238 writenum((long) MAJOR(info->st_rdev), (short) 3);
239 fputs(", ", stdout);
240 writenum((long) MINOR(info->st_rdev), (short) 3);
241 } else
242 writenum((long) info->st_size, (short) 8);
243 fputs(" ", stdout);
244 //tab(32);
245#ifdef BB_FEATURE_LS_TIMESTAMPS
246 {
247 time_t cal;
248 char *string;
249 267
250 switch (time_fmt) { 268 if (dn==NULL || nfiles < 1) return(NULL);
251 case TIME_CHANGE: 269
252 cal = info->st_ctime; 270 /* count how many dirs and regular files there are */
253 break; 271 dncnt= countdirs(dn, nfiles); /* assume we are looking for dirs */
254 case TIME_ACCESS: 272 if (which != SPLIT_DIR)
255 cal = info->st_atime; 273 dncnt= nfiles - dncnt; /* looking for files */
256 break; 274
257 default: 275 /* allocate a file array and a dir array */
258 cal = info->st_mtime; 276 dnp= dnalloc(dncnt);
259 break; 277
260 } 278 /* copy the entrys into the file or dir array */
261 string = ctime(&cal); 279 for (d= i=0; i<nfiles; i++) {
262 if (opts & DISP_FULLTIME) 280 if (which == SPLIT_DIR) {
263 wr(string, 24); 281 if (S_ISDIR(dn[i]->dstat.st_mode)) {
264 else { 282 dnp[d++]= dn[i];
265 time_t age = time(NULL) - cal; 283 } /* else skip the file */
266 284 } else {
267 wr(string + 4, 7); /* mmm_dd_ */ 285 if (!(S_ISDIR(dn[i]->dstat.st_mode))) {
268 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) 286 dnp[d++]= dn[i];
269 /* hh:mm if less than 6 months old */ 287 } /* else skip the dir */
270 wr(string + 11, 5);
271 else
272 /* _yyyy otherwise */
273 wr(string + 19, 5);
274 }
275 wr(" ", 1);
276 }
277#else
278 fputs("--- -- ----- ", stdout);
279#endif
280 wr(name, len);
281 if (S_ISLNK(mode)) {
282 wr(" -> ", 4);
283 len = readlink(fullname, scratch, sizeof scratch);
284 if (len > 0)
285 fwrite(scratch, 1, len, stdout);
286#ifdef BB_FEATURE_LS_FILETYPES
287 /* show type of destination */
288 if (opts & DISP_FTYPE) {
289 if (!stat(fullname, info)) {
290 append = append_char(info->st_mode);
291 if (append)
292 fputc(append, stdout);
293 }
294 }
295#endif
296 } 288 }
297#ifdef BB_FEATURE_LS_FILETYPES
298 else if (append)
299 wr(&append, 1);
300#endif
301 } else {
302 static short nexttab = 0;
303
304 /* sort out column alignment */
305 if (column == 0); /* nothing to do */
306 else if (display_fmt == FMT_SINGLE)
307 newline();
308 else {
309 if (nexttab + column_width > terminal_width
310#ifndef BB_FEATURE_AUTOWIDTH
311 || nexttab + len >= terminal_width
312#endif
313 )
314 newline();
315 else
316 tab(nexttab);
317 }
318 /* work out where next column starts */
319#ifdef BB_FEATURE_AUTOWIDTH
320 /* we know the calculated width is big enough */
321 nexttab = column + column_width + COLUMN_GAP;
322#else
323 /* might cover more than one fixed-width column */
324 nexttab = column;
325 do
326 nexttab += column_width + COLUMN_GAP;
327 while (nexttab < (column + len + COLUMN_GAP));
328#endif
329 /* now write the data */
330 wr(name, len);
331 column = column + len;
332#ifdef BB_FEATURE_LS_FILETYPES
333 if (append)
334 wr(&append, 1), column++;
335#endif
336 } 289 }
290 return(dnp);
337} 291}
338 292
293/*----------------------------------------------------------------------*/
339#ifdef BB_FEATURE_LS_SORTFILES 294#ifdef BB_FEATURE_LS_SORTFILES
340void shellsort(struct dnode *dn[], int size) 295int sortcmp(struct dnode *d1, struct dnode *d2)
341{ 296{
342 struct dnode *temp; 297 int cmp, dif;
343 int gap, i, j; 298
344 299 cmp= 0;
345 /* shell short the array */ 300 if (sort_opts == SORT_SIZE) {
346 for (gap= size/2; gap>0; gap /=2) { 301 dif= (int)(d1->dstat.st_size - d2->dstat.st_size);
347 for (i=gap; i<size; i++) { 302 } else if (sort_opts == SORT_ATIME) {
348 for (j= i-gap; j>=0; j-=gap) { 303 dif= (int)(d1->dstat.st_atime - d2->dstat.st_atime);
349 if (strcmp(dn[j]->name, dn[j+gap]->name) <= 0) 304 } else if (sort_opts == SORT_CTIME) {
350 break; 305 dif= (int)(d1->dstat.st_ctime - d2->dstat.st_ctime);
351 temp= dn[j]; 306 } else if (sort_opts == SORT_MTIME) {
352 dn[j]= dn[j+gap]; 307 dif= (int)(d1->dstat.st_mtime - d2->dstat.st_mtime);
353 dn[j+gap]= temp; 308 } else if (sort_opts == SORT_DIR) {
354 } 309 dif= S_ISDIR(d1->dstat.st_mode) - S_ISDIR(d2->dstat.st_mode);
355 } 310 /* } else if (sort_opts == SORT_VERSION) { */
356 } 311 /* } else if (sort_opts == SORT_EXT) { */
312 } else { /* assume SORT_NAME */
313 dif= 0;
314 }
315
316 if (dif > 0) cmp= -1;
317 if (dif < 0) cmp= 1;
318 if (dif == 0) {
319 /* sort by name- may be a tie_breaker for time or size cmp */
320 dif= strcmp(d1->name, d2->name);
321 if (dif > 0) cmp= 1;
322 if (dif < 0) cmp= -1;
323 }
324
325 if (sort_order == SORT_REVERSE) {
326 cmp= -1 * cmp;
327 }
328 return(cmp);
357} 329}
358 330
359void showdnodes(struct dnode *dn[], int nfiles) 331/*----------------------------------------------------------------------*/
332void shellsort(struct dnode **dn, int size)
360{ 333{
361 int nf, nc; 334 struct dnode *temp;
362 int ncols, fpc, i; 335 int gap, i, j;
363 336
364 ncols= (int)(terminal_width / (column_width + COLUMN_GAP)); 337 /* shell short the array */
365 /* files per column. The +1 means the last col is shorter than others */ 338 if(dn==NULL || size < 2) return;
366 fpc= (nfiles / ncols) + 1; 339
367 for (nf=0; nf<fpc; nf++) { 340 for (gap= size/2; gap>0; gap /=2) {
368 for (nc=0; nc<ncols; nc++) { 341 for (i=gap; i<size; i++) {
369 /* reach into the array based on the column and row */ 342 for (j= i-gap; j>=0; j-=gap) {
370 i= (nc * fpc) + nf; 343 if (sortcmp(dn[j], dn[j+gap]) <= 0)
371 if (i >= nfiles) { 344 break;
372 newline(); 345 /* they are out of order, swap them */
373 } else { 346 temp= dn[j];
374 list_single(dn[i]->name, &dn[i]->dstat, dn[i]->fullname); 347 dn[j]= dn[j+gap];
348 dn[j+gap]= temp;
375 } 349 }
376 } 350 }
377 } 351 }
378} 352}
379#endif 353#endif
380 354
381/** 355/*----------------------------------------------------------------------*/
382 ** 356void showfiles(struct dnode **dn, int nfiles)
383 ** List the given file or directory, expanding a directory
384 ** to show its contents if required
385 **
386 **/
387
388static int list_item(const char *name)
389{ 357{
390 struct stat info; 358 int i, ncols, nrows, row, nc;
391 DIR *dir; 359#ifdef BB_FEATURE_AUTOWIDTH
392 struct dirent *entry; 360 int len;
393 char fullname[BUFSIZ + 1], *fnend;
394#ifdef BB_FEATURE_LS_SORTFILES
395 int ni=0, nfiles=0;
396 struct dnode **dnp;
397 dnode_t *cur;
398#endif 361#endif
399 362
400 if (lstat(name, &info)) 363 if(dn==NULL || nfiles < 1) return;
401 goto listerr;
402 364
403 if (!S_ISDIR(info.st_mode) || (opts & DIR_NOLIST)) {
404#ifdef BB_FEATURE_AUTOWIDTH 365#ifdef BB_FEATURE_AUTOWIDTH
405 column_width = toplevel_column_width; 366 /* find the longest file name- use that as the column width */
367 column_width= 0;
368 for (i=0; i<nfiles; i++) {
369 len= strlen(dn[i]->name) +
370 ((list_fmt & LIST_INO) ? 8 : 0) +
371 ((list_fmt & LIST_BLOCKS) ? 5 : 0)
372 ;
373 if (column_width < len) column_width= len;
374 }
406#endif 375#endif
407 list_single(name, &info, name); 376 ncols= (int)(terminal_width / (column_width + COLUMN_GAP));
408 return 0; 377 switch (style_fmt) {
378 case STYLE_LONG: /* one record per line, extended info */
379 case STYLE_SINGLE: /* one record per line */
380 ncols= 1;
381 break;
409 } 382 }
410 383
411 /* Otherwise, it's a directory we want to list the contents of */ 384 nrows= nfiles / ncols;
385 if ((nrows * ncols) < nfiles) nrows++; /* round up fractionals */
412 386
413 if (opts & DISP_DIRNAME) { /* identify the directory */ 387 if (nrows > nfiles) nrows= nfiles;
414 if (column) 388 for (row=0; row<nrows; row++) {
415 wr("\n\n", 2), column = 0; 389 for (nc=0; nc<ncols; nc++) {
416 wr(name, strlen(name)); 390 /* reach into the array based on the column and row */
417 wr(":\n", 2); 391 i= (nc * nrows) + row; /* assume display by column */
392 if (disp_opts & DISP_ROWS)
393 i= (row * ncols) + nc; /* display across row */
394 if (i < nfiles) {
395 nexttabstop();
396 list_single(dn[i]);
397 }
398 }
399 newline();
418 } 400 }
401}
419 402
420 dir = opendir(name); 403/*----------------------------------------------------------------------*/
421 if (!dir) 404void showdirs(struct dnode **dn, int ndirs)
422 goto listerr; 405{
423#ifdef BB_FEATURE_AUTOWIDTH 406 int i, nfiles;
424 column_width = 0; 407 struct dnode **subdnp;
425 while ((entry = readdir(dir)) != NULL) {
426 short w = strlen(entry->d_name);
427#ifdef BB_FEATURE_LS_SORTFILES 408#ifdef BB_FEATURE_LS_SORTFILES
428 const char *en = entry->d_name; 409 int dndirs;
429 410 struct dnode **dnd;
430 if (en[0] == '.') {
431 if (!en[1] || (en[1] == '.' && !en[2])) { /* . or .. */
432 if (!(opts & DISP_DOT))
433 continue;
434 } else if (!(opts & DISP_HIDDEN))
435 continue;
436 }
437 nfiles++; /* count how many files there will be */
438#endif 411#endif
439 412
440 if (column_width < w) 413 if (dn==NULL || ndirs < 1) return;
441 column_width = w; 414
442 } 415 for (i=0; i<ndirs; i++) {
443#ifdef HAS_REWINDDIR 416 if (disp_opts & (DISP_DIRNAME | DISP_RECURSIVE)) {
444 rewinddir(dir); 417 fprintf(stdout, "\n%s:\n", dn[i]->fullname);
445#else 418 }
446 closedir(dir); 419 subdnp= list_dir(dn[i]->fullname);
447 dir = opendir(name); 420 nfiles= countfiles(subdnp);
448 if (!dir) 421 if (nfiles > 0) {
449 goto listerr; 422 /* list all files at this level */
450#endif
451#endif
452#ifdef BB_FEATURE_LS_SORTFILES 423#ifdef BB_FEATURE_LS_SORTFILES
453 /* now that we know how many files there are 424 shellsort(subdnp, nfiles);
454 * allocate memory for an array to hold dnode pointers 425#endif
455 */ 426 showfiles(subdnp, nfiles);
456 dnp= (struct dnode **)calloc((size_t)nfiles, (size_t)(sizeof(struct dnode *))); 427#ifdef BB_FEATURE_LS_RECURSIVE
428 if (disp_opts & DISP_RECURSIVE) {
429 /* recursive- list the sub-dirs */
430 dnd= splitdnarray(subdnp, nfiles, SPLIT_DIR);
431 dndirs= countdirs(subdnp, nfiles);
432 if (dndirs > 0) {
433 shellsort(dnd, dndirs);
434 showdirs(dnd, dndirs);
435 free(dnd); /* free the array of dnode pointers to the dirs */
436 }
437 }
438 dfree(subdnp); /* free the dnodes and the fullname mem */
457#endif 439#endif
440 }
441 }
442}
458 443
459 /* List the contents */ 444/*----------------------------------------------------------------------*/
445struct dnode **list_dir(char *path)
446{
447 struct dnode *dn, *cur, **dnp;
448 struct dirent *entry;
449 DIR *dir;
450 char *fnend, fullname[BUFSIZ+1] ;
451 int i, nfiles;
460 452
461 strcpy(fullname, name); /* *** ignore '.' by itself */ 453 if (path==NULL) return(NULL);
454 strcpy(fullname, path);
462 fnend = fullname + strlen(fullname); 455 fnend = fullname + strlen(fullname);
463 if (fnend[-1] != '/') 456 if (fnend[-1] != '/') {
464 *fnend++ = '/'; 457 strcat(fullname, "/");
458 fnend++;
459 }
465 460
461 dn= NULL;
462 nfiles= 0;
463 dir = opendir(fullname);
464 if (dir == NULL) {
465 errorMsg("%s: %s\n", fullname, strerror(errno));
466 return(NULL); /* could not open the dir */
467 }
466 while ((entry = readdir(dir)) != NULL) { 468 while ((entry = readdir(dir)) != NULL) {
467 const char *en = entry->d_name; 469 /* are we going to list the file- it may be . or .. or a hidden file */
468
469 if (en[0] == '.') {
470 if (!en[1] || (en[1] == '.' && !en[2])) { /* . or .. */
471 if (!(opts & DISP_DOT))
472 continue;
473 } else if (!(opts & DISP_HIDDEN))
474 continue;
475 }
476 /* FIXME: avoid stat if not required */
477 strcpy(fnend, entry->d_name); 470 strcpy(fnend, entry->d_name);
478#ifdef BB_FEATURE_LS_SORTFILES 471 if ((strcmp(fnend, ".")==0) && !(disp_opts & DISP_DOT)) continue;
479 /* allocate memory for a node and memory for the file name */ 472 if ((strcmp(fnend, "..")==0) && !(disp_opts & DISP_DOT)) continue;
480 cur= (struct dnode *)malloc(sizeof(struct dnode)); 473 if ((fnend[0] == '.') && !(disp_opts & DISP_HIDDEN)) continue;
481 cur->fullname= strcpy((char *)malloc(strlen(fullname)+1), fullname); 474 cur= (struct dnode *)xmalloc(sizeof(struct dnode));
475 cur->fullname= xstrdup(fullname);
482 cur->name= cur->fullname + (int)(fnend - fullname) ; 476 cur->name= cur->fullname + (int)(fnend - fullname) ;
483 lstat(fullname, &cur->dstat); /* get file stat info into node */ 477 if (lstat(fullname, &cur->dstat)) { /* get file stat info into node */
484 dnp[ni++]= cur; /* save pointer to node in array */ 478 errorMsg("%s: %s\n", fullname, strerror(errno));
485#else 479 free(cur->fullname);
486 if (lstat(fullname, &info)) { 480 free(cur);
487 closedir(dir); 481 continue;
488 goto listerr; /* (shouldn't fail) */
489 } 482 }
490 list_single(entry->d_name, &info, fullname); 483 cur->next= dn;
491#endif 484 dn= cur;
485 nfiles++;
492 } 486 }
493 closedir(dir); 487 closedir(dir);
494#ifdef BB_FEATURE_LS_SORTFILES
495 shellsort(dnp, nfiles);
496 showdnodes(dnp, nfiles);
497#endif
498 488
499 if (opts & DISP_DIRNAME) { /* separate the directory */ 489 /* now that we know how many files there are
500 if (column) { 490 ** allocate memory for an array to hold dnode pointers
501 wr("\n", 1); 491 */
502 } 492 if (nfiles < 1) return(NULL);
503 wr("\n", 1); 493 dnp= dnalloc(nfiles);
504 column = 0; 494 for (i=0, cur=dn; i<nfiles; i++) {
495 dnp[i]= cur; /* save pointer to node in array */
496 cur= cur->next;
505 } 497 }
506 498
507 return 0; 499 return(dnp);
508
509 listerr:
510 newline();
511 perror(name);
512 return 1;
513} 500}
514 501
515#ifdef BB_FEATURE_LS_RECURSIVE 502/*----------------------------------------------------------------------*/
516static int dirAction(const char *fileName, struct stat *statbuf, void* junk) 503int list_single(struct dnode *dn)
517{ 504{
518 int i; 505 int i, len;
519 fprintf(stdout, "\n%s:\n", fileName); 506 char scratch[BUFSIZ + 1];
520 i = list_item(fileName); 507#ifdef BB_FEATURE_LS_TIMESTAMPS
521 newline(); 508 char *filetime;
522 return (i); 509 time_t ttime, age;
523} 510#endif
511#ifdef BB_FEATURE_LS_FILETYPES
512 struct stat info;
513 char append;
524#endif 514#endif
525 515
526extern int ls_main(int argc, char **argv) 516 if (dn==NULL || dn->fullname==NULL) return(0);
527{
528 int argi = 1, i;
529 517
530 /* process options */ 518#ifdef BB_FEATURE_LS_TIMESTAMPS
531 while (argi < argc && argv[argi][0] == '-') { 519 ttime= dn->dstat.st_mtime; /* the default time */
532 const char *p = &argv[argi][1]; 520 if (time_fmt & TIME_ACCESS) ttime= dn->dstat.st_atime;
533 521 if (time_fmt & TIME_CHANGE) ttime= dn->dstat.st_ctime;
534 if (!*p) 522 filetime= ctime(&ttime);
535 goto print_usage_message; /* "-" by itself not allowed */ 523#endif
536 if (*p == '-') { 524#ifdef BB_FEATURE_LS_FILETYPES
537 if (!p[1]) { /* "--" forces end of options */ 525 append = append_char(dn->dstat.st_mode);
538 argi++; 526#endif
539 break;
540 }
541 /* it's a long option name - we don't support them */
542 goto print_usage_message;
543 }
544 527
545 while (*p) 528 for (i=0; i<=31; i++) {
546 switch (*p++) { 529 switch (list_fmt & (1<<i)) {
547 case 'l': 530 case LIST_INO:
548 display_fmt = FMT_LONG; 531 fprintf(stdout, "%7ld ", dn->dstat.st_ino);
549 break; 532 column += 8;
550 case '1':
551 display_fmt = FMT_SINGLE;
552 break; 533 break;
553 case 'x': 534 case LIST_BLOCKS:
554 display_fmt = FMT_ROWS; 535 fprintf(stdout, "%4ld ", dn->dstat.st_blocks>>1);
536 column += 5;
555 break; 537 break;
556 case 'C': 538 case LIST_MODEBITS:
557 display_fmt = FMT_COLUMNS; 539 fprintf(stdout, "%10s", (char *)modeString(dn->dstat.st_mode));
540 column += 10;
558 break; 541 break;
559#ifdef BB_FEATURE_LS_FILETYPES 542 case LIST_NLINKS:
560 case 'p': 543 fprintf(stdout, "%4d ", dn->dstat.st_nlink);
561 opts |= DISP_FTYPE; 544 column += 10;
562 break; 545 break;
563 case 'F': 546 case LIST_ID_NAME:
564 opts |= DISP_FTYPE | DISP_EXEC; 547#ifdef BB_FEATURE_LS_USERNAME
548 {
549 memset(&info, 0, sizeof(struct stat));
550 memset(scratch, 0, sizeof(scratch));
551 if (!stat(dn->fullname, &info)) {
552 my_getpwuid(scratch, info.st_uid);
553 }
554 if (*scratch) {
555 fprintf(stdout, "%-8.8s ", scratch);
556 } else {
557 fprintf(stdout, "%-8d ", dn->dstat.st_uid);
558 }
559 memset(scratch, 0, sizeof(scratch));
560 if (info.st_ctime != 0) {
561 my_getgrgid(scratch, info.st_gid);
562 }
563 if (*scratch) {
564 fprintf(stdout, "%-8.8s", scratch);
565 } else {
566 fprintf(stdout, "%-8d", dn->dstat.st_gid);
567 }
568 column += 17;
569 }
565 break; 570 break;
566#endif 571#endif
567 case 'A': 572 case LIST_ID_NUMERIC:
568 opts |= DISP_HIDDEN; 573 fprintf(stdout, "%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid);
569 break; 574 column += 17;
570 case 'a':
571 opts |= DISP_HIDDEN | DISP_DOT;
572 break;
573 case 'n':
574 opts |= DISP_NUMERIC;
575 break; 575 break;
576 case 'd': 576 case LIST_SIZE:
577 opts |= DIR_NOLIST; 577 case LIST_DEV:
578 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
579 fprintf(stdout, "%4d, %3d ", (int)MAJOR(dn->dstat.st_rdev), (int)MINOR(dn->dstat.st_rdev));
580 } else {
581 fprintf(stdout, "%9ld ", dn->dstat.st_size);
582 }
583 column += 10;
578 break; 584 break;
579#ifdef BB_FEATURE_LS_TIMESTAMPS 585#ifdef BB_FEATURE_LS_TIMESTAMPS
580 case 'u': 586 case LIST_FULLTIME:
581 time_fmt = TIME_ACCESS; 587 case LIST_DATE_TIME:
588 if (list_fmt & LIST_FULLTIME) {
589 fprintf(stdout, "%24.24s ", filetime);
590 column += 25;
591 break;
592 }
593 age = time(NULL) - ttime;
594 fprintf(stdout, "%6.6s ", filetime+4);
595 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
596 /* hh:mm if less than 6 months old */
597 fprintf(stdout, "%5.5s ", filetime+11);
598 } else {
599 fprintf(stdout, " %4.4s ", filetime+20);
600 }
601 column += 13;
582 break; 602 break;
583 case 'c': 603#endif
584 time_fmt = TIME_CHANGE; 604 case LIST_FILENAME:
605 fprintf(stdout, "%s", dn->name);
606 column += strlen(dn->name);
585 break; 607 break;
586 case 'e': 608 case LIST_SYMLINK:
587 opts |= DISP_FULLTIME; 609 if (S_ISLNK(dn->dstat.st_mode)) {
610 len= readlink(dn->fullname, scratch, (sizeof scratch)-1);
611 if (len > 0) {
612 scratch[len]= '\0';
613 fprintf(stdout, " -> %s", scratch);
614#ifdef BB_FEATURE_LS_FILETYPES
615 if (!stat(dn->fullname, &info)) {
616 append = append_char(info.st_mode);
617 }
618#endif
619 column += len+4;
620 }
621 }
622 break;
623#ifdef BB_FEATURE_LS_FILETYPES
624 case LIST_FILETYPE:
625 if (append != '\0') {
626 fprintf(stdout, "%1c", append);
627 column++;
628 }
588 break; 629 break;
589#endif 630#endif
631 }
632 }
633
634 return(0);
635}
636
637/*----------------------------------------------------------------------*/
638extern int ls_main(int argc, char **argv)
639{
640 struct dnode **dnf, **dnd;
641 int dnfiles, dndirs;
642 struct dnode *dn, *cur, **dnp;
643 int i, nfiles;
644 int opt;
645 int oi, ac;
646 char **av;
647
648 disp_opts= DISP_NORMAL;
649 style_fmt= STYLE_AUTO;
650 list_fmt= LIST_SHORT;
651#ifdef BB_FEATURE_LS_SORTFILES
652 sort_opts= SORT_NAME;
653#endif
654#ifdef BB_FEATURE_LS_TIMESTAMPS
655 time_fmt= TIME_MOD;
656#endif
657 nfiles=0;
658
659 applet_name= argv[0];
660 /* process options */
661 while ((opt = getopt(argc, argv, "1AaCdgilnsx"
662#ifdef BB_FEATURE_AUTOWIDTH
663"T:w:"
664#endif
665#ifdef BB_FEATURE_LS_FILETYPES
666"Fp"
667#endif
590#ifdef BB_FEATURE_LS_RECURSIVE 668#ifdef BB_FEATURE_LS_RECURSIVE
591 case 'R': 669"R"
592 opts |= DISP_RECURSIVE; 670#endif
593 break; 671#ifdef BB_FEATURE_LS_SORTFILES
672"rSvX"
673#endif
674#ifdef BB_FEATURE_LS_TIMESTAMPS
675"cetu"
676#endif
677 )) > 0) {
678 switch (opt) {
679 case '1': style_fmt = STYLE_SINGLE; break;
680 case 'A': disp_opts |= DISP_HIDDEN; break;
681 case 'a': disp_opts |= DISP_HIDDEN | DISP_DOT; break;
682 case 'C': style_fmt = STYLE_COLUMNS; break;
683 case 'd': disp_opts |= DISP_NOLIST; break;
684 case 'e': list_fmt |= LIST_FULLTIME; break;
685 case 'g': /* ignore -- for ftp servers */ break;
686 case 'i': list_fmt |= LIST_INO; break;
687 case 'l': style_fmt = STYLE_LONG; list_fmt |= LIST_LONG; break;
688 case 'n': list_fmt |= LIST_ID_NUMERIC; break;
689 case 's': list_fmt |= LIST_BLOCKS; break;
690 case 'x': disp_opts = DISP_ROWS; break;
691#ifdef BB_FEATURE_LS_FILETYPES
692 case 'F': list_fmt |= LIST_FILETYPE | LIST_EXEC; break;
693 case 'p': list_fmt |= LIST_FILETYPE; break;
694#endif
695#ifdef BB_FEATURE_LS_RECURSIVE
696 case 'R': disp_opts |= DISP_RECURSIVE; break;
697#endif
698#ifdef BB_FEATURE_LS_SORTFILES
699 case 'r': sort_order |= SORT_REVERSE; break;
700 case 'S': sort_opts= SORT_SIZE; break;
701 case 'v': sort_opts= SORT_VERSION; break;
702 case 'X': sort_opts= SORT_EXT; break;
703#endif
704#ifdef BB_FEATURE_LS_TIMESTAMPS
705 case 'c': time_fmt = TIME_CHANGE; sort_opts= SORT_CTIME; break;
706 case 't': sort_opts= SORT_MTIME; break;
707 case 'u': time_fmt = TIME_ACCESS; sort_opts= SORT_ATIME; break;
708#endif
709#ifdef BB_FEATURE_AUTOWIDTH
710 case 'T': tabstops= atoi(optarg); break;
711 case 'w': terminal_width= atoi(optarg); break;
594#endif 712#endif
595 case 'g': /* ignore -- for ftp servers */
596 break;
597 default: 713 default:
598 goto print_usage_message; 714 goto print_usage_message;
599 } 715 }
600
601 argi++;
602 } 716 }
603 717
718 /* sort out which command line options take precedence */
719#ifdef BB_FEATURE_LS_RECURSIVE
720 if (disp_opts & DISP_NOLIST)
721 disp_opts &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
722#endif
723#ifdef BB_FEATURE_LS_TIMESTAMPS
724 if (time_fmt & TIME_CHANGE) sort_opts= SORT_CTIME;
725 if (time_fmt & TIME_ACCESS) sort_opts= SORT_ATIME;
726#endif
727 if (style_fmt != STYLE_LONG)
728 list_fmt &= ~LIST_ID_NUMERIC; /* numeric uid only for long list */
729#ifdef BB_FEATURE_LS_USERNAME
730 if (style_fmt == STYLE_LONG && (list_fmt & LIST_ID_NUMERIC))
731 list_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
732#endif
733
604 /* choose a display format */ 734 /* choose a display format */
605 if (display_fmt == FMT_AUTO) 735 if (style_fmt == STYLE_AUTO)
606 display_fmt = isatty(fileno(stdout)) ? FMT_COLUMNS : FMT_SINGLE; 736 style_fmt = isatty(fileno(stdout)) ? STYLE_COLUMNS : STYLE_SINGLE;
607 if (argi < argc - 1) 737
608 opts |= DISP_DIRNAME; /* 2 or more items? label directories */ 738 /*
609#ifdef BB_FEATURE_AUTOWIDTH 739 * when there are no cmd line args we have to supply a default "." arg.
610 /* could add a -w option and/or TIOCGWINSZ call */ 740 * we will create a second argv array, "av" that will hold either
611 if (terminal_width < 1) 741 * our created "." arg, or the real cmd line args. The av array
612 terminal_width = TERMINAL_WIDTH; 742 * just holds the pointers- we don't move the date the pointers
743 * point to.
744 */
745 ac= argc - optind; /* how many cmd line args are left */
746 if (ac < 1) {
747 av= (char **)xcalloc((size_t)1, (size_t)(sizeof(char *)));
748 av[0]= xstrdup(".");
749 ac=1;
750 } else {
751 av= (char **)xcalloc((size_t)ac, (size_t)(sizeof(char *)));
752 for (oi=0 ; oi < ac; oi++) {
753 av[oi]= argv[optind++]; /* copy pointer to real cmd line arg */
754 }
755 }
613 756
614 for (i = argi; i < argc; i++) { 757 /* now, everything is in the av array */
615 int len = strlen(argv[i]); 758 if (ac > 1)
759 disp_opts |= DISP_DIRNAME; /* 2 or more items? label directories */
760
761 /* stuff the command line file names into an dnode array */
762 dn=NULL;
763 for (oi=0 ; oi < ac; oi++) {
764 cur= (struct dnode *)xmalloc(sizeof(struct dnode));
765 cur->fullname= xstrdup(av[oi]);
766 cur->name= cur->fullname ;
767 if (lstat(av[oi], &cur->dstat)) { /* get file info into node */
768 errorMsg("%s: %s\n", av[oi], strerror(errno));
769 free(cur->fullname);
770 free(cur);
771 continue;
772 }
773 cur->next= dn;
774 dn= cur;
775 nfiles++;
776 }
616 777
617 if (toplevel_column_width < len) 778 /* now that we know how many files there are
618 toplevel_column_width = len; 779 ** allocate memory for an array to hold dnode pointers
780 */
781 dnp= dnalloc(nfiles);
782 for (i=0, cur=dn; i<nfiles; i++) {
783 dnp[i]= cur; /* save pointer to node in array */
784 cur= cur->next;
619 } 785 }
620#endif
621 786
622 /* process files specified, or current directory if none */ 787
623#ifdef BB_FEATURE_LS_RECURSIVE 788 if (disp_opts & DISP_NOLIST) {
624 if (opts & DISP_RECURSIVE) { 789#ifdef BB_FEATURE_LS_SORTFILES
625 i = 0; 790 shellsort(dnp, nfiles);
626 if (argi == argc) { 791#endif
627 i = recursiveAction(".", TRUE, FALSE, FALSE, NULL, dirAction, NULL); 792 if (nfiles > 0) showfiles(dnp, nfiles);
793 } else {
794 dnd= splitdnarray(dnp, nfiles, SPLIT_DIR);
795 dnf= splitdnarray(dnp, nfiles, SPLIT_FILE);
796 dndirs= countdirs(dnp, nfiles);
797 dnfiles= nfiles - dndirs;
798 if (dnfiles > 0) {
799#ifdef BB_FEATURE_LS_SORTFILES
800 shellsort(dnf, dnfiles);
801#endif
802 showfiles(dnf, dnfiles);
628 } 803 }
629 while (argi < argc) { 804 if (dndirs > 0) {
630 i |= recursiveAction(argv[argi++], TRUE, FALSE, FALSE, NULL, dirAction, NULL); 805#ifdef BB_FEATURE_LS_SORTFILES
806 shellsort(dnd, dndirs);
807#endif
808 showdirs(dnd, dndirs);
631 } 809 }
632 } else
633#endif
634 {
635 i = 0;
636 if (argi == argc)
637 i = list_item(".");
638 while (argi < argc)
639 i |= list_item(argv[argi++]);
640 newline();
641 } 810 }
642 exit(i); 811
812 return(0);
643 813
644 print_usage_message: 814 print_usage_message:
645 usage(ls_usage); 815 usage(ls_usage);
646 exit(FALSE); 816 return(FALSE);
647} 817}