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 * Copyright 2017 OmniTI Computer Consulting, Inc. All rights reserved.
25 * Copyright 2017 Jason King
26 * Copyright 2025 Edgecast Cloud LLC
27 */
28
29 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
30 /* All Rights Reserved */
31
32 /*
33 * du -- summarize disk usage
34 * du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] [file...]
35 */
36
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <sys/stat.h>
40 #include <sys/avl.h>
41 #include <sys/sysmacros.h>
42 #include <fcntl.h>
43 #include <dirent.h>
44 #include <limits.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <locale.h>
50 #include <libcmdutils.h>
51
52
53 static int aflg = 0;
54 static int rflg = 0;
55 static int sflg = 0;
56 static int kflg = 0;
57 static int mflg = 0;
58 static int oflg = 0;
59 static int dflg = 0;
60 static int hflg = 0;
61 static int Aflg = 0;
62 static int Hflg = 0;
63 static int Lflg = 0;
64 static int cmdarg = 0; /* Command line argument */
65 static char *dot = ".";
66 static int level = 0; /* Level of recursion */
67
68 static char *base;
69 static char *name;
70 static size_t base_len = PATH_MAX + 1; /* # of chars for base */
71 static size_t name_len = PATH_MAX + 1; /* # of chars for name */
72
73 /*
74 * Output formats. illumos uses a tab as separator, XPG4 a space.
75 */
76 #ifdef XPG4
77 #define FORMAT1 "%s %s\n"
78 #define FORMAT2 "%llu %s\n"
79 #else
80 #define FORMAT1 "%s\t%s\n"
81 #define FORMAT2 "%llu\t%s\n"
82 #endif
83
84 /*
85 * convert bytes to blocks
86 */
87 #define DEV_BSHIFT 9
88 #define DEV_KSHIFT 10
89 #define DEV_MSHIFT 20
90
91 static u_longlong_t descend(char *curname, int curfd, int *retcode,
92 dev_t device);
93 static void printsize(blkcnt_t blocks, char *path);
94 static void exitdu(int exitcode);
95
96 static avl_tree_t *tree = NULL;
97
98 int
main(int argc,char ** argv)99 main(int argc, char **argv)
100 {
101 blkcnt_t blocks = 0;
102 int c;
103 extern int optind;
104 char *np;
105 pid_t pid, wpid;
106 int status, retcode = 0;
107 setbuf(stderr, NULL);
108 (void) setlocale(LC_ALL, "");
109 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
110 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
111 #endif
112 (void) textdomain(TEXT_DOMAIN);
113
114 #ifdef XPG4
115 rflg++; /* "-r" is not an option but ON always */
116 #endif
117
118 while ((c = getopt(argc, argv, "aAdhHkLmorsx")) != EOF)
119 switch (c) {
120
121 case 'a':
122 aflg++;
123 continue;
124
125 case 'h':
126 hflg++;
127 kflg = 0;
128 mflg = 0;
129 continue;
130
131 case 'r':
132 rflg++;
133 continue;
134
135 case 's':
136 sflg++;
137 continue;
138
139 case 'k':
140 kflg++;
141 hflg = 0;
142 mflg = 0;
143 continue;
144
145 case 'm':
146 mflg++;
147 hflg = 0;
148 kflg = 0;
149 continue;
150
151 case 'o':
152 oflg++;
153 continue;
154
155 case 'd':
156 dflg++;
157 continue;
158
159 case 'x':
160 dflg++;
161 continue;
162
163 case 'A':
164 Aflg++;
165 continue;
166
167 case 'H':
168 Hflg++;
169 /* -H and -L are mutually exclusive */
170 Lflg = 0;
171 cmdarg++;
172 continue;
173
174 case 'L':
175 Lflg++;
176 /* -H and -L are mutually exclusive */
177 Hflg = 0;
178 cmdarg = 0;
179 continue;
180 case '?':
181 (void) fprintf(stderr, gettext(
182 "usage: du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] "
183 "[file...]\n"));
184 exit(2);
185 }
186 if (optind == argc) {
187 argv = ˙
188 argc = 1;
189 optind = 0;
190 }
191
192 /* "-o" and "-s" don't make any sense together. */
193 if (oflg && sflg)
194 oflg = 0;
195
196 if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) {
197 perror("du");
198 exit(1);
199 }
200 if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) {
201 perror("du");
202 free(base);
203 exit(1);
204 }
205 do {
206 pid = (pid_t)-1;
207 if (optind < argc - 1) {
208 pid = fork();
209 if (pid == (pid_t)-1) {
210 perror(gettext("du: No more processes"));
211 exitdu(1);
212 }
213 if (pid != 0) {
214 while ((wpid = wait(&status)) != pid &&
215 wpid != (pid_t)-1)
216 ;
217 if (pid != (pid_t)-1 && status != 0)
218 retcode = 1;
219 }
220 }
221 if (optind == argc - 1 || pid == 0) {
222 while (base_len < (strlen(argv[optind]) + 1)) {
223 base_len = base_len * 2;
224 if ((base = (char *)realloc(base, base_len *
225 sizeof (char))) == NULL) {
226 if (rflg) {
227 (void) fprintf(stderr, gettext(
228 "du: can't process %s"),
229 argv[optind]);
230 perror("");
231 }
232 exitdu(1);
233 }
234 }
235 if (base_len > name_len) {
236 name_len = base_len;
237 if ((name = (char *)realloc(name, name_len *
238 sizeof (char))) == NULL) {
239 if (rflg) {
240 (void) fprintf(stderr, gettext(
241 "du: can't process %s"),
242 argv[optind]);
243 perror("");
244 }
245 exitdu(1);
246 }
247 }
248 (void) strcpy(base, argv[optind]);
249 (void) strcpy(name, argv[optind]);
250 np = strrchr(name, '/');
251 if (np != NULL) {
252 *np++ = '\0';
253 if (chdir(*name ? name : "/") < 0) {
254 if (rflg) {
255 (void) fprintf(stderr, "du: ");
256 perror(*name ? name : "/");
257 exitdu(1);
258 }
259 exitdu(0);
260 }
261 } else
262 np = base;
263 blocks = descend(*np ? np : ".", 0, &retcode,
264 (dev_t)0);
265 if (sflg)
266 printsize(blocks, base);
267 if (optind < argc - 1)
268 exitdu(retcode);
269 }
270 optind++;
271 } while (optind < argc);
272 exitdu(retcode);
273
274 return (retcode);
275 }
276
277 /*
278 * descend recursively, adding up the allocated blocks.
279 * If curname is NULL, curfd is used.
280 */
281 static u_longlong_t
descend(char * curname,int curfd,int * retcode,dev_t device)282 descend(char *curname, int curfd, int *retcode, dev_t device)
283 {
284 static DIR *dirp = NULL;
285 char *ebase0, *ebase;
286 struct stat stb, stb1;
287 int i, j, ret, fd, tmpflg;
288 int follow_symlinks;
289 blkcnt_t blocks = 0;
290 off_t curoff = 0;
291 ptrdiff_t offset;
292 ptrdiff_t offset0;
293 struct dirent *dp;
294 char dirbuf[PATH_MAX + 1];
295 u_longlong_t retval;
296
297 ebase0 = ebase = strchr(base, 0);
298 if (ebase > base && ebase[-1] == '/')
299 ebase--;
300 offset = ebase - base;
301 offset0 = ebase0 - base;
302
303 if (curname)
304 curfd = AT_FDCWD;
305
306 /*
307 * If neither a -L or a -H was specified, don't follow symlinks.
308 * If a -H was specified, don't follow symlinks if the file is
309 * not a command line argument.
310 */
311 follow_symlinks = (Lflg || (Hflg && cmdarg));
312 if (follow_symlinks) {
313 i = fstatat(curfd, curname, &stb, 0);
314 j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW);
315
316 /*
317 * Make sure any files encountered while traversing the
318 * hierarchy are not considered command line arguments.
319 */
320 if (Hflg) {
321 cmdarg = 0;
322 }
323 } else {
324 i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW);
325 j = 0;
326 }
327
328 if ((i < 0) || (j < 0)) {
329 if (rflg) {
330 (void) fprintf(stderr, "du: ");
331 perror(base);
332 }
333
334 /*
335 * POSIX states that non-zero status codes are only set
336 * when an error message is printed out on stderr
337 */
338 *retcode = (rflg ? 1 : 0);
339 *ebase0 = 0;
340 return (0);
341 }
342 if (device) {
343 if (dflg && stb.st_dev != device) {
344 *ebase0 = 0;
345 return (0);
346 }
347 }
348 else
349 device = stb.st_dev;
350
351 /*
352 * If following links (-L) we need to keep track of all inodes
353 * visited so they are only visited/reported once and cycles
354 * are avoided. Otherwise, only keep track of files which are
355 * hard links so they only get reported once, and of directories
356 * so we don't report a directory and its hierarchy more than
357 * once in the special case in which it lies under the
358 * hierarchy of a directory which is a hard link.
359 * Note: Files with multiple links should only be counted
360 * once. Since each inode could possibly be referenced by a
361 * symbolic link, we need to keep track of all inodes when -L
362 * is specified.
363 */
364 if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) ||
365 (stb.st_nlink > 1)) {
366 int rc;
367 if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) {
368 if (rc == 0) {
369 /*
370 * This hierarchy, or file with multiple
371 * links, has already been visited/reported.
372 */
373 return (0);
374 } else {
375 /*
376 * An error occurred while trying to add the
377 * node to the tree.
378 */
379 if (rflg) {
380 perror("du");
381 }
382 exitdu(1);
383 }
384 }
385 }
386 blocks = Aflg ? stb.st_size : stb.st_blocks;
387
388 /*
389 * If there are extended attributes on the current file, add their
390 * block usage onto the block count. Note: Since pathconf() always
391 * follows symlinks, only test for extended attributes using pathconf()
392 * if we are following symlinks or the current file is not a symlink.
393 */
394 if (curname && (follow_symlinks ||
395 ((stb.st_mode & S_IFMT) != S_IFLNK)) &&
396 pathconf(curname, _PC_XATTR_EXISTS) == 1) {
397 if ((fd = attropen(curname, ".", O_RDONLY)) < 0) {
398 if (rflg)
399 perror(gettext(
400 "du: can't access extended attributes"));
401 }
402 else
403 {
404 tmpflg = sflg;
405 sflg = 1;
406 blocks += descend(NULL, fd, retcode, device);
407 sflg = tmpflg;
408 }
409 }
410 if ((stb.st_mode & S_IFMT) != S_IFDIR) {
411 /*
412 * Don't print twice: if sflg, file will get printed in main().
413 * Otherwise, level == 0 means this file is listed on the
414 * command line, so print here; aflg means print all files.
415 */
416 if (sflg == 0 && (aflg || level == 0))
417 printsize(blocks, base);
418 return (blocks);
419 }
420 if (dirp != NULL)
421 /*
422 * Close the parent directory descriptor, we will reopen
423 * the directory when we pop up from this level of the
424 * recursion.
425 */
426 (void) closedir(dirp);
427 if (curname == NULL)
428 dirp = fdopendir(curfd);
429 else
430 dirp = opendir(curname);
431 if (dirp == NULL) {
432 if (rflg) {
433 (void) fprintf(stderr, "du: ");
434 perror(base);
435 }
436 *retcode = 1;
437 *ebase0 = 0;
438 return (0);
439 }
440 level++;
441 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) {
442 if (getcwd(dirbuf, PATH_MAX) == NULL) {
443 if (rflg) {
444 (void) fprintf(stderr, "du: ");
445 perror(base);
446 }
447 exitdu(1);
448 }
449 }
450 if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) {
451 if (rflg) {
452 (void) fprintf(stderr, "du: ");
453 perror(base);
454 }
455 *retcode = 1;
456 *ebase0 = 0;
457 (void) closedir(dirp);
458 dirp = NULL;
459 level--;
460 return (0);
461 }
462 while ((dp = readdir(dirp)) != NULL) {
463 if ((strcmp(dp->d_name, ".") == 0) ||
464 (strcmp(dp->d_name, "..") == 0))
465 continue;
466 /*
467 * we're about to append "/" + dp->d_name
468 * onto end of base; make sure there's enough
469 * space
470 */
471 while ((offset + strlen(dp->d_name) + 2) > base_len) {
472 base_len = base_len * 2;
473 if ((base = (char *)realloc(base,
474 base_len * sizeof (char))) == NULL) {
475 if (rflg) {
476 perror("du");
477 }
478 exitdu(1);
479 }
480 ebase = base + offset;
481 ebase0 = base + offset0;
482 }
483 /* LINTED - unbounded string specifier */
484 (void) sprintf(ebase, "/%s", dp->d_name);
485 curoff = telldir(dirp);
486 retval = descend(ebase + 1, 0, retcode, device);
487 /* base may have been moved via realloc in descend() */
488 ebase = base + offset;
489 ebase0 = base + offset0;
490 *ebase = 0;
491 blocks += retval;
492 if (dirp == NULL) {
493 if ((dirp = opendir(".")) == NULL) {
494 if (rflg) {
495 (void) fprintf(stderr,
496 gettext("du: Can't reopen in "));
497 perror(base);
498 }
499 *retcode = 1;
500 level--;
501 return (0);
502 }
503 seekdir(dirp, curoff);
504 }
505 }
506 (void) closedir(dirp);
507 level--;
508 dirp = NULL;
509 if (sflg == 0)
510 printsize(blocks, base);
511 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode)))
512 ret = chdir(dirbuf);
513 else
514 ret = chdir("..");
515 if (ret < 0) {
516 if (rflg) {
517 (void) sprintf(strchr(base, '\0'), "/..");
518 (void) fprintf(stderr,
519 gettext("du: Can't change dir to '..' in "));
520 perror(base);
521 }
522 exitdu(1);
523 }
524 *ebase0 = 0;
525 if (oflg)
526 return (0);
527 else
528 return (blocks);
529 }
530
531 static u_longlong_t
mkb(blkcnt_t n,size_t shift)532 mkb(blkcnt_t n, size_t shift)
533 {
534 u_longlong_t v = (u_longlong_t)n;
535
536 /*
537 * If hflg was not used, we need to output number of blocks
538 * rounded up. Block sizes can be 1M, 1K or 512 bytes.
539 * First, convert blocks to 1 byte units and then round up.
540 */
541 if (!Aflg)
542 v <<= DEV_BSHIFT;
543
544 return (P2ROUNDUP(v, 1 << shift) >> shift);
545 }
546
547 static void
printsize(blkcnt_t blocks,char * path)548 printsize(blkcnt_t blocks, char *path)
549 {
550 if (hflg) {
551 u_longlong_t bsize = Aflg ? 1 : (1 << DEV_BSHIFT);
552
553 char buf[NN_NUMBUF_SZ] = { 0 };
554
555 nicenum_scale(blocks, bsize, buf, sizeof (buf), 0);
556 (void) printf(FORMAT1, buf, path);
557 return;
558 }
559
560 if (kflg) {
561 (void) printf(FORMAT2, mkb(blocks, DEV_KSHIFT), path);
562 } else if (mflg) {
563 (void) printf(FORMAT2, mkb(blocks, DEV_MSHIFT), path);
564 } else {
565 (void) printf(FORMAT2, mkb(blocks, DEV_BSHIFT), path);
566 }
567 }
568
569 static void
exitdu(int exitcode)570 exitdu(int exitcode)
571 {
572 free(base);
573 free(name);
574 exit(exitcode);
575 }
576