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 2017 OmniTI Computer Consulting, Inc. All rights reserved.
23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 * Copyright 2017 Jason King
26 */
27
28 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
29 /* All Rights Reserved */
30
31 /*
32 * du -- summarize disk usage
33 * du [-Adorx] [-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 Aflg = 0;
59 static int Hflg = 0;
60 static int Lflg = 0;
61 static int cmdarg = 0; /* Command line argument */
62 static char *dot = ".";
63 static int level = 0; /* Level of recursion */
64
65 static char *base;
66 static char *name;
67 static size_t base_len = PATH_MAX + 1; /* # of chars for base */
68 static size_t name_len = PATH_MAX + 1; /* # of chars for name */
69
70 /*
71 * Output formats. illumos uses a tab as separator, XPG4 a space.
72 */
73 #ifdef XPG4
74 #define FORMAT1 "%s %s\n"
75 #define FORMAT2 "%lld %s\n"
76 #else
77 #define FORMAT1 "%s\t%s\n"
78 #define FORMAT2 "%lld\t%s\n"
79 #endif
80
81 /*
82 * convert DEV_BSIZE blocks to K blocks
83 */
84 #define DEV_BSIZE 512
85 #define DEV_KSHIFT 1
86 #define DEV_MSHIFT 11
87 #define kb(n) (((u_longlong_t)(n)) >> DEV_KSHIFT)
88 #define mb(n) (((u_longlong_t)(n)) >> DEV_MSHIFT)
89
90 long wait();
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 if (optind < argc - 1) {
207 pid = fork();
208 if (pid == (pid_t)-1) {
209 perror(gettext("du: No more processes"));
210 exitdu(1);
211 }
212 if (pid != 0) {
213 while ((wpid = wait(&status)) != pid &&
214 wpid != (pid_t)-1)
215 ;
216 if (pid != (pid_t)-1 && status != 0)
217 retcode = 1;
218 }
219 }
220 if (optind == argc - 1 || pid == 0) {
221 while (base_len < (strlen(argv[optind]) + 1)) {
222 base_len = base_len * 2;
223 if ((base = (char *)realloc(base, base_len *
224 sizeof (char))) == NULL) {
225 if (rflg) {
226 (void) fprintf(stderr, gettext(
227 "du: can't process %s"),
228 argv[optind]);
229 perror("");
230 }
231 exitdu(1);
232 }
233 }
234 if (base_len > name_len) {
235 name_len = base_len;
236 if ((name = (char *)realloc(name, name_len *
237 sizeof (char))) == NULL) {
238 if (rflg) {
239 (void) fprintf(stderr, gettext(
240 "du: can't process %s"),
241 argv[optind]);
242 perror("");
243 }
244 exitdu(1);
245 }
246 }
247 (void) strcpy(base, argv[optind]);
248 (void) strcpy(name, argv[optind]);
249 if (np = strrchr(name, '/')) {
250 *np++ = '\0';
251 if (chdir(*name ? name : "/") < 0) {
252 if (rflg) {
253 (void) fprintf(stderr, "du: ");
254 perror(*name ? name : "/");
255 exitdu(1);
256 }
257 exitdu(0);
258 }
259 } else
260 np = base;
261 blocks = descend(*np ? np : ".", 0, &retcode,
262 (dev_t)0);
263 if (sflg)
264 printsize(blocks, base);
265 if (optind < argc - 1)
266 exitdu(retcode);
267 }
268 optind++;
269 } while (optind < argc);
270 exitdu(retcode);
271
272 return (retcode);
273 }
274
275 /*
276 * descend recursively, adding up the allocated blocks.
277 * If curname is NULL, curfd is used.
278 */
279 static u_longlong_t
descend(char * curname,int curfd,int * retcode,dev_t device)280 descend(char *curname, int curfd, int *retcode, dev_t device)
281 {
282 static DIR *dirp = NULL;
283 char *ebase0, *ebase;
284 struct stat stb, stb1;
285 int i, j, ret, fd, tmpflg;
286 int follow_symlinks;
287 blkcnt_t blocks = 0;
288 off_t curoff = 0;
289 ptrdiff_t offset;
290 ptrdiff_t offset0;
291 struct dirent *dp;
292 char dirbuf[PATH_MAX + 1];
293 u_longlong_t retval;
294
295 ebase0 = ebase = strchr(base, 0);
296 if (ebase > base && ebase[-1] == '/')
297 ebase--;
298 offset = ebase - base;
299 offset0 = ebase0 - base;
300
301 if (curname)
302 curfd = AT_FDCWD;
303
304 /*
305 * If neither a -L or a -H was specified, don't follow symlinks.
306 * If a -H was specified, don't follow symlinks if the file is
307 * not a command line argument.
308 */
309 follow_symlinks = (Lflg || (Hflg && cmdarg));
310 if (follow_symlinks) {
311 i = fstatat(curfd, curname, &stb, 0);
312 j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW);
313
314 /*
315 * Make sure any files encountered while traversing the
316 * hierarchy are not considered command line arguments.
317 */
318 if (Hflg) {
319 cmdarg = 0;
320 }
321 } else {
322 i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW);
323 j = 0;
324 }
325
326 if ((i < 0) || (j < 0)) {
327 if (rflg) {
328 (void) fprintf(stderr, "du: ");
329 perror(base);
330 }
331
332 /*
333 * POSIX states that non-zero status codes are only set
334 * when an error message is printed out on stderr
335 */
336 *retcode = (rflg ? 1 : 0);
337 *ebase0 = 0;
338 return (0);
339 }
340 if (device) {
341 if (dflg && stb.st_dev != device) {
342 *ebase0 = 0;
343 return (0);
344 }
345 }
346 else
347 device = stb.st_dev;
348
349 /*
350 * If following links (-L) we need to keep track of all inodes
351 * visited so they are only visited/reported once and cycles
352 * are avoided. Otherwise, only keep track of files which are
353 * hard links so they only get reported once, and of directories
354 * so we don't report a directory and its hierarchy more than
355 * once in the special case in which it lies under the
356 * hierarchy of a directory which is a hard link.
357 * Note: Files with multiple links should only be counted
358 * once. Since each inode could possibly be referenced by a
359 * symbolic link, we need to keep track of all inodes when -L
360 * is specified.
361 */
362 if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) ||
363 (stb.st_nlink > 1)) {
364 int rc;
365 if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) {
366 if (rc == 0) {
367 /*
368 * This hierarchy, or file with multiple
369 * links, has already been visited/reported.
370 */
371 return (0);
372 } else {
373 /*
374 * An error occurred while trying to add the
375 * node to the tree.
376 */
377 if (rflg) {
378 perror("du");
379 }
380 exitdu(1);
381 }
382 }
383 }
384 blocks = Aflg ? stb.st_size : stb.st_blocks;
385
386 /*
387 * If there are extended attributes on the current file, add their
388 * block usage onto the block count. Note: Since pathconf() always
389 * follows symlinks, only test for extended attributes using pathconf()
390 * if we are following symlinks or the current file is not a symlink.
391 */
392 if (curname && (follow_symlinks ||
393 ((stb.st_mode & S_IFMT) != S_IFLNK)) &&
394 pathconf(curname, _PC_XATTR_EXISTS) == 1) {
395 if ((fd = attropen(curname, ".", O_RDONLY)) < 0) {
396 if (rflg)
397 perror(gettext(
398 "du: can't access extended attributes"));
399 }
400 else
401 {
402 tmpflg = sflg;
403 sflg = 1;
404 blocks += descend(NULL, fd, retcode, device);
405 sflg = tmpflg;
406 }
407 }
408 if ((stb.st_mode & S_IFMT) != S_IFDIR) {
409 /*
410 * Don't print twice: if sflg, file will get printed in main().
411 * Otherwise, level == 0 means this file is listed on the
412 * command line, so print here; aflg means print all files.
413 */
414 if (sflg == 0 && (aflg || level == 0))
415 printsize(blocks, base);
416 return (blocks);
417 }
418 if (dirp != NULL)
419 /*
420 * Close the parent directory descriptor, we will reopen
421 * the directory when we pop up from this level of the
422 * recursion.
423 */
424 (void) closedir(dirp);
425 if (curname == NULL)
426 dirp = fdopendir(curfd);
427 else
428 dirp = opendir(curname);
429 if (dirp == NULL) {
430 if (rflg) {
431 (void) fprintf(stderr, "du: ");
432 perror(base);
433 }
434 *retcode = 1;
435 *ebase0 = 0;
436 return (0);
437 }
438 level++;
439 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) {
440 if (getcwd(dirbuf, PATH_MAX) == NULL) {
441 if (rflg) {
442 (void) fprintf(stderr, "du: ");
443 perror(base);
444 }
445 exitdu(1);
446 }
447 }
448 if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) {
449 if (rflg) {
450 (void) fprintf(stderr, "du: ");
451 perror(base);
452 }
453 *retcode = 1;
454 *ebase0 = 0;
455 (void) closedir(dirp);
456 dirp = NULL;
457 level--;
458 return (0);
459 }
460 while (dp = readdir(dirp)) {
461 if ((strcmp(dp->d_name, ".") == 0) ||
462 (strcmp(dp->d_name, "..") == 0))
463 continue;
464 /*
465 * we're about to append "/" + dp->d_name
466 * onto end of base; make sure there's enough
467 * space
468 */
469 while ((offset + strlen(dp->d_name) + 2) > base_len) {
470 base_len = base_len * 2;
471 if ((base = (char *)realloc(base,
472 base_len * sizeof (char))) == NULL) {
473 if (rflg) {
474 perror("du");
475 }
476 exitdu(1);
477 }
478 ebase = base + offset;
479 ebase0 = base + offset0;
480 }
481 /* LINTED - unbounded string specifier */
482 (void) sprintf(ebase, "/%s", dp->d_name);
483 curoff = telldir(dirp);
484 retval = descend(ebase + 1, 0, retcode, device);
485 /* base may have been moved via realloc in descend() */
486 ebase = base + offset;
487 ebase0 = base + offset0;
488 *ebase = 0;
489 blocks += retval;
490 if (dirp == NULL) {
491 if ((dirp = opendir(".")) == NULL) {
492 if (rflg) {
493 (void) fprintf(stderr,
494 gettext("du: Can't reopen in "));
495 perror(base);
496 }
497 *retcode = 1;
498 level--;
499 return (0);
500 }
501 seekdir(dirp, curoff);
502 }
503 }
504 (void) closedir(dirp);
505 level--;
506 dirp = NULL;
507 if (sflg == 0)
508 printsize(blocks, base);
509 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode)))
510 ret = chdir(dirbuf);
511 else
512 ret = chdir("..");
513 if (ret < 0) {
514 if (rflg) {
515 (void) sprintf(strchr(base, '\0'), "/..");
516 (void) fprintf(stderr,
517 gettext("du: Can't change dir to '..' in "));
518 perror(base);
519 }
520 exitdu(1);
521 }
522 *ebase0 = 0;
523 if (oflg)
524 return (0);
525 else
526 return (blocks);
527 }
528
529 static void
printsize(blkcnt_t blocks,char * path)530 printsize(blkcnt_t blocks, char *path)
531 {
532 u_longlong_t bsize;
533
534 bsize = Aflg ? 1 : DEV_BSIZE;
535
536 if (hflg) {
537 char buf[NN_NUMBUF_SZ] = { 0 };
538
539 nicenum_scale(blocks, bsize, buf, sizeof (buf), 0);
540 (void) printf(FORMAT1, buf, path);
541 } else if (kflg) {
542 (void) printf(FORMAT2, (long long)kb(blocks), path);
543 } else if (mflg) {
544 (void) printf(FORMAT2, (long long)mb(blocks), path);
545 } else {
546 (void) printf(FORMAT2, (long long)blocks, path);
547 }
548 }
549
550 static void
exitdu(int exitcode)551 exitdu(int exitcode)
552 {
553 free(base);
554 free(name);
555 exit(exitcode);
556 }
557