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