ls.c - sbase - suckless unix tools
(HTM) git clone git://git.suckless.org/sbase
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
ls.c (9612B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include <sys/stat.h>
3 #include <sys/types.h>
4 #ifndef major
5 #include <sys/sysmacros.h>
6 #endif
7
8 #include <dirent.h>
9 #include <grp.h>
10 #include <pwd.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <time.h>
15 #include <unistd.h>
16
17 #include "utf.h"
18 #include "util.h"
19
20 struct entry {
21 char *name;
22 mode_t mode, tmode;
23 nlink_t nlink;
24 uid_t uid;
25 gid_t gid;
26 off_t size;
27 struct timespec t;
28 dev_t dev;
29 dev_t rdev;
30 ino_t ino, tino;
31 };
32
33 static struct {
34 dev_t dev;
35 ino_t ino;
36 } *tree;
37
38 static int ret = 0;
39 static int Aflag = 0;
40 static int aflag = 0;
41 static int cflag = 0;
42 static int dflag = 0;
43 static int Fflag = 0;
44 static int fflag = 0;
45 static int Hflag = 0;
46 static int hflag = 0;
47 static int iflag = 0;
48 static int Lflag = 0;
49 static int lflag = 0;
50 static int nflag = 0;
51 static int pflag = 0;
52 static int qflag = 0;
53 static int Rflag = 0;
54 static int rflag = 0;
55 static int Uflag = 0;
56 static int uflag = 0;
57 static int first = 1;
58 static char sort = 0;
59 static int showdirs;
60
61 static void ls(const char *, const struct entry *, int);
62
63 static void
64 mkent(struct entry *ent, char *path, int dostat, int follow)
65 {
66 struct stat st;
67
68 ent->name = path;
69 if (!dostat)
70 return;
71 if ((follow ? stat : lstat)(path, &st) < 0)
72 eprintf("%s %s:", follow ? "stat" : "lstat", path);
73 ent->mode = st.st_mode;
74 ent->nlink = st.st_nlink;
75 ent->uid = st.st_uid;
76 ent->gid = st.st_gid;
77 ent->size = st.st_size;
78 if (cflag)
79 ent->t = st.st_ctim;
80 else if (uflag)
81 ent->t = st.st_atim;
82 else
83 ent->t = st.st_mtim;
84 ent->dev = st.st_dev;
85 ent->rdev = st.st_rdev;
86 ent->ino = st.st_ino;
87 if (S_ISLNK(ent->mode)) {
88 if (stat(path, &st) == 0) {
89 ent->tmode = st.st_mode;
90 ent->dev = st.st_dev;
91 ent->tino = st.st_ino;
92 } else {
93 ent->tmode = ent->tino = 0;
94 }
95 }
96 }
97
98 static char *
99 indicator(mode_t mode)
100 {
101 if (pflag || Fflag)
102 if (S_ISDIR(mode))
103 return "/";
104
105 if (Fflag) {
106 if (S_ISLNK(mode))
107 return "@";
108 else if (S_ISFIFO(mode))
109 return "|";
110 else if (S_ISSOCK(mode))
111 return "=";
112 else if (mode & S_IXUSR || mode & S_IXGRP || mode & S_IXOTH)
113 return "*";
114 }
115
116 return "";
117 }
118
119 static void
120 printname(const char *name)
121 {
122 const char *c;
123 Rune r;
124 size_t l;
125
126 for (c = name; *c; c += l) {
127 l = chartorune(&r, c);
128 if (!qflag || isprintrune(r))
129 fwrite(c, 1, l, stdout);
130 else
131 putchar('?');
132 }
133 }
134
135 static void
136 output(const struct entry *ent)
137 {
138 struct group *gr;
139 struct passwd *pw;
140 struct tm *tm;
141 ssize_t len;
142 char *fmt, buf[BUFSIZ], pwname[_SC_LOGIN_NAME_MAX],
143 grname[_SC_LOGIN_NAME_MAX], mode[] = "----------";
144
145 if (iflag)
146 printf("%lu ", (unsigned long)ent->ino);
147 if (!lflag) {
148 printname(ent->name);
149 puts(indicator(ent->mode));
150 return;
151 }
152 if (S_ISREG(ent->mode))
153 mode[0] = '-';
154 else if (S_ISBLK(ent->mode))
155 mode[0] = 'b';
156 else if (S_ISCHR(ent->mode))
157 mode[0] = 'c';
158 else if (S_ISDIR(ent->mode))
159 mode[0] = 'd';
160 else if (S_ISFIFO(ent->mode))
161 mode[0] = 'p';
162 else if (S_ISLNK(ent->mode))
163 mode[0] = 'l';
164 else if (S_ISSOCK(ent->mode))
165 mode[0] = 's';
166 else
167 mode[0] = '?';
168
169 if (ent->mode & S_IRUSR) mode[1] = 'r';
170 if (ent->mode & S_IWUSR) mode[2] = 'w';
171 if (ent->mode & S_IXUSR) mode[3] = 'x';
172 if (ent->mode & S_IRGRP) mode[4] = 'r';
173 if (ent->mode & S_IWGRP) mode[5] = 'w';
174 if (ent->mode & S_IXGRP) mode[6] = 'x';
175 if (ent->mode & S_IROTH) mode[7] = 'r';
176 if (ent->mode & S_IWOTH) mode[8] = 'w';
177 if (ent->mode & S_IXOTH) mode[9] = 'x';
178
179 if (ent->mode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
180 if (ent->mode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
181 if (ent->mode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
182
183 if (!nflag && (pw = getpwuid(ent->uid)))
184 snprintf(pwname, sizeof(pwname), "%s", pw->pw_name);
185 else
186 snprintf(pwname, sizeof(pwname), "%d", ent->uid);
187
188 if (!nflag && (gr = getgrgid(ent->gid)))
189 snprintf(grname, sizeof(grname), "%s", gr->gr_name);
190 else
191 snprintf(grname, sizeof(grname), "%d", ent->gid);
192
193 if (time(NULL) > ent->t.tv_sec + (180 * 24 * 60 * 60)) /* 6 months ago? */
194 fmt = "%b %d %Y";
195 else
196 fmt = "%b %d %H:%M";
197
198 if ((tm = localtime(&ent->t.tv_sec)))
199 strftime(buf, sizeof(buf), fmt, tm);
200 else
201 snprintf(buf, sizeof(buf), "%lld", (long long)(ent->t.tv_sec));
202 printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname, grname);
203
204 if (S_ISBLK(ent->mode) || S_ISCHR(ent->mode))
205 printf("%4u, %4u ", major(ent->rdev), minor(ent->rdev));
206 else if (hflag)
207 printf("%10s ", humansize(ent->size));
208 else
209 printf("%10lu ", (unsigned long)ent->size);
210 printf("%s ", buf);
211 printname(ent->name);
212 fputs(indicator(ent->mode), stdout);
213 if (S_ISLNK(ent->mode)) {
214 if ((len = readlink(ent->name, buf, sizeof(buf) - 1)) < 0)
215 eprintf("readlink %s:", ent->name);
216 buf[len] = '\0';
217 printf(" -> %s%s", buf, indicator(ent->tmode));
218 }
219 putchar('\n');
220 }
221
222 static int
223 entcmp(const void *va, const void *vb)
224 {
225 int cmp = 0;
226 const struct entry *a = va, *b = vb;
227
228 switch (sort) {
229 case 'S':
230 cmp = b->size - a->size;
231 break;
232 case 't':
233 if (!(cmp = b->t.tv_sec - a->t.tv_sec))
234 cmp = b->t.tv_nsec - a->t.tv_nsec;
235 break;
236 }
237
238 if (!cmp)
239 cmp = strcmp(a->name, b->name);
240
241 return rflag ? 0 - cmp : cmp;
242 }
243
244 static void
245 lsdir(const char *path, const struct entry *dir)
246 {
247 DIR *dp;
248 struct entry *ent, *ents = NULL;
249 struct dirent *d;
250 size_t i, n = 0;
251 char prefix[PATH_MAX];
252
253 if (!(dp = opendir(dir->name))) {
254 ret = 1;
255 weprintf("opendir %s%s:", path, dir->name);
256 return;
257 }
258 if (chdir(dir->name) < 0)
259 eprintf("chdir %s:", dir->name);
260
261 while ((d = readdir(dp))) {
262 if (d->d_name[0] == '.' && !aflag && !Aflag)
263 continue;
264 else if (Aflag)
265 if (strcmp(d->d_name, ".") == 0 ||
266 strcmp(d->d_name, "..") == 0)
267 continue;
268
269 ents = ereallocarray(ents, ++n, sizeof(*ents));
270 mkent(&ents[n - 1], estrdup(d->d_name), Fflag || iflag ||
271 lflag || pflag || Rflag || sort, Lflag);
272 }
273
274 closedir(dp);
275
276 if (!Uflag)
277 qsort(ents, n, sizeof(*ents), entcmp);
278
279 if (path[0] || showdirs) {
280 fputs(path, stdout);
281 printname(dir->name);
282 puts(":");
283 }
284 for (i = 0; i < n; i++)
285 output(&ents[i]);
286
287 if (Rflag) {
288 if (snprintf(prefix, PATH_MAX, "%s%s/", path, dir->name) >=
289 PATH_MAX)
290 eprintf("path too long: %s%s\n", path, dir->name);
291
292 for (i = 0; i < n; i++) {
293 ent = &ents[i];
294 if (strcmp(ent->name, ".") == 0 ||
295 strcmp(ent->name, "..") == 0)
296 continue;
297 if (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Lflag)
298 continue;
299
300 ls(prefix, ent, 1);
301 }
302 }
303
304 for (i = 0; i < n; ++i)
305 free(ents[i].name);
306 free(ents);
307 }
308
309 static int
310 visit(const struct entry *ent)
311 {
312 dev_t dev;
313 ino_t ino;
314 int i;
315
316 dev = ent->dev;
317 ino = S_ISLNK(ent->mode) ? ent->tino : ent->ino;
318
319 for (i = 0; i < PATH_MAX && tree[i].ino; ++i) {
320 if (ino == tree[i].ino && dev == tree[i].dev)
321 return -1;
322 }
323
324 tree[i].ino = ino;
325 tree[i].dev = dev;
326
327 return i;
328 }
329
330 static void
331 ls(const char *path, const struct entry *ent, int listdir)
332 {
333 int treeind;
334 char cwd[PATH_MAX];
335
336 if (!listdir) {
337 output(ent);
338 } else if (S_ISDIR(ent->mode) ||
339 (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode))) {
340 if ((treeind = visit(ent)) < 0) {
341 ret = 1;
342 weprintf("%s%s: Already visited\n", path, ent->name);
343 return;
344 }
345
346 if (!getcwd(cwd, PATH_MAX))
347 eprintf("getcwd:");
348
349 if (first)
350 first = 0;
351 else
352 putchar('\n');
353
354 lsdir(path, ent);
355 tree[treeind].ino = 0;
356
357 if (chdir(cwd) < 0)
358 eprintf("chdir %s:", cwd);
359 }
360 }
361
362 static void
363 usage(void)
364 {
365 eprintf("usage: %s [-1AacdFfHhiLlnpqRrtUu] [file ...]\n", argv0);
366 }
367
368 int
369 main(int argc, char *argv[])
370 {
371 struct entry ent, *dents, *fents;
372 size_t i, ds, fs;
373
374 tree = ereallocarray(NULL, PATH_MAX, sizeof(*tree));
375
376 ARGBEGIN {
377 case '1':
378 /* force output to 1 entry per line */
379 qflag = 1;
380 break;
381 case 'A':
382 Aflag = 1;
383 break;
384 case 'a':
385 aflag = 1;
386 break;
387 case 'c':
388 cflag = 1;
389 uflag = 0;
390 break;
391 case 'd':
392 dflag = 1;
393 break;
394 case 'f':
395 aflag = 1;
396 fflag = 1;
397 Uflag = 1;
398 break;
399 case 'F':
400 Fflag = 1;
401 break;
402 case 'H':
403 Hflag = 1;
404 break;
405 case 'h':
406 hflag = 1;
407 break;
408 case 'i':
409 iflag = 1;
410 break;
411 case 'L':
412 Lflag = 1;
413 break;
414 case 'l':
415 lflag = 1;
416 break;
417 case 'n':
418 lflag = 1;
419 nflag = 1;
420 break;
421 case 'p':
422 pflag = 1;
423 break;
424 case 'q':
425 qflag = 1;
426 break;
427 case 'R':
428 Rflag = 1;
429 break;
430 case 'r':
431 rflag = 1;
432 break;
433 case 'S':
434 sort = 'S';
435 break;
436 case 't':
437 sort = 't';
438 break;
439 case 'U':
440 Uflag = 1;
441 break;
442 case 'u':
443 uflag = 1;
444 cflag = 0;
445 break;
446 default:
447 usage();
448 } ARGEND
449
450 switch (argc) {
451 case 0: /* fallthrough */
452 *--argv = ".", ++argc;
453 case 1:
454 mkent(&ent, argv[0], 1, Hflag || Lflag);
455 ls("", &ent, (!dflag && S_ISDIR(ent.mode)) ||
456 (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
457 !(dflag || Fflag || lflag)));
458
459 break;
460 default:
461 for (i = ds = fs = 0, fents = dents = NULL; i < argc; ++i) {
462 mkent(&ent, argv[i], 1, Hflag || Lflag);
463
464 if ((!dflag && S_ISDIR(ent.mode)) ||
465 (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
466 !(dflag || Fflag || lflag))) {
467 dents = ereallocarray(dents, ++ds, sizeof(*dents));
468 memcpy(&dents[ds - 1], &ent, sizeof(ent));
469 } else {
470 fents = ereallocarray(fents, ++fs, sizeof(*fents));
471 memcpy(&fents[fs - 1], &ent, sizeof(ent));
472 }
473 }
474
475 showdirs = ds > 1 || (ds && fs);
476
477 qsort(fents, fs, sizeof(ent), entcmp);
478 qsort(dents, ds, sizeof(ent), entcmp);
479
480 for (i = 0; i < fs; ++i)
481 ls("", &fents[i], 0);
482 free(fents);
483 if (fs && ds)
484 putchar('\n');
485 for (i = 0; i < ds; ++i)
486 ls("", &dents[i], 1);
487 free(dents);
488 }
489
490 return (fshut(stdout, "<stdout>") | ret);
491 }