1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1989-2012 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 * du -- report number of blocks used by . | file ...
26 */
27
28 static const char usage[] =
29 "[-?\n@(#)$Id: du (AT&T Research) 2012-01-26 $\n]"
30 USAGE_LICENSE
31 "[+NAME?du - summarize disk usage]"
32 "[+DESCRIPTION?\bdu\b reports the number of blocks contained in all files"
33 " and recursively all directories named by the \apath\a arguments."
34 " The current directory is used if no \apath\a is given. Usage for"
35 " all files and directories is counted, even when the listing is"
36 " omitted. Directories and files are only counted once, even if"
37 " they appear more than once as a hard link, a target of a"
38 " symbolic link, or an operand.]"
39 "[+?The default block size is 512. The block count includes only the actual"
40 " data blocks used by each file and directory, and may not include"
41 " other filesystem data required to represent the file. Blocks are"
42 " counted only for the first link to a file; subsequent links are"
43 " ignored. Partial blocks are rounded up for each file.]"
44 "[+?If more than one of \b-b\b, \b-h\b, \b-k\b, \b-K,\b or \b-m\b are"
45 " specified only the rightmost takes affect.]"
46
47 "[a:all?List usage for each file. If neither \b--all\b nor \b--summary\b"
48 " is specified then only usage for the \apath\a arguments and"
49 " directories is listed.]"
50 "[b:blocksize?Set the block size to \asize\a. If omitted, \asize\a defaults"
51 " to 512.]#?[size:=512]"
52 "[f:silent?Do not report file and directory access errors.]"
53 "[h:binary-scale|human-readable?Scale disk usage to powers of 1024.]"
54 "[K:decimal-scale?Scale disk usage to powers of 1000.]"
55 "[k:kilobytes?List usage in units of 1024 bytes.]"
56 "[m:megabytes?List usage in units of 1024K bytes.]"
57 "[s:summary|summarize?Only display the total for each \apath\a argument.]"
58 "[t|c:total?Display a grand total for all files and directories.]"
59 "[v|r:verbose?Report all file and directory access errors. This is the"
60 " default.]"
61 "[x|X|l:xdev|local|mount|one-file-system?Do not descend into directories in"
62 " different filesystems than their parents.]"
63 "[L:logical|follow?Follow symbolic links. The default is \b--physical\b.]"
64 "[H:metaphysical?Follow command argument symbolic links, otherwise don't"
65 " follow. The default is \b--physical\b.]"
66 "[P:physical?Don't follow symbolic links. The default is \b--physical\b.]"
67
68 "\n"
69 "\n[ path ... ]\n"
70 "\n"
71
72 "[+SEE ALSO?\bfind\b(1), \bls\b(1), \btw\b(1)]"
73 ;
74
75 #include <ast.h>
76 #include <ls.h>
77 #include <cdt.h>
78 #include <fts.h>
79 #include <error.h>
80
81 #define BLOCKS(n) (Count_t)((blocksize==LS_BLOCKSIZE)?(n):(((n)*LS_BLOCKSIZE+blocksize-1)/blocksize))
82
83 typedef Sfulong_t Count_t;
84
85 typedef struct Fileid_s /* unique file id */
86 {
87 ino_t ino;
88 dev_t dev;
89 } Fileid_t;
90
91 typedef struct Hit_s /* file already seen */
92 {
93 Dtlink_t link; /* dictionary link */
94 Fileid_t id; /* unique file id */
95 } Hit_t;
96
97 static void
mark(Dt_t * dict,Hit_t * key,FTSENT * ent)98 mark(Dt_t* dict, Hit_t* key, FTSENT* ent)
99 {
100 Hit_t* hit;
101
102 static int warned;
103
104 if (hit = newof(0, Hit_t, 1, 0))
105 {
106 *hit = *key;
107 dtinsert(dict, hit);
108 }
109 else if (!warned)
110 {
111 warned = 1;
112 error(1, "%s: file id dictionary out of space", ent->fts_path);
113 }
114 }
115
116 int
main(int argc,register char ** argv)117 main(int argc, register char** argv)
118 {
119 register FTS* fts;
120 register FTSENT* ent;
121 char* s;
122 char* d;
123 Dt_t* dict;
124 Count_t n;
125 Count_t b;
126 int dirs;
127 int flags;
128 int list;
129 int logical;
130 int multiple;
131 struct stat st;
132 Hit_t hit;
133 Dtdisc_t disc;
134
135 int all = 0;
136 int silent = 0;
137 int summary = 0;
138 int scale = 0;
139 int total = 0;
140 unsigned long blocksize = 0;
141 Count_t count = 0;
142
143 NoP(argc);
144 error_info.id = "du";
145 blocksize = 0;
146 flags = FTS_PHYSICAL|FTS_NOSEEDOTDIR;
147 for (;;)
148 {
149 switch (optget(argv, usage))
150 {
151 case 'a':
152 all = 1;
153 continue;
154 case 'b':
155 blocksize = (opt_info.num <= 0) ? 512 : opt_info.num;
156 continue;
157 case 'f':
158 silent = 1;
159 continue;
160 case 'h':
161 scale = 1024;
162 blocksize = 0;
163 continue;
164 case 'K':
165 scale = 1000;
166 blocksize = 0;
167 continue;
168 case 'k':
169 blocksize = 1024;
170 continue;
171 case 'm':
172 blocksize = 1024 * 1024;
173 continue;
174 case 's':
175 summary = 1;
176 continue;
177 case 't':
178 total = 1;
179 continue;
180 case 'x':
181 flags |= FTS_XDEV;
182 continue;
183 case 'v':
184 silent = 0;
185 continue;
186 case 'H':
187 flags |= FTS_META|FTS_PHYSICAL;
188 continue;
189 case 'L':
190 flags &= ~(FTS_META|FTS_PHYSICAL);
191 continue;
192 case 'P':
193 flags &= ~FTS_META;
194 flags |= FTS_PHYSICAL;
195 continue;
196 case '?':
197 error(ERROR_USAGE|4, "%s", opt_info.arg);
198 break;
199 case ':':
200 error(2, "%s", opt_info.arg);
201 break;
202 }
203 break;
204 }
205 argv += opt_info.index;
206 if (error_info.errors)
207 error(ERROR_USAGE|4, "%s", optusage(NiL));
208 memset(&hit, 0, sizeof(hit));
209 memset(&disc, 0, sizeof(disc));
210 disc.key = offsetof(Hit_t, id);
211 disc.size = sizeof(Fileid_t);
212 if (!(dict = dtopen(&disc, Dtset)))
213 error(3, "not enough space for file id dictionary");
214 if (blocksize)
215 scale = 0;
216 else
217 blocksize = LS_BLOCKSIZE;
218 if (logical = !(flags & (FTS_META|FTS_PHYSICAL)))
219 flags |= FTS_PHYSICAL;
220 multiple = argv[0] && argv[1];
221 dirs = logical || multiple;
222 if (!(fts = fts_open(argv, flags, NiL)))
223 error(ERROR_system(1), "%s: not found", argv[1]);
224 while (ent = fts_read(fts))
225 {
226 if (ent->fts_info != FTS_DP)
227 {
228 if (multiple && !ent->fts_level)
229 {
230 if (s = strrchr(ent->fts_path, '/'))
231 {
232 *s = 0;
233 d = ent->fts_path;
234 }
235 else
236 d = "..";
237 if (stat(d, &st))
238 {
239 error(ERROR_SYSTEM|2, "%s: cannot stat", d);
240 continue;
241 }
242 hit.id.dev = st.st_dev;
243 hit.id.ino = st.st_ino;
244 if (dtsearch(dict, &hit))
245 {
246 fts_set(NiL, ent, FTS_SKIP);
247 continue;
248 }
249 if (s)
250 *s = '/';
251 }
252 hit.id.dev = ent->fts_statp->st_dev;
253 hit.id.ino = ent->fts_statp->st_ino;
254 if (dirs && dtsearch(dict, &hit))
255 {
256 fts_set(NiL, ent, FTS_SKIP);
257 continue;
258 }
259 }
260 list = !summary;
261 n = 0;
262 switch (ent->fts_info)
263 {
264 case FTS_NS:
265 if (!silent)
266 error(ERROR_SYSTEM|2, "%s: not found", ent->fts_path);
267 continue;
268 case FTS_D:
269 if (!(ent->fts_pointer = newof(0, Count_t, 1, 0)))
270 error(ERROR_SYSTEM|3, "out of space");
271 if (dirs)
272 mark(dict, &hit, ent);
273 continue;
274 case FTS_DC:
275 if (!silent)
276 error(2, "%s: directory causes cycle", ent->fts_path);
277 continue;
278 case FTS_DNR:
279 if (!silent)
280 error(ERROR_SYSTEM|2, "%s: cannot read directory", ent->fts_path);
281 break;
282 case FTS_DNX:
283 if (!silent)
284 error(ERROR_SYSTEM|2, "%s: cannot search directory", ent->fts_path);
285 fts_set(NiL, ent, FTS_SKIP);
286 break;
287 case FTS_DP:
288 if (ent->fts_pointer)
289 {
290 n = *(Count_t*)ent->fts_pointer;
291 free(ent->fts_pointer);
292 }
293 break;
294 case FTS_SL:
295 if (logical)
296 {
297 fts_set(NiL, ent, FTS_FOLLOW);
298 continue;
299 }
300 /*FALLTHROUGH*/
301 default:
302 if (ent->fts_statp->st_nlink > 1 || dirs && !ent->fts_level)
303 mark(dict, &hit, ent);
304 if (!all)
305 list = 0;
306 break;
307 }
308 b = iblocks(ent->fts_statp);
309 count += b;
310 n += b;
311 if (ent->fts_parent->fts_pointer)
312 *(Count_t*)ent->fts_parent->fts_pointer += n;
313 if (!total && (list || ent->fts_level <= 0))
314 {
315 if (scale)
316 sfprintf(sfstdout, "%s\t%s\n", fmtscale((Sfulong_t)n * blocksize, scale), ent->fts_path);
317 else
318 sfprintf(sfstdout, "%I*u\t%s\n", sizeof(Count_t), BLOCKS(n), ent->fts_path);
319 }
320 }
321 if (total)
322 {
323 if (scale)
324 sfprintf(sfstdout, "%s\n", fmtscale(count * blocksize, scale));
325 else
326 sfprintf(sfstdout, "%I*u\n", sizeof(Count_t), BLOCKS(count));
327 }
328 return error_info.errors != 0;
329 }
330