Add a fsck symlink to btrfsck
[platform/upstream/btrfs-progs.git] / cmds-fi-du.c
index 836d357..7e6bb7f 100644 (file)
 
 #include <sys/ioctl.h>
 #include <linux/fs.h>
+#include <linux/version.h>
 #include <linux/fiemap.h>
 
+#if !defined(FIEMAP_EXTENT_SHARED) && (HAVE_OWN_FIEMAP_EXTENT_SHARED_DEFINE == 1)
+#define FIEMAP_EXTENT_SHARED           0x00002000
+#endif
+
 #include "utils.h"
 #include "commands.h"
 #include "kerncompat.h"
 #include "rbtree.h"
 
 #include "interval_tree_generic.h"
+#include "help.h"
+#include "fsfeatures.h"
 
 static int summarize = 0;
 static unsigned unit_mode = UNITS_RAW;
@@ -79,11 +86,11 @@ static int add_shared_extent(u64 start, u64 len, struct rb_root *root)
 {
        struct shared_extent *sh;
 
-       BUG_ON(len == 0);
+       ASSERT(len != 0);
 
        sh = calloc(1, sizeof(*sh));
        if (!sh)
-               return ENOMEM;
+               return -ENOMEM;
 
        sh->start = start;
        sh->last = (start + len - 1);
@@ -111,7 +118,7 @@ static void cleanup_shared_extents(struct rb_root *root)
        }
 }
 
