xref: /dragonfly/usr.bin/du/du.c (revision 5fb3968e)
1 /*
2  * Copyright (c) 1989, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Chris Newcomb.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * @(#) Copyright (c) 1989, 1993, 1994 The Regents of the University of California.  All rights reserved.
33  * @(#)du.c	8.5 (Berkeley) 5/4/95
34  * $FreeBSD: src/usr.bin/du/du.c,v 1.17.2.4 2002/12/12 16:29:39 trhodes Exp $
35  */
36 
37 #include <sys/param.h>
38 #include <sys/queue.h>
39 #include <sys/stat.h>
40 
41 #include <err.h>
42 #include <errno.h>
43 #include <fnmatch.h>
44 #include <fts.h>
45 #include <libutil.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <sysexits.h>
50 #include <unistd.h>
51 
52 #define	HASHSIZE	256		/* power of 2 only */
53 #define HASHMASK	(HASHSIZE - 1)
54 
55 #define STBLOCKS(statp)	_stblocks(tflag, statp)
56 
57 static SLIST_HEAD(ignhead, ignentry) ignores;
58 struct ignentry {
59 	char			*mask;
60 	SLIST_ENTRY(ignentry)	next;
61 };
62 
63 static int	linkchk(FTSENT *);
64 static void	usage(void);
65 void		prthumanval(int64_t);
66 void		ignoreadd(const char *);
67 void		ignoreclean(void);
68 int		ignorep(FTSENT *);
69 
70 static char period[] = ".";
71 
72 typedef long long	du_number_t;
73 
74 static blkcnt_t
75 _stblocks(int tflag, struct stat *st)
76 {
77 	if (tflag)
78 		return (st->st_size + 511) / 512;
79 	else
80 		return st->st_blocks;
81 }
82 
83 int
84 main(int argc, char **argv)
85 {
86 	FTS		*fts;
87 	FTSENT		*p;
88 	long		blocksize;
89 	du_number_t	savednumber = 0;
90 	int		ftsoptions;
91 	int		listall;
92 	int		depth;
93 	int		Hflag, Lflag, Pflag;
94 	int		aflag, sflag, dflag, cflag, hflag, tflag;
95 	int		ch, notused, rval;
96 	char 		**save;
97 
98 	Hflag = Lflag = Pflag = 0;
99 	aflag = sflag = dflag = cflag = hflag = tflag = 0;
100 
101 	save = argv;
102 	ftsoptions = 0;
103 	depth = INT_MAX;
104 	SLIST_INIT(&ignores);
105 
106 	while ((ch = getopt(argc, argv, "HI:LPastd:chkrx")) != -1)
107 		switch (ch) {
108 			case 'H':
109 				Hflag = 1;
110 				break;
111 			case 'I':
112 				ignoreadd(optarg);
113 				break;
114 			case 'L':
115 				if (Pflag)
116 					usage();
117 				Lflag = 1;
118 				break;
119 			case 'P':
120 				if (Lflag)
121 					usage();
122 				Pflag = 1;
123 				break;
124 			case 'a':
125 				aflag = 1;
126 				break;
127 			case 's':
128 				sflag = 1;
129 				break;
130 			case 't':
131 				tflag = 1;
132 				break;
133 			case 'd':
134 				dflag = 1;
135 				errno = 0;
136 				depth = atoi(optarg);
137 				if (errno == ERANGE || depth < 0) {
138 					warnx("invalid argument to option d: %s", optarg);
139 					usage();
140 				}
141 				break;
142 			case 'c':
143 				cflag = 1;
144 				break;
145 			case 'h':
146 				if (setenv("BLOCKSIZE", "512", 1) == -1)
147 					warn("setenv: cannot set BLOCKSIZE=512");
148 				hflag = 1;
149 				break;
150 			case 'k':
151 				hflag = 0;
152 				if (setenv("BLOCKSIZE", "1024", 1) == -1)
153 					warn("setenv: cannot set BLOCKSIZE=1024");
154 				break;
155 			case 'r':		 /* Compatibility. */
156 				break;
157 			case 'x':
158 				ftsoptions |= FTS_XDEV;
159 				break;
160 			case '?':
161 			default:
162 				usage();
163 		}
164 
165 	argc -= optind;
166 	argv += optind;
167 
168 	/*
169 	 * XXX
170 	 * Because of the way that fts(3) works, logical walks will not count
171 	 * the blocks actually used by symbolic links.  We rationalize this by
172 	 * noting that users computing logical sizes are likely to do logical
173 	 * copies, so not counting the links is correct.  The real reason is
174 	 * that we'd have to re-implement the kernel's symbolic link traversing
175 	 * algorithm to get this right.  If, for example, you have relative
176 	 * symbolic links referencing other relative symbolic links, it gets
177 	 * very nasty, very fast.  The bottom line is that it's documented in
178 	 * the man page, so it's a feature.
179 	 */
180 
181 	if (Hflag + Lflag + Pflag > 1)
182 		usage();
183 
184 	if (Hflag + Lflag + Pflag == 0)
185 		Pflag = 1;			/* -P (physical) is default */
186 
187 	if (Hflag)
188 		ftsoptions |= FTS_COMFOLLOW;
189 
190 	if (Lflag)
191 		ftsoptions |= FTS_LOGICAL;
192 
193 	if (Pflag)
194 		ftsoptions |= FTS_PHYSICAL;
195 
196 	listall = 0;
197 
198 	if (aflag) {
199 		if (sflag || dflag)
200 			usage();
201 		listall = 1;
202 	} else if (sflag) {
203 		if (dflag)
204 			usage();
205 		depth = 0;
206 	}
207 
208 	if (!*argv) {
209 		argv = save;
210 		argv[0] = period;
211 		argv[1] = NULL;
212 	}
213 
214 	(void) getbsize(&notused, &blocksize);
215 	blocksize /= 512;
216 
217 	rval = 0;
218 
219 	if ((fts = fts_open(argv, ftsoptions, NULL)) == NULL)
220 		err(1, "fts_open");
221 
222 	while ((p = fts_read(fts)) != NULL) {
223 		switch (p->fts_info) {
224 			case FTS_D:			/* Ignore. */
225 				if (ignorep(p))
226 					fts_set(fts, p, FTS_SKIP);
227 				break;
228 			case FTS_DP:
229 				if (ignorep(p))
230 					break;
231 
232 				if (p->fts_pointer == NULL) {
233 					p->fts_pointer = malloc(sizeof(du_number_t));
234 					*(du_number_t *)p->fts_pointer = 0;
235 				}
236 				*(du_number_t *)p->fts_pointer += STBLOCKS(p->fts_statp);
237 
238 				if (p->fts_parent->fts_pointer == NULL) {
239 					p->fts_parent->fts_pointer = malloc(sizeof(du_number_t));
240 					*(du_number_t *)p->fts_parent->fts_pointer = 0;
241 				}
242 				*(du_number_t *)p->fts_parent->fts_pointer += *(du_number_t *)p->fts_pointer += STBLOCKS(p->fts_statp);
243 
244 				if (p->fts_level <= depth) {
245 					if (hflag) {
246 						(void) prthumanval(howmany(*(du_number_t *)p->fts_pointer, blocksize));
247 						(void) printf("\t%s\n", p->fts_path);
248 					} else {
249 					(void) printf("%lld\t%s\n",
250 					    howmany(*(du_number_t *)p->fts_pointer, blocksize),
251 					    p->fts_path);
252 					}
253 				}
254 				if (p->fts_pointer) {
255 					free(p->fts_pointer);
256 					p->fts_pointer = NULL;
257 				}
258 				break;
259 			case FTS_DC:			/* Ignore. */
260 				break;
261 			case FTS_DNR:			/* Warn, continue. */
262 			case FTS_ERR:
263 			case FTS_NS:
264 				warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
265 				rval = 1;
266 				break;
267 			default:
268 				if (ignorep(p))
269 					break;
270 
271 				if (p->fts_statp->st_nlink > 1 && linkchk(p))
272 					break;
273 
274 				if (listall || p->fts_level == 0) {
275 					if (hflag) {
276 						(void) prthumanval(howmany(STBLOCKS(p->fts_statp),
277 							blocksize));
278 						(void) printf("\t%s\n", p->fts_path);
279 					} else {
280 						(void) printf("%lld\t%s\n",
281 							howmany((long long)STBLOCKS(p->fts_statp), blocksize),
282 							p->fts_path);
283 					}
284 				}
285 				if (p->fts_parent->fts_pointer == NULL) {
286 					p->fts_parent->fts_pointer = malloc(sizeof(du_number_t));
287 					*(du_number_t *)p->fts_parent->fts_pointer = 0;
288 				}
289 				*(du_number_t *)p->fts_parent->fts_pointer += STBLOCKS(p->fts_statp);
290 		}
291 		if (p->fts_parent->fts_pointer)
292 			savednumber = *(du_number_t *)p->fts_parent->fts_pointer;
293 	}
294 
295 	if (errno)
296 		err(1, "fts_read");
297 
298 	fts_close(fts);
299 
300 	if (cflag) {
301 		if (hflag) {
302 			(void) prthumanval(howmany(savednumber, blocksize));
303 			(void) printf("\ttotal\n");
304 		} else {
305 			(void) printf("%lld\ttotal\n", howmany(savednumber, blocksize));
306 		}
307 	}
308 
309 	ignoreclean();
310 	exit(rval);
311 }
312 
313 static int
314 linkchk(FTSENT *p)
315 {
316 	struct links_entry {
317 		struct links_entry *next;
318 		struct links_entry *previous;
319 		int		links;
320 		dev_t		dev;
321 		ino_t		ino;
322 	};
323 
324 	static const size_t links_hash_initial_size = 8192;
325 	static struct links_entry **buckets;
326 	static struct links_entry *free_list;
327 	static size_t number_buckets;
328 	static unsigned long number_entries;
329 	static char stop_allocating;
330 	struct links_entry *le, **new_buckets;
331 	struct stat *st;
332 	size_t i, new_size;
333 	int hash;
334 
335 	st = p->fts_statp;
336 
337 	/* If necessary, initialize the hash table. */
338 	if (buckets == NULL) {
339 		number_buckets = links_hash_initial_size;
340 		buckets = malloc(number_buckets * sizeof(buckets[0]));
341 		if (buckets == NULL)
342 			errx(1, "No memory for hardlink detection");
343 		for (i = 0; i < number_buckets; i++)
344 			buckets[i] = NULL;
345 	}
346 
347 	/* If the hash table is getting too full, enlarge it. */
348 	if (number_entries > number_buckets * 10 && !stop_allocating) {
349 		new_size = number_buckets * 2;
350 		new_buckets = malloc(new_size * sizeof(struct links_entry *));
351 
352 		/* Try releasing the free list to see if that helps. */
353 		if (new_buckets == NULL && free_list != NULL) {
354 			while (free_list != NULL) {
355 				le = free_list;
356 				free_list = le->next;
357 				free(le);
358 			}
359 			new_buckets = malloc(new_size * sizeof(new_buckets[0]));
360 		}
361 
362 		if (new_buckets == NULL) {
363 			stop_allocating = 1;
364 			warnx("No more memory for tracking hard links");
365 		} else {
366 			memset(new_buckets, 0, new_size * sizeof(struct links_entry *));
367 			for (i = 0; i < number_buckets; i++) {
368 				while (buckets[i] != NULL) {
369 					/* Remove entry from old bucket. */
370 					le = buckets[i];
371 					buckets[i] = le->next;
372 
373 					/* Add entry to new bucket. */
374 					hash = (le->dev ^ le->ino) % new_size;
375 
376 					if (new_buckets[hash] != NULL)
377 						new_buckets[hash]->previous = le;
378 					le->next = new_buckets[hash];
379 					le->previous = NULL;
380 					new_buckets[hash] = le;
381 				}
382 			}
383 			free(buckets);
384 			buckets = new_buckets;
385 			number_buckets = new_size;
386 		}
387 	}
388 
389 	/* Try to locate this entry in the hash table. */
390 	hash = ( st->st_dev ^ st->st_ino ) % number_buckets;
391 	for (le = buckets[hash]; le != NULL; le = le->next) {
392 		if (le->dev == st->st_dev && le->ino == st->st_ino) {
393 			/*
394 			 * Save memory by releasing an entry when we've seen
395 			 * all of it's links.
396 			 */
397 			if (--le->links <= 0) {
398 				if (le->previous != NULL)
399 					le->previous->next = le->next;
400 				if (le->next != NULL)
401 					le->next->previous = le->previous;
402 				if (buckets[hash] == le)
403 					buckets[hash] = le->next;
404 				number_entries--;
405 				/* Recycle this node through the free list */
406 				if (stop_allocating) {
407 					free(le);
408 				} else {
409 					le->next = free_list;
410 					free_list = le;
411 				}
412 			}
413 			return (1);
414 		}
415 	}
416 
417 	if (stop_allocating)
418 		return (0);
419 
420 	/* Add this entry to the links cache. */
421 	if (free_list != NULL) {
422 		/* Pull a node from the free list if we can. */
423 		le = free_list;
424 		free_list = le->next;
425 	} else
426 		/* Malloc one if we have to. */
427 		le = malloc(sizeof(struct links_entry));
428 	if (le == NULL) {
429 		stop_allocating = 1;
430 		warnx("No more memory for tracking hard links");
431 		return (0);
432 	}
433 	le->dev = st->st_dev;
434 	le->ino = st->st_ino;
435 	le->links = st->st_nlink - 1;
436 	number_entries++;
437 	le->next = buckets[hash];
438 	le->previous = NULL;
439 	if (buckets[hash] != NULL)
440 		buckets[hash]->previous = le;
441 	buckets[hash] = le;
442 	return (0);
443 }
444 
445 void
446 prthumanval(int64_t bytes)
447 {
448 	char buf[sizeof("999M")];
449 
450 	bytes *= DEV_BSIZE;
451 
452 	humanize_number(buf, sizeof(buf), bytes, "", HN_AUTOSCALE,
453 			HN_B | HN_NOSPACE | HN_DECIMAL);
454 
455 	(void) printf("%4s", buf);
456 }
457 
458 static void
459 usage(void)
460 {
461 	(void)fprintf(stderr,
462 		"usage: du [-H | -L | -P] [-a | -s | -d depth] [-c] [-h | -k] [-x] [-I mask] [file ...]\n");
463 	exit(EX_USAGE);
464 }
465 
466 void
467 ignoreadd(const char *mask)
468 {
469 	struct ignentry *ign;
470 
471 	ign = calloc(1, sizeof(*ign));
472 	if (ign == NULL)
473 		errx(1, "cannot allocate memory");
474 	ign->mask = strdup(mask);
475 	if (ign->mask == NULL)
476 		errx(1, "cannot allocate memory");
477 	SLIST_INSERT_HEAD(&ignores, ign, next);
478 }
479 
480 void
481 ignoreclean(void)
482 {
483 	struct ignentry *ign;
484 
485 	while (!SLIST_EMPTY(&ignores)) {
486 		ign = SLIST_FIRST(&ignores);
487 		SLIST_REMOVE_HEAD(&ignores, next);
488 		free(ign->mask);
489 		free(ign);
490 	}
491 }
492 
493 int
494 ignorep(FTSENT *ent)
495 {
496 	struct ignentry *ign;
497 
498 	SLIST_FOREACH(ign, &ignores, next)
499 		if (fnmatch(ign->mask, ent->fts_name, 0) != FNM_NOMATCH)
500 			return 1;
501 	return 0;
502 }
503