1/*
2 * stat.c - stat builtin interface to system call
3 *
4 * This file is part of zsh, the Z shell.
5 *
6 * Copyright (c) 1996-1997 Peter Stephenson
7 * All rights reserved.
8 *
9 * Permission is hereby granted, without written agreement and without
10 * license or royalty fees, to use, copy, modify, and distribute this
11 * software and to distribute modified versions of this software for any
12 * purpose, provided that the above copyright notice and the following
13 * two paragraphs appear in all copies of this software.
14 *
15 * In no event shall Peter Stephenson or the Zsh Development Group be liable
16 * to any party for direct, indirect, special, incidental, or consequential
17 * damages arising out of the use of this software and its documentation,
18 * even if Peter Stephenson and the Zsh Development Group have been advised of
19 * the possibility of such damage.
20 *
21 * Peter Stephenson and the Zsh Development Group specifically disclaim any
22 * warranties, including, but not limited to, the implied warranties of
23 * merchantability and fitness for a particular purpose.  The software
24 * provided hereunder is on an "as is" basis, and Peter Stephenson and the
25 * Zsh Development Group have no obligation to provide maintenance,
26 * support, updates, enhancements, or modifications.
27 *
28 */
29
30#include "stat.mdh"
31#include "stat.pro"
32
33enum statnum { ST_DEV, ST_INO, ST_MODE, ST_NLINK, ST_UID, ST_GID,
34		   ST_RDEV, ST_SIZE, ST_ATIM, ST_MTIM, ST_CTIM,
35		   ST_BLKSIZE, ST_BLOCKS, ST_READLINK, ST_COUNT };
36enum statflags { STF_NAME = 1,  STF_FILE = 2, STF_STRING = 4, STF_RAW = 8,
37		     STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64,
38		     STF_HASH = 128, STF_OCTAL = 256 };
39static char *statelts[] = { "device", "inode", "mode", "nlink",
40				"uid", "gid", "rdev", "size", "atime",
41				"mtime", "ctime", "blksize", "blocks",
42				"link", NULL };
43#define HNAMEKEY "name"
44
45/**/
46static void
47statmodeprint(mode_t mode, char *outbuf, int flags)
48{
49    if (flags & STF_RAW) {
50	sprintf(outbuf, (flags & STF_OCTAL) ? "0%lo" : "%lu",
51		(unsigned long)mode);
52	if (flags & STF_STRING)
53	    strcat(outbuf, " (");
54    }
55    if (flags & STF_STRING) {
56	static const char *modes = "?rwxrwxrwx";
57#ifdef __CYGWIN__
58	static mode_t mflags[9] = { 0 };
59#else
60	static const mode_t mflags[9] = {
61	    S_IRUSR, S_IWUSR, S_IXUSR,
62	    S_IRGRP, S_IWGRP, S_IXGRP,
63	    S_IROTH, S_IWOTH, S_IXOTH
64	};
65#endif
66	const mode_t *mfp = mflags;
67	char pm[11];
68	int i;
69
70#ifdef __CYGWIN__
71	if (mflags[0] == 0) {
72	    mflags[0] = S_IRUSR;
73	    mflags[1] = S_IWUSR;
74	    mflags[2] = S_IXUSR;
75	    mflags[3] = S_IRGRP;
76	    mflags[4] = S_IWGRP;
77	    mflags[5] = S_IXGRP;
78	    mflags[6] = S_IROTH;
79	    mflags[7] = S_IWOTH;
80	    mflags[8] = S_IXOTH;
81	}
82#endif
83
84	if (S_ISBLK(mode))
85	    *pm = 'b';
86	else if (S_ISCHR(mode))
87	    *pm = 'c';
88	else if (S_ISDIR(mode))
89	    *pm = 'd';
90	else if (S_ISDOOR(mode))
91	    *pm = 'D';
92	else if (S_ISFIFO(mode))
93	    *pm = 'p';
94	else if (S_ISLNK(mode))
95	    *pm = 'l';
96	else if (S_ISMPC(mode))
97	    *pm = 'm';
98	else if (S_ISNWK(mode))
99	    *pm = 'n';
100	else if (S_ISOFD(mode))
101	    *pm = 'M';
102	else if (S_ISOFL(mode))
103	    *pm = 'M';
104	else if (S_ISREG(mode))
105	    *pm = '-';
106	else if (S_ISSOCK(mode))
107	    *pm = 's';
108	else
109	    *pm = '?';
110
111	for (i = 1; i <= 9; i++)
112	    pm[i] = (mode & *mfp++) ? modes[i] : '-';
113	pm[10] = '\0';
114
115	if (mode & S_ISUID)
116	    pm[3] = (mode & S_IXUSR) ? 's' : 'S';
117	if (mode & S_ISGID)
118	    pm[6] = (mode & S_IXGRP) ? 's' : 'S';
119	if (mode & S_ISVTX)
120	    pm[9] = (mode & S_IXOTH) ? 't' : 'T';
121
122	pm[10] = 0;
123	strcat(outbuf, pm);
124	if (flags & STF_RAW)
125	    strcat(outbuf, ")");
126    }
127}
128
129
130/**/
131static void
132statuidprint(uid_t uid, char *outbuf, int flags)
133{
134    if (flags & STF_RAW) {
135	sprintf(outbuf, "%lu", (unsigned long)uid);
136	if (flags & STF_STRING)
137	    strcat(outbuf, " (");
138    }
139    if (flags & STF_STRING) {
140#ifdef HAVE_GETPWUID
141	struct passwd *pwd;
142	pwd = getpwuid(uid);
143	if (pwd)
144	    strcat(outbuf, pwd->pw_name);
145	else
146#endif /* !HAVE_GETPWUID */
147	{
148	    char *optr;
149	    for (optr = outbuf; *optr; optr++)
150		;
151	    sprintf(optr, "%lu", (unsigned long)uid);
152	}
153	if (flags & STF_RAW)
154	    strcat(outbuf, ")");
155    }
156}
157
158
159/**/
160static void
161statgidprint(gid_t gid, char *outbuf, int flags)
162{
163    if (flags & STF_RAW) {
164	sprintf(outbuf, "%lu", (unsigned long)gid);
165	if (flags & STF_STRING)
166	    strcat(outbuf, " (");
167    }
168    if (flags & STF_STRING) {
169#ifdef USE_GETGRGID
170	struct group *gr;
171	gr = getgrgid(gid);
172	if (gr)
173	    strcat(outbuf, gr->gr_name);
174	else
175#endif /* !USE_GETGRGID */
176	{
177	    char *optr;
178	    for (optr = outbuf; *optr; optr++)
179		;
180	    sprintf(optr, "%lu", (unsigned long)gid);
181	}
182	if (flags & STF_RAW)
183	    strcat(outbuf, ")");
184    }
185}
186
187static char *timefmt;
188
189/**/
190static void
191stattimeprint(time_t tim, char *outbuf, int flags)
192{
193    if (flags & STF_RAW) {
194	sprintf(outbuf, "%ld", (unsigned long)tim);
195	if (flags & STF_STRING)
196	    strcat(outbuf, " (");
197    }
198    if (flags & STF_STRING) {
199	char *oend = outbuf + strlen(outbuf);
200	ztrftime(oend, 40, timefmt, (flags & STF_GMT) ? gmtime(&tim) :
201		 localtime(&tim));
202	if (flags & STF_RAW)
203	    strcat(oend, ")");
204    }
205}
206
207
208/**/
209static void
210statulprint(unsigned long num, char *outbuf)
211{
212    sprintf(outbuf, "%lu", num);
213}
214
215
216/**/
217static void
218statlinkprint(struct stat *sbuf, char *outbuf, char *fname)
219{
220    int num;
221
222    /* fname is NULL if we are looking at an fd */
223    if (fname && S_ISLNK(sbuf->st_mode) &&
224 	(num = readlink(fname, outbuf, PATH_MAX)) > 0) {
225	/* readlink doesn't terminate the buffer itself */
226	outbuf[num] = '\0';
227    }
228}
229
230
231/**/
232static void
233statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags)
234{
235    char *optr = outbuf;
236
237    if (flags & STF_NAME) {
238	sprintf(outbuf, (flags & (STF_PICK|STF_ARRAY)) ?
239		"%s " : "%-8s", statelts[iwhich]);
240	optr += strlen(outbuf);
241    }
242    *optr = '\0';
243
244    /* cast values to unsigned long as safest bet */
245    switch (iwhich) {
246    case ST_DEV:
247	statulprint((unsigned long)sbuf->st_dev, optr);
248	break;
249
250    case ST_INO:
251#ifdef INO_T_IS_64_BIT
252	convbase(optr, sbuf->st_ino, 0);
253#else
254	DPUTS(sizeof(sbuf->st_ino) > sizeof(unsigned long),
255	      "Shell compiled with wrong ino_t size");
256	statulprint((unsigned long)sbuf->st_ino, optr);
257#endif
258	break;
259
260    case ST_MODE:
261	statmodeprint(sbuf->st_mode, optr, flags);
262	break;
263
264    case ST_NLINK:
265	statulprint((unsigned long)sbuf->st_nlink, optr);
266	break;
267
268    case ST_UID:
269	statuidprint(sbuf->st_uid, optr, flags);
270	break;
271
272    case ST_GID:
273	statgidprint(sbuf->st_gid, optr, flags);
274	break;
275
276    case ST_RDEV:
277	statulprint((unsigned long)sbuf->st_rdev, optr);
278	break;
279
280    case ST_SIZE:
281#ifdef OFF_T_IS_64_BIT
282	convbase(optr, sbuf->st_size, 0);
283#else
284	DPUTS(sizeof(sbuf->st_size) > sizeof(unsigned long),
285	      "Shell compiled with wrong off_t size");
286	statulprint((unsigned long)sbuf->st_size, optr);
287#endif
288	break;
289
290    case ST_ATIM:
291	stattimeprint(sbuf->st_atime, optr, flags);
292	break;
293
294    case ST_MTIM:
295	stattimeprint(sbuf->st_mtime, optr, flags);
296	break;
297
298    case ST_CTIM:
299	stattimeprint(sbuf->st_ctime, optr, flags);
300	break;
301
302    case ST_BLKSIZE:
303	statulprint((unsigned long)sbuf->st_blksize, optr);
304	break;
305
306    case ST_BLOCKS:
307	statulprint((unsigned long)sbuf->st_blocks, optr);
308	break;
309
310    case ST_READLINK:
311	statlinkprint(sbuf, optr, fname);
312	break;
313
314    case ST_COUNT:			/* keep some compilers happy */
315	break;
316    }
317}
318
319
320/*
321 *
322 * Options:
323 *  -f fd:   stat fd instead of file
324 *  -g:   use GMT rather than local time for time strings (forces -s on).
325 *  -n:   always print file name of file being statted
326 *  -N:   never print file name
327 *  -l:   list stat types
328 *  -L:   do lstat (else links are implicitly dereferenced by stat)
329 *  -t:   always print name of stat type
330 *  -T:   never print name of stat type
331 *  -r:   print raw alongside string data
332 *  -s:   string, print mode, times, uid, gid as appropriate strings:
333 *        harmless but unnecessary when combined with -r.
334 *  -A array:  assign results to given array, one stat result per element.
335 *        File names and type names are only added if explicitly requested:
336 *        file names are returned as a separate array element, type names as
337 *        prefix to element.  Note the formatting deliberately contains
338 *        fewer frills when -A is used.
339 *  -H hash:  as for -A array, but returns a hash with the keys being those
340 *        from stat -l
341 *  -F fmt: specify a $TIME-like format for printing times; the default
342 *        is the (CTIME-like) "%a %b %e %k:%M:%S %Z %Y".  This option implies
343 *        -s as it is not useful for numerical times.
344 *
345 *  +type selects just element type of stat buffer (-l gives list):
346 *        type can be shortened to unambiguous string.  only one type is
347 *        allowed.  The extra type, +link, reads the target of a symbolic
348 *        link; it is empty if the stat was not an lstat or if
349 *        a file descriptor was stat'd, if the stat'd file is
350 *        not a symbolic link, or if symbolic links are not
351 *        supported.  If +link is explicitly requested, the -L (lstat)
352 *        option is set automatically.
353 */
354/**/
355static int
356bin_stat(char *name, char **args, Options ops, UNUSED(int func))
357{
358    char **aptr, *arrnam = NULL, **array = NULL, **arrptr = NULL;
359    char *hashnam = NULL, **hash = NULL, **hashptr = NULL;
360    int len, iwhich = -1, ret = 0, flags = 0, arrsize = 0, fd = 0;
361    struct stat statbuf;
362    int found = 0, nargs;
363
364    timefmt = "%a %b %e %k:%M:%S %Z %Y";
365
366    for (; *args && (**args == '+' || **args == '-'); args++) {
367	char *arg = *args+1;
368	if (!*arg || *arg == '-' || *arg == '+') {
369	    args++;
370	    break;
371	}
372
373	if (**args == '+') {
374	    if (found)
375		break;
376	    len = strlen(arg);
377	    for (aptr = statelts; *aptr; aptr++)
378		if (!strncmp(*aptr, arg, len)) {
379		    found++;
380		    iwhich = aptr - statelts;
381		}
382	    if (found > 1) {
383		zwarnnam(name, "%s: ambiguous stat element", arg);
384		return 1;
385	    } else if (found == 0) {
386		zwarnnam(name, "%s: no such stat element", arg);
387		return 1;
388	    }
389	    /* if name of link requested, turn on lstat */
390	    if (iwhich == ST_READLINK)
391		ops->ind['L'] = 1;
392	    flags |= STF_PICK;
393	} else {
394	    for (; *arg; arg++) {
395		if (strchr("glLnNorstT", *arg))
396		    ops->ind[STOUC(*arg)] = 1;
397		else if (*arg == 'A') {
398		    if (arg[1]) {
399			arrnam = arg+1;
400		    } else if (!(arrnam = *++args)) {
401			zwarnnam(name, "missing parameter name");
402			return 1;
403		    }
404		    flags |= STF_ARRAY;
405		    break;
406		} else if (*arg == 'H') {
407		    if (arg[1]) {
408			hashnam = arg+1;
409		    } else if (!(hashnam = *++args)) {
410			zwarnnam(name, "missing parameter name");
411			return 1;
412		    }
413		    flags |= STF_HASH;
414		    break;
415		} else if (*arg == 'f') {
416		    char *sfd;
417		    ops->ind['f'] = 1;
418		    if (arg[1]) {
419			sfd = arg+1;
420		    } else if (!(sfd = *++args)) {
421			zwarnnam(name, "missing file descriptor");
422			return 1;
423		    }
424		    fd = zstrtol(sfd, &sfd, 10);
425		    if (*sfd) {
426			zwarnnam(name, "bad file descriptor");
427			return 1;
428		    }
429		    break;
430		} else if (*arg == 'F') {
431		    if (arg[1]) {
432			timefmt = arg+1;
433		    } else if (!(timefmt = *++args)) {
434			zwarnnam(name, "missing time format");
435			return 1;
436		    }
437		    /* force string format in order to use time format */
438		    ops->ind['s'] = 1;
439		    break;
440		} else {
441		    zwarnnam(name, "bad option: -%c", *arg);
442		    return 1;
443		}
444	    }
445	}
446    }
447
448    if ((flags & STF_ARRAY) && (flags & STF_HASH)) {
449    	/* We don't implement setting multiple variables at once */
450	zwarnnam(name, "both array and hash requested");
451	return 1;
452	/* Alternate method would be to make -H blank arrnam etc etc *
453	 * and so get 'silent loss' of earlier choice, which would   *
454	 * be similar to stat -A foo -A bar filename                 */
455    }
456
457    if (OPT_ISSET(ops,'l')) {
458	/* list types and return:  can also list to array */
459	if (arrnam) {
460	    arrptr = array = (char **)zalloc((ST_COUNT+1)*sizeof(char *));
461	    array[ST_COUNT] = NULL;
462	}
463	for (aptr = statelts; *aptr; aptr++) {
464	    if (arrnam) {
465		*arrptr++ = ztrdup(*aptr);
466	    } else {
467		printf("%s", *aptr);
468		if (aptr[1])
469		    putchar(' ');
470	    }
471	}
472	if (arrnam) {
473	    setaparam(arrnam, array);
474	    if (errflag)
475		return 1;
476	} else
477	    putchar('\n');
478	return 0;
479    }
480
481    if (!*args && !OPT_ISSET(ops,'f')) {
482	zwarnnam(name, "no files given");
483	return 1;
484    } else if (*args && OPT_ISSET(ops,'f')) {
485	zwarnnam(name, "no files allowed with -f");
486	return 1;
487    }
488
489    nargs = 0;
490    if (OPT_ISSET(ops,'f'))
491	nargs = 1;
492    else
493	for (aptr = args; *aptr; aptr++)
494	    nargs++;
495
496    if (OPT_ISSET(ops,'g')) {
497	flags |= STF_GMT;
498	ops->ind['s'] = 1;
499    }
500    if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'r'))
501	flags |= STF_STRING;
502    if (OPT_ISSET(ops,'r') || !OPT_ISSET(ops,'s'))
503	flags |= STF_RAW;
504    if (OPT_ISSET(ops,'n'))
505	flags |= STF_FILE;
506    if (OPT_ISSET(ops,'o'))
507	flags |= STF_OCTAL;
508    if (OPT_ISSET(ops,'t'))
509	flags |= STF_NAME;
510
511    if (!(arrnam || hashnam)) {
512	if (nargs > 1)
513	    flags |= STF_FILE;
514	if (!(flags & STF_PICK))
515	    flags |= STF_NAME;
516    }
517
518    if (OPT_ISSET(ops,'N') || OPT_ISSET(ops,'f'))
519	flags &= ~STF_FILE;
520    if (OPT_ISSET(ops,'T') || OPT_ISSET(ops,'H'))
521	flags &= ~STF_NAME;
522
523    if (hashnam) {
524    	if (nargs > 1) {
525	    zwarnnam(name, "only one file allowed with -H");
526	    return 1;
527	}
528	arrsize = (flags & STF_PICK) ? 1 : ST_COUNT;
529	if (flags & STF_FILE)
530	    arrsize++;
531	hashptr = hash = (char **)zshcalloc((arrsize+1)*2*sizeof(char *));
532    }
533
534    if (arrnam) {
535	arrsize = (flags & STF_PICK) ? 1 : ST_COUNT;
536	if (flags & STF_FILE)
537	    arrsize++;
538	arrsize *= nargs;
539	arrptr = array = (char **)zshcalloc((arrsize+1)*sizeof(char *));
540    }
541
542    for (; OPT_ISSET(ops,'f') || *args; args++) {
543	char outbuf[PATH_MAX + 9]; /* "link   " + link name + NULL */
544	int rval = OPT_ISSET(ops,'f') ? fstat(fd, &statbuf) :
545	    OPT_ISSET(ops,'L') ? lstat(unmeta(*args), &statbuf) :
546	    stat(unmeta(*args), &statbuf);
547	if (rval) {
548	    if (OPT_ISSET(ops,'f'))
549		sprintf(outbuf, "%d", fd);
550	    zwarnnam(name, "%s: %e", OPT_ISSET(ops,'f') ? outbuf : *args,
551		     errno);
552	    ret = 1;
553	    if (OPT_ISSET(ops,'f') || arrnam)
554		break;
555	    else
556		continue;
557	}
558
559	if (flags & STF_FILE) {
560	    if (arrnam)
561		*arrptr++ = ztrdup(*args);
562	    else if (hashnam) {
563	    	*hashptr++ = ztrdup(HNAMEKEY);
564		*hashptr++ = ztrdup(*args);
565	    } else
566		printf("%s%s", *args, (flags & STF_PICK) ? " " : ":\n");
567	}
568	if (iwhich > -1) {
569	    statprint(&statbuf, outbuf, *args, iwhich, flags);
570	    if (arrnam)
571		*arrptr++ = metafy(outbuf, -1, META_DUP);
572	    else if (hashnam) {
573		/* STF_NAME explicitly turned off for ops.ind['H'] above */
574	    	*hashptr++ = ztrdup(statelts[iwhich]);
575		*hashptr++ = metafy(outbuf, -1, META_DUP);
576	    } else
577		printf("%s\n", outbuf);
578	} else {
579	    int i;
580	    for (i = 0; i < ST_COUNT; i++) {
581		statprint(&statbuf, outbuf, *args, i, flags);
582		if (arrnam)
583		    *arrptr++= metafy(outbuf, -1, META_DUP);
584		else if (hashnam) {
585		    /* STF_NAME explicitly turned off for ops.ind['H'] above */
586		    *hashptr++ = ztrdup(statelts[i]);
587		    *hashptr++ = metafy(outbuf, -1, META_DUP);
588		} else
589		    printf("%s\n", outbuf);
590	    }
591	}
592	if (OPT_ISSET(ops,'f'))
593	    break;
594
595	if (!arrnam && !hashnam && args[1] && !(flags & STF_PICK))
596	    putchar('\n');
597    }
598
599    if (arrnam) {
600	if (ret)
601	    freearray(array);
602	else {
603	    setaparam(arrnam, array);
604	    if (errflag)
605		return 1;
606	}
607    }
608
609    if (hashnam) {
610    	if (ret)
611	    freearray(hash);
612	else {
613	    sethparam(hashnam, hash);
614	    if (errflag)
615		return 1;
616	}
617    }
618
619    return ret;
620}
621
622static struct builtin bintab[] = {
623    BUILTIN("stat", 0, bin_stat, 0, -1, 0, NULL, NULL),
624    BUILTIN("zstat", 0, bin_stat, 0, -1, 0, NULL, NULL),
625};
626
627static struct features module_features = {
628    bintab, sizeof(bintab)/sizeof(*bintab),
629    NULL, 0,
630    NULL, 0,
631    NULL, 0,
632    0
633};
634
635/**/
636int
637setup_(UNUSED(Module m))
638{
639    return 0;
640}
641
642/**/
643int
644features_(Module m, char ***features)
645{
646    *features = featuresarray(m, &module_features);
647    return 0;
648}
649
650/**/
651int
652enables_(Module m, int **enables)
653{
654    return handlefeatures(m, &module_features, enables);
655}
656
657/**/
658int
659boot_(Module m)
660{
661    return 0;
662}
663
664/**/
665int
666cleanup_(Module m)
667{
668    return setfeatureenables(m, &module_features, NULL);
669}
670
671/**/
672int
673finish_(UNUSED(Module m))
674{
675    return 0;
676}
677