1/* See LICENSE file for copyright and license details. */
2
3
4#include <sys/stat.h>
5#include <sys/types.h>
6
7#include <errno.h>
8#include <fcntl.h>
9#include <limits.h>
10#include <search.h>
11#include <stdint.h>
12#include <stdlib.h>
13#include <stdio.h>
14#include <unistd.h>
15
16#include "fs.h"
17#include "util.h"
18
19static size_t maxdepth = SIZE_MAX;
20static size_t blksize = 512;
21
22static int aflag = 0;
23static int sflag = 0;
24static int hflag = 0;
25
26struct file {
27 dev_t devno;
28 ino_t inode;
29};
30
31static void
32printpath(off_t n, const char *path)
33{
34 if (hflag)
35 printf("%s\t%s\n", humansize(n * blksize), path);
36 else
37 printf("%jd\t%s\n", (intmax_t)n, path);
38}
39
40static off_t
41nblks(blkcnt_t blocks)
42{
43 return (512 * blocks + blksize - 1) / blksize;
44}
45
46static int
47cmp(const void *p1, const void *p2)
48{
49 const struct file *f1 = p1, *f2 = p2;
50
51 if (f1->devno > f2->devno)
52 return -1;
53 if (f1->devno < f2->devno)
54 return 1;
55
56 /* f1->devno == f2->devno */
57 if (f1->inode < f2->inode)
58 return -1;
59 if (f1->inode > f2->inode)
60 return 1;
61
62 return 0;
63}
64
65static int
66duplicated(dev_t dev, ino_t ino)
67{
68 static void *tree;
69 struct file **fpp, *fp, file = {dev, ino};
70
71 if ((fpp = tsearch(&file, &tree, cmp)) == NULL)
72 eprintf("%s:", argv0);
73
74 if (*fpp != &file)
75 return 1;
76
77 /* new file added */
78 fp = emalloc(sizeof(*fp));
79 *fp = file;
80 *fpp = fp;
81
82 return 0;
83}
84
85static void
86du(int dirfd, const char *path, struct stat *st, void *data, struct recursor *r)
87{
88 off_t *total = data, subtotal;
89
90 subtotal = nblks(st->st_blocks);
91 if (S_ISDIR(st->st_mode)) {
92 recurse(dirfd, path, &subtotal, r);
93 } else if (r->follow != 'P' || st->st_nlink > 1) {
94 if (duplicated(st->st_dev, st->st_ino))
95 goto print;
96 }
97
98 *total += subtotal;
99
100print:
101 if (!r->depth)
102 printpath(*total, r->path);
103 else if (!sflag && (size_t)r->depth <= maxdepth && (S_ISDIR(st->st_mode) || aflag))
104 printpath(subtotal, r->path);
105}
106
107static void
108usage(void)
109{
110 eprintf("usage: %s [-a | -s] [-d depth] [-h] [-k] [-H | -L | -P] [-x] [file ...]\n", argv0);
111}
112
113// ?man du: estimate file space usage
114// ?man arguments: file ...
115// ?man display disk space used by files and directories
116int
117main(int argc, char *argv[])
118{
119 struct recursor r = { .fn = du, .follow = 'P' };
120 off_t n = 0;
121 int kflag = 0, dflag = 0;
122 char *bsize;
123
124 ARGBEGIN {
125 // ?man -a: print or show all entries
126 case 'a':
127 aflag = 1;
128 break;
129 // ?man -d:num: specify directory
130 case 'd':
131 dflag = 1;
132 maxdepth = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
133 break;
134 // ?man -h: suppress headers or print help
135 case 'h':
136 hflag = 1;
137 break;
138 // ?man -k: specify option flag
139 case 'k':
140 kflag = 1;
141 break;
142 // ?man -s: silent mode or print summary
143 case 's':
144 sflag = 1;
145 break;
146 // ?man -x: hex format or match whole lines
147 case 'x':
148 r.flags |= SAMEDEV;
149 break;
150 // ?man -H: specify option flag
151 case 'H':
152 // ?man -L: specify option flag
153 case 'L':
154 // ?man -P: specify option flag
155 case 'P':
156 r.follow = ARGC();
157 break;
158 default:
159 usage();
160 } ARGEND
161
162 if ((aflag && sflag) || (dflag && sflag))
163 usage();
164
165 bsize = getenv("BLOCKSIZE");
166 if (bsize)
167 blksize = estrtonum(bsize, 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
168 if (kflag)
169 blksize = 1024;
170
171 if (!argc) {
172 recurse(AT_FDCWD, ".", &n, &r);
173 } else {
174 for (; *argv; argc--, argv++) {
175 n = 0;
176 recurse(AT_FDCWD, *argv, &n, &r);
177 }
178 }
179
180 return fshut(stdout, "<stdout>") || recurse_status;
181}