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"
45 static int summarize = 0;
46 static unsigned unit_mode = UNITS_RAW;
47 static char path[PATH_MAX] = { 0, };
48 static char *pathp = path;
49 static char *path_max = &path[PATH_MAX - 1];
51 struct shared_extent {
53 u64 start; /* Start of interval */
54 u64 last; /* Last location _in_ interval */
59 * extent_tree_* functions are defined in the massive interval tree
60 * macro below. This serves to illustrate the api in human-readable
64 extent_tree_insert(struct shared_extent *node, struct rb_root *root);
67 extent_tree_remove(struct shared_extent *node, struct rb_root *root);
69 static struct shared_extent *
70 extent_tree_iter_first(struct rb_root *root,
73 static struct shared_extent *
74 extent_tree_iter_next(struct shared_extent *node,
77 #define START(node) ((node)->start)
78 #define LAST(node) ((node)->last)
80 INTERVAL_TREE_DEFINE(struct shared_extent, rb,
82 START, LAST, static, extent_tree)
84 static int add_shared_extent(u64 start, u64 len, struct rb_root *root)
86 struct shared_extent *sh;
90 sh = calloc(1, sizeof(*sh));
95 sh->last = (start + len - 1);
97 extent_tree_insert(sh, root);
102 static void cleanup_shared_extents(struct rb_root *root)
104 struct shared_extent *s;
105 struct shared_extent *tmp;
110 s = extent_tree_iter_first(root, 0, -1ULL);
112 tmp = extent_tree_iter_next(s, 0, -1ULL);
113 extent_tree_remove(s, root);
120 #define dbgprintf(...)
123 * Find all extents which overlap 'n', calculate the space
124 * covered by them and remove those nodes from the tree.
126 static u64 count_unique_bytes(struct rb_root *root, struct shared_extent *n)
128 struct shared_extent *tmp;
129 u64 wstart = n->start;
132 dbgprintf("Count overlaps:");
136 * Expand our search window based on the lastest
137 * overlapping extent. Doing this will allow us to
138 * find all possible overlaps
140 if (wstart > n->start)
145 dbgprintf(" (%llu, %llu)", n->start, n->last);
148 n = extent_tree_iter_next(n, wstart, wlast);
150 extent_tree_remove(tmp, root);
154 dbgprintf("; wstart: %llu wlast: %llu total: %llu\n", wstart,
155 wlast, wlast - wstart + 1);
157 return wlast - wstart + 1;
161 * What we want to do here is get a count of shared bytes within the
162 * set of extents we have collected. Specifically, we don't want to
163 * count any byte more than once, so just adding them up doesn't
166 * For each set of overlapping extents we find the lowest start and
167 * highest end. From there we have the actual number of bytes which is
168 * shared across all of the extents in our set. A sum of each sets
169 * extent length is returned.
171 static void count_shared_bytes(struct rb_root *root, u64 *ret_cnt)
174 struct shared_extent *s = extent_tree_iter_first(root,
182 * Find all extents which overlap 's', calculate the space
183 * covered by them and remove those nodes from the tree.
185 count += count_unique_bytes(root, s);
188 * Since count_unique_bytes will be emptying the tree,
189 * we can grab the first node here
191 s = extent_tree_iter_first(root, 0, -1ULL);
194 BUG_ON(!RB_EMPTY_ROOT(root));
199 /* Track which inodes we've seen for the purposes of hardlink detection. */
201 struct rb_node i_node;
205 static struct rb_root seen_inodes = RB_ROOT;
207 static int cmp_si(struct seen_inode *si, u64 ino, u64 subvol)
211 else if (ino > si->i_ino)
213 if (subvol < si->i_subvol)
215 else if (subvol > si->i_subvol)
220 static int mark_inode_seen(u64 ino, u64 subvol)
223 struct rb_node **p = &seen_inodes.rb_node;
224 struct rb_node *parent = NULL;
225 struct seen_inode *si;
230 si = rb_entry(parent, struct seen_inode, i_node);
231 cmp = cmp_si(si, ino, subvol);
240 si = calloc(1, sizeof(*si));
245 si->i_subvol = subvol;
247 rb_link_node(&si->i_node, parent, p);
248 rb_insert_color(&si->i_node, &seen_inodes);
253 static int inode_seen(u64 ino, u64 subvol)
256 struct rb_node *n = seen_inodes.rb_node;
257 struct seen_inode *si;
260 si = rb_entry(n, struct seen_inode, i_node);
262 cmp = cmp_si(si, ino, subvol);
273 static void clear_seen_inodes(void)
275 struct rb_node *n = rb_first(&seen_inodes);
276 struct seen_inode *si;
279 si = rb_entry(n, struct seen_inode, i_node);
281 rb_erase(&si->i_node, &seen_inodes);
284 n = rb_first(&seen_inodes);
289 * Inline extents are skipped because they do not take data space,
290 * delalloc and unknown are skipped because we do not know how much
291 * space they will use yet.
293 #define SKIP_FLAGS (FIEMAP_EXTENT_UNKNOWN|FIEMAP_EXTENT_DELALLOC|FIEMAP_EXTENT_DATA_INLINE)
294 static int du_calc_file_space(int fd, struct rb_root *shared_extents,
295 u64 *ret_total, u64 *ret_shared)
298 struct fiemap *fiemap = (struct fiemap *)buf;
299 struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
300 int count = (sizeof(buf) - sizeof(*fiemap)) /
301 sizeof(struct fiemap_extent);
310 memset(fiemap, 0, sizeof(struct fiemap));
313 fiemap->fm_length = ~0ULL;
314 fiemap->fm_extent_count = count;
315 rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
321 /* If 0 extents are returned, then more ioctls are not needed */
322 if (fiemap->fm_mapped_extents == 0)
325 for (i = 0; i < fiemap->fm_mapped_extents; i++) {
326 ext_len = fm_ext[i].fe_length;
327 flags = fm_ext[i].fe_flags;
329 if (flags & FIEMAP_EXTENT_LAST)
332 if (flags & SKIP_FLAGS)
336 warning("extent %llu has length 0, skipping",
337 (unsigned long long)fm_ext[i].fe_physical);
341 file_total += ext_len;
342 if (flags & FIEMAP_EXTENT_SHARED) {
343 file_shared += ext_len;
345 if (shared_extents) {
346 ret = add_shared_extent(fm_ext[i].fe_physical,
355 fiemap->fm_start = (fm_ext[i - 1].fe_logical +
356 fm_ext[i - 1].fe_length);
359 *ret_total = file_total;
360 *ret_shared = file_shared;
371 struct rb_root shared_extents;
373 #define INIT_DU_DIR_CTXT (struct du_dir_ctxt) { 0ULL, 0ULL, NULL, RB_ROOT }
375 static int du_add_file(const char *filename, int dirfd,
376 struct rb_root *shared_extents, u64 *ret_total,
377 u64 *ret_shared, int top_level);
379 static int du_walk_dir(struct du_dir_ctxt *ctxt, struct rb_root *shared_extents)
382 struct dirent *entry;
383 DIR *dirstream = ctxt->dirstream;
390 entry = readdir(dirstream);
392 if (strcmp(entry->d_name, ".") == 0
393 || strcmp(entry->d_name, "..") == 0)
396 type = entry->d_type;
397 if (type == DT_REG || type == DT_DIR) {
400 ret = du_add_file(entry->d_name,
402 shared_extents, &tot, &shr,
404 if (ret == -ENOTTY) {
408 "failed to walk dir/file: %s :%s\n",
409 entry->d_name, strerror(-ret));
413 ctxt->bytes_total += tot;
414 ctxt->bytes_shared += shr;
417 } while (entry != NULL);
422 static int du_add_file(const char *filename, int dirfd,
423 struct rb_root *shared_extents, u64 *ret_total,
424 u64 *ret_shared, int top_level)
426 int ret, len = strlen(filename);
429 struct du_dir_ctxt dir = INIT_DU_DIR_CTXT;
433 u64 dir_set_shared = 0;
436 DIR *dirstream = NULL;
438 ret = fstatat(dirfd, filename, &st, 0);
442 if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode))
445 if (len > (path_max - pathp)) {
446 error("path too long: %s %s", path, filename);
447 return -ENAMETOOLONG;
452 ret = sprintf(pathp, "%s", filename);
454 ret = sprintf(pathp, "/%s", filename);
457 fd = open_file_or_dir3(path, &dirstream, O_RDONLY);
463 ret = lookup_path_rootid(fd, &subvol);
467 if (inode_seen(st.st_ino, subvol))
470 ret = mark_inode_seen(st.st_ino, subvol);
474 if (S_ISREG(st.st_mode)) {
475 ret = du_calc_file_space(fd, shared_extents, &file_total,
479 } else if (S_ISDIR(st.st_mode)) {
480 struct rb_root *root = shared_extents;
483 * We collect shared extents in an rb_root, the top
484 * level caller will not pass a root down, so use the
485 * one on our dir context.
488 root = &dir.shared_extents;
492 dir.dirstream = dirstream;
493 ret = du_walk_dir(&dir, root);
497 cleanup_shared_extents(root);
501 file_total = dir.bytes_total;
502 file_shared = dir.bytes_shared;
504 count_shared_bytes(root, &dir_set_shared);
507 if (!summarize || top_level) {
508 u64 excl = file_total - file_shared;
511 u64 set_shared = file_shared;
514 set_shared = dir_set_shared;
516 printf("%10s %10s %10s %s\n",
517 pretty_size_mode(file_total, unit_mode),
518 pretty_size_mode(excl, unit_mode),
519 pretty_size_mode(set_shared, unit_mode),
522 printf("%10s %10s %10s %s\n",
523 pretty_size_mode(file_total, unit_mode),
524 pretty_size_mode(excl, unit_mode),
530 *ret_total = file_total;
532 *ret_shared = file_shared;
535 close_file_or_dir(fd, dirstream);
537 /* reset path to just before this element */
543 const char * const cmd_filesystem_du_usage[] = {
544 "btrfs filesystem du [options] <path> [<path>..]",
545 "Summarize disk usage of each file.",
546 "-s|--summarize display only a total for each argument",
551 int cmd_filesystem_du(int argc, char **argv)
553 int ret = 0, err = 0;
557 unit_mode = get_unit_mode_from_arg(&argc, argv, 1);
560 static const struct option long_options[] = {
561 { "summarize", no_argument, NULL, 's'},
564 int c = getopt_long(argc, argv, "s", long_options, NULL);
573 usage(cmd_filesystem_du_usage);
577 if (check_argc_min(argc - optind, 1))
578 usage(cmd_filesystem_du_usage);
580 kernel_version = get_running_kernel_version();
582 if (kernel_version < KERNEL_VERSION(2,6,33)) {
584 "old kernel version detected, shared space will be reported as exclusive\n"
585 "due to missing support for FIEMAP_EXTENT_SHARED flag");
588 printf("%10s %10s %10s %s\n", "Total", "Exclusive", "Set shared",
591 for (i = optind; i < argc; i++) {
592 ret = du_add_file(argv[i], AT_FDCWD, NULL, NULL, NULL, 1);
594 error("cannot check space of '%s': %s", argv[i],
599 /* reset hard-link detection for each argument */