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 }