master xplshn/aruu / cmd / posix / ls.c
  1/* see license file for copyright and license details */
  2
  3
  4#include <sys/stat.h>
  5#include <sys/types.h>
  6#ifndef major
  7#include <sys/sysmacros.h>
  8#endif
  9
 10#include <dirent.h>
 11#include <grp.h>
 12#include <pwd.h>
 13#include <stdio.h>
 14#include <stdlib.h>
 15#include <string.h>
 16#include <time.h>
 17#include <unistd.h>
 18
 19#include "utf.h"
 20#include "util.h"
 21
 22struct entry {
 23	char   *name;
 24	mode_t  mode, tmode;
 25	nlink_t nlink;
 26	uid_t   uid;
 27	gid_t   gid;
 28	off_t   size;
 29	struct timespec t;
 30	dev_t   dev;
 31	dev_t   rdev;
 32	ino_t   ino, tino;
 33};
 34
 35static struct {
 36	dev_t dev;
 37	ino_t ino;
 38} *tree;
 39
 40static int ret   = 0;
 41static int Aflag = 0;
 42static int aflag = 0;
 43static int cflag = 0;
 44static int dflag = 0;
 45static int Fflag = 0;
 46static int fflag = 0;
 47static int Hflag = 0;
 48static int hflag = 0;
 49static int iflag = 0;
 50static int Lflag = 0;
 51static int lflag = 0;
 52static int nflag = 0;
 53static int pflag = 0;
 54static int qflag = 0;
 55static int Rflag = 0;
 56static int rflag = 0;
 57static int Uflag = 0;
 58static int uflag = 0;
 59static int first = 1;
 60static char sort = 0;
 61static int showdirs;
 62
 63static int gflag = 0;
 64static int oflag = 0;
 65
 66static int Cflag   = 0;
 67static int one_flag = 0;
 68static int termwidth = 80;
 69
 70#if FEATURE_LS_COLOR
 71#define COLOR_DIR	"\033[1;34m"
 72#define COLOR_LNK	"\033[1;36m"
 73#define COLOR_FIFO	"\033[33m"
 74#define COLOR_SOCK	"\033[1;35m"
 75#define COLOR_DEV	"\033[1;33m"
 76#define COLOR_EXE	"\033[1;32m"
 77#define COLOR_RST	"\033[0m"
 78
 79enum { COLOR_NEVER, COLOR_ALWAYS, COLOR_AUTO };
 80static int color_mode = COLOR_NEVER;
 81#endif
 82
 83static void ls(const char *, const struct entry *, int);
 84static void printname_colored(const char *, mode_t);
 85static void printcols(const struct entry *, size_t);
 86static void output(const struct entry *);
 87
 88static void
 89mkent(struct entry *ent, char *path, int dostat, int follow)
 90{
 91	struct stat st;
 92
 93	ent->name = path;
 94	if (!dostat)
 95		return;
 96	if ((follow ? stat : lstat)(path, &st) < 0)
 97		eprintf("%s %s:", follow ? "stat" : "lstat", path);
 98	ent->mode  = st.st_mode;
 99	ent->nlink = st.st_nlink;
100	ent->uid   = st.st_uid;
101	ent->gid   = st.st_gid;
102	ent->size  = st.st_size;
103	if (cflag)
104		ent->t = st.st_ctim;
105	else if (uflag)
106		ent->t = st.st_atim;
107	else
108		ent->t = st.st_mtim;
109	ent->dev   = st.st_dev;
110	ent->rdev  = st.st_rdev;
111	ent->ino   = st.st_ino;
112	if (S_ISLNK(ent->mode)) {
113		if (stat(path, &st) == 0) {
114			ent->tmode = st.st_mode;
115			ent->dev   = st.st_dev;
116			ent->tino  = st.st_ino;
117		} else {
118			ent->tmode = ent->tino = 0;
119		}
120	}
121}
122
123static char *
124indicator(mode_t mode)
125{
126	if (pflag || Fflag)
127		if (S_ISDIR(mode))
128			return "/";
129
130	if (Fflag) {
131		if (S_ISLNK(mode))
132			return "@";
133		else if (S_ISFIFO(mode))
134			return "|";
135		else if (S_ISSOCK(mode))
136			return "=";
137		else if (mode & S_IXUSR || mode & S_IXGRP || mode & S_IXOTH)
138			return "*";
139	}
140
141	return "";
142}
143
144static void
145printname(const char *name)
146{
147	const char *c;
148	Rune r;
149	size_t l;
150
151	for (c = name; *c; c += l) {
152		l = chartorune(&r, c);
153		if (!qflag || isprintrune(r))
154			fwrite(c, 1, l, stdout);
155		else
156			putchar('?');
157	}
158}
159
160static int
161should_color(void)
162{
163#if FEATURE_LS_COLOR
164	if (color_mode == COLOR_ALWAYS)
165		return 1;
166	if (color_mode == COLOR_AUTO)
167		return isatty(STDOUT_FILENO);
168#endif
169	return 0;
170}
171
172static void
173printname_colored(const char *name, mode_t mode)
174{
175#if FEATURE_LS_COLOR
176	int need_reset = 0;
177
178	if (should_color()) {
179		if (S_ISDIR(mode)) {
180			fputs(COLOR_DIR, stdout);
181			need_reset = 1;
182		} else if (S_ISLNK(mode)) {
183			fputs(COLOR_LNK, stdout);
184			need_reset = 1;
185		} else if (S_ISFIFO(mode)) {
186			fputs(COLOR_FIFO, stdout);
187			need_reset = 1;
188		} else if (S_ISSOCK(mode)) {
189			fputs(COLOR_SOCK, stdout);
190			need_reset = 1;
191		} else if (S_ISBLK(mode) || S_ISCHR(mode)) {
192			fputs(COLOR_DEV, stdout);
193			need_reset = 1;
194		} else if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
195			fputs(COLOR_EXE, stdout);
196			need_reset = 1;
197		}
198	}
199	printname(name);
200	if (need_reset)
201		fputs(COLOR_RST, stdout);
202#else
203	(void)mode;
204	printname(name);
205#endif
206}
207
208#include <sys/ioctl.h>
209
210static void
211gettermwidth(void)
212{
213	struct winsize ws;
214
215	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
216		termwidth = ws.ws_col;
217	else
218		termwidth = 80;
219}
220
221static size_t
222entrywidth(const struct entry *ent)
223{
224	size_t w;
225	char buf[32];
226
227	w = utflen(ent->name);
228	if (iflag) {
229		snprintf(buf, sizeof(buf), "%lu ", (unsigned long)ent->ino);
230		w += strlen(buf);
231	}
232	w += strlen(indicator(ent->mode));
233	return w;
234}
235
236static void
237printcols(const struct entry *ents, size_t n)
238{
239	int i, r, c, ncols, nrows, total_width;
240	int *colwidths;
241	int maxcols;
242
243	if (n == 0)
244		return;
245
246	gettermwidth();
247
248	colwidths = ecalloc(n, sizeof(*colwidths));
249
250	maxcols = termwidth / 2;
251	if (maxcols > (int)n)
252		maxcols = n;
253
254	for (ncols = maxcols; ncols > 1; ncols--) {
255		nrows = (n + ncols - 1) / ncols;
256		total_width = 0;
257
258		for (c = 0; c < ncols; c++) {
259			int maxw = 0;
260			for (r = 0; r < nrows; r++) {
261				int idx = c * nrows + r;
262				if (idx < (int)n) {
263					int w = entrywidth(&ents[idx]);
264					if (w > maxw)
265						maxw = w;
266				}
267			}
268			colwidths[c] = maxw;
269			total_width += maxw;
270		}
271		total_width += 2 * (ncols - 1);
272
273		if (total_width < termwidth)
274			break;
275	}
276
277	if (ncols <= 1) {
278		for (i = 0; i < (int)n; i++) {
279			output(&ents[i]);
280		}
281		free(colwidths);
282		return;
283	}
284
285	nrows = (n + ncols - 1) / ncols;
286	for (r = 0; r < nrows; r++) {
287		for (c = 0; c < ncols; c++) {
288			int idx = c * nrows + r;
289			if (idx < (int)n) {
290				int w = entrywidth(&ents[idx]);
291				if (iflag)
292					printf("%lu ", (unsigned long)ents[idx].ino);
293				printname_colored(ents[idx].name, ents[idx].mode);
294				fputs(indicator(ents[idx].mode), stdout);
295
296				if (c < ncols - 1 && (c + 1) * nrows + r < (int)n) {
297					int pad = colwidths[c] - w + 2;
298					while (pad-- > 0)
299						putchar(' ');
300				}
301			}
302		}
303		putchar('\n');
304	}
305
306	free(colwidths);
307}
308
309static void
310output(const struct entry *ent)
311{
312	struct group *gr;
313	struct passwd *pw;
314	struct tm *tm;
315	ssize_t len;
316	char *fmt, buf[BUFSIZ], pwname[_SC_LOGIN_NAME_MAX],
317	     grname[_SC_LOGIN_NAME_MAX], mode[] = "----------";
318
319	if (iflag)
320		printf("%lu ", (unsigned long)ent->ino);
321	if (!lflag) {
322		printname_colored(ent->name, ent->mode);
323		puts(indicator(ent->mode));
324		return;
325	}
326	if (S_ISREG(ent->mode))
327		mode[0] = '-';
328	else if (S_ISBLK(ent->mode))
329		mode[0] = 'b';
330	else if (S_ISCHR(ent->mode))
331		mode[0] = 'c';
332	else if (S_ISDIR(ent->mode))
333		mode[0] = 'd';
334	else if (S_ISFIFO(ent->mode))
335		mode[0] = 'p';
336	else if (S_ISLNK(ent->mode))
337		mode[0] = 'l';
338	else if (S_ISSOCK(ent->mode))
339		mode[0] = 's';
340	else
341		mode[0] = '?';
342
343	if (ent->mode & S_IRUSR) mode[1] = 'r';
344	if (ent->mode & S_IWUSR) mode[2] = 'w';
345	if (ent->mode & S_IXUSR) mode[3] = 'x';
346	if (ent->mode & S_IRGRP) mode[4] = 'r';
347	if (ent->mode & S_IWGRP) mode[5] = 'w';
348	if (ent->mode & S_IXGRP) mode[6] = 'x';
349	if (ent->mode & S_IROTH) mode[7] = 'r';
350	if (ent->mode & S_IWOTH) mode[8] = 'w';
351	if (ent->mode & S_IXOTH) mode[9] = 'x';
352
353	if (ent->mode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
354	if (ent->mode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
355	if (ent->mode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
356
357	if (!nflag && (pw = getpwuid(ent->uid)))
358		snprintf(pwname, sizeof(pwname), "%s", pw->pw_name);
359	else
360		snprintf(pwname, sizeof(pwname), "%d", ent->uid);
361
362	if (!nflag && (gr = getgrgid(ent->gid)))
363		snprintf(grname, sizeof(grname), "%s", gr->gr_name);
364	else
365		snprintf(grname, sizeof(grname), "%d", ent->gid);
366
367	if (time(NULL) > ent->t.tv_sec + (180 * 24 * 60 * 60)) /* 6 months ago? */
368		fmt = "%b %d  %Y";
369	else
370		fmt = "%b %d %H:%M";
371
372	if ((tm = localtime(&ent->t.tv_sec)))
373		strftime(buf, sizeof(buf), fmt, tm);
374	else
375		snprintf(buf, sizeof(buf), "%lld", (long long)(ent->t.tv_sec));
376	printf("%s %4ld ", mode, (long)ent->nlink);
377	if (!gflag)
378		printf("%-8.8s ", pwname);
379	if (!oflag)
380		printf("%-8.8s ", grname);
381
382	if (S_ISBLK(ent->mode) || S_ISCHR(ent->mode))
383		printf("%4u, %4u ", major(ent->rdev), minor(ent->rdev));
384	else if (hflag)
385		printf("%10s ", humansize(ent->size));
386	else
387		printf("%10lu ", (unsigned long)ent->size);
388	printf("%s ", buf);
389	printname_colored(ent->name, ent->mode);
390	fputs(indicator(ent->mode), stdout);
391	if (S_ISLNK(ent->mode)) {
392		if ((len = readlink(ent->name, buf, sizeof(buf) - 1)) < 0)
393			eprintf("readlink %s:", ent->name);
394		buf[len] = '\0';
395		printf(" -> ");
396		printname_colored(buf, ent->tmode);
397		fputs(indicator(ent->tmode), stdout);
398	}
399	putchar('\n');
400}
401
402static int
403entcmp(const void *va, const void *vb)
404{
405	int cmp = 0;
406	const struct entry *a = va, *b = vb;
407
408	switch (sort) {
409	// ?man -S: sort by file size
410	case 'S':
411		cmp = b->size - a->size;
412		break;
413	// ?man -t: sort by modification time
414	case 't':
415		if (!(cmp = b->t.tv_sec - a->t.tv_sec))
416			cmp = b->t.tv_nsec - a->t.tv_nsec;
417		break;
418	}
419
420	if (!cmp)
421		cmp = strcmp(a->name, b->name);
422
423	return rflag ? 0 - cmp : cmp;
424}
425
426static void
427lsdir(const char *path, const struct entry *dir)
428{
429	DIR *dp;
430	struct entry *ent, *ents = NULL;
431	struct dirent *d;
432	size_t i, n = 0;
433	char prefix[PATH_MAX];
434
435	if (!(dp = opendir(dir->name))) {
436		ret = 1;
437		weprintf("opendir %s%s:", path, dir->name);
438		return;
439	}
440	if (chdir(dir->name) < 0)
441		eprintf("chdir %s:", dir->name);
442
443	while ((d = readdir(dp))) {
444		if (d->d_name[0] == '.' && !aflag && !Aflag)
445			continue;
446		else if (Aflag)
447			if (strcmp(d->d_name, ".") == 0 ||
448			    strcmp(d->d_name, "..") == 0)
449				continue;
450
451		ents = ereallocarray(ents, ++n, sizeof(*ents));
452		mkent(&ents[n - 1], estrdup(d->d_name), Fflag || iflag ||
453		    lflag || pflag || Rflag || sort || should_color(), Lflag);
454	}
455
456	closedir(dp);
457
458	if (!Uflag)
459		qsort(ents, n, sizeof(*ents), entcmp);
460
461	if (path[0] || showdirs) {
462		fputs(path, stdout);
463		printname(dir->name);
464		puts(":");
465	}
466	if (!lflag && Cflag) {
467		printcols(ents, n);
468	} else {
469		for (i = 0; i < n; i++)
470			output(&ents[i]);
471	}
472
473	if (Rflag) {
474		if (snprintf(prefix, PATH_MAX, "%s%s/", path, dir->name) >=
475		    PATH_MAX)
476			eprintf("path too long: %s%s\n", path, dir->name);
477
478		for (i = 0; i < n; i++) {
479			ent = &ents[i];
480			if (strcmp(ent->name, ".") == 0 ||
481			    strcmp(ent->name, "..") == 0)
482				continue;
483			if (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Lflag)
484				continue;
485
486			ls(prefix, ent, 1);
487		}
488	}
489
490	for (i = 0; i < n; ++i)
491		free(ents[i].name);
492	free(ents);
493}
494
495static int
496visit(const struct entry *ent)
497{
498	dev_t dev;
499	ino_t ino;
500	int i;
501
502	dev = ent->dev;
503	ino = S_ISLNK(ent->mode) ? ent->tino : ent->ino;
504
505	for (i = 0; i < PATH_MAX && tree[i].ino; ++i) {
506		if (ino == tree[i].ino && dev == tree[i].dev)
507			return -1;
508	}
509
510	tree[i].ino = ino;
511	tree[i].dev = dev;
512
513	return i;
514}
515
516static void
517ls(const char *path, const struct entry *ent, int listdir)
518{
519	int treeind;
520	char cwd[PATH_MAX];
521
522	if (!listdir) {
523		output(ent);
524	} else if (S_ISDIR(ent->mode) ||
525	    (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode))) {
526		if ((treeind = visit(ent)) < 0) {
527			ret = 1;
528			weprintf("%s%s: Already visited\n", path, ent->name);
529			return;
530		}
531
532		if (!getcwd(cwd, PATH_MAX))
533			eprintf("getcwd:");
534
535		if (first)
536			first = 0;
537		else
538			putchar('\n');
539
540		lsdir(path, ent);
541		tree[treeind].ino = 0;
542
543		if (chdir(cwd) < 0)
544			eprintf("chdir %s:", cwd);
545	}
546}
547
548static void
549usage(void)
550{
551	eprintf("usage: %s [-1ACacdFfGghiLlnopqRrtUu] [--color[=always|never|auto]] [file ...]\n", argv0);
552}
553
554// ?man ls: list directory contents
555// ?man arguments: [file ...
556// ?man list information about files and directories
557int
558main(int argc, char *argv[])
559{
560	struct entry ent, *dents, *fents;
561	size_t i, ds, fs;
562#if FEATURE_LS_COLOR
563	char *val;
564#endif
565
566	if (isatty(STDOUT_FILENO))
567		Cflag = 1;
568	else
569		one_flag = 1;
570
571#if FEATURE_LS_COLOR
572	if ((val = getenv("CLICOLOR_FORCE"))) {
573		if (*val && strcmp(val, "0") != 0)
574			color_mode = COLOR_ALWAYS;
575		else
576			color_mode = COLOR_NEVER;
577	} else if ((val = getenv("CLICOLOR"))) {
578		if (*val && strcmp(val, "0") != 0)
579			color_mode = COLOR_AUTO;
580		else
581			color_mode = COLOR_NEVER;
582	}
583#endif
584
585	tree = ereallocarray(NULL, PATH_MAX, sizeof(*tree));
586
587	ARGBEGIN {
588	// ?man -1: list one file per line
589	case '1':
590		one_flag = 1;
591		Cflag = 0;
592		lflag = 0;
593		break;
594	// ?man -A: list all entries except dot and dot dot
595	case 'A':
596		Aflag = 1;
597		break;
598	// ?man -a: list all entries including those starting with a dot
599	case 'a':
600		aflag = 1;
601		break;
602	// ?man -c: sort by ctime or use ctime for long listing
603	case 'c':
604		cflag = 1;
605		uflag = 0;
606		break;
607	// ?man -C: list entries in columns sorted vertically
608	case 'C':
609		Cflag = 1;
610		one_flag = 0;
611		lflag = 0;
612		break;
613	// ?man -d: list directory entries instead of their contents
614	case 'd':
615		dflag = 1;
616		break;
617	// ?man -f: do not sort and enable a and U
618	case 'f':
619		aflag = 1;
620		fflag = 1;
621		Uflag = 1;
622		break;
623	// ?man -F: append type indicators
624	case 'F':
625		Fflag = 1;
626		break;
627#if FEATURE_LS_COLOR
628	// ?man -G: enable colored output
629	case 'G':
630		color_mode = COLOR_AUTO;
631		break;
632#endif
633	// ?man -g: list in long format without owner name
634	case 'g':
635		gflag = 1;
636		lflag = 1;
637		Cflag = 0;
638		one_flag = 0;
639		break;
640	// ?man -H: follow symlinks on the command line
641	case 'H':
642		Hflag = 1;
643		break;
644	// ?man -h: print human readable sizes
645	case 'h':
646		hflag = 1;
647		break;
648	// ?man -i: print inode number of each file
649	case 'i':
650		iflag = 1;
651		break;
652	// ?man -L: follow all symlinks
653	case 'L':
654		Lflag = 1;
655		break;
656	// ?man -l: use a long listing format
657	case 'l':
658		lflag = 1;
659		Cflag = 0;
660		one_flag = 0;
661		break;
662	// ?man -n: list numeric uids and gids
663	case 'n':
664		lflag = 1;
665		nflag = 1;
666		Cflag = 0;
667		one_flag = 0;
668		break;
669	// ?man -o: list in long format without group name
670	case 'o':
671		oflag = 1;
672		lflag = 1;
673		Cflag = 0;
674		one_flag = 0;
675		break;
676	// ?man -p: append slash indicator to directories
677	case 'p':
678		pflag = 1;
679		break;
680	// ?man -q: print non printable characters as question marks
681	case 'q':
682		qflag = 1;
683		break;
684	// ?man -R: list subdirectories recursively
685	case 'R':
686		Rflag = 1;
687		break;
688	// ?man -r: reverse sort order
689	case 'r':
690		rflag = 1;
691		break;
692	// ?man -S: sort by file size
693	case 'S':
694		sort = 'S';
695		break;
696	// ?man -t: sort by modification time
697	case 't':
698		sort = 't';
699		break;
700	// ?man -U: do not sort
701	case 'U':
702		Uflag = 1;
703		break;
704	// ?man -u: sort by atime or use atime for long listing
705	case 'u':
706		uflag = 1;
707		cflag = 0;
708		break;
709	// ?man --: specify - option
710	case '-':
711#if FEATURE_LS_COLOR
712		// ?man --color [when]: control coloring
713		if (strncmp(argv[0], "-color", 6) == 0) {
714			char *val = NULL;
715			if (argv[0][6] == '=') {
716				val = &argv[0][7];
717			} else if (argv[0][6] == '\0') {
718				val = "always";
719			}
720			if (val) {
721				if (strcmp(val, "always") == 0)
722					color_mode = COLOR_ALWAYS;
723				else if (strcmp(val, "never") == 0)
724					color_mode = COLOR_NEVER;
725				else if (strcmp(val, "auto") == 0)
726					color_mode = COLOR_AUTO;
727				else {
728					fprintf(stderr, "ls: invalid --color value: %s\n", val);
729					usage();
730				}
731			}
732			brk_ = 1;
733		} else {
734			usage();
735		}
736#else
737		usage();
738#endif
739		break;
740	default:
741		usage();
742	} ARGEND
743
744	switch (argc) {
745	case 0:
746		*--argv = ".", ++argc;
747		/* fallthrough */
748	case 1:
749		mkent(&ent, argv[0], 1, Hflag || Lflag);
750		ls("", &ent, (!dflag && S_ISDIR(ent.mode)) ||
751		    (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
752		     !(dflag || Fflag || lflag)));
753
754		break;
755	default:
756		for (i = ds = fs = 0, fents = dents = NULL; i < (size_t)argc; ++i) {
757			mkent(&ent, argv[i], 1, Hflag || Lflag);
758
759			if ((!dflag && S_ISDIR(ent.mode)) ||
760			    (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
761			     !(dflag || Fflag || lflag))) {
762				dents = ereallocarray(dents, ++ds, sizeof(*dents));
763				memcpy(&dents[ds - 1], &ent, sizeof(ent));
764			} else {
765				fents = ereallocarray(fents, ++fs, sizeof(*fents));
766				memcpy(&fents[fs - 1], &ent, sizeof(ent));
767			}
768		}
769
770		showdirs = ds > 1 || (ds && fs);
771
772		qsort(fents, fs, sizeof(ent), entcmp);
773		qsort(dents, ds, sizeof(ent), entcmp);
774
775		if (!lflag && Cflag && fs > 0) {
776			printcols(fents, fs);
777		} else {
778			for (i = 0; i < fs; ++i)
779				ls("", &fents[i], 0);
780		}
781		free(fents);
782		if (fs && ds)
783			putchar('\n');
784		for (i = 0; i < ds; ++i)
785			ls("", &dents[i], 1);
786		free(dents);
787	}
788
789	return (fshut(stdout, "<stdout>") | ret);
790}