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