-#define dprintf(...)
+#define dbgprintf(...)
 
 /*
  * Find all extents which overlap 'n', calculate the space
@@ -123,7 +130,7 @@ static u64 count_unique_bytes(struct rb_root *root, struct shared_extent *n)
        u64 wstart = n->start;
        u64 wlast = n->last;
 
-       dprintf("Count overlaps:");
+       dbgprintf("Count overlaps:");
 
        do {
                /*
@@ -136,7 +143,7 @@ static u64 count_unique_bytes(struct rb_root *root, struct shared_extent *n)
                if (wlast < n->last)
                        wlast = n->last;
 
-               dprintf(" (%llu, %llu)", n->start, n->last);
+               dbgprintf(" (%llu, %llu)", n->start, n->last);
 
                tmp = n;
                n = extent_tree_iter_next(n, wstart, wlast);
@@ -145,7 +152,7 @@ static u64 count_unique_bytes(struct rb_root *root, struct shared_extent *n)
                free(tmp);
        } while (n);
 
-       dprintf("; wstart: %llu wlast: %llu total: %llu\n", wstart,
+       dbgprintf("; wstart: %llu wlast: %llu total: %llu\n", wstart,
                wlast, wlast - wstart + 1);
 
        return wlast - wstart + 1;
@@ -153,7 +160,7 @@ static u64 count_unique_bytes(struct rb_root *root, struct shared_extent *n)
 
 /*
  * What we want to do here is get a count of shared bytes within the
- * set of extents we have collected. Specifcally, we don't want to
+ * set of extents we have collected. Specifically, we don't want to
  * count any byte more than once, so just adding them up doesn't
  * work.
  *
@@ -228,12 +235,12 @@ static int mark_inode_seen(u64 ino, u64 subvol)
                else if (cmp > 0)
                        p = &(*p)->rb_right;
                else
-                       BUG();
+                       return -EEXIST;
        }
 
        si = calloc(1, sizeof(*si));
        if (!si)
-               return ENOMEM;
+               return -ENOMEM;
 
        si->i_ino = ino;
        si->i_subvol = subvol;
@@ -259,7 +266,7 @@ static int inode_seen(u64 ino, u64 subvol)
                else if (cmp > 0)
                        n = n->rb_right;
                else
-                       return EEXIST;
+                       return -EEXIST;
        }
        return 0;
 }
@@ -279,15 +286,6 @@ static void clear_seen_inodes(void)
        }
 }
 
-const char * const cmd_filesystem_du_usage[] = {
-       "btrfs filesystem du [options] <path> [<path>..]",
-       "Summarize disk usage of each file.",
-       "-h|--human-readable",
-       "                   human friendly numbers, base 1024 (default)",
-       "-s                 display only a total for each argument",
-       NULL
-};
-
 /*
  * Inline extents are skipped because they do not take data space,
  * delalloc and unknown are skipped because we do not know how much
@@ -295,7 +293,7 @@ const char * const cmd_filesystem_du_usage[] = {
  */
 #define        SKIP_FLAGS      (FIEMAP_EXTENT_UNKNOWN|FIEMAP_EXTENT_DELALLOC|FIEMAP_EXTENT_DATA_INLINE)
 static int du_calc_file_space(int fd, struct rb_root *shared_extents,
-                             uint64_t *ret_total, uint64_t *ret_shared)
+                             u64 *ret_total, u64 *ret_shared)
 {
        char buf[16384];
        struct fiemap *fiemap = (struct fiemap *)buf;
@@ -317,7 +315,7 @@ static int du_calc_file_space(int fd, struct rb_root *shared_extents,
                fiemap->fm_extent_count = count;
                rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
                if (rc < 0) {
-                       ret = errno;
+                       ret = -errno;
                        goto out;
                }
 
@@ -335,6 +333,12 @@ static int du_calc_file_space(int fd, struct rb_root *shared_extents,
                        if (flags & SKIP_FLAGS)
                                continue;
 
+                       if (ext_len == 0) {
+                               warning("extent %llu has length 0, skipping",
+                                       (unsigned long long)fm_ext[i].fe_physical);
+                               continue;
+                       }
+
                        file_total += ext_len;
                        if (flags & FIEMAP_EXTENT_SHARED) {
                                file_shared += ext_len;
@@ -362,16 +366,16 @@ out:
 }
 
 struct du_dir_ctxt {
-       uint64_t        bytes_total;
-       uint64_t        bytes_shared;
+       u64             bytes_total;
+       u64             bytes_shared;
        DIR             *dirstream;
        struct rb_root  shared_extents;
 };
 #define INIT_DU_DIR_CTXT       (struct du_dir_ctxt) { 0ULL, 0ULL, NULL, RB_ROOT }
 
 static int du_add_file(const char *filename, int dirfd,
-                      struct rb_root *shared_extents, uint64_t *ret_total,
-                      uint64_t *ret_shared, int top_level);
+                      struct rb_root *shared_extents, u64 *ret_total,
+                      u64 *ret_shared, int top_level);
 
 static int du_walk_dir(struct du_dir_ctxt *ctxt, struct rb_root *shared_extents)
 {
@@ -381,7 +385,7 @@ static int du_walk_dir(struct du_dir_ctxt *ctxt, struct rb_root *shared_extents)
 
        ret = 0;
        do {
-               uint64_t tot, shr;
+               u64 tot, shr;
 
                errno = 0;
                entry = readdir(dirstream);
@@ -398,8 +402,15 @@ static int du_walk_dir(struct du_dir_ctxt *ctxt, struct rb_root *shared_extents)
                                                  dirfd(dirstream),
                                                  shared_extents, &tot, &shr,
                                                  0);
-                               if (ret)
+                               if (ret == -ENOTTY) {
+                                       ret = 0;
+                                       continue;
+                               } else if (ret) {
+                                       fprintf(stderr,
+                                               "failed to walk dir/file: %s :%s\n",
+                                               entry->d_name, strerror(-ret));
                                        break;
+                               }
 
                                ctxt->bytes_total += tot;
                                ctxt->bytes_shared += shr;
@@ -411,59 +422,63 @@ static int du_walk_dir(struct du_dir_ctxt *ctxt, struct rb_root *shared_extents)
 }
 
 static int du_add_file(const char *filename, int dirfd,
-                      struct rb_root *shared_extents, uint64_t *ret_total,
-                      uint64_t *ret_shared, int top_level)
+                      struct rb_root *shared_extents, u64 *ret_total,
+                      u64 *ret_shared, int top_level)
 {
        int ret, len = strlen(filename);
        char *pathtmp;
        struct stat st;
        struct du_dir_ctxt dir = INIT_DU_DIR_CTXT;
        int is_dir = 0;
-       uint64_t file_total = 0;
-       uint64_t file_shared = 0;
+       u64 file_total = 0;
+       u64 file_shared = 0;
        u64 dir_set_shared = 0;
-       u64 subvol;
        int fd;
        DIR *dirstream = NULL;
 
        ret = fstatat(dirfd, filename, &st, 0);
-       if (ret) {
-               ret = errno;
-               return ret;
-       }
+       if (ret)
+               return -errno;
 
        if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode))
                return 0;
 
        if (len > (path_max - pathp)) {
-               fprintf(stderr, "ERROR: Path max exceeded: %s %s\n", path,
-                       filename);
-               return ENAMETOOLONG;
+               error("path too long: %s %s", path, filename);
+               return -ENAMETOOLONG;
        }
 
        pathtmp = pathp;
-       if (pathp == path)
+       if (pathp == path || *(pathp - 1) == '/')
                ret = sprintf(pathp, "%s", filename);
        else
                ret = sprintf(pathp, "/%s", filename);
        pathp += ret;
 
