1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
27 /* All Rights Reserved */
28
29 #pragma ident "%Z%%M% %I% %E% SMI"
30
31 /*
32 * du -- summarize disk usage
33 * du [-dorx] [-a|-s] [-h|-k|-m] [-H|-L] [file...]
34 */
35
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/avl.h>
39 #include <fcntl.h>
40 #include <dirent.h>
41 #include <limits.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <locale.h>
47 #include <libcmdutils.h>
48
49
50 static int aflg = 0;
51 static int rflg = 0;
52 static int sflg = 0;
53 static int kflg = 0;
54 static int mflg = 0;
55 static int oflg = 0;
56 static int dflg = 0;
57 static int hflg = 0;
58 static int Hflg = 0;
59 static int Lflg = 0;
60 static int cmdarg = 0; /* Command line argument */
61 static char *dot = ".";
62 static int level = 0; /* Level of recursion */
63
64 static char *base;
65 static char *name;
66 static size_t base_len = PATH_MAX + 1; /* # of chars for base */
67 static size_t name_len = PATH_MAX + 1; /* # of chars for name */
68
69 #define NUMBER_WIDTH 64
70 typedef char numbuf_t[NUMBER_WIDTH];
71
72 /*
73 * Output formats. Solaris uses a tab as separator, XPG4 a space.
74 */
75 #ifdef XPG4
76 #define FORMAT1 "%s %s\n"
77 #define FORMAT2 "%lld %s\n"
78 #else
79 #define FORMAT1 "%s\t%s\n"
80 #define FORMAT2 "%lld\t%s\n"
81 #endif
82
83 /*
84 * convert DEV_BSIZE blocks to K blocks
85 */
86 #define DEV_BSIZE 512
87 #define DEV_KSHIFT 1
88 #define DEV_MSHIFT 11
89 #define kb(n) (((u_longlong_t)(n)) >> DEV_KSHIFT)
90 #define mb(n) (((u_longlong_t)(n)) >> DEV_MSHIFT)
91
92 long wait();
93 static u_longlong_t descend(char *curname, int curfd, int *retcode,
94 dev_t device);
95 static void printsize(blkcnt_t blocks, char *path);
96 static void exitdu(int exitcode);
97
98 static avl_tree_t *tree = NULL;
99
100 int
main(int argc,char ** argv)101 main(int argc, char **argv)
102 {
103 blkcnt_t blocks = 0;
104 int c;
105 extern int optind;
106 char *np;
107 pid_t pid, wpid;
108 int status, retcode = 0;
109 setbuf(stderr, NULL);
110 (void) setlocale(LC_ALL, "");
111 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
112 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
113 #endif
114 (void) textdomain(TEXT_DOMAIN);
115
116 #ifdef XPG4
117 rflg++; /* "-r" is not an option but ON always */
118 #endif
119
120 while ((c = getopt(argc, argv, "adhHkLmorsx")) != EOF)
121 switch (c) {
122
123 case 'a':
124 aflg++;
125 continue;
126
127 case 'h':
128 hflg++;
129 kflg = 0;
130 mflg = 0;
131 continue;
132
133 case 'r':
134 rflg++;
135 continue;
136
137 case 's':
138 sflg++;
139 continue;
140
141 case 'k':
142 kflg++;
143 hflg = 0;
144 mflg = 0;
145 continue;
146
147 case 'm':
148 mflg++;
149 hflg = 0;
150 kflg = 0;
151 continue;
152
153 case 'o':
154 oflg++;
155 continue;
156
157 case 'd':
158 dflg++;
159 continue;
160
161 case 'x':
162 dflg++;
163 continue;
164
165 case 'H':
166 Hflg++;
167 /* -H and -L are mutually exclusive */
168 Lflg = 0;
169 cmdarg++;
170 continue;
171
172 case 'L':
173 Lflg++;
174 /* -H and -L are mutually exclusive */
175 Hflg = 0;
176 cmdarg = 0;
177 continue;
178 case '?':
179 (void) fprintf(stderr, gettext(
180 "usage: du [-dorx] [-a|-s] [-h|-k|-m] [-H|-L] "
181 "[file...]\n"));
182 exit(2);
183 }
184 if (optind == argc) {
185 argv = ˙
186 argc = 1;
187 optind = 0;
188 }
189
190 /* "-o" and "-s" don't make any sense together. */
191 if (oflg && sflg)
192 oflg = 0;
193
194 if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) {
195 perror("du");
196 exit(1);
197 }
198 if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) {
199 perror("du");
200 free(base);
201 exit(1);
202 }
203 do {
204 if (optind < argc - 1) {
205 pid = fork();
206 if (pid == (pid_t)-1) {
207 perror(gettext("du: No more processes"));
208 exitdu(1);
209 }
210 if (pid != 0) {
211 while ((wpid = wait(&status)) != pid &&
212 wpid != (pid_t)-1)
213 ;
214 if (pid != (pid_t)-1 && status != 0)
215 retcode = 1;
216 }
217 }
218 if (optind == argc - 1 || pid == 0) {
219 while (base_len < (strlen(argv[optind]) + 1)) {
220 base_len = base_len * 2;
221 if ((base = (char *)realloc(base, base_len *
222 sizeof (char))) == NULL) {
223 if (rflg) {
224 (void) fprintf(stderr, gettext(
225 "du: can't process %s"),
226 argv[optind]);
227 perror("");
228 }
229 exitdu(1);
230 }
231 }
232 if (base_len > name_len) {
233 name_len = base_len;
234 if ((name = (char *)realloc(name, name_len *
235 sizeof (char))) == NULL) {
236 if (rflg) {
237 (void) fprintf(stderr, gettext(
238 "du: can't process %s"),
239 argv[optind]);
240 perror("");
241 }
242 exitdu(1);
243 }
244 }
245 (void) strcpy(base, argv[optind]);
246 (void) strcpy(name, argv[optind]);
247 if (np = strrchr(name, '/')) {
248 *np++ = '\0';
249 if (chdir(*name ? name : "/") < 0) {
250 if (rflg) {
251 (void) fprintf(stderr, "du: ");
252 perror(*name ? name : "/");
253 exitdu(1);
254 }
255 exitdu(0);
256 }
257 } else
258 np = base;
259 blocks = descend(*np ? np : ".", 0, &retcode,
260 (dev_t)0);
261 if (sflg)
262 printsize(blocks, base);
263 if (optind < argc - 1)
264 exitdu(retcode);
265 }
266 optind++;
267 } while (optind < argc);
268 exitdu(retcode);
269
270 return (retcode);
271 }
272
273 /*
274 * descend recursively, adding up the allocated blocks.
275 * If curname is NULL, curfd is used.
276 */
277 static u_longlong_t
descend(char * curname,int curfd,int * retcode,dev_t device)278 descend(char *curname, int curfd, int *retcode, dev_t device)
279 {
280 static DIR *dirp = NULL;
281 char *ebase0, *ebase;
282 struct stat stb, stb1;
283 int i, j, ret, fd, tmpflg;
284 int follow_symlinks;
285 blkcnt_t blocks = 0;
286 off_t curoff = 0;
287 ptrdiff_t offset;
288 ptrdiff_t offset0;
289 struct dirent *dp;
290 char dirbuf[PATH_MAX + 1];
291 u_longlong_t retval;
292
293 ebase0 = ebase = strchr(base, 0);
294 if (ebase > base && ebase[-1] == '/')
295 ebase--;
296 offset = ebase - base;
297 offset0 = ebase0 - base;
298
299 if (curname)
300 curfd = AT_FDCWD;
301
302 /*
303 * If neither a -L or a -H was specified, don't follow symlinks.
304 * If a -H was specified, don't follow symlinks if the file is
305 * not a command line argument.
306 */
307 follow_symlinks = (Lflg || (Hflg && cmdarg));
308 if (follow_symlinks) {
309 i = fstatat(curfd, curname, &stb, 0);
310 j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW);
311
312 /*
313 * Make sure any files encountered while traversing the
314 * hierarchy are not considered command line arguments.
315 */
316 if (Hflg) {
317 cmdarg = 0;
318 }
319 } else {
320 i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW);
321 j = 0;
322 }
323
324 if ((i < 0) || (j < 0)) {
325 if (rflg) {
326 (void) fprintf(stderr, "du: ");
327 perror(base);
328 }
329
330 /*
331 * POSIX states that non-zero status codes are only set
332 * when an error message is printed out on stderr
333 */
334 *retcode = (rflg ? 1 : 0);
335 *ebase0 = 0;
336 return (0);
337 }
338 if (device) {
339 if (dflg && stb.st_dev != device) {
340 *ebase0 = 0;
341 return (0);
342 }
343 }
344 else
345 device = stb.st_dev;
346
347 /*
348 * If following links (-L) we need to keep track of all inodes
349 * visited so they are only visited/reported once and cycles
350 * are avoided. Otherwise, only keep track of files which are
351 * hard links so they only get reported once, and of directories
352 * so we don't report a directory and its hierarchy more than
353 * once in the special case in which it lies under the
354 * hierarchy of a directory which is a hard link.
355 * Note: Files with multiple links should only be counted
356 * once. Since each inode could possibly be referenced by a
357 * symbolic link, we need to keep track of all inodes when -L
358 * is specified.
359 */
360 if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) ||
361 (stb.st_nlink > 1)) {
362 int rc;
363 if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) {
364 if (rc == 0) {
365 /*
366 * This hierarchy, or file with multiple
367 * links, has already been visited/reported.
368 */
369 return (0);
370 } else {
371 /*
372 * An error occurred while trying to add the
373 * node to the tree.
374 */
375 if (rflg) {
376 perror("du");
377 }
378 exitdu(1);
379 }
380 }
381 }
382 blocks = stb.st_blocks;
383 /*
384 * If there are extended attributes on the current file, add their
385 * block usage onto the block count. Note: Since pathconf() always
386 * follows symlinks, only test for extended attributes using pathconf()
387 * if we are following symlinks or the current file is not a symlink.
388 */
389 if (curname && (follow_symlinks ||
390 ((stb.st_mode & S_IFMT) != S_IFLNK)) &&
391 pathconf(curname, _PC_XATTR_EXISTS) == 1) {
392 if ((fd = attropen(curname, ".", O_RDONLY)) < 0) {
393 if (rflg)
394 perror(gettext(
395 "du: can't access extended attributes"));
396 }
397 else
398 {
399 tmpflg = sflg;
400 sflg = 1;
401 blocks += descend(NULL, fd, retcode, device);
402 sflg = tmpflg;
403 }
404 }
405 if ((stb.st_mode & S_IFMT) != S_IFDIR) {
406 /*
407 * Don't print twice: if sflg, file will get printed in main().
408 * Otherwise, level == 0 means this file is listed on the
409 * command line, so print here; aflg means print all files.
410 */
411 if (sflg == 0 && (aflg || level == 0))
412 printsize(blocks, base);
413 return (blocks);
414 }
415 if (dirp != NULL)
416 /*
417 * Close the parent directory descriptor, we will reopen
418 * the directory when we pop up from this level of the
419 * recursion.
420 */
421 (void) closedir(dirp);
422 if (curname == NULL)
423 dirp = fdopendir(curfd);
424 else
425 dirp = opendir(curname);
426 if (dirp == NULL) {
427 if (rflg) {
428 (void) fprintf(stderr, "du: ");
429 perror(base);
430 }
431 *retcode = 1;
432 *ebase0 = 0;
433 return (0);
434 }
435 level++;
436 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) {
437 if (getcwd(dirbuf, PATH_MAX) == NULL) {
438 if (rflg) {
439 (void) fprintf(stderr, "du: ");
440 perror(base);
441 }
442 exitdu(1);
443 }
444 }
445 if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) {
446 if (rflg) {
447 (void) fprintf(stderr, "du: ");
448 perror(base);
449 }
450 *retcode = 1;
451 *ebase0 = 0;
452 (void) closedir(dirp);
453 dirp = NULL;
454 level--;
455 return (0);
456 }
457 while (dp = readdir(dirp)) {
458 if ((strcmp(dp->d_name, ".") == 0) ||
459 (strcmp(dp->d_name, "..") == 0))
460 continue;
461 /*
462 * we're about to append "/" + dp->d_name
463 * onto end of base; make sure there's enough
464 * space
465 */
466 while ((offset + strlen(dp->d_name) + 2) > base_len) {
467 base_len = base_len * 2;
468 if ((base = (char *)realloc(base,
469 base_len * sizeof (char))) == NULL) {
470 if (rflg) {
471 perror("du");
472 }
473 exitdu(1);
474 }
475 ebase = base + offset;
476 ebase0 = base + offset0;
477 }
478 /* LINTED - unbounded string specifier */
479 (void) sprintf(ebase, "/%s", dp->d_name);
480 curoff = telldir(dirp);
481 retval = descend(ebase + 1, 0, retcode, device);
482 /* base may have been moved via realloc in descend() */
483 ebase = base + offset;
484 ebase0 = base + offset0;
485 *ebase = 0;
486 blocks += retval;
487 if (dirp == NULL) {
488 if ((dirp = opendir(".")) == NULL) {
489 if (rflg) {
490 (void) fprintf(stderr,
491 gettext("du: Can't reopen in "));
492 perror(base);
493 }
494 *retcode = 1;
495 level--;
496 return (0);
497 }
498 seekdir(dirp, curoff);
499 }
500 }
501 (void) closedir(dirp);
502 level--;
503 dirp = NULL;
504 if (sflg == 0)
505 printsize(blocks, base);
506 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode)))
507 ret = chdir(dirbuf);
508 else
509 ret = chdir("..");
510 if (ret < 0) {
511 if (rflg) {
512 (void) sprintf(strchr(base, '\0'), "/..");
513 (void) fprintf(stderr,
514 gettext("du: Can't change dir to '..' in "));
515 perror(base);
516 }
517 exitdu(1);
518 }
519 *ebase0 = 0;
520 if (oflg)
521 return (0);
522 else
523 return (blocks);
524 }
525
526 /*
527 * Convert an unsigned long long to a string representation and place the
528 * result in the caller-supplied buffer.
529 * The given number is in units of "unit_from" size,
530 * this will first be converted to a number in 1024 or 1000 byte size,
531 * depending on the scaling factor.
532 * Then the number is scaled down until it is small enough to be in a good
533 * human readable format i.e. in the range 0 thru scale-1.
534 * If it's smaller than 10 there's room enough to provide one decimal place.
535 * The value "(unsigned long long)-1" is a special case and is always
536 * converted to "-1".
537 * Returns a pointer to the caller-supplied buffer.
538 */
539 static char *
number_to_scaled_string(numbuf_t buf,unsigned long long number,unsigned long long unit_from,unsigned long long scale)540 number_to_scaled_string(
541 numbuf_t buf, /* put the result here */
542 unsigned long long number, /* convert this number */
543 unsigned long long unit_from, /* number of bytes per input unit */
544 unsigned long long scale) /* 1024 (-h) or 1000 (-H) */
545 {
546 unsigned long long save = 0;
547 char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */
548 char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */
549
550 if ((long long)number == (long long)-1) {
551 (void) strcpy(buf, "-1");
552 return (buf);
553 }
554
555 /*
556 * Convert number from unit_from to given scale (1024 or 1000)
557 * This means multiply number with unit_from and divide by scale.
558 * if number is large enough, we first divide and then multiply
559 * to avoid an overflow
560 * (large enough here means 100 (rather arbitrary value)
561 * times scale in order to reduce rounding errors)
562 * otherwise, we first multiply and then divide
563 * to avoid an underflow
564 */
565 if (number >= 100L * scale) {
566 number = number / scale;
567 number = number * unit_from;
568 } else {
569 number = number * unit_from;
570 number = number / scale;
571 }
572
573 /*
574 * Now we have number as a count of scale units.
575 * Stop scaling when we reached exa bytes, then something is
576 * probably wrong with our number.
577 */
578 while ((number >= scale) && (*uom != 'E')) {
579 uom++; /* next unit of measurement */
580 save = number;
581 number = (number + (scale / 2)) / scale;
582 }
583
584 /* check if we should output a decimal place after the point */
585 if (save && ((save / scale) < 10)) {
586 /* sprintf() will round for us */
587 float fnum = (float)save / scale;
588 (void) sprintf(buf, "%4.1f%c", fnum, *uom);
589 } else {
590 (void) sprintf(buf, "%4llu%c", number, *uom);
591 }
592 return (buf);
593 }
594
595 static void
printsize(blkcnt_t blocks,char * path)596 printsize(blkcnt_t blocks, char *path)
597 {
598 if (hflg) {
599 numbuf_t numbuf;
600 unsigned long long scale = 1024L;
601 (void) printf(FORMAT1,
602 number_to_scaled_string(numbuf, blocks, DEV_BSIZE, scale),
603 path);
604 } else if (kflg) {
605 (void) printf(FORMAT2, (long long)kb(blocks), path);
606 } else if (mflg) {
607 (void) printf(FORMAT2, (long long)mb(blocks), path);
608 } else {
609 (void) printf(FORMAT2, (long long)blocks, path);
610 }
611 }
612
613 static void
exitdu(int exitcode)614 exitdu(int exitcode)
615 {
616 free(base);
617 free(name);
618 exit(exitcode);
619 }
620