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