Btrfs progs v4.17.1
[btrfs-progs-unstable/devel.git] / cmds-fi-du.c
blob4e639f6dc231b770203616327cbe6bf4a0dac01e
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public
4 * License v2 as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 * General Public License for more details.
11 * You should have received a copy of the GNU General Public
12 * License along with this program; if not, write to the
13 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
14 * Boston, MA 021110-1307, USA.
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdarg.h>
24 #include <getopt.h>
25 #include <fcntl.h>
26 #include <dirent.h>
28 #include <sys/ioctl.h>
29 #include <linux/fs.h>
30 #include <linux/version.h>
31 #include <linux/fiemap.h>
33 #if !defined(FIEMAP_EXTENT_SHARED) && (HAVE_OWN_FIEMAP_EXTENT_SHARED_DEFINE == 1)
34 #define FIEMAP_EXTENT_SHARED 0x00002000
35 #endif
37 #include "utils.h"
38 #include "commands.h"
39 #include "kerncompat.h"
40 #include "rbtree.h"
42 #include "interval_tree_generic.h"
43 #include "help.h"
44 #include "fsfeatures.h"
46 static int summarize = 0;
47 static unsigned unit_mode = UNITS_RAW;
48 static char path[PATH_MAX] = { 0, };
49 static char *pathp = path;
50 static char *path_max = &path[PATH_MAX - 1];
52 struct shared_extent {
53 struct rb_node rb;
54 u64 start; /* Start of interval */
55 u64 last; /* Last location _in_ interval */
56 u64 __subtree_last;
60 * extent_tree_* functions are defined in the massive interval tree
61 * macro below. This serves to illustrate the api in human-readable
62 * terms.
64 static void
65 extent_tree_insert(struct shared_extent *node, struct rb_root *root);
67 static void
68 extent_tree_remove(struct shared_extent *node, struct rb_root *root);
70 static struct shared_extent *
71 extent_tree_iter_first(struct rb_root *root,
72 u64 start, u64 last);
74 static struct shared_extent *
75 extent_tree_iter_next(struct shared_extent *node,
76 u64 start, u64 last);
78 #define START(node) ((node)->start)
79 #define LAST(node) ((node)->last)
81 INTERVAL_TREE_DEFINE(struct shared_extent, rb,
82 u64, __subtree_last,
83 START, LAST, static, extent_tree)
85 static int add_shared_extent(u64 start, u64 len, struct rb_root *root)
87 struct shared_extent *sh;
89 ASSERT(len != 0);
91 sh = calloc(1, sizeof(*sh));
92 if (!sh)
93 return -ENOMEM;
95 sh->start = start;
96 sh->last = (start + len - 1);
98 extent_tree_insert(sh, root);
100 return 0;
103 static void cleanup_shared_extents(struct rb_root *root)
105 struct shared_extent *s;
106 struct shared_extent *tmp;
108 if (!root)
109 return;
111 s = extent_tree_iter_first(root, 0, -1ULL);
112 while (s) {
113 tmp = extent_tree_iter_next(s, 0, -1ULL);
114 extent_tree_remove(s, root);
116 free(s);
117 s = tmp;
121 #define dbgprintf(...)
124 * Find all extents which overlap 'n', calculate the space
125 * covered by them and remove those nodes from the tree.
127 static u64 count_unique_bytes(struct rb_root *root, struct shared_extent *n)
129 struct shared_extent *tmp;
130 u64 wstart = n->start;
131 u64 wlast = n->last;
133 dbgprintf("Count overlaps:");
135 do {
137 * Expand our search window based on the lastest
138 * overlapping extent. Doing this will allow us to
139 * find all possible overlaps
141 if (wstart > n->start)
142 wstart = n->start;
143 if (wlast < n->last)
144 wlast = n->last;
146 dbgprintf(" (%llu, %llu)", n->start, n->last);
148 tmp = n;
149 n = extent_tree_iter_next(n, wstart, wlast);
151 extent_tree_remove(tmp, root);
152 free(tmp);
153 } while (n);
155 dbgprintf("; wstart: %llu wlast: %llu total: %llu\n", wstart,
156 wlast, wlast - wstart + 1);
158 return wlast - wstart + 1;
162 * What we want to do here is get a count of shared bytes within the
163 * set of extents we have collected. Specifically, we don't want to
164 * count any byte more than once, so just adding them up doesn't
165 * work.
167 * For each set of overlapping extents we find the lowest start and
168 * highest end. From there we have the actual number of bytes which is
169 * shared across all of the extents in our set. A sum of each sets
170 * extent length is returned.
172 static void count_shared_bytes(struct rb_root *root, u64 *ret_cnt)
174 u64 count = 0;
175 struct shared_extent *s = extent_tree_iter_first(root,
176 0, -1ULL);
178 if (!s)
179 goto out;
181 while (s) {
183 * Find all extents which overlap 's', calculate the space
184 * covered by them and remove those nodes from the tree.
186 count += count_unique_bytes(root, s);
189 * Since count_unique_bytes will be emptying the tree,
190 * we can grab the first node here
192 s = extent_tree_iter_first(root, 0, -1ULL);
195 BUG_ON(!RB_EMPTY_ROOT(root));
196 out:
197 *ret_cnt = count;
200 /* Track which inodes we've seen for the purposes of hardlink detection. */
201 struct seen_inode {
202 struct rb_node i_node;
203 u64 i_ino;
204 u64 i_subvol;
206 static struct rb_root seen_inodes = RB_ROOT;
208 static int cmp_si(struct seen_inode *si, u64 ino, u64 subvol)
210 if (ino < si->i_ino)
211 return -1;
212 else if (ino > si->i_ino)
213 return 1;
214 if (subvol < si->i_subvol)
215 return -1;
216 else if (subvol > si->i_subvol)
217 return 1;
218 return 0;
221 static int mark_inode_seen(u64 ino, u64 subvol)
223 int cmp;
224 struct rb_node **p = &seen_inodes.rb_node;
225 struct rb_node *parent = NULL;
226 struct seen_inode *si;
228 while (*p) {
229 parent = *p;
231 si = rb_entry(parent, struct seen_inode, i_node);
232 cmp = cmp_si(si, ino, subvol);
233 if (cmp < 0)
234 p = &(*p)->rb_left;
235 else if (cmp > 0)
236 p = &(*p)->rb_right;
237 else
238 return -EEXIST;
241 si = calloc(1, sizeof(*si));
242 if (!si)
243 return -ENOMEM;
245 si->i_ino = ino;
246 si->i_subvol = subvol;
248 rb_link_node(&si->i_node, parent, p);
249 rb_insert_color(&si->i_node, &seen_inodes);
251 return 0;
254 static int inode_seen(u64 ino, u64 subvol)
256 int cmp;
257 struct rb_node *n = seen_inodes.rb_node;
258 struct seen_inode *si;
260 while (n) {
261 si = rb_entry(n, struct seen_inode, i_node);
263 cmp = cmp_si(si, ino, subvol);
264 if (cmp < 0)
265 n = n->rb_left;
266 else if (cmp > 0)
267 n = n->rb_right;
268 else
269 return -EEXIST;
271 return 0;
274 static void clear_seen_inodes(void)
276 struct rb_node *n = rb_first(&seen_inodes);
277 struct seen_inode *si;
279 while (n) {
280 si = rb_entry(n, struct seen_inode, i_node);
282 rb_erase(&si->i_node, &seen_inodes);
283 free(si);
285 n = rb_first(&seen_inodes);
290 * Inline extents are skipped because they do not take data space,
291 * delalloc and unknown are skipped because we do not know how much
292 * space they will use yet.
294 #define SKIP_FLAGS (FIEMAP_EXTENT_UNKNOWN|FIEMAP_EXTENT_DELALLOC|FIEMAP_EXTENT_DATA_INLINE)
295 static int du_calc_file_space(int fd, struct rb_root *shared_extents,
296 u64 *ret_total, u64 *ret_shared)
298 char buf[16384];
299 struct fiemap *fiemap = (struct fiemap *)buf;
300 struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
301 int count = (sizeof(buf) - sizeof(*fiemap)) /
302 sizeof(struct fiemap_extent);
303 unsigned int i, ret;
304 int last = 0;
305 int rc;
306 u64 ext_len;
307 u64 file_total = 0;
308 u64 file_shared = 0;
309 u32 flags;
311 memset(fiemap, 0, sizeof(struct fiemap));
313 do {
314 fiemap->fm_length = ~0ULL;
315 fiemap->fm_extent_count = count;
316 rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
317 if (rc < 0) {
318 ret = -errno;
319 goto out;
322 /* If 0 extents are returned, then more ioctls are not needed */
323 if (fiemap->fm_mapped_extents == 0)
324 break;
326 for (i = 0; i < fiemap->fm_mapped_extents; i++) {
327 ext_len = fm_ext[i].fe_length;
328 flags = fm_ext[i].fe_flags;
330 if (flags & FIEMAP_EXTENT_LAST)
331 last = 1;
333 if (flags & SKIP_FLAGS)
334 continue;
336 if (ext_len == 0) {
337 warning("extent %llu has length 0, skipping",
338 (unsigned long long)fm_ext[i].fe_physical);
339 continue;
342 file_total += ext_len;
343 if (flags & FIEMAP_EXTENT_SHARED) {
344 file_shared += ext_len;
346 if (shared_extents) {
347 ret = add_shared_extent(fm_ext[i].fe_physical,
348 ext_len,
349 shared_extents);
350 if (ret)
351 goto out;
356 fiemap->fm_start = (fm_ext[i - 1].fe_logical +
357 fm_ext[i - 1].fe_length);
358 } while (last == 0);
360 *ret_total = file_total;
361 *ret_shared = file_shared;
363 ret = 0;
364 out:
365 return ret;
368 struct du_dir_ctxt {
369 u64 bytes_total;
370 u64 bytes_shared;
371 DIR *dirstream;
372 struct rb_root shared_extents;
374 #define INIT_DU_DIR_CTXT (struct du_dir_ctxt) { 0ULL, 0ULL, NULL, RB_ROOT }
376 static int du_add_file(const char *filename, int dirfd,
377 struct rb_root *shared_extents, u64 *ret_total,
378 u64 *ret_shared, int top_level);
380 static int du_walk_dir(struct du_dir_ctxt *ctxt, struct rb_root *shared_extents)
382 int ret, type;
383 struct dirent *entry;
384 DIR *dirstream = ctxt->dirstream;
386 ret = 0;
387 do {
388 u64 tot, shr;
390 errno = 0;
391 entry = readdir(dirstream);
392 if (entry) {
393 if (strcmp(entry->d_name, ".") == 0
394 || strcmp(entry->d_name, "..") == 0)
395 continue;
397 type = entry->d_type;
398 if (type == DT_REG || type == DT_DIR) {
399 tot = shr = 0;
401 ret = du_add_file(entry->d_name,
402 dirfd(dirstream),
403 shared_extents, &tot, &shr,
405 if (ret == -ENOTTY) {
406 ret = 0;
407 continue;
408 } else if (ret) {
409 fprintf(stderr,
410 "failed to walk dir/file: %s :%s\n",
411 entry->d_name, strerror(-ret));
412 break;
415 ctxt->bytes_total += tot;
416 ctxt->bytes_shared += shr;
419 } while (entry != NULL);
421 return ret;
424 static int du_add_file(const char *filename, int dirfd,
425 struct rb_root *shared_extents, u64 *ret_total,
426 u64 *ret_shared, int top_level)
428 int ret, len = strlen(filename);
429 char *pathtmp;
430 struct stat st;
431 struct du_dir_ctxt dir = INIT_DU_DIR_CTXT;
432 int is_dir = 0;
433 u64 file_total = 0;
434 u64 file_shared = 0;
435 u64 dir_set_shared = 0;
436 int fd;
437 DIR *dirstream = NULL;
439 ret = fstatat(dirfd, filename, &st, 0);
440 if (ret)
441 return -errno;
443 if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode))
444 return 0;
446 if (len > (path_max - pathp)) {
447 error("path too long: %s %s", path, filename);
448 return -ENAMETOOLONG;
451 pathtmp = pathp;
452 if (pathp == path || *(pathp - 1) == '/')
453 ret = sprintf(pathp, "%s", filename);
454 else
455 ret = sprintf(pathp, "/%s", filename);
456 pathp += ret;
458 fd = open_file_or_dir3(path, &dirstream, O_RDONLY);
459 if (fd < 0) {
460 ret = -errno;
461 goto out;
465 * If st.st_ino == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID ==2, there is no any
466 * related tree
468 if (st.st_ino != BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
469 u64 subvol;
471 ret = lookup_path_rootid(fd, &subvol);
472 if (ret)
473 goto out_close;
475 if (inode_seen(st.st_ino, subvol))
476 goto out_close;
478 ret = mark_inode_seen(st.st_ino, subvol);
479 if (ret)
480 goto out_close;
483 if (S_ISREG(st.st_mode)) {
484 ret = du_calc_file_space(fd, shared_extents, &file_total,
485 &file_shared);
486 if (ret)
487 goto out_close;
488 } else if (S_ISDIR(st.st_mode)) {
489 struct rb_root *root = shared_extents;
492 * We collect shared extents in an rb_root, the top
493 * level caller will not pass a root down, so use the
494 * one on our dir context.
496 if (top_level)
497 root = &dir.shared_extents;
499 is_dir = 1;
501 dir.dirstream = dirstream;
502 ret = du_walk_dir(&dir, root);
503 *pathp = '\0';
504 if (ret) {
505 if (top_level)
506 cleanup_shared_extents(root);
507 goto out_close;
510 file_total = dir.bytes_total;
511 file_shared = dir.bytes_shared;
512 if (top_level)
513 count_shared_bytes(root, &dir_set_shared);
516 if (!summarize || top_level) {
517 u64 excl = file_total - file_shared;
519 if (top_level) {
520 u64 set_shared = file_shared;
522 if (is_dir)
523 set_shared = dir_set_shared;
525 printf("%10s %10s %10s %s\n",
526 pretty_size_mode(file_total, unit_mode),
527 pretty_size_mode(excl, unit_mode),
528 pretty_size_mode(set_shared, unit_mode),
529 path);
530 } else {
531 printf("%10s %10s %10s %s\n",
532 pretty_size_mode(file_total, unit_mode),
533 pretty_size_mode(excl, unit_mode),
534 "-", path);
538 if (ret_total)
539 *ret_total = file_total;
540 if (ret_shared)
541 *ret_shared = file_shared;
543 out_close:
544 close_file_or_dir(fd, dirstream);
545 out:
546 /* reset path to just before this element */
547 pathp = pathtmp;
549 return ret;
552 const char * const cmd_filesystem_du_usage[] = {
553 "btrfs filesystem du [options] <path> [<path>..]",
554 "Summarize disk usage of each file.",
555 "-s|--summarize display only a total for each argument",
556 HELPINFO_UNITS_LONG,
557 NULL
560 int cmd_filesystem_du(int argc, char **argv)
562 int ret = 0, err = 0;
563 int i;
564 u32 kernel_version;
566 unit_mode = get_unit_mode_from_arg(&argc, argv, 1);
568 optind = 0;
569 while (1) {
570 static const struct option long_options[] = {
571 { "summarize", no_argument, NULL, 's'},
572 { NULL, 0, NULL, 0 }
574 int c = getopt_long(argc, argv, "s", long_options, NULL);
576 if (c < 0)
577 break;
578 switch (c) {
579 case 's':
580 summarize = 1;
581 break;
582 default:
583 usage(cmd_filesystem_du_usage);
587 if (check_argc_min(argc - optind, 1))
588 usage(cmd_filesystem_du_usage);
590 kernel_version = get_running_kernel_version();
592 if (kernel_version < KERNEL_VERSION(2,6,33)) {
593 warning(
594 "old kernel version detected, shared space will be reported as exclusive\n"
595 "due to missing support for FIEMAP_EXTENT_SHARED flag");
598 printf("%10s %10s %10s %s\n", "Total", "Exclusive", "Set shared",
599 "Filename");
601 for (i = optind; i < argc; i++) {
602 ret = du_add_file(argv[i], AT_FDCWD, NULL, NULL, NULL, 1);
603 if (ret) {
604 error("cannot check space of '%s': %s", argv[i],
605 strerror(-ret));
606 err = 1;
609 /* reset hard-link detection for each argument */
610 clear_seen_inodes();
613 return err;