-       fd = open_file_or_dir(path, &dirstream);
+       fd = open_file_or_dir3(path, &dirstream, O_RDONLY);
        if (fd < 0) {
-               ret = fd;
+               ret = -errno;
                goto out;
        }
 
-       ret = lookup_ino_rootid(fd, &subvol);
-       if (ret)
-               goto out_close;
+       /*
+        * If st.st_ino == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID ==2, there is no any
+        * related tree
+        */
+       if (st.st_ino != BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
+               u64 subvol;
+
+               ret = lookup_path_rootid(fd, &subvol);
+               if (ret)
+                       goto out_close;
 
-       if (inode_seen(st.st_ino, subvol))
-               goto out_close;
+               if (inode_seen(st.st_ino, subvol))
+                       goto out_close;
 
-       ret = mark_inode_seen(st.st_ino, subvol);
-       if (ret)
-               goto out_close;
+               ret = mark_inode_seen(st.st_ino, subvol);
+               if (ret)
+                       goto out_close;
+       }
 
        if (S_ISREG(st.st_mode)) {
                ret = du_calc_file_space(fd, shared_extents, &file_total,
@@ -507,15 +522,16 @@ static int du_add_file(const char *filename, int dirfd,
                        if (is_dir)
                                set_shared = dir_set_shared;
 
-                       printf("%s\t%s\t%s\t%s\n",
+                       printf("%10s  %10s  %10s  %s\n",
                               pretty_size_mode(file_total, unit_mode),
                               pretty_size_mode(excl, unit_mode),
                               pretty_size_mode(set_shared, unit_mode),
                               path);
                } else {
-                       printf("%s\t%s\t\t\t%s\n",
+                       printf("%10s  %10s  %10s  %s\n",
                               pretty_size_mode(file_total, unit_mode),
-                              pretty_size_mode(excl, unit_mode), path);
+                              pretty_size_mode(excl, unit_mode),
+                              "-", path);
                }
        }
 
@@ -533,28 +549,32 @@ out:
        return ret;
 }
 
+const char * const cmd_filesystem_du_usage[] = {
+       "btrfs filesystem du [options] <path> [<path>..]",
+       "Summarize disk usage of each file.",
+       "-s|--summarize     display only a total for each argument",
+       HELPINFO_UNITS_LONG,
+       NULL
+};
+
 int cmd_filesystem_du(int argc, char **argv)
 {
-       int ret = 0, error = 0;
+       int ret = 0, err = 0;
        int i;
+       u32 kernel_version;
+
+       unit_mode = get_unit_mode_from_arg(&argc, argv, 1);
 
-       optind = 1;
        while (1) {
-               int long_index;
                static const struct option long_options[] = {
                        { "summarize", no_argument, NULL, 's'},
-                       { "human-readable", no_argument, NULL, 'h'},
                        { NULL, 0, NULL, 0 }
                };
-               int c = getopt_long(argc, argv, "sh", long_options,
-                               &long_index);
+               int c = getopt_long(argc, argv, "s", long_options, NULL);
 
                if (c < 0)
                        break;
                switch (c) {
-               case 'h':
-                       unit_mode = UNITS_HUMAN;
-                       break;
                case 's':
                        summarize = 1;
                        break;
@@ -566,19 +586,28 @@ int cmd_filesystem_du(int argc, char **argv)
        if (check_argc_min(argc - optind, 1))
                usage(cmd_filesystem_du_usage);
 
-       printf("total\texclusive\tset shared\tfilename\n");
+       kernel_version = get_running_kernel_version();
+
+       if (kernel_version < KERNEL_VERSION(2,6,33)) {
+               warning(
+"old kernel version detected, shared space will be reported as exclusive\n"
+"due to missing support for FIEMAP_EXTENT_SHARED flag");
+       }
+
+       printf("%10s  %10s  %10s  %s\n", "Total", "Exclusive", "Set shared",
+                       "Filename");
 
        for (i = optind; i < argc; i++) {
                ret = du_add_file(argv[i], AT_FDCWD, NULL, NULL, NULL, 1);
                if (ret) {
-                       fprintf(stderr, "ERROR: can't check space of '%s': %s\n",
-                               argv[i], strerror(ret));
-                       error = 1;
+                       error("cannot check space of '%s': %s", argv[i],
+                                       strerror(-ret));
+                       err = 1;
                }
 
                /* reset hard-link detection for each argument */
                clear_seen_inodes();
        }
 
-       return error;
+       return err;
 }