sbase

suckless unix tools
git clone git://git.suckless.org/sbase
Log | Files | Refs | README | LICENSE

ls.c (9566B)


      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[PATH_MAX];
     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 	ARGBEGIN {
    375 	case '1':
    376 		/* force output to 1 entry per line */
    377 		qflag = 1;
    378 		break;
    379 	case 'A':
    380 		Aflag = 1;
    381 		break;
    382 	case 'a':
    383 		aflag = 1;
    384 		break;
    385 	case 'c':
    386 		cflag = 1;
    387 		uflag = 0;
    388 		break;
    389 	case 'd':
    390 		dflag = 1;
    391 		break;
    392 	case 'f':
    393 		aflag = 1;
    394 		fflag = 1;
    395 		Uflag = 1;
    396 		break;
    397 	case 'F':
    398 		Fflag = 1;
    399 		break;
    400 	case 'H':
    401 		Hflag = 1;
    402 		break;
    403 	case 'h':
    404 		hflag = 1;
    405 		break;
    406 	case 'i':
    407 		iflag = 1;
    408 		break;
    409 	case 'L':
    410 		Lflag = 1;
    411 		break;
    412 	case 'l':
    413 		lflag = 1;
    414 		break;
    415 	case 'n':
    416 		lflag = 1;
    417 		nflag = 1;
    418 		break;
    419 	case 'p':
    420 		pflag = 1;
    421 		break;
    422 	case 'q':
    423 		qflag = 1;
    424 		break;
    425 	case 'R':
    426 		Rflag = 1;
    427 		break;
    428 	case 'r':
    429 		rflag = 1;
    430 		break;
    431 	case 'S':
    432 		sort = 'S';
    433 		break;
    434 	case 't':
    435 		sort = 't';
    436 		break;
    437 	case 'U':
    438 		Uflag = 1;
    439 		break;
    440 	case 'u':
    441 		uflag = 1;
    442 		cflag = 0;
    443 		break;
    444 	default:
    445 		usage();
    446 	} ARGEND
    447 
    448 	switch (argc) {
    449 	case 0: /* fallthrough */
    450 		*--argv = ".", ++argc;
    451 	case 1:
    452 		mkent(&ent, argv[0], 1, Hflag || Lflag);
    453 		ls("", &ent, (!dflag && S_ISDIR(ent.mode)) ||
    454 		    (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
    455 		     !(dflag || Fflag || lflag)));
    456 
    457 		break;
    458 	default:
    459 		for (i = ds = fs = 0, fents = dents = NULL; i < argc; ++i) {
    460 			mkent(&ent, argv[i], 1, Hflag || Lflag);
    461 
    462 			if ((!dflag && S_ISDIR(ent.mode)) ||
    463 			    (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
    464 			     !(dflag || Fflag || lflag))) {
    465 				dents = ereallocarray(dents, ++ds, sizeof(*dents));
    466 				memcpy(&dents[ds - 1], &ent, sizeof(ent));
    467 			} else {
    468 				fents = ereallocarray(fents, ++fs, sizeof(*fents));
    469 				memcpy(&fents[fs - 1], &ent, sizeof(ent));
    470 			}
    471 		}
    472 
    473 		showdirs = ds > 1 || (ds && fs);
    474 
    475 		qsort(fents, fs, sizeof(ent), entcmp);
    476 		qsort(dents, ds, sizeof(ent), entcmp);
    477 
    478 		for (i = 0; i < fs; ++i)
    479 			ls("", &fents[i], 0);
    480 		free(fents);
    481 		if (fs && ds)
    482 			putchar('\n');
    483 		for (i = 0; i < ds; ++i)
    484 			ls("", &dents[i], 1);
    485 		free(dents);
    486 	}
    487 
    488 	return (fshut(stdout, "<stdout>") | ret);
    489 }