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>
28 #include <sys/ioctl.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
39 #include "kerncompat.h"
42 #include "interval_tree_generic.h"
44 static int summarize = 0;
45 static unsigned unit_mode = UNITS_RAW;
46 static char path[PATH_MAX] = { 0, };
47 static char *pathp = path;
48 static char *path_max = &path[PATH_MAX - 1];
50 struct shared_extent {
52 u64 start; /* Start of interval */
53 u64 last; /* Last location _in_ interval */
58 * extent_tree_* functions are defined in the massive interval tree
59 * macro below. This serves to illustrate the api in human-readable
63 extent_tree_insert(struct shared_extent *node, struct rb_root *root);
66 extent_tree_remove(struct shared_extent *node, struct rb_root *root);
68 static struct shared_extent *
69 extent_tree_iter_first(struct rb_root *root,
72 static struct shared_extent *
73 extent_tree_iter_next(struct shared_extent *node,
76 #define START(node) ((node)->start)
77 #define LAST(node) ((node)->last)
79 INTERVAL_TREE_DEFINE(struct shared_extent, rb,
81 START, LAST, static, extent_tree)
83 static int add_shared_extent(u64 start, u64 len, struct rb_root *root)
85 struct shared_extent *sh;
89 sh = calloc(1, sizeof(*sh));
94 sh->last = (start + len - 1);
96 extent_tree_insert(sh, root);
101 static void cleanup_shared_extents(struct rb_root *root)
103 struct shared_extent *s;
104 struct shared_extent *tmp;
109 s = extent_tree_iter_first(root, 0, -1ULL);
111 tmp = extent_tree_iter_next(s, 0, -1ULL);
112 extent_tree_remove(s, root);
119 #define dbgprintf(...)
122 * Find all extents which overlap 'n', calculate the space
123 * covered by them and remove those nodes from the tree.
125 static u64 count_unique_bytes(struct rb_root *root, struct shared_extent *n)
127 struct shared_extent *tmp;
128 u64 wstart = n->start;
131 dbgprintf("Count overlaps:");
135 * Expand our search window based on the lastest
136 * overlapping extent. Doing this will allow us to
137 * find all possible overlaps
139 if (wstart > n->start)
144 dbgprintf(" (%llu, %llu)", n->start, n->last);
147 n = extent_tree_iter_next(n, wstart, wlast);
149 extent_tree_remove(tmp, root);
153 dbgprintf("; wstart: %llu wlast: %llu total: %llu\n", wstart,
154 wlast, wlast - wstart + 1);
156 return wlast - wstart + 1;
160 * What we want to do here is get a count of shared bytes within the
161 * set of extents we have collected. Specifically, we don't want to
162 * count any byte more than once, so just adding them up doesn't
165 * For each set of overlapping extents we find the lowest start and
166 * highest end. From there we have the actual number of bytes which is
167 * shared across all of the extents in our set. A sum of each sets
168 * extent length is returned.
170 static void count_shared_bytes(struct rb_root *root, u64 *ret_cnt)
173 struct shared_extent *s = extent_tree_iter_first(root,
181 * Find all extents which overlap 's', calculate the space
182 * covered by them and remove those nodes from the tree.
184 count += count_unique_bytes(root, s);
187 * Since count_unique_bytes will be emptying the tree,
188 * we can grab the first node here
190 s = extent_tree_iter_first(root, 0, -1ULL);
193 BUG_ON(!RB_EMPTY_ROOT(root));
198 /* Track which inodes we've seen for the purposes of hardlink detection. */
200 struct rb_node i_node;
204 static struct rb_root seen_inodes = RB_ROOT;
206 static int cmp_si(struct seen_inode *si, u64 ino, u64 subvol)
210 else if (ino > si->i_ino)
212 if (subvol < si->i_subvol)
214 else if (subvol > si->i_subvol)
219 static int mark_inode_seen(u64 ino, u64 subvol)
222 struct rb_node **p = &seen_inodes.rb_node;
223 struct rb_node *parent = NULL;
224 struct seen_inode *si;
229 si = rb_entry(parent, struct seen_inode, i_node);
230 cmp = cmp_si(si, ino, subvol);
239 si = calloc(1, sizeof(*si));
244 si->i_subvol = subvol;
246 rb_link_node(&si->i_node, parent, p);
247 rb_insert_color(&si->i_node, &seen_inodes);
252 static int inode_seen(u64 ino, u64 subvol)
255 struct rb_node *n = seen_inodes.rb_node;
256 struct seen_inode *si;
259 si = rb_entry(n, struct seen_inode, i_node);
261 cmp = cmp_si(si, ino, subvol);
272 static void clear_seen_inodes(void)
274 struct rb_node *n = rb_first(&seen_inodes);
275 struct seen_inode *si;
278 si = rb_entry(n, struct seen_inode, i_node);
280 rb_erase(&si->i_node, &seen_inodes);
283 n = rb_first(&seen_inodes);
288 * Inline extents are skipped because they do not take data space,
289 * delalloc and unknown are skipped because we do not know how much
290 * space they will use yet.
292 #define SKIP_FLAGS (FIEMAP_EXTENT_UNKNOWN|FIEMAP_EXTENT_DELALLOC|FIEMAP_EXTENT_DATA_INLINE)
293 static int du_calc_file_space(int fd, struct rb_root *shared_extents,
294 u64 *ret_total, u64 *ret_shared)
297 struct fiemap *fiemap = (struct fiemap *)buf;
298 struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
299 int count = (sizeof(buf) - sizeof(*fiemap)) /
300 sizeof(struct fiemap_extent);
309 memset(fiemap, 0, sizeof(struct fiemap));
312 fiemap->fm_length = ~0ULL;
313 fiemap->fm_extent_count = count;
314 rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
320 /* If 0 extents are returned, then more ioctls are not needed */
321 if (fiemap->fm_mapped_extents == 0)
324 for (i = 0; i < fiemap->fm_mapped_extents; i++) {
325 ext_len = fm_ext[i].fe_length;
326 flags = fm_ext[i].fe_flags;
328 if (flags & FIEMAP_EXTENT_LAST)
331 if (flags & SKIP_FLAGS)
335 warning("extent %llu has length 0, skipping",
336 (unsigned long long)fm_ext[i].fe_physical);
340 file_total += ext_len;
341 if (flags & FIEMAP_EXTENT_SHARED) {
342 file_shared += ext_len;
344 if (shared_extents) {
345 ret = add_shared_extent(fm_ext[i].fe_physical,
354 fiemap->fm_start = (fm_ext[i - 1].fe_logical +
355 fm_ext[i - 1].fe_length);
358 *ret_total = file_total;
359 *ret_shared = file_shared;
370 struct rb_root shared_extents;
372 #define INIT_DU_DIR_CTXT (struct du_dir_ctxt) { 0ULL, 0ULL, NULL, RB_ROOT }
374 static int du_add_file(const char *filename, int dirfd,
375 struct rb_root *shared_extents, u64 *ret_total,
376 u64 *ret_shared, int top_level);
378 static int du_walk_dir(struct du_dir_ctxt *ctxt, struct rb_root *shared_extents)
381 struct dirent *entry;
382 DIR *dirstream = ctxt->dirstream;
389 entry = readdir(dirstream);
391 if (strcmp(entry->d_name, ".") == 0
392 || strcmp(entry->d_name, "..") == 0)
395 type = entry->d_type;
396 if (type == DT_REG || type == DT_DIR) {
399 ret = du_add_file(entry->d_name,
401 shared_extents, &tot, &shr,
403 if (ret == -ENOTTY) {
407 "failed to walk dir/file: %s :%s\n",
408 entry->d_name, strerror(-ret));
412 ctxt->bytes_total += tot;
413 ctxt->bytes_shared += shr;
416 } while (entry != NULL);
421 static int du_add_file(const char *filename, int dirfd,
422 struct rb_root *shared_extents, u64 *ret_total,
423 u64 *ret_shared, int top_level)
425 int ret, len = strlen(filename);
428 struct du_dir_ctxt dir = INIT_DU_DIR_CTXT;
432 u64 dir_set_shared = 0;
435 DIR *dirstream = NULL;
437 ret = fstatat(dirfd, filename, &st, 0);
441 if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode))
444 if (len > (path_max - pathp)) {
445 error("path too long: %s %s", path, filename);
446 return -ENAMETOOLONG;
451 ret = sprintf(pathp, "%s", filename);
453 ret = sprintf(pathp, "/%s", filename);
456 fd = open_file_or_dir3(path, &dirstream, O_RDONLY);
462 ret = lookup_path_rootid(fd, &subvol);
466 if (inode_seen(st.st_ino, subvol))
469 ret = mark_inode_seen(st.st_ino, subvol);
473 if (S_ISREG(st.st_mode)) {
474 ret = du_calc_file_space(fd, shared_extents, &file_total,
478 } else if (S_ISDIR(st.st_mode)) {
479 struct rb_root *root = shared_extents;
482 * We collect shared extents in an rb_root, the top
483 * level caller will not pass a root down, so use the
484 * one on our dir context.
487 root = &dir.shared_extents;
491 dir.dirstream = dirstream;
492 ret = du_walk_dir(&dir, root);
496 cleanup_shared_extents(root);
500 file_total = dir.bytes_total;
501 file_shared = dir.bytes_shared;
503 count_shared_bytes(root, &dir_set_shared);
506 if (!summarize || top_level) {
507 u64 excl = file_total - file_shared;
510 u64 set_shared = file_shared;
513 set_shared = dir_set_shared;
515 printf("%10s %10s %10s %s\n",
516 pretty_size_mode(file_total, unit_mode),
517 pretty_size_mode(excl, unit_mode),
518 pretty_size_mode(set_shared, unit_mode),
521 printf("%10s %10s %10s %s\n",
522 pretty_size_mode(file_total, unit_mode),
523 pretty_size_mode(excl, unit_mode),
529 *ret_total = file_total;
531 *ret_shared = file_shared;
534 close_file_or_dir(fd, dirstream);
536 /* reset path to just before this element */
542 const char * const cmd_filesystem_du_usage[] = {
543 "btrfs filesystem du [options] <path> [<path>..]",
544 "Summarize disk usage of each file.",
545 "-s|--summarize display only a total for each argument",
550 int cmd_filesystem_du(int argc, char **argv)
552 int ret = 0, err = 0;
556 unit_mode = get_unit_mode_from_arg(&argc, argv, 1);
559 static const struct option long_options[] = {
560 { "summarize", no_argument, NULL, 's'},
563 int c = getopt_long(argc, argv, "s", long_options, NULL);
572 usage(cmd_filesystem_du_usage);
576 if (check_argc_min(argc - optind, 1))
577 usage(cmd_filesystem_du_usage);
579 kernel_version = get_running_kernel_version();
581 if (kernel_version < KERNEL_VERSION(2,6,33)) {
583 "old kernel version detected, shared space will be reported as exclusive\n"
584 "due to missing support for FIEMAP_EXTENT_SHARED flag");
587 printf("%10s %10s %10s %s\n", "Total", "Exclusive", "Set shared",
590 for (i = optind; i < argc; i++) {
591 ret = du_add_file(argv[i], AT_FDCWD, NULL, NULL, NULL, 1);
593 error("cannot check space of '%s': %s", argv[i],
598 /* reset hard-link detection for each argument */