Mercurial > hg > toybox
comparison toys/ls.c @ 565:44abf4d901f3
Rewrite dirtree so we don't need readdir, scandir, and fts.h. Rewrite ls (from scratch) to use new dirtree infrastructure. (This breaks everything else that currently uses dirtree.)
author | Rob Landley <rob@landley.net> |
---|---|
date | Sat, 14 Apr 2012 22:30:41 -0500 |
parents | 31215cc6c9f2 |
children | 2e0367cb9585 |
comparison
equal
deleted
inserted
replaced
564:9530899eee51 | 565:44abf4d901f3 |
---|---|
1 /* vi: set sw=4 ts=4: | 1 /* vi: set sw=4 ts=4: |
2 * | 2 * |
3 * ls.c - list files | 3 * ls.c - list files |
4 * | 4 * |
5 * Copyright 2012 Andre Renaud <andre@bluewatersys.com> | 5 * Copyright 2012 Andre Renaud <andre@bluewatersys.com> |
6 * Copyright 2012 Rob Landley <rob@landley.net> | |
6 * | 7 * |
7 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html | 8 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html |
8 | 9 |
9 USE_LS(NEWTOY(ls, "AnRlF1a", TOYFLAG_BIN)) | 10 USE_LS(NEWTOY(ls, "ACFHLRSacdfiklmnpqrstux1", TOYFLAG_BIN)) |
10 | 11 |
11 config LS | 12 config LS |
12 bool "ls" | 13 bool "ls" |
13 default n | 14 default y |
14 help | 15 help |
15 usage: ls [-lFaA1] [directory...] | 16 usage: ls [-ACFHLRSacdfiklmnpqrstux1] [directory...] |
16 list files | 17 list files |
17 | 18 |
18 -1 list one file per line | 19 -1 list one file per line |
19 -a list all files | 20 -a list all files |
20 -A list all files except . and .. | 21 -A list all files except . and .. |
22 -l show full details for each file | 23 -l show full details for each file |
23 */ | 24 */ |
24 | 25 |
25 #include "toys.h" | 26 #include "toys.h" |
26 | 27 |
27 #define FLAG_a 1 | 28 #define FLAG_1 (1<<0) |
28 #define FLAG_1 2 | 29 //#define FLAG_x (1<<1) |
29 #define FLAG_F 4 | 30 //#define FLAG_u (1<<2) |
30 #define FLAG_l 8 | 31 //#define FLAG_t (1<<3) |
31 #define FLAG_R 16 | 32 //#define FLAG_s (1<<4) |
32 #define FLAG_n 32 | 33 //#define FLAG_r (1<<5) |
33 #define FLAG_A 64 | 34 //#define FLAG_q (1<<6) |
34 | 35 #define FLAG_p (1<<7) |
35 static int dir_filter(const struct dirent *d) | 36 //#define FLAG_n (1<<8) |
36 { | 37 #define FLAG_m (1<<9) |
37 /* Skip over all '.*' entries, unless -a is given */ | 38 #define FLAG_l (1<<10) |
38 if (!(toys.optflags & FLAG_a)) { | 39 //#define FLAG_k (1<<11) |
39 /* -A means show everything except the . & .. entries */ | 40 #define FLAG_i (1<<12) |
40 if (toys.optflags & FLAG_A) { | 41 #define FLAG_f (1<<13) |
41 if (strcmp(d->d_name, ".") == 0 || | 42 #define FLAG_d (1<<14) |
42 strcmp(d->d_name, "..") == 0) | 43 //#define FLAG_c (1<<15) |
43 return 0; | 44 #define FLAG_a (1<<16) |
44 } else if (d->d_name[0] == '.') | 45 //#define FLAG_S (1<<17) |
45 return 0; | 46 #define FLAG_R (1<<18) |
46 } | 47 //#define FLAG_L (1<<19) |
47 return 1; | 48 //#define FLAG_H (1<<20) |
48 } | 49 #define FLAG_F (1<<21) |
49 | 50 //#define FLAG_C (1<<21) |
50 static void do_ls(int fd, char *name) | 51 #define FLAG_A (1<<22) |
51 { | 52 |
52 struct dirent **entries; | 53 // test sst output (suid/sticky in ls flaglist) |
53 int nentries; | 54 |
54 int i; | 55 // ls -lR starts .: then ./subdir: |
55 int maxwidth = -1; | 56 |
56 int ncolumns = 1; | 57 DEFINE_GLOBALS( |
57 struct dirent file_dirent; | 58 struct dirtree *files; |
58 struct dirent *file_direntp; | 59 |
59 | 60 unsigned width; |
60 if (!name || strcmp(name, "-") == 0) | 61 int again; |
61 name = "."; | 62 ) |
62 | 63 |
63 if (toys.optflags & FLAG_R) | 64 #define TT this.ls |
64 xprintf("\n%s:\n", name); | 65 |
65 | 66 void dlist_to_dirtree(struct dirtree *parent) |
66 /* Get all the files in this directory */ | 67 { |
67 nentries = scandir(name, &entries, dir_filter, alphasort); | 68 // Turn double_list into dirtree |
68 if (nentries < 0) { | 69 struct dirtree *dt = parent->child; |
69 /* We've just selected a single file, so create a single-length list */ | 70 if (dt) { |
70 /* FIXME: This means that ls *.x results in a whole bunch of single | 71 dt->parent->next = NULL; |
71 * listings, not one combined listing. | 72 while (dt) { |
72 */ | 73 dt->parent = parent; |
73 if (errno == ENOTDIR) { | 74 dt = dt->next; |
74 nentries = 1; | 75 } |
75 strcpy(file_dirent.d_name, name); | 76 } |
76 file_direntp = &file_dirent; | 77 } |
77 entries = &file_direntp; | 78 |
78 } else | 79 static char endtype(struct stat *st) |
79 perror_exit("ls: cannot access %s'", name); | 80 { |
80 } | 81 mode_t mode = st->st_mode; |
81 | 82 if ((toys.optflags&(FLAG_F|FLAG_p)) && S_ISDIR(mode)) return '/'; |
82 | 83 if (toys.optflags & FLAG_F) { |
83 /* Determine the widest entry so we can flow them properly */ | 84 if (S_ISLNK(mode) && !(toys.optflags & FLAG_F)) return '@'; |
84 if (!(toys.optflags & FLAG_1)) { | 85 if (S_ISREG(mode) && (mode&0111)) return '*'; |
85 int columns; | 86 if (S_ISFIFO(mode)) return '|'; |
86 char *columns_str; | 87 if (S_ISSOCK(mode)) return '='; |
87 | 88 } |
88 for (i = 0; i < nentries; i++) { | 89 return 0; |
89 struct dirent *ent = entries[i]; | 90 } |
90 int width; | 91 |
91 | 92 static char *getusername(uid_t uid) |
92 width = strlen(ent->d_name); | 93 { |
93 if (width > maxwidth) | 94 struct passwd *pw = getpwuid(uid); |
94 maxwidth = width; | 95 return pw ? pw->pw_name : utoa(uid); |
95 } | 96 } |
96 /* We always want at least a single space for each entry */ | 97 |
97 maxwidth++; | 98 static char *getgroupname(gid_t gid) |
98 if (toys.optflags & FLAG_F) | 99 { |
99 maxwidth++; | 100 struct group *gr = getgrgid(gid); |
100 | 101 return gr ? gr->gr_name : utoa(gid); |
101 columns_str = getenv("COLUMNS"); | 102 } |
102 columns = columns_str ? atoi(columns_str) : 80; | 103 |
103 ncolumns = maxwidth ? columns / maxwidth : 1; | 104 // Figure out size of printable entry fields for display indent/wrap |
104 } | 105 |
105 | 106 static void entrylen(struct dirtree *dt, unsigned *len) |
106 for (i = 0; i < nentries; i++) { | 107 { |
107 struct dirent *ent = entries[i]; | 108 struct stat *st = &(dt->st); |
108 int len = strlen(ent->d_name); | 109 unsigned flags = toys.optflags; |
109 struct stat st; | 110 |
110 int stat_valid = 0; | 111 *len = strlen(dt->name); |
111 | 112 if (endtype(st)) ++*len; |
112 sprintf(toybuf, "%s/%s", name, ent->d_name); | 113 if (flags & FLAG_m) ++*len; |
113 | 114 |
114 /* Provide the ls -l long output */ | 115 if (flags & FLAG_i) *len += (len[1] = numlen(st->st_ino)); |
115 if (toys.optflags & FLAG_l) { | 116 if (flags & FLAG_l) { |
116 char type; | 117 len[2] = numlen(st->st_nlink); |
117 char timestamp[64]; | 118 len[3] = strlen(getusername(st->st_uid)); |
118 struct tm mtime; | 119 len[4] = strlen(getgroupname(st->st_gid)); |
119 | 120 len[5] = numlen(st->st_size); |
120 if (lstat(toybuf, &st)) | 121 } |
121 perror_exit("Can't stat %s", toybuf); | 122 } |
122 stat_valid = 1; | 123 |
123 if (S_ISDIR(st.st_mode)) | 124 static int compare(void *a, void *b) |
124 type = 'd'; | 125 { |
125 else if (S_ISCHR(st.st_mode)) | 126 struct dirtree *dta = *(struct dirtree **)a; |
126 type = 'c'; | 127 struct dirtree *dtb = *(struct dirtree **)b; |
127 else if (S_ISBLK(st.st_mode)) | 128 |
128 type = 'b'; | 129 // TODO handle flags |
129 else if (S_ISLNK(st.st_mode)) | 130 return strcmp(dta->name, dtb->name); |
130 type = 'l'; | 131 } |
131 else | 132 |
132 type = '-'; | 133 static int filter(struct dirtree *new) |
133 | 134 { |
134 xprintf("%c%c%c%c%c%c%c%c%c%c ", type, | 135 int ret = DIRTREE_NORECURSE; |
135 (st.st_mode & S_IRUSR) ? 'r' : '-', | 136 |
136 (st.st_mode & S_IWUSR) ? 'w' : '-', | 137 // TODO -1f should print here to handle enormous dirs without runing out of mem. |
137 (st.st_mode & S_IXUSR) ? 'x' : '-', | 138 |
138 (st.st_mode & S_IRGRP) ? 'r' : '-', | 139 if (!(toys.optflags & (FLAG_a|FLAG_A)) && new->name[0]=='.') |
139 (st.st_mode & S_IWGRP) ? 'w' : '-', | 140 ret |= DIRTREE_NOSAVE; |
140 (st.st_mode & S_IXGRP) ? 'x' : '-', | 141 else if (!(toys.optflags & FLAG_a)) ret |= dirtree_isdotdot(new); |
141 (st.st_mode & S_IROTH) ? 'r' : '-', | 142 |
142 (st.st_mode & S_IWOTH) ? 'w' : '-', | 143 return ret; |
143 (st.st_mode & S_IXOTH) ? 'x' : '-'); | 144 } |
144 | 145 |
145 xprintf("%2d ", st.st_nlink); | 146 // Display a list of dirtree entries, according to current format |
146 if (toys.optflags & FLAG_n) { | 147 // Output types -1, -l, -C, or stream |
147 xprintf("%4d ", st.st_uid); | 148 |
148 xprintf("%4d ", st.st_gid); | 149 static void listfiles(struct dirtree *indir) |
150 { | |
151 struct dirtree *dt, **sort = 0; | |
152 unsigned long dtlen = 0, ul = 0; | |
153 unsigned width, flags = toys.optflags, totals[6], len[6]; | |
154 int showdirs = 1; | |
155 | |
156 // Figure out if we should show directories and current directory name | |
157 if (indir == TT.files) showdirs = (flags & (FLAG_d|FLAG_R)); | |
158 else if (indir->parent == TT.files && toys.optc <= 1 && !(flags&FLAG_R)); | |
159 else { | |
160 char *path = dirtree_path(indir, 0); | |
161 if (TT.again++) xputc('\n'); | |
162 xprintf("%s:\n", path); | |
163 free(path); | |
164 } | |
165 | |
166 | |
167 // Copy linked list to array and sort it. Directories go in array because | |
168 // we visit them in sorted order. | |
169 | |
170 for (;;) { | |
171 for (dt = indir->child; dt; dt = dt->next) { | |
172 if (sort) sort[dtlen] = dt; | |
173 dtlen++; | |
174 } | |
175 if (sort) break; | |
176 sort = xmalloc(dtlen * sizeof(void *)); | |
177 dtlen = 0; | |
178 continue; | |
179 } | |
180 | |
181 if (flags & FLAG_l) xprintf("total %lu\n", dtlen); | |
182 | |
183 if (!(flags & FLAG_f)) qsort(sort, dtlen, sizeof(void *), (void *)compare); | |
184 | |
185 // Find largest entry in each field for everything but -1 | |
186 | |
187 memset(totals, 0, 6*sizeof(unsigned)); | |
188 if ((flags & (FLAG_1|FLAG_l)) != FLAG_1) { | |
189 for (ul = 0; ul<dtlen; ul++) { | |
190 if (!showdirs && S_ISDIR(sort[ul]->st.st_mode)) continue; | |
191 entrylen(sort[ul], len); | |
192 if (flags & FLAG_l) { | |
193 for (width=0; width<6; width++) | |
194 if (len[width] > totals[width]) totals[width] = len[width]; | |
195 //TODO } else if (flags & FLAG_C) { | |
196 } else if (*len > *totals) *totals = *len; | |
197 } | |
198 } | |
199 | |
200 // Loop through again to produce output. | |
201 width = 0; | |
202 memset(toybuf, ' ', 256); | |
203 for (ul = 0; ul<dtlen; ul++) { | |
204 struct stat *st = &(sort[ul]->st); | |
205 mode_t mode = st->st_mode; | |
206 char et = endtype(st); | |
207 | |
208 if (S_ISDIR(mode) && !showdirs) continue; | |
209 entrylen(sort[ul], len); | |
210 | |
211 if (ul) { | |
212 if (toys.optflags & FLAG_m) xputc(','); | |
213 if ((flags & FLAG_1) || width+1+*len > TT.width) { | |
214 xputc('\n'); | |
215 width = 0; | |
149 } else { | 216 } else { |
150 struct passwd *pwd = getpwuid(st.st_uid); | 217 xputc(' '); |
151 struct group *grp = getgrgid(st.st_gid); | 218 width++; |
152 if (!pwd) | |
153 xprintf("%4d ", st.st_uid); | |
154 else | |
155 xprintf("%-10s ", pwd->pw_name); | |
156 if (!grp) | |
157 xprintf("%4d ", st.st_gid); | |
158 else | |
159 xprintf("%-10s ", grp->gr_name); | |
160 } | 219 } |
161 if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) | 220 } |
162 xprintf("%3d, %3d ", major(st.st_rdev), minor(st.st_rdev)); | 221 width += *len; |
163 else | 222 |
164 xprintf("%12lld ", st.st_size); | 223 if (flags & FLAG_i) |
165 | 224 xprintf("% *lu ", len[1], (unsigned long)st->st_ino); |
166 localtime_r(&st.st_mtime, &mtime); | 225 |
167 | 226 if (flags & FLAG_l) { |
168 strftime(timestamp, sizeof(timestamp), "%b %e %H:%M", &mtime); | 227 struct tm *tm; |
169 xprintf("%s ", timestamp); | 228 char perm[11], thyme[64], c, d; |
170 } | 229 int i, bit; |
171 | 230 |
172 xprintf("%s", ent->d_name); | 231 perm[10]=0; |
173 | 232 for (i=0; i<9; i++) { |
174 /* Append the file-type indicator character */ | 233 bit = mode & (1<<i); |
175 if (toys.optflags & FLAG_F) { | 234 c = i%3; |
176 if (!stat_valid) { | 235 if (!c && (mode & (1<<((d=i/3)+9)))) { |
177 if (lstat(toybuf, &st)) | 236 c = "tss"[d]; |
178 perror_exit("Can't stat %s", toybuf); | 237 if (!bit) c &= 0x20; |
179 stat_valid = 1; | 238 } else c = bit ? "xwr"[c] : '-'; |
239 perm[9-i] = c; | |
180 } | 240 } |
181 if (S_ISDIR(st.st_mode)) { | 241 |
182 xprintf("/"); | 242 if (S_ISDIR(mode)) c = 'd'; |
183 len++; | 243 else if (S_ISBLK(mode)) c = 'b'; |
184 } else if (S_ISREG(st.st_mode) && | 244 else if (S_ISCHR(mode)) c = 'c'; |
185 (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { | 245 else if (S_ISLNK(mode)) c = 'l'; |
186 xprintf("*"); | 246 else if (S_ISFIFO(mode)) c = 'p'; |
187 len++; | 247 else if (S_ISSOCK(mode)) c = 's'; |
188 } else if (S_ISLNK(st.st_mode)) { | 248 else c = '-'; |
189 xprintf("@"); | 249 *perm = c; |
190 len++; | 250 |
191 } | 251 tm = localtime(&(st->st_mtime)); |
192 } | 252 strftime(thyme, sizeof(thyme), "%F %H:%M", tm); |
193 if (toys.optflags & FLAG_1) { | 253 |
194 xprintf("\n"); | 254 xprintf("%s% *d %s%s%s%s% *d %s ", perm, totals[2]+1, st->st_nlink, |
195 } else { | 255 getusername(st->st_uid), toybuf+255-(totals[3]-len[3]), |
196 if (i % ncolumns == ncolumns - 1) | 256 getgroupname(st->st_gid), toybuf+256-(totals[4]-len[4]), |
197 xprintf("\n"); | 257 totals[5]+1, st->st_size, thyme); |
198 else | 258 } |
199 xprintf("%*s", maxwidth - len, ""); | 259 |
200 } | 260 xprintf("%s", sort[ul]->name); |
201 } | 261 if ((flags & FLAG_l) && S_ISLNK(mode)) |
202 /* Make sure we put at a trailing new line in */ | 262 xprintf(" -> %s", sort[ul]->symlink); |
203 if (!(toys.optflags & FLAG_1) && (i % ncolumns)) | 263 |
204 xprintf("\n"); | 264 if (et) xputc(et); |
205 | 265 } |
206 if (toys.optflags & FLAG_R) { | 266 |
207 for (i = 0; i < nentries; i++) { | 267 if (width) xputc('\n'); |
208 struct dirent *ent = entries[i]; | 268 |
209 struct stat st; | 269 for (ul = 0; ul<dtlen; free(sort[ul++])) { |
210 char dirname[PATH_MAX]; | 270 // TODO follow symlinks when? |
211 | 271 if (!S_ISDIR(sort[ul]->st.st_mode) || dirtree_isdotdot(sort[ul])) |
212 sprintf(dirname, "%s/%s", name, ent->d_name); | 272 continue; |
213 if (lstat(dirname, &st)) | 273 if (indir == TT.files || (flags & FLAG_R)) { |
214 perror_exit("Can't stat %s", dirname); | 274 sort[ul]->data = openat(indir->data, sort[ul]->name, 0); |
215 if (S_ISDIR(st.st_mode)) | 275 dirtree_recurse(sort[ul], filter); |
216 do_ls(0, dirname); | 276 listfiles(sort[ul]); |
217 } | 277 } |
218 } | 278 } |
279 free(sort); | |
280 close(indir->data); | |
281 | |
282 | |
219 } | 283 } |
220 | 284 |
221 void ls_main(void) | 285 void ls_main(void) |
222 { | 286 { |
223 /* If the output is not a TTY, then just do one-file per line | 287 char **s, *noargs[] = {".", 0}; |
224 * This makes ls easier to use with other command line tools (grep/awk etc...) | 288 |
225 */ | 289 // Do we have an implied -1 |
226 if (!isatty(fileno(stdout))) | 290 if (!isatty(1) || (toys.optflags&FLAG_l)) toys.optflags |= FLAG_1; |
227 toys.optflags |= FLAG_1; | 291 else { |
228 /* Long output must be one-file per line */ | 292 TT.width = 80; |
229 if (toys.optflags & FLAG_l) | 293 terminal_size(&TT.width, NULL); |
230 toys.optflags |= FLAG_1; | 294 } |
231 loopfiles(toys.optargs, do_ls); | 295 |
232 } | 296 // Iterate through command line arguments, collecting directories and files. |
297 // Non-absolute paths are relative to current directory. | |
298 TT.files = dirtree_add_node(0, 0); | |
299 TT.files->data =open(".", 0); | |
300 for (s = toys.optargs ? toys.optargs : noargs; *s; s++) { | |
301 struct dirtree *dt = dirtree_add_node(TT.files->data, *s); | |
302 | |
303 if (!dt) { | |
304 toys.exitval = 1; | |
305 continue; | |
306 } | |
307 | |
308 // Typecast means double_list->prev temporarirly goes in dirtree->parent | |
309 dlist_add_nomalloc((struct double_list **)&TT.files->child, | |
310 (struct double_list *)dt); | |
311 } | |
312 | |
313 // Turn double_list into dirtree | |
314 dlist_to_dirtree(TT.files); | |
315 | |
316 // Display the files we collected | |
317 listfiles(TT.files); | |
318 } |