diff options
Diffstat (limited to 'coreutils/ls.c')
-rw-r--r-- | coreutils/ls.c | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/coreutils/ls.c b/coreutils/ls.c new file mode 100644 index 000000000..2566beea0 --- /dev/null +++ b/coreutils/ls.c | |||
@@ -0,0 +1,542 @@ | |||
1 | #include "internal.h" | ||
2 | /* | ||
3 | * tiny-ls.c version 0.1.0: A minimalist 'ls' | ||
4 | * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com> | ||
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 | ||
14 | * GNU 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., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
19 | */ | ||
20 | |||
21 | /* | ||
22 | * To achieve a small memory footprint, this version of 'ls' doesn't do any | ||
23 | * file sorting, and only has the most essential command line switches | ||
24 | * (i.e. the ones I couldn't live without :-) All features which involve | ||
25 | * linking in substantial chunks of libc can be disabled. | ||
26 | * | ||
27 | * Although I don't really want to add new features to this program to | ||
28 | * keep it small, I *am* interested to receive bug fixes and ways to make | ||
29 | * it more portable. | ||
30 | * | ||
31 | * KNOWN BUGS: | ||
32 | * 1. messy output if you mix files and directories on the command line | ||
33 | * 2. ls -l of a directory doesn't give "total <blocks>" header | ||
34 | * 3. ls of a symlink to a directory doesn't list directory contents | ||
35 | * 4. hidden files can make column width too large | ||
36 | * NON-OPTIMAL BEHAVIOUR: | ||
37 | * 1. autowidth reads directories twice | ||
38 | * 2. if you do a short directory listing without filetype characters | ||
39 | * appended, there's no need to stat each one | ||
40 | * PORTABILITY: | ||
41 | * 1. requires lstat (BSD) - how do you do it without? | ||
42 | */ | ||
43 | |||
44 | #define FEATURE_USERNAME /* show username/groupnames (libc6 uses NSS) */ | ||
45 | #define FEATURE_TIMESTAMPS /* show file timestamps */ | ||
46 | #define FEATURE_AUTOWIDTH /* calculate terminal & column widths */ | ||
47 | #define FEATURE_FILETYPECHAR /* enable -p and -F */ | ||
48 | |||
49 | #undef OP_BUF_SIZE 1024 /* leave undefined for unbuffered output */ | ||
50 | |||
51 | #define TERMINAL_WIDTH 80 /* use 79 if your terminal has linefold bug */ | ||
52 | #define COLUMN_WIDTH 14 /* default if AUTOWIDTH not defined */ | ||
53 | #define COLUMN_GAP 2 /* includes the file type char, if present */ | ||
54 | |||
55 | /************************************************************************/ | ||
56 | |||
57 | #define HAS_REWINDDIR | ||
58 | |||
59 | #if 1 /* FIXME libc 6 */ | ||
60 | # include <linux/types.h> | ||
61 | #else | ||
62 | # include <sys/types.h> | ||
63 | #endif | ||
64 | #include <sys/stat.h> | ||
65 | #include <stdio.h> | ||
66 | #include <unistd.h> | ||
67 | #include <dirent.h> | ||
68 | #include <errno.h> | ||
69 | #include <stdio.h> | ||
70 | #ifdef FEATURE_USERNAME | ||
71 | #include <pwd.h> | ||
72 | #include <grp.h> | ||
73 | #endif | ||
74 | #ifdef FEATURE_TIMESTAMPS | ||
75 | #include <time.h> | ||
76 | #endif | ||
77 | |||
78 | #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) | ||
79 | #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)]) | ||
80 | #ifdef FEATURE_FILETYPECHAR | ||
81 | #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)]) | ||
82 | #endif | ||
83 | |||
84 | #ifndef MAJOR | ||
85 | #define MAJOR(dev) (((dev)>>8)&0xff) | ||
86 | #define MINOR(dev) ((dev)&0xff) | ||
87 | #endif | ||
88 | |||
89 | #define MODE1 "rwxrwxrwx" | ||
90 | #define MODE0 "---------" | ||
91 | #define SMODE1 "..s..s..t" | ||
92 | #define SMODE0 "..S..S..T" | ||
93 | |||
94 | /* The 9 mode bits to test */ | ||
95 | |||
96 | static const umode_t MBIT[] = { | ||
97 | S_IRUSR, S_IWUSR, S_IXUSR, | ||
98 | S_IRGRP, S_IWGRP, S_IXGRP, | ||
99 | S_IROTH, S_IWOTH, S_IXOTH | ||
100 | }; | ||
101 | |||
102 | /* The special bits. If set, display SMODE0/1 instead of MODE0/1 */ | ||
103 | |||
104 | static const umode_t SBIT[] = { | ||
105 | 0, 0, S_ISUID, | ||
106 | 0, 0, S_ISGID, | ||
107 | 0, 0, S_ISVTX | ||
108 | }; | ||
109 | |||
110 | #define FMT_AUTO 0 | ||
111 | #define FMT_LONG 1 /* one record per line, extended info */ | ||
112 | #define FMT_SINGLE 2 /* one record per line */ | ||
113 | #define FMT_ROWS 3 /* print across rows */ | ||
114 | #define FMT_COLUMNS 3 /* fill columns (same, since we don't sort) */ | ||
115 | |||
116 | #define TIME_MOD 0 | ||
117 | #define TIME_CHANGE 1 | ||
118 | #define TIME_ACCESS 2 | ||
119 | |||
120 | #define DISP_FTYPE 1 /* show character for file type */ | ||
121 | #define DISP_EXEC 2 /* show '*' if regular executable file */ | ||
122 | #define DISP_HIDDEN 4 /* show files starting . (except . and ..) */ | ||
123 | #define DISP_DOT 8 /* show . and .. */ | ||
124 | #define DISP_NUMERIC 16 /* numeric uid and gid */ | ||
125 | #define DISP_FULLTIME 32 /* show extended time display */ | ||
126 | #define DIR_NOLIST 64 /* show directory as itself, not contents */ | ||
127 | #define DISP_DIRNAME 128 /* show directory name (for internal use) */ | ||
128 | #define DIR_RECURSE 256 /* -R (not yet implemented) */ | ||
129 | |||
130 | static unsigned char display_fmt = FMT_AUTO; | ||
131 | static unsigned short opts = 0; | ||
132 | static unsigned short column = 0; | ||
133 | |||
134 | #ifdef FEATURE_AUTOWIDTH | ||
135 | static unsigned short terminal_width = 0, column_width = 0; | ||
136 | #else | ||
137 | #define terminal_width TERMINAL_WIDTH | ||
138 | #define column_width COLUMN_WIDTH | ||
139 | #endif | ||
140 | |||
141 | #ifdef FEATURE_TIMESTAMPS | ||
142 | static unsigned char time_fmt = TIME_MOD; | ||
143 | #endif | ||
144 | |||
145 | #define wr(data,len) fwrite(data, 1, len, stdout) | ||
146 | |||
147 | static void writenum(long val, short minwidth) | ||
148 | { | ||
149 | char scratch[20]; | ||
150 | |||
151 | char *p = scratch + sizeof(scratch); | ||
152 | short len = 0; | ||
153 | short neg = (val < 0); | ||
154 | |||
155 | if (neg) val = -val; | ||
156 | do | ||
157 | *--p = (val % 10) + '0', len++, val /= 10; | ||
158 | while (val); | ||
159 | if (neg) | ||
160 | *--p = '-', len++; | ||
161 | while (len < minwidth) | ||
162 | *--p = ' ', len++; | ||
163 | wr(p, len); | ||
164 | column += len; | ||
165 | } | ||
166 | |||
167 | static void newline(void) | ||
168 | { | ||
169 | if (column > 0) { | ||
170 | wr("\n", 1); | ||
171 | column = 0; | ||
172 | } | ||
173 | } | ||
174 | |||
175 | static void tab(short col) | ||
176 | { | ||
177 | static const char spaces[] = " "; | ||
178 | #define nspaces ((sizeof spaces)-1) /* null terminator! */ | ||
179 | |||
180 | short n = col - column; | ||
181 | |||
182 | if (n > 0) { | ||
183 | column = col; | ||
184 | while (n > nspaces) { | ||
185 | wr(spaces, nspaces); | ||
186 | n -= nspaces; | ||
187 | } | ||
188 | /* must be 1...(sizeof spaces) left */ | ||
189 | wr(spaces, n); | ||
190 | } | ||
191 | #undef nspaces | ||
192 | } | ||
193 | |||
194 | #ifdef FEATURE_FILETYPECHAR | ||
195 | static char append_char(umode_t mode) | ||
196 | { | ||
197 | if (!(opts & DISP_FTYPE)) | ||
198 | return '\0'; | ||
199 | if ((opts & DISP_EXEC) && S_ISREG(mode) && (mode & (S_IXUSR|S_IXGRP|S_IXOTH))) | ||
200 | return '*'; | ||
201 | return APPCHAR(mode); | ||
202 | } | ||
203 | #endif | ||
204 | |||
205 | /** | ||
206 | ** | ||
207 | ** Display a file or directory as a single item | ||
208 | ** (in either long or short format) | ||
209 | ** | ||
210 | **/ | ||
211 | |||
212 | static void list_single(const char *name, struct stat *info) | ||
213 | { | ||
214 | char scratch[20]; | ||
215 | short len = strlen(name); | ||
216 | #ifdef FEATURE_FILETYPECHAR | ||
217 | char append = append_char(info->st_mode); | ||
218 | #endif | ||
219 | |||
220 | if (display_fmt == FMT_LONG) { | ||
221 | umode_t mode = info->st_mode; | ||
222 | int i; | ||
223 | |||
224 | scratch[0] = TYPECHAR(mode); | ||
225 | for (i=0; i<9; i++) | ||
226 | if (mode & SBIT[i]) | ||
227 | scratch[i+1] = (mode & MBIT[i]) | ||
228 | ? SMODE1[i] | ||
229 | : SMODE0[i]; | ||
230 | else | ||
231 | scratch[i+1] = (mode & MBIT[i]) | ||
232 | ? MODE1[i] | ||
233 | : MODE0[i]; | ||
234 | newline(); | ||
235 | wr(scratch, 10); | ||
236 | column=10; | ||
237 | writenum((long)info->st_nlink,(short)4); | ||
238 | fputs(" ", stdout); | ||
239 | #ifdef FEATURE_USERNAME | ||
240 | if (!(opts & DISP_NUMERIC)) { | ||
241 | struct passwd *pw = getpwuid(info->st_uid); | ||
242 | if (pw) | ||
243 | fputs(pw->pw_name, stdout); | ||
244 | else | ||
245 | writenum((long)info->st_uid,(short)0); | ||
246 | } else | ||
247 | #endif | ||
248 | writenum((long)info->st_uid,(short)0); | ||
249 | tab(24); | ||
250 | #ifdef FEATURE_USERNAME | ||
251 | if (!(opts & DISP_NUMERIC)) { | ||
252 | struct group *gr = getgrgid(info->st_gid); | ||
253 | if (gr) | ||
254 | fputs(gr->gr_name, stdout); | ||
255 | else | ||
256 | writenum((long)info->st_gid,(short)0); | ||
257 | } else | ||
258 | #endif | ||
259 | writenum((long)info->st_gid,(short)0); | ||
260 | tab(33); | ||
261 | if (S_ISBLK(mode) || S_ISCHR(mode)) { | ||
262 | writenum((long)MAJOR(info->st_rdev),(short)3); | ||
263 | fputs(", ", stdout); | ||
264 | writenum((long)MINOR(info->st_rdev),(short)3); | ||
265 | } | ||
266 | else | ||
267 | writenum((long)info->st_size,(short)8); | ||
268 | fputs(" ", stdout); | ||
269 | #ifdef FEATURE_TIMESTAMPS | ||
270 | { | ||
271 | time_t cal; | ||
272 | char *string; | ||
273 | |||
274 | switch(time_fmt) { | ||
275 | case TIME_CHANGE: | ||
276 | cal=info->st_ctime; break; | ||
277 | case TIME_ACCESS: | ||
278 | cal=info->st_atime; break; | ||
279 | default: | ||
280 | cal=info->st_mtime; break; | ||
281 | } | ||
282 | string=ctime(&cal); | ||
283 | if (opts & DISP_FULLTIME) | ||
284 | wr(string,24); | ||
285 | else { | ||
286 | time_t age = time(NULL) - cal; | ||
287 | wr(string+4,7); /* mmm_dd_ */ | ||
288 | if(age < 3600L*24*365/2 && age > -15*60) | ||
289 | /* hh:mm if less than 6 months old */ | ||
290 | wr(string+11,5); | ||
291 | else | ||
292 | /* _yyyy otherwise */ | ||
293 | wr(string+19,5); | ||
294 | } | ||
295 | wr(" ", 1); | ||
296 | } | ||
297 | #else | ||
298 | fputs("--- -- ----- ", stdout); | ||
299 | #endif | ||
300 | wr(name, len); | ||
301 | if (S_ISLNK(mode)) { | ||
302 | wr(" -> ", 4); | ||
303 | len = readlink(name, scratch, sizeof scratch); | ||
304 | if (len > 0) fwrite(scratch, 1, len, stdout); | ||
305 | #ifdef FEATURE_FILETYPECHAR | ||
306 | /* show type of destination */ | ||
307 | if (opts & DISP_FTYPE) { | ||
308 | if (!stat(name, info)) { | ||
309 | append = append_char(info->st_mode); | ||
310 | if (append) | ||
311 | fputc(append, stdout); | ||
312 | } | ||
313 | } | ||
314 | #endif | ||
315 | } | ||
316 | #ifdef FEATURE_FILETYPECHAR | ||
317 | else if (append) | ||
318 | wr(&append, 1); | ||
319 | #endif | ||
320 | } else { | ||
321 | static short nexttab = 0; | ||
322 | |||
323 | /* sort out column alignment */ | ||
324 | if (column == 0) | ||
325 | ; /* nothing to do */ | ||
326 | else if (display_fmt == FMT_SINGLE) | ||
327 | newline(); | ||
328 | else { | ||
329 | if (nexttab + column_width > terminal_width | ||
330 | #ifndef FEATURE_AUTOWIDTH | ||
331 | || nexttab + len >= terminal_width | ||
332 | #endif | ||
333 | ) | ||
334 | newline(); | ||
335 | else | ||
336 | tab(nexttab); | ||
337 | } | ||
338 | /* work out where next column starts */ | ||
339 | #ifdef FEATURE_AUTOWIDTH | ||
340 | /* we know the calculated width is big enough */ | ||
341 | nexttab = column + column_width + COLUMN_GAP; | ||
342 | #else | ||
343 | /* might cover more than one fixed-width column */ | ||
344 | nexttab = column; | ||
345 | do | ||
346 | nexttab += column_width + COLUMN_GAP; | ||
347 | while (nexttab < (column + len + COLUMN_GAP)); | ||
348 | #endif | ||
349 | /* now write the data */ | ||
350 | wr(name, len); | ||
351 | column = column + len; | ||
352 | #ifdef FEATURE_FILETYPECHAR | ||
353 | if (append) | ||
354 | wr(&append, 1), column++; | ||
355 | #endif | ||
356 | } | ||
357 | } | ||
358 | |||
359 | /** | ||
360 | ** | ||
361 | ** List the given file or directory, expanding a directory | ||
362 | ** to show its contents if required | ||
363 | ** | ||
364 | **/ | ||
365 | |||
366 | static int list_item(const char *name) | ||
367 | { | ||
368 | struct stat info; | ||
369 | DIR *dir; | ||
370 | struct dirent *entry; | ||
371 | char fullname[MAXNAMLEN+1], *fnend; | ||
372 | |||
373 | if (lstat(name, &info)) | ||
374 | goto listerr; | ||
375 | |||
376 | if (!S_ISDIR(info.st_mode) || | ||
377 | (opts & DIR_NOLIST)) { | ||
378 | list_single(name, &info); | ||
379 | return 0; | ||
380 | } | ||
381 | |||
382 | /* Otherwise, it's a directory we want to list the contents of */ | ||
383 | |||
384 | if (opts & DISP_DIRNAME) { /* identify the directory */ | ||
385 | if (column) | ||
386 | wr("\n\n", 2), column = 0; | ||
387 | wr(name, strlen(name)); | ||
388 | wr(":\n", 2); | ||
389 | } | ||
390 | |||
391 | dir = opendir(name); | ||
392 | if (!dir) goto listerr; | ||
393 | #ifdef FEATURE_AUTOWIDTH | ||
394 | column_width = 0; | ||
395 | while ((entry = readdir(dir)) != NULL) { | ||
396 | short w = strlen(entry->d_name); | ||
397 | if (column_width < w) | ||
398 | column_width = w; | ||
399 | } | ||
400 | #ifdef HAS_REWINDDIR | ||
401 | rewinddir(dir); | ||
402 | #else | ||
403 | closedir(dir); | ||
404 | dir = opendir(name); | ||
405 | if (!dir) goto listerr; | ||
406 | #endif | ||
407 | #endif | ||
408 | |||
409 | /* List the contents */ | ||
410 | |||
411 | strcpy(fullname,name); /* *** ignore '.' by itself */ | ||
412 | fnend=fullname+strlen(fullname); | ||
413 | if (fnend[-1] != '/') | ||
414 | *fnend++ = '/'; | ||
415 | |||
416 | while ((entry = readdir(dir)) != NULL) { | ||
417 | const char *en=entry->d_name; | ||
418 | if (en[0] == '.') { | ||
419 | if (!en[1] || (en[1] == '.' && !en[2])) { /* . or .. */ | ||
420 | if (!(opts & DISP_DOT)) | ||
421 | continue; | ||
422 | } | ||
423 | else if (!(opts & DISP_HIDDEN)) | ||
424 | continue; | ||
425 | } | ||
426 | /* FIXME: avoid stat if not required */ | ||
427 | strcpy(fnend, entry->d_name); | ||
428 | if (lstat(fullname, &info)) | ||
429 | goto direrr; /* (shouldn't fail) */ | ||
430 | list_single(entry->d_name, &info); | ||
431 | } | ||
432 | closedir(dir); | ||
433 | return 0; | ||
434 | |||
435 | direrr: | ||
436 | closedir(dir); | ||
437 | listerr: | ||
438 | newline(); | ||
439 | name_and_error(name); | ||
440 | return 1; | ||
441 | } | ||
442 | |||
443 | const char ls_usage[] = "Usage: ls [-1a" | ||
444 | #ifdef FEATURE_TIMESTAMPS | ||
445 | "c" | ||
446 | #endif | ||
447 | "d" | ||
448 | #ifdef FEATURE_TIMESTAMPS | ||
449 | "e" | ||
450 | #endif | ||
451 | "ln" | ||
452 | #ifdef FEATURE_FILETYPECHAR | ||
453 | "p" | ||
454 | #endif | ||
455 | #ifdef FEATURE_TIMESTAMPS | ||
456 | "u" | ||
457 | #endif | ||
458 | "xAC" | ||
459 | #ifdef FEATURE_FILETYPECHAR | ||
460 | "F" | ||
461 | #endif | ||
462 | #ifdef FEATURE_RECURSIVE | ||
463 | "R" | ||
464 | #endif | ||
465 | "] [filenames...]\n"; | ||
466 | |||
467 | extern int | ||
468 | ls_main(struct FileInfo * not_used, int argc, char * * argv) | ||
469 | { | ||
470 | int argi=1, i; | ||
471 | |||
472 | /* process options */ | ||
473 | while (argi < argc && argv[argi][0] == '-') { | ||
474 | const char *p = &argv[argi][1]; | ||
475 | |||
476 | if (!*p) goto print_usage_message; /* "-" by itself not allowed */ | ||
477 | if (*p == '-') { | ||
478 | if (!p[1]) { /* "--" forces end of options */ | ||
479 | argi++; | ||
480 | break; | ||
481 | } | ||
482 | /* it's a long option name - we don't support them */ | ||
483 | goto print_usage_message; | ||
484 | } | ||
485 | |||
486 | while (*p) | ||
487 | switch (*p++) { | ||
488 | case 'l': display_fmt = FMT_LONG; break; | ||
489 | case '1': display_fmt = FMT_SINGLE; break; | ||
490 | case 'x': display_fmt = FMT_ROWS; break; | ||
491 | case 'C': display_fmt = FMT_COLUMNS; break; | ||
492 | #ifdef FEATURE_FILETYPECHAR | ||
493 | case 'p': opts |= DISP_FTYPE; break; | ||
494 | case 'F': opts |= DISP_FTYPE|DISP_EXEC; break; | ||
495 | #endif | ||
496 | case 'A': opts |= DISP_HIDDEN; break; | ||
497 | case 'a': opts |= DISP_HIDDEN|DISP_DOT; break; | ||
498 | case 'n': opts |= DISP_NUMERIC; break; | ||
499 | case 'd': opts |= DIR_NOLIST; break; | ||
500 | #ifdef FEATURE_RECURSIVE | ||
501 | case 'R': opts |= DIR_RECURSE; break; | ||
502 | #endif | ||
503 | #ifdef FEATURE_TIMESTAMPS | ||
504 | case 'u': time_fmt = TIME_ACCESS; break; | ||
505 | case 'c': time_fmt = TIME_CHANGE; break; | ||
506 | case 'e': opts |= DISP_FULLTIME; break; | ||
507 | #endif | ||
508 | default: goto print_usage_message; | ||
509 | } | ||
510 | |||
511 | argi++; | ||
512 | } | ||
513 | |||
514 | /* choose a display format */ | ||
515 | if (display_fmt == FMT_AUTO) | ||
516 | display_fmt = isatty(STDOUT_FILENO) ? FMT_COLUMNS : FMT_SINGLE; | ||
517 | if (argi < argc - 1) | ||
518 | opts |= DISP_DIRNAME; /* 2 or more items? label directories */ | ||
519 | #ifdef FEATURE_AUTOWIDTH | ||
520 | /* could add a -w option and/or TIOCGWINSZ call */ | ||
521 | if (terminal_width < 1) terminal_width = TERMINAL_WIDTH; | ||
522 | |||
523 | for (i = argi; i < argc; i++) { | ||
524 | int len = strlen(argv[i]); | ||
525 | if (column_width < len) | ||
526 | column_width = len; | ||
527 | } | ||
528 | #endif | ||
529 | |||
530 | /* process files specified, or current directory if none */ | ||
531 | i=0; | ||
532 | if (argi == argc) | ||
533 | i = list_item("."); | ||
534 | while (argi < argc) | ||
535 | i |= list_item(argv[argi++]); | ||
536 | newline(); | ||
537 | return i; | ||
538 | |||
539 | print_usage_message: | ||
540 | usage(ls_usage); | ||
541 | return 1; | ||
542 | } | ||