1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1989-2011 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *               Glenn Fowler <glenn.s.fowler@gmail.com>                *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22  * Glenn Fowler
23  * AT&T Research
24  *
25  * df -- free disk block report
26  */
27 
28 static const char usage[] =
29 "[-?\n@(#)$Id: df (AT&T Research) 2010-08-11 $\n]"
30 USAGE_LICENSE
31 "[+NAME?df - summarize disk free space]"
32 "[+DESCRIPTION?\bdf\b displays the available disk space for the filesystem"
33 "	of each file argument. If no \afile\a arguments are given then all"
34 "	mounted filesystems are displayed.]"
35 
36 "[b:blockbytes?Measure disk usage in 512 byte blocks. This is the default"
37 "	if \bgetconf CONFORMANCE\b is \bstandard\b.]"
38 "[D:define?Define \akey\a with optional \avalue\a. \avalue\a will be expanded"
39 "	when \b%(\b\akey\a\b)\b is specified in \b--format\b. \akey\a may"
40 "	override internal \b--format\b identifiers.]:[key[=value]]]"
41 "[e:decimal-scale|thousands?Scale disk usage to powers of 1000.]"
42 "[f:format?Append to the listing format string. The \bls\b(1), \bpax\b(1) and"
43 "	\bps\b(1) commands also have \b--format\b options in this same style."
44 "	\aformat\a follows \bprintf\b(3) conventions, except that \bsfio\b(3)"
45 "	inline ids are used instead of arguments:"
46 "	%[#-+]][\awidth\a[.\aprecis\a[.\abase\a]]]]]](\aid\a[:\asubformat\a]])\achar\a."
47 "	If \b#\b is specified then the internal width and precision are used."
48 "	If \abase\a is non-zero and \b--posix\b is not on then the field values"
49 "	are wrapped when they exceed the field width. If \achar\a is \bs\b then"
50 "	the string form of the item is listed, otherwise the corresponding"
51 "	numeric form is listed. If \achar\a is \bq\b then the string form of"
52 "	the item is $'...' quoted if it contains space or non-printing"
53 "	characters. If \awidth\a is omitted then the default width"
54 "	is assumed. \asubformat\a overrides the default formatting for \aid\a."
55 "	Supported \aid\as and \asubformat\as are:]:[format]{\fformats\f}"
56 "[g:gigabytes?Measure disk usage in 1024M byte blocks.]"
57 "[h!:header|heading?Display a heading line.]"
58 "[i:inodes?Display inode usage instead of block usage. There is at least one"
59 "	inode for each active file and directory on a filesystem.]"
60 "[k:kilobytes?Measure disk usage in 1024 byte blocks.]"
61 "[K:scale|binary-scale|human-readable?Scale disk usage to powers of 1024."
62 "	This is the default if \bgetconf CONFORMANCE\b is not \bstandard\b.]"
63 "[l:local?List information on local filesystems only, i.e., network"
64 "	mounts are omitted.]"
65 "[m:megabytes?Measure disk usage in 1024K byte blocks.]"
66 "[n:native-block?Measure disk usage in the native filesystem block size."
67 "	This size may vary between filesystems; it is displayed by the"
68 "	\bsize\b format identifier.]"
69 "[O:options?Display the \bmount\b(1) options.]"
70 "[P:portable?Display each filesystem on one line. By default output is"
71 "	folded for readability. Also implies \b--blockbytes\b.]"
72 "[s:sync?Call \bsync\b(2) before querying the filesystems.]"
73 "[F|t:type?Display all filesystems of type \atype\a. Unknown types are"
74 "	listed as \blocal\b. Typical (but not supported on all systems) values"
75 "	are:]:[type]{"
76 "		[+ufs?default UNIX file system]"
77 "		[+ext2?default linux file system]"
78 "		[+xfs?sgi XFS]"
79 "		[+nfs?network file system version 2]"
80 "		[+nfs3?network file system version 3]"
81 "		[+afs3?Andrew file system]"
82 "		[+proc?process file system]"
83 "		[+fat?DOS FAT file system]"
84 "		[+ntfs?nt file system]"
85 "		[+reg?windows/nt registry file system]"
86 "		[+lofs?loopback file system for submounts]"
87 "}"
88 "[v:verbose?Report all filesystem query errors.]"
89 "[q|T?Ignored by this implementation.]"
90 
91 "\n"
92 "\n[ file ... ]\n"
93 "\n"
94 "[+EXAMPLES?The default \b--format\b is"
95 " \"%#..1(filesystem)s %#(type)s %#(blocks)s %#(used)s %#(available)s  %#(capacity)s  %(mounted)s\"]"
96 "[+SEE ALSO?\bgetconf\b(1), \bmount\b(1), \bls\b(1), \bpax\b(1), \bps\b(1),"
97 "	\bmount\b(2)]"
98 ;
99 
100 #include <ast.h>
101 #include <error.h>
102 #include <cdt.h>
103 #include <ls.h>
104 #include <mnt.h>
105 #include <sig.h>
106 #include <sfdisc.h>
107 
108 typedef struct				/* df entry			*/
109 {
110 	Mnt_t*		mnt;		/* mnt info			*/
111 	struct statvfs	vfs;		/* statvfs() info		*/
112 	unsigned long	avail;
113 	unsigned long	tavail;
114 	unsigned long	total;
115 	unsigned long	ttotal;
116 	unsigned long	used;
117 	unsigned long	tused;
118 	unsigned long	itotal;
119 	unsigned long	iavail;
120 	unsigned long	iused;
121 	int		percent;
122 	int		fraction;
123 	int		ipercent;
124 } Df_t;
125 
126 typedef struct				/* sfkeyprintf() keys		*/
127 {
128 	char*		name;		/* key name			*/
129 	char*		heading;	/* key heading			*/
130 	char*		description;	/* key description		*/
131 	short		index;		/* key index			*/
132 	short		width;		/* default width		*/
133 	short		abbrev;		/* abbreviation width		*/
134 	short		disable;	/* macro being expanded		*/
135 	char*		macro;		/* macro definition		*/
136 	Dtlink_t	hashed;		/* hash link			*/
137 } Key_t;
138 
139 #define KEY_environ		(-1)
140 
141 #define KEY_available		1
142 #define KEY_blocks		2
143 #define KEY_capacity		3
144 #define KEY_filesystem		4
145 #define KEY_iavailable		5
146 #define KEY_icapacity		6
147 #define KEY_inodes		7
148 #define KEY_iused		8
149 #define KEY_mounted		9
150 #define KEY_native		10
151 #define KEY_options		11
152 #define KEY_type		12
153 #define KEY_used		13
154 
155 static Key_t	keys[] =
156 {
157 
158 	{
159 		0
160 	},
161 	{
162 		"available",
163 		"Available",
164 		"Unused block count.",
165 		KEY_available,
166 		0,
167 		5
168 	},
169 	{
170 		"blocks",
171 		0,
172 		"Total block count.",
173 		KEY_blocks,
174 		0,
175 		0
176 	},
177 	{
178 		"capacity",
179 		"Capacity",
180 		"Percent of total blocks used.",
181 		KEY_capacity,
182 		4,
183 		3
184 	},
185 	{
186 		"filesystem",
187 		"Filesystem",
188 		"Filesystem special device name.",
189 		KEY_filesystem,
190 		-19,
191 		0
192 	},
193 	{
194 		"iavailable",
195 		"Iavailable",
196 		"Unused inode count.",
197 		KEY_iavailable,
198 		7,
199 		6
200 	},
201 	{
202 		"icapacity",
203 		"Icapacity",
204 		"Percent of total inodes used.",
205 		KEY_icapacity,
206 		4,
207 		4
208 	},
209 	{
210 		"inodes",
211 		"Inodes",
212 		"Total inode count.",
213 		KEY_inodes,
214 		7,
215 		3
216 	},
217 	{
218 		"iused",
219 		"Iused",
220 		"Used inode count.",
221 		KEY_iused,
222 		7,
223 		3
224 	},
225 	{
226 		"mounted",
227 		"Mounted on",
228 		"Mounted on path.",
229 		KEY_mounted,
230 		-19,
231 		5
232 	},
233 	{
234 		"size",
235 		"Size",
236 		"Native block size.",
237 		KEY_native,
238 		4,
239 		3
240 	},
241 	{
242 		"options",
243 		"Options",
244 		"\bmount\b(1) options.",
245 		KEY_options,
246 		-29,
247 		3
248 	},
249 	{
250 		"type",
251 		"Type",
252 		"Filesystem type.",
253 		KEY_type,
254 		10,
255 		3
256 	},
257 	{
258 		"used",
259 		"Used",
260 		"Used block count.",
261 		KEY_used,
262 		0,
263 		3
264 	},
265 
266 };
267 
268 static const char	fmt_def[] = "%#..1(filesystem)s %#(type)s %#(blocks)s %#(used)s %#(available)s  %#(capacity)s  %(mounted)s";
269 static const char	fmt_ino[] = "%#..1(filesystem)s %#(type)s %#(blocks)s %#(available)s %#(capacity)s %#(inodes)s %#(iavailable)s %#(icapacity)s %(mounted)s";
270 static const char	fmt_opt[] = "%#..1(filesystem)s %#(type)s  %#(options)s %(mounted)s";
271 static const char	fmt_std[] = "%#..1(filesystem)s %#(blocks)s %#(used)s %#(available)s %8(capacity)s %(mounted)s";
272 
273 /*
274  * man page and header comments notwithstanding
275  * some systems insist on reporting block counts in 512 units
276  * let me know how you probe for this
277  */
278 
279 #if _SCO_COFF || _SCO_ELF
280 #define F_FRSIZE(v)	(512)
281 #else
282 #if _mem_f_frsize_statvfs
283 #define F_FRSIZE(v)	((v)->f_frsize?(v)->f_frsize:(v)->f_bsize?(v)->f_bsize:1024)
284 #else
285 #define F_FRSIZE(v)	((v)->f_bsize?(v)->f_bsize:1024)
286 #endif
287 #endif
288 
289 #if _mem_f_basetype_statvfs
290 #define F_BASETYPE(v,p)	((v)->f_basetype)
291 #else
292 static char*
basetype(const char * path)293 basetype(const char* path)
294 {
295 	struct stat	st;
296 
297 	return stat(path, &st) ? "ufs" : fmtfs(&st);
298 }
299 #define F_BASETYPE(v,p)	(basetype(p))
300 #endif
301 
302 #define REALITY(n)	((((long)(n))<0)?0:(n))
303 #define UNKNOWN		"local"
304 
305 #if _lib_sync
306 extern void	sync(void);
307 #endif
308 
309 static struct
310 {
311 	int		block;		/* block unit			*/
312 	int		local;		/* local mounts only		*/
313 	int		posix;		/* posix format			*/
314 	int		scale;		/* metric scale power		*/
315 	int		sync;		/* sync() first			*/
316 	int		timeout;	/* status() timed out		*/
317 	int		verbose;	/* verbose message level {-1,1}	*/
318 	char*		type;		/* type pattern			*/
319 	Sfio_t*		mac;		/* temporary macro stream	*/
320 	Sfio_t*		tmp;		/* really temporary stream	*/
321 	Dt_t*		keys;		/* format key table		*/
322 	char		buf[1024];	/* format item buffer		*/
323 } state;
324 
325 /*
326  * optget() info discipline function
327  */
328 
329 static int
optinfo(Opt_t * op,Sfio_t * sp,const char * s,Optdisc_t * dp)330 optinfo(Opt_t* op, Sfio_t* sp, const char* s, Optdisc_t* dp)
331 {
332 	register int	i;
333 
334 	if (streq(s, "formats"))
335 		for (i = 1; i < elementsof(keys); i++)
336 		{
337 			sfprintf(sp, "[+%s?%s The title string ", keys[i].name, keys[i].description);
338 			if (keys[i].heading)
339 				sfprintf(sp, "is \b%s\b ", keys[i].heading, keys[i].width);
340 			sfprintf(sp, "and the default width ");
341 			if (keys[i].width)
342 				sfprintf(sp, "is %d.]", keys[i].width);
343 			else
344 				sfprintf(sp, "%s determined by the \b-b\b, \b-g\b, \b-k\b, \b-m\b and \b-n\b options.]", keys[i].heading ? "is" : "are");
345 		}
346 	return 0;
347 }
348 
349 /*
350  * append format string to fmt
351  */
352 
353 static void
append(Sfio_t * fmt,const char * format)354 append(Sfio_t* fmt, const char* format)
355 {
356 	if (sfstrtell(fmt))
357 		sfputc(fmt, ' ');
358 	sfputr(fmt, format, -1);
359 }
360 
361 /*
362  * scale <m,w,p> into op
363  */
364 
365 static char*
scale(int m,unsigned long w,unsigned long p)366 scale(int m, unsigned long w, unsigned long p)
367 {
368 	Sfulong_t	n;
369 
370 	if (state.scale)
371 	{
372 		n = w;
373 		n *= state.block;
374 		return fmtscale(n, state.scale);
375 	}
376 	if (state.block < 1024 * 1024)
377 		sfsprintf(state.buf, sizeof(state.buf), "%lu", w);
378 	else if (!m || !w && !p || w > 9)
379 		sfsprintf(state.buf, sizeof(state.buf), "%lu", w);
380 	else
381 		sfsprintf(state.buf, sizeof(state.buf), "%lu.%lu", w, p);
382 	return state.buf;
383 }
384 
385 /*
386  * sfkeyprintf() lookup
387  * handle==0 for heading
388  */
389 
390 static int
key(void * handle,register Sffmt_t * fp,const char * arg,char ** ps,Sflong_t * pn)391 key(void* handle, register Sffmt_t* fp, const char* arg, char** ps, Sflong_t* pn)
392 {
393 	register Df_t*		df = (Df_t*)handle;
394 	register char*		s = 0;
395 	register Sflong_t	n = 0;
396 	register Key_t*		kp;
397 	char*			t;
398 
399 	if (!fp->t_str)
400 		return 0;
401 	if (!(kp = (Key_t*)dtmatch(state.keys, fp->t_str)))
402 	{
403 		if (*fp->t_str != '$')
404 		{
405 			error(3, "%s: unknown format key", fp->t_str);
406 			return 0;
407 		}
408 		if (!(kp = newof(0, Key_t, 1, strlen(fp->t_str) + 1)))
409 			error(3, "out of space [key]");
410 		kp->name = strcpy((char*)(kp + 1), fp->t_str);
411 		kp->macro = getenv(fp->t_str + 1);
412 		kp->index = KEY_environ;
413 		kp->disable = 1;
414 		dtinsert(state.keys, kp);
415 	}
416 	if (kp->macro && !kp->disable)
417 	{
418 		kp->disable = 1;
419 		sfkeyprintf(state.mac, handle, kp->macro, key, NiL);
420 		if (!(*ps = sfstruse(state.mac)))
421 			error(ERROR_SYSTEM|3, "out of space");
422 		kp->disable = 0;
423 	}
424 	else if (!df)
425 	{
426 		if (fp->base >= 0)
427 			fp->base = -1;
428 		s = kp->heading;
429 		if (fp->flags & SFFMT_ALTER)
430 		{
431 			if (!kp->width)
432 				kp->width = keys[KEY_blocks].width;
433 			if ((fp->width = kp->width) < 0)
434 			{
435 				fp->width = -fp->width;
436 				fp->flags |= SFFMT_LEFT;
437 			}
438 			fp->precis = fp->width;
439 		}
440 		if (fp->width > 0 && fp->width < strlen(kp->heading))
441 		{
442 			if (fp->width < kp->abbrev)
443 			{
444 				fp->width = kp->abbrev;
445 				if (fp->precis >= 0 && fp->precis < fp->width)
446 					fp->precis = fp->width;
447 			}
448 			if (t = newof(0, char, kp->abbrev, 1))
449 				s = kp->heading = (char*)memcpy(t, kp->heading, kp->abbrev);
450 		}
451 		kp->width = fp->width;
452 		if (fp->flags & SFFMT_LEFT)
453 			kp->width = -kp->width;
454 		fp->fmt = 's';
455 		*ps = s;
456 	}
457 	else
458 	{
459 		if ((fp->flags & SFFMT_ALTER) && (fp->width = kp->width) < 0)
460 		{
461 			fp->width = -fp->width;
462 			fp->flags |= SFFMT_LEFT;
463 		}
464 		switch (kp->index)
465 		{
466 		case KEY_available:
467 			s = (df->total || df->ttotal) ? scale(df->fraction, df->avail, df->tavail) : "-";
468 			break;
469 		case KEY_blocks:
470 			s = (df->total || df->ttotal) ? scale(df->fraction, df->total, df->ttotal) : "-";
471 			break;
472 		case KEY_capacity:
473 			s = (df->total || df->ttotal) ? (sfsprintf(state.buf, sizeof(state.buf), "%3d%%", df->percent), state.buf) : "-";
474 			break;
475 		case KEY_environ:
476 			if (!(s = kp->macro))
477 				return 0;
478 			break;
479 		case KEY_filesystem:
480 			if (!(s = df->mnt->fs))
481 				s = "";
482 			break;
483 		case KEY_iavailable:
484 			if (df->total || df->ttotal)
485 				n = df->iavail;
486 			else
487 				s = "-";
488 			break;
489 		case KEY_icapacity:
490 			s = (df->total || df->ttotal) ? (sfsprintf(state.buf, sizeof(state.buf), "%3d%%", df->ipercent), state.buf) : "-";
491 			break;
492 		case KEY_inodes:
493 			if (df->total || df->ttotal)
494 				n = df->itotal;
495 			else
496 				s = "-";
497 			break;
498 		case KEY_iused:
499 			if (df->total || df->ttotal)
500 				n = df->iused;
501 			else
502 				s = "-";
503 			break;
504 		case KEY_mounted:
505 			if (!(s = df->mnt->dir))
506 				s = "";
507 			break;
508 		case KEY_native:
509 			if ((n = F_FRSIZE(&df->vfs)) >= 1024)
510 				sfsprintf(state.buf, sizeof(state.buf), "%I*3dk", sizeof(n), n / 1024);
511 			else
512 				sfsprintf(state.buf, sizeof(state.buf), "%I*4d", sizeof(n), n);
513 			s = state.buf;
514 			break;
515 		case KEY_options:
516 			if (!(s = df->mnt->options))
517 				s = "";
518 			break;
519 		case KEY_type:
520 			if (!(s = df->mnt->type))
521 				s = "";
522 			break;
523 		case KEY_used:
524 			s = (df->total || df->ttotal) ? scale(df->fraction, df->used, df->tused) : "-";
525 			break;
526 		default:
527 			return 0;
528 		}
529 		if (s)
530 		{
531 			if (fp->base >= 0)
532 			{
533 				fp->base = -1;
534 				if (!state.posix && strlen(s) >= fp->width)
535 				{
536 					sfprintf(state.tmp, "%s%-*.*s", s, fp->width + 1, fp->width + 1, "\n");
537 					if (!(s = sfstruse(state.tmp)))
538 						error(ERROR_SYSTEM|3, "out of space");
539 				}
540 			}
541 			*ps = s;
542 		}
543 		else
544 			*pn = n;
545 	}
546 	return 1;
547 }
548 
549 /*
550  * catch statvfs() timeout
551  */
552 
553 static void
timeout(int sig)554 timeout(int sig)
555 {
556 	state.timeout = 1;
557 }
558 
559 /*
560  * statvfs() with timeout
561  */
562 
563 static int
status(const char * path,struct statvfs * vfs)564 status(const char* path, struct statvfs* vfs)
565 {
566 	int		r;
567 
568 	state.timeout = 0;
569 	signal(SIGALRM, timeout);
570 	alarm(4);
571 	r = statvfs(path, vfs);
572 	alarm(0);
573 	signal(SIGALRM, SIG_DFL);
574 	if (r)
575 	{
576 		if (state.timeout)
577 			error(ERROR_SYSTEM|2, "%s: filesystem stat timed out", path);
578 		else
579 			error(ERROR_SYSTEM|2, "%s: cannot stat filesystem", path);
580 	}
581 	return r;
582 }
583 
584 /*
585  * list one entry
586  */
587 
588 static void
entry(Df_t * df,const char * format)589 entry(Df_t* df, const char* format)
590 {
591 	unsigned long	b;
592 	int		s;
593 
594 	if ((!state.type || strmatch(df->mnt->type, state.type)) && (!state.local || !(df->mnt->flags & MNT_REMOTE)))
595 	{
596 		if (REALITY(df->vfs.f_blocks) == 0)
597 		{
598 			df->total = df->avail = df->used = 0;
599 			df->percent = 0;
600 		}
601 		else
602 		{
603 			/*
604 			 * NOTE: on some systems vfs.f_* are unsigned,
605 			 *	 and on others signed negative values
606 			 *	 denote error
607 			 */
608 
609 			df->total = df->vfs.f_blocks;
610 			df->used = ((long)df->vfs.f_blocks <= (long)df->vfs.f_bfree) ? 0 : (df->vfs.f_blocks - df->vfs.f_bfree);
611 			df->avail = ((long)df->vfs.f_bavail < 0) ? 0 : df->vfs.f_bavail;
612 			df->percent = (df->ttotal = df->avail + df->used) ? (unsigned long)(((double)df->used / (double)df->ttotal + 0.005) * 100.0) : 0;
613 		}
614 		df->fraction = 0;
615 		df->ttotal = df->tavail = df->tused = 0;
616 		if (state.scale)
617 			state.block = F_FRSIZE(&df->vfs);
618 		else if (state.block)
619 		{
620 			b = F_FRSIZE(&df->vfs);
621 			if (b > state.block)
622 			{
623 				s = b / state.block;
624 				df->total *= s;
625 				df->avail *= s;
626 				df->used *= s;
627 			}
628 			else if (b < state.block)
629 			{
630 				s = state.block / b;
631 				if (df->fraction = s / 10)
632 				{
633 					df->ttotal = (df->total / df->fraction) % 10;
634 					df->tavail = (df->avail / df->fraction) % 10;
635 					df->tused = (df->used / df->fraction) % 10;
636 				}
637 				df->total /= s;
638 				df->avail /= s;
639 				df->used /= s;
640 			}
641 		}
642 		df->itotal = REALITY(df->vfs.f_files);
643 		df->iavail = REALITY(df->vfs.f_ffree);
644 		if (df->itotal < df->iavail)
645 			df->iused = 0;
646 		else
647 			df->iused = df->itotal - df->iavail;
648 		df->iavail = REALITY(df->vfs.f_favail);
649 		df->ipercent = (s = df->iused + df->iavail) ? (unsigned long)(((double)df->iused / (double)s + 0.005) * 100.0) : 0;
650 		sfkeyprintf(sfstdout, df, format, key, NiL);
651 	}
652 }
653 
654 int
main(int argc,register char ** argv)655 main(int argc, register char** argv)
656 {
657 	register int	n;
658 	int		rem;
659 	int		head;
660 	int		i;
661 	int*		match;
662 	dev_t		dirdev;
663 	dev_t		mntdev;
664 	dev_t*		dev;
665 	void*		mp;
666 	Sfio_t*		fmt;
667 	Key_t*		kp;
668 	char*		s;
669 	char*		format;
670 	struct stat	st;
671 	struct statvfs	vfs;
672 	Dtdisc_t	keydisc;
673 	Optdisc_t	optdisc;
674 	Mnt_t		mnt;
675 	Df_t		df;
676 
677 	error_info.id = "df";
678 	state.block = -1;
679 	state.posix = -1;
680 	state.verbose = -1;
681 	dev = 0;
682 	head = 1;
683 
684 	/*
685 	 * set up the disciplines
686 	 */
687 
688 	optinit(&optdisc, optinfo);
689 	memset(&keydisc, 0, sizeof(keydisc));
690 	keydisc.key = offsetof(Key_t, name);
691 	keydisc.size = -1;
692 	keydisc.link = offsetof(Key_t, hashed);
693 
694 	/*
695 	 * initialize the tables and string streams
696 	 */
697 
698 	if (!(fmt = sfstropen()) || !(state.mac = sfstropen()) || !(state.tmp = sfstropen()))
699 		error(3, "out of space [fmt]");
700 	if (!(state.keys = dtopen(&keydisc, Dtset)))
701 		error(3, "out of space [dict]");
702 	for (n = 1; n < elementsof(keys); n++)
703 		dtinsert(state.keys, keys + n);
704 
705 	/*
706 	 * grab the options
707 	 */
708 
709 	for (;;)
710 	{
711 		switch (optget(argv, usage))
712 		{
713 		case 'b':
714 			state.block = 512;
715 			continue;
716 		case 'D':
717 			if (s = strchr(opt_info.arg, '='))
718 				*s++ = 0;
719 			if (*opt_info.arg == 'n' && *(opt_info.arg + 1) == 'o')
720 			{
721 				opt_info.arg += 2;
722 				s = 0;
723 			}
724 			if (!(kp = (Key_t*)dtmatch(state.keys, opt_info.arg)))
725 			{
726 				if (!s)
727 					continue;
728 				if (!(kp = newof(0, Key_t, 1, strlen(opt_info.arg) + 1)))
729 					error(ERROR_SYSTEM|3, "out of space [macro]");
730 				kp->name = strcpy((char*)(kp + 1), opt_info.arg);
731 				dtinsert(state.keys, kp);
732 			}
733 			if (kp->macro = s)
734 				stresc(s);
735 			continue;
736 		case 'e':
737 			state.scale = 1000;
738 			continue;
739 		case 'f':
740 			append(fmt, opt_info.arg);
741 			continue;
742 		case 'F':
743 		case 't':
744 			state.type = opt_info.arg;
745 			continue;
746 		case 'g':
747 			state.block = 1024 * 1024 * 1024;
748 			continue;
749 		case 'h':
750 			head = opt_info.num;
751 			continue;
752 		case 'i':
753 			append(fmt, fmt_ino);
754 			continue;
755 		case 'k':
756 			state.block = 1024;
757 			continue;
758 		case 'K':
759 			state.scale = 1024;
760 			continue;
761 		case 'l':
762 			state.local = 1;
763 			continue;
764 		case 'm':
765 			state.block = 1024 * 1024;
766 			continue;
767 		case 'n':
768 			state.block = 0;
769 			continue;
770 		case 'O':
771 			append(fmt, fmt_opt);
772 			continue;
773 		case 'P':
774 			state.posix = 1;
775 			continue;
776 		case 'q':
777 			continue;
778 		case 's':
779 			state.sync = opt_info.num;
780 			continue;
781 		case 'T':
782 			continue;
783 		case 'v':
784 			state.verbose = ERROR_SYSTEM|1;
785 			continue;
786 		case '?':
787 			error(ERROR_USAGE|4, "%s", opt_info.arg);
788 			break;
789 		case ':':
790 			error(2, "%s", opt_info.arg);
791 			break;
792 		}
793 		break;
794 	}
795 	if (error_info.errors)
796 		error(ERROR_USAGE|4, "%s", optusage(NiL));
797 	argc -= opt_info.index;
798 	argv += opt_info.index;
799 	if (!sfstrtell(fmt))
800 		append(fmt, state.posix > 0 ? fmt_std : fmt_def);
801 	sfputc(fmt, '\n');
802 	if (!(format = sfstruse(fmt)))
803 		error(ERROR_SYSTEM|3, "out of space");
804 	stresc(format);
805 	if (state.posix < 0)
806 		state.posix = !!conformance(0, 0);
807 	if (state.block < 0)
808 	{
809 		if (state.posix)
810 			state.block = 512;
811 		else
812 		{
813 			state.block = 1024 * 1024;
814 			if (!state.scale)
815 				state.scale = 1024;
816 		}
817 	}
818 	s = 0;
819 	if (state.scale)
820 	{
821 		n = 5;
822 		s = "Size";
823 	}
824 	else if (state.block <= 512)
825 	{
826 		n = 10;
827 		if (!state.block)
828 			s = "blocks";
829 	}
830 	else if (state.block <= 1024)
831 	{
832 		n = 8;
833 		if (state.block == 1024)
834 			s = "Kbytes";
835 	}
836 	else if (state.block <= 1024 * 1024)
837 	{
838 		n = 6;
839 		if (state.block == 1024 * 1024)
840 			s = "Mbytes";
841 	}
842 	else if (state.block <= 1024 * 1024 * 1024)
843 	{
844 		n = 6;
845 		if (state.block == 1024 * 1024 * 1024)
846 			s = "Gbytes";
847 	}
848 	if (!s)
849 	{
850 		sfprintf(state.mac, "%d-blocks", state.block);
851 		if (!(s = strdup(sfstruse(state.mac))))
852 			error(ERROR_SYSTEM|3, "out of space [heading]");
853 		n = strlen(s);
854 	}
855 	keys[KEY_blocks].width = n;
856 	keys[KEY_blocks].heading = s;
857 #if _lib_sync
858 	if (state.sync)
859 		sync();
860 #endif
861 	if (!(mp = mntopen(NiL, "r")))
862 	{
863 		error(ERROR_SYSTEM|(argc > 0 ? 1 : 3), "cannot access mount table");
864 		sfkeyprintf(head ? sfstdout : state.tmp, NiL, format, key, NiL);
865 		sfstrseek(state.tmp, 0, SEEK_SET);
866 		df.mnt = &mnt;
867 		mnt.dir = UNKNOWN;
868 		mnt.type = 0;
869 		mnt.flags = 0;
870 		while (mnt.fs = *argv++)
871 			if (!status(mnt.fs, &df.vfs))
872 				entry(&df, format);
873 	}
874 	else
875 	{
876 		sfkeyprintf(head ? sfstdout : state.tmp, NiL, format, key, NiL);
877 		sfstrseek(state.tmp, 0, SEEK_SET);
878 		if (argc)
879 		{
880 			rem = argc;
881 			if (!(dev = newof(0, dev_t, argc, 0)))
882 				error(ERROR_SYSTEM|3, "out of space [dev_t]");
883 			for (n = 0; n < argc; n++)
884 				if (stat(argv[n], &st))
885 				{
886 					error(ERROR_SYSTEM|2, "%s: cannot stat", argv[n]);
887 					argv[n] = 0;
888 					rem--;
889 				}
890 				else
891 					dev[n] =
892 #if _mem_st_rdev_stat
893 					S_ISBLK(st.st_mode) ? st.st_rdev :
894 #endif
895 					st.st_dev;
896 		}
897 		else
898 			rem = 0;
899 		if (rem && (match = newof(0, int, n, 0)))
900 		{
901 			/*
902 			 * scan the mount table (up to 3x) for prefix matches
903 			 * to avoid the more expensive stat() and statvfs()
904 			 * calls in the final loop below
905 			 */
906 
907 			while (df.mnt = mntread(mp))
908 
909 				for (n = 0; n < argc; n++)
910 					if (argv[n] && (i = strlen(df.mnt->dir)) > 1 && i > match[n] && strneq(argv[n], df.mnt->dir, i) && (argv[n][i] == '/' || !argv[n][i]))
911 						match[n] = i;
912 			mntclose(mp);
913 			if (!(mp = mntopen(NiL, "r")))
914 				error(ERROR_SYSTEM|3, "cannot reopen mount table");
915 			while (rem && (df.mnt = mntread(mp)))
916 				for (n = 0; n < argc; n++)
917 					if (match[n] && (i = strlen(df.mnt->dir)) == match[n] && strneq(argv[n], df.mnt->dir, i) && (argv[n][i] == '/' || !argv[n][i]))
918 					{
919 						argv[n][match[n]] = 0;
920 						if (!status(argv[n], &df.vfs))
921 						{
922 							entry(&df, format);
923 							argv[n] = 0;
924 							match[n] = 0;
925 							if (!--rem)
926 								break;
927 						}
928 					}
929 			free(match);
930 			if (rem)
931 			{
932 				mntclose(mp);
933 				if (!(mp = mntopen(NiL, "r")))
934 					error(ERROR_SYSTEM|3, "cannot reopen mount table");
935 			}
936 		}
937 		while ((!argc || rem) && (df.mnt = mntread(mp)))
938 		{
939 			if (stat(df.mnt->dir, &st))
940 			{
941 				if (errno != ENOENT && errno != ENOTDIR)
942 					error(state.verbose, "%s: cannot stat", df.mnt->dir);
943 				continue;
944 			}
945 			dirdev = st.st_dev;
946 			mntdev = (!rem || *df.mnt->fs != '/' || stat(df.mnt->fs, &st)) ? dirdev : st.st_dev;
947 			if (rem)
948 			{
949 				for (n = 0; n < argc; n++)
950 					if (argv[n] && (dev[n] == dirdev || dev[n] == mntdev))
951 					{
952 						argv[n] = 0;
953 						rem--;
954 						break;
955 					}
956 				if (n >= argc)
957 					continue;
958 			}
959 			if (!status(df.mnt->dir, &df.vfs) || !status(df.mnt->fs, &df.vfs))
960 				entry(&df, format);
961 			if (rem)
962 			{
963 				while (++n < argc)
964 					if (dev[n] == dirdev || dev[n] == mntdev)
965 					{
966 						argv[n] = 0;
967 						rem--;
968 					}
969 				if (rem <= 0)
970 					break;
971 			}
972 		}
973 		mntclose(mp);
974 		if (argc > 0)
975 		{
976 			df.mnt = &mnt;
977 			memset(&mnt, 0, sizeof(mnt));
978 			for (n = 0; n < argc; n++)
979 				if ((mnt.dir = argv[n]) && !status(mnt.dir, &vfs))
980 					entry(&df, format);
981 		}
982 	}
983 	return error_info.errors != 0;
984 }
985