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/fiemap.h>
34 #include "kerncompat.h"
37 static int summarize = 0;
38 static unsigned unit_mode = UNITS_RAW;
39 static char path[PATH_MAX] = { 0, };
40 static char *pathp = path;
41 static char *path_max = &path[PATH_MAX - 1];
43 /* Track which inodes we've seen for the purposes of hardlink detection. */
45 struct rb_node i_node;
49 static struct rb_root seen_inodes = RB_ROOT;
51 static int cmp_si(struct seen_inode *si, u64 ino, u64 subvol)
55 else if (ino > si->i_ino)
57 if (subvol < si->i_subvol)
59 else if (subvol > si->i_subvol)
64 static int mark_inode_seen(u64 ino, u64 subvol)
67 struct rb_node **p = &seen_inodes.rb_node;
68 struct rb_node *parent = NULL;
69 struct seen_inode *si;
74 si = rb_entry(parent, struct seen_inode, i_node);
75 cmp = cmp_si(si, ino, subvol);
84 si = calloc(1, sizeof(*si));
89 si->i_subvol = subvol;
91 rb_link_node(&si->i_node, parent, p);
92 rb_insert_color(&si->i_node, &seen_inodes);
97 static int inode_seen(u64 ino, u64 subvol)
100 struct rb_node *n = seen_inodes.rb_node;
101 struct seen_inode *si;
104 si = rb_entry(n, struct seen_inode, i_node);
106 cmp = cmp_si(si, ino, subvol);
118 const char * const cmd_filesystem_du_usage[] = {
119 "btrfs filesystem du [options] <path> [<path>..]",
120 "Summarize disk usage of each file.",
121 "-h|--human-readable",
122 " human friendly numbers, base 1024 (default)",
123 "-s display only a total for each argument",
128 * Inline extents are skipped because they do not take data space,
129 * delalloc and unknown are skipped because we do not know how much
130 * space they will use yet.
132 #define SKIP_FLAGS (FIEMAP_EXTENT_UNKNOWN|FIEMAP_EXTENT_DELALLOC|FIEMAP_EXTENT_DATA_INLINE)
133 static int du_calc_file_space(int dirfd, const char *filename,
134 uint64_t *ret_total, uint64_t *ret_shared)
137 struct fiemap *fiemap = (struct fiemap *)buf;
138 struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
139 int count = (sizeof(buf) - sizeof(*fiemap)) /
140 sizeof(struct fiemap_extent);
150 memset(fiemap, 0, sizeof(struct fiemap));
152 fd = openat(dirfd, filename, O_RDONLY);
155 fprintf(stderr, "ERROR: can't access '%s': %s\n",
156 filename, strerror(ret));
161 fiemap->fm_length = ~0ULL;
162 fiemap->fm_extent_count = count;
163 rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
169 /* If 0 extents are returned, then more ioctls are not needed */
170 if (fiemap->fm_mapped_extents == 0)
173 for (i = 0; i < fiemap->fm_mapped_extents; i++) {
174 ext_len = fm_ext[i].fe_length;
175 flags = fm_ext[i].fe_flags;
177 if (flags & FIEMAP_EXTENT_LAST)
180 if (flags & SKIP_FLAGS)
183 file_total += ext_len;
184 if (flags & FIEMAP_EXTENT_SHARED)
185 file_shared += ext_len;
188 fiemap->fm_start = (fm_ext[i - 1].fe_logical +
189 fm_ext[i - 1].fe_length);
192 *ret_total = file_total;
193 *ret_shared = file_shared;
202 uint64_t bytes_total;
203 uint64_t bytes_shared;
206 static int du_add_file(const char *filename, int dirfd, uint64_t *ret_total,
207 uint64_t *ret_shared, int top_level);
209 static int du_walk_dir(struct du_dir_ctxt *ctxt)
212 DIR *dirstream = NULL;
213 struct dirent *entry;
215 fd = open_file_or_dir(path, &dirstream);
224 entry = readdir(dirstream);
226 if (strcmp(entry->d_name, ".") == 0
227 || strcmp(entry->d_name, "..") == 0)
230 type = entry->d_type;
231 if (type == DT_REG || type == DT_DIR) {
234 ret = du_add_file(entry->d_name,
235 dirfd(dirstream), &tot,
240 ctxt->bytes_total += tot;
241 ctxt->bytes_shared += shr;
244 } while (entry != NULL);
246 close_file_or_dir(fd, dirstream);
250 static int du_add_file(const char *filename, int dirfd, uint64_t *ret_total,
251 uint64_t *ret_shared, int top_level)
253 int ret, len = strlen(filename);
256 struct du_dir_ctxt dir;
257 uint64_t file_total = 0;
258 uint64_t file_shared = 0;
261 DIR *dirstream = NULL;
263 ret = fstatat(dirfd, filename, &st, 0);
269 if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode))
272 if (len > (path_max - pathp)) {
273 fprintf(stderr, "ERROR: Path max exceeded: %s %s\n", path,
280 ret = sprintf(pathp, "%s", filename);
282 ret = sprintf(pathp, "/%s", filename);
285 fd = open_file_or_dir(path, &dirstream);
291 ret = lookup_ino_rootid(fd, &subvol);
295 if (inode_seen(st.st_ino, subvol))
298 ret = mark_inode_seen(st.st_ino, subvol);
302 if (S_ISREG(st.st_mode)) {
303 ret = du_calc_file_space(dirfd, filename, &file_total,
307 } else if (S_ISDIR(st.st_mode)) {
308 memset(&dir, 0, sizeof(dir));
310 ret = du_walk_dir(&dir);
315 file_total = dir.bytes_total;
316 file_shared = dir.bytes_shared;
319 if (!summarize || top_level) {
320 printf("%s\t%s\t%s\n", pretty_size_mode(file_total, unit_mode),
321 pretty_size_mode((file_total - file_shared), unit_mode),
326 *ret_total = file_total;
328 *ret_shared = file_shared;
331 close_file_or_dir(fd, dirstream);
333 /* reset path to just before this element */
339 int cmd_filesystem_du(int argc, char **argv)
341 int ret = 0, error = 0;
347 static const struct option long_options[] = {
348 { "summarize", no_argument, NULL, 's'},
349 { "human-readable", no_argument, NULL, 'h'},
352 int c = getopt_long(argc, argv, "sh", long_options,
359 unit_mode = UNITS_HUMAN;
365 usage(cmd_filesystem_du_usage);
369 if (check_argc_min(argc - optind, 1))
370 usage(cmd_filesystem_du_usage);
372 printf("total\texclusive\tfilename\n");
374 for (i = optind; i < argc; i++) {
375 ret = du_add_file(argv[i], AT_FDCWD, NULL, NULL, 1);
377 fprintf(stderr, "ERROR: can't check space of '%s': %s\n",
378 argv[i], strerror(ret));