xref: /dragonfly/usr.bin/du/du.c (revision d50f9ae3448247db98eb135b85b2a32e6e4187f4)
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
_stblocks(int tflag,struct stat * st)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
main(int argc,char ** argv)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
linkchk(FTSENT * p)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
prthumanval(int64_t bytes)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
usage(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
ignoreadd(const char * mask)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
ignoreclean(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
ignorep(FTSENT * ent)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