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 #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
{
54 u64 start
; /* Start of interval */
55 u64 last
; /* Last location _in_ interval */
60 * extent_tree_* functions are defined in the massive interval tree
61 * macro below. This serves to illustrate the api in human-readable
65 extent_tree_insert(struct shared_extent
*node
, struct rb_root
*root
);
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
,
74 static struct shared_extent
*
75 extent_tree_iter_next(struct shared_extent
*node
,
78 #define START(node) ((node)->start)
79 #define LAST(node) ((node)->last)
81 INTERVAL_TREE_DEFINE(struct shared_extent
, rb
,
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
;
91 sh
= calloc(1, sizeof(*sh
));
96 sh
->last
= (start
+ len
- 1);
98 extent_tree_insert(sh
, root
);
103 static void cleanup_shared_extents(struct rb_root
*root
)
105 struct shared_extent
*s
;
106 struct shared_extent
*tmp
;
111 s
= extent_tree_iter_first(root
, 0, -1ULL);
113 tmp
= extent_tree_iter_next(s
, 0, -1ULL);
114 extent_tree_remove(s
, root
);
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
;
133 dbgprintf("Count overlaps:");
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
)
146 dbgprintf(" (%llu, %llu)", n
->start
, n
->last
);
149 n
= extent_tree_iter_next(n
, wstart
, wlast
);
151 extent_tree_remove(tmp
, root
);
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
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
)
175 struct shared_extent
*s
= extent_tree_iter_first(root
,
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
));
200 /* Track which inodes we've seen for the purposes of hardlink detection. */
202 struct rb_node i_node
;
206 static struct rb_root seen_inodes
= RB_ROOT
;
208 static int cmp_si(struct seen_inode
*si
, u64 ino
, u64 subvol
)
212 else if (ino
> si
->i_ino
)
214 if (subvol
< si
->i_subvol
)
216 else if (subvol
> si
->i_subvol
)
221 static int mark_inode_seen(u64 ino
, u64 subvol
)
224 struct rb_node
**p
= &seen_inodes
.rb_node
;
225 struct rb_node
*parent
= NULL
;
226 struct seen_inode
*si
;
231 si
= rb_entry(parent
, struct seen_inode
, i_node
);
232 cmp
= cmp_si(si
, ino
, subvol
);
241 si
= calloc(1, sizeof(*si
));
246 si
->i_subvol
= subvol
;
248 rb_link_node(&si
->i_node
, parent
, p
);
249 rb_insert_color(&si
->i_node
, &seen_inodes
);
254 static int inode_seen(u64 ino
, u64 subvol
)
257 struct rb_node
*n
= seen_inodes
.rb_node
;
258 struct seen_inode
*si
;
261 si
= rb_entry(n
, struct seen_inode
, i_node
);
263 cmp
= cmp_si(si
, ino
, subvol
);
274 static void clear_seen_inodes(void)
276 struct rb_node
*n
= rb_first(&seen_inodes
);
277 struct seen_inode
*si
;
280 si
= rb_entry(n
, struct seen_inode
, i_node
);
282 rb_erase(&si
->i_node
, &seen_inodes
);
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
)
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
);
311 memset(fiemap
, 0, sizeof(struct fiemap
));
314 fiemap
->fm_length
= ~0ULL;
315 fiemap
->fm_extent_count
= count
;
316 rc
= ioctl(fd
, FS_IOC_FIEMAP
, (unsigned long) fiemap
);
322 /* If 0 extents are returned, then more ioctls are not needed */
323 if (fiemap
->fm_mapped_extents
== 0)
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
)
333 if (flags
& SKIP_FLAGS
)
337 warning("extent %llu has length 0, skipping",
338 (unsigned long long)fm_ext
[i
].fe_physical
);
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
,
356 fiemap
->fm_start
= (fm_ext
[i
- 1].fe_logical
+
357 fm_ext
[i
- 1].fe_length
);
360 *ret_total
= file_total
;
361 *ret_shared
= file_shared
;
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
)
383 struct dirent
*entry
;
384 DIR *dirstream
= ctxt
->dirstream
;
391 entry
= readdir(dirstream
);
393 if (strcmp(entry
->d_name
, ".") == 0
394 || strcmp(entry
->d_name
, "..") == 0)
397 type
= entry
->d_type
;
398 if (type
== DT_REG
|| type
== DT_DIR
) {
401 ret
= du_add_file(entry
->d_name
,
403 shared_extents
, &tot
, &shr
,
405 if (ret
== -ENOTTY
) {
410 "failed to walk dir/file: %s :%s\n",
411 entry
->d_name
, strerror(-ret
));
415 ctxt
->bytes_total
+= tot
;
416 ctxt
->bytes_shared
+= shr
;
419 } while (entry
!= NULL
);
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
);
431 struct du_dir_ctxt dir
= INIT_DU_DIR_CTXT
;
435 u64 dir_set_shared
= 0;
437 DIR *dirstream
= NULL
;
439 ret
= fstatat(dirfd
, filename
, &st
, 0);
443 if (!S_ISREG(st
.st_mode
) && !S_ISDIR(st
.st_mode
))
446 if (len
> (path_max
- pathp
)) {
447 error("path too long: %s %s", path
, filename
);
448 return -ENAMETOOLONG
;
452 if (pathp
== path
|| *(pathp
- 1) == '/')
453 ret
= sprintf(pathp
, "%s", filename
);
455 ret
= sprintf(pathp
, "/%s", filename
);
458 fd
= open_file_or_dir3(path
, &dirstream
, O_RDONLY
);
465 * If st.st_ino == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID ==2, there is no any
468 if (st
.st_ino
!= BTRFS_EMPTY_SUBVOL_DIR_OBJECTID
) {
471 ret
= lookup_path_rootid(fd
, &subvol
);
475 if (inode_seen(st
.st_ino
, subvol
))
478 ret
= mark_inode_seen(st
.st_ino
, subvol
);
483 if (S_ISREG(st
.st_mode
)) {
484 ret
= du_calc_file_space(fd
, shared_extents
, &file_total
,
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.
497 root
= &dir
.shared_extents
;
501 dir
.dirstream
= dirstream
;
502 ret
= du_walk_dir(&dir
, root
);
506 cleanup_shared_extents(root
);
510 file_total
= dir
.bytes_total
;
511 file_shared
= dir
.bytes_shared
;
513 count_shared_bytes(root
, &dir_set_shared
);
516 if (!summarize
|| top_level
) {
517 u64 excl
= file_total
- file_shared
;
520 u64 set_shared
= file_shared
;
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
),
531 printf("%10s %10s %10s %s\n",
532 pretty_size_mode(file_total
, unit_mode
),
533 pretty_size_mode(excl
, unit_mode
),
539 *ret_total
= file_total
;
541 *ret_shared
= file_shared
;
544 close_file_or_dir(fd
, dirstream
);
546 /* reset path to just before this element */
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",
560 int cmd_filesystem_du(int argc
, char **argv
)
562 int ret
= 0, err
= 0;
566 unit_mode
= get_unit_mode_from_arg(&argc
, argv
, 1);
570 static const struct option long_options
[] = {
571 { "summarize", no_argument
, NULL
, 's'},
574 int c
= getopt_long(argc
, argv
, "s", long_options
, NULL
);
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)) {
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",
601 for (i
= optind
; i
< argc
; i
++) {
602 ret
= du_add_file(argv
[i
], AT_FDCWD
, NULL
, NULL
, NULL
, 1);
604 error("cannot check space of '%s': %s", argv
[i
],
609 /* reset hard-link detection for each argument */