btrfs-progs: use ftw() unstead of system("du")
authorZach Brown <zab@redhat.com>
Thu, 17 Jan 2013 23:23:10 +0000 (15:23 -0800)
committerZach Brown <zab@redhat.com>
Wed, 6 Feb 2013 00:09:38 +0000 (16:09 -0800)
size_sourcedir() uses shockingly bad code to try and estimate the size
of the files and directories in a subtree.

- Its use of snprintf(), strcat(), and sscanf() with arbitrarily small
  on-stack buffers manages to overflow the stack a few times when given
  long file names.

  $ BIG=$(perl -e 'print "a" x 200')
  $ mkdir -p /tmp/$BIG/$BIG/$BIG/$BIG/$BIG
  $ mkfs.btrfs /tmp/img -r /tmp/$BIG/$BIG/$BIG/$BIG/$BIG
  *** stack smashing detected ***: mkfs.btrfs terminated

- It passes raw paths to system() allowing interpreting file names as
  shell control characters.

  $ mkfs.btrfs /tmp/img -r /tmp/spacey\ dir/
  du: cannot access `/tmp/spacey': No such file or directory
  du: cannot access `dir/': No such file or directory

- It redirects du output to "temp_file" in the current directory,
  allowing overwriting of files through symlinks.

  $ echo hi > target
  $ ln -s target temp_file
  $ mkfs.btrfs /tmp/img -r /tmp/somedir/
  $ cat target
  3 /tmp/somedir/

This fixes the worst problems while maintaining -r functionality by
tearing out the system() code and using ftw() to walk the source tree
and sum up st.st_size.

Signed-off-by: Zach Brown <zab@redhat.com>
kerncompat.h
mkfs.c

index a38a9b0..c18fdf3 100644 (file)
@@ -203,6 +203,16 @@ static inline long IS_ERR(const void *ptr)
        ({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })
 
 /*
+ * This looks more complex than it should be. But we need to
+ * get the type for the ~ right in round_down (it needs to be
+ * as wide as the result!), and we want to evaluate the macro
+ * arguments just once each.
+ */
+#define __round_mask(x, y) ((__typeof__(x))((y)-1))
+#define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1)
+#define round_down(x, y) ((x) & ~__round_mask(x, y))
+
+/*
  * printk
  */
 #define printk(fmt, args...) fprintf(stderr, fmt, ##args)
diff --git a/mkfs.c b/mkfs.c
index 0e042c7..0eee8b5 100644 (file)
--- a/mkfs.c
+++ b/mkfs.c
@@ -40,6 +40,8 @@
 #include <ctype.h>
 #include <attr/xattr.h>
 #include <blkid/blkid.h>
+#include <ftw.h>
+#include "kerncompat.h"
 #include "ctree.h"
 #include "disk-io.h"
 #include "volumes.h"
@@ -1097,16 +1099,30 @@ fail:
        return -1;
 }
 
+/*
+ * This ignores symlinks with unreadable targets and subdirs that can't
+ * be read.  It's a best-effort to give a rough estimate of the size of
+ * a subdir.  It doesn't guarantee that prepopulating btrfs from this
+ * tree won't still run out of space. 
+ *
+ * The rounding up to 4096 is questionable.  Previous code used du -B 4096.
+ */
+static u64 global_total_size;
+static int ftw_add_entry_size(const char *fpath, const struct stat *st,
+                             int type)
+{
+       if (type == FTW_F || type == FTW_D)
+               global_total_size += round_up(st->st_size, 4096);
+
+       return 0;
+}
+
 static u64 size_sourcedir(char *dir_name, u64 sectorsize,
                          u64 *num_of_meta_chunks_ret, u64 *size_of_data_ret)
 {
        u64 dir_size = 0;
        u64 total_size = 0;
        int ret;
-       char command[1024];
-       char path[512];
-       char *file_name = "temp_file";
-       FILE *file;
        u64 default_chunk_size = 8 * 1024 * 1024;       /* 8MB */
        u64 allocated_meta_size = 8 * 1024 * 1024;      /* 8MB */
        u64 allocated_total_size = 20 * 1024 * 1024;    /* 20MB */
@@ -1114,23 +1130,14 @@ static u64 size_sourcedir(char *dir_name, u64 sectorsize,
        u64 num_of_allocated_meta_chunks =
                        allocated_meta_size / default_chunk_size;
 
-       ret = sprintf(command, "du -B 4096 -s ");
+       global_total_size = 0;
+       ret = ftw(dir_name, ftw_add_entry_size, 10);
+       dir_size = global_total_size;
        if (ret < 0) {
-               fprintf(stderr, "error executing sprintf for du command\n");
-               return -1;
+               fprintf(stderr, "ftw subdir walk of '%s' failed: %s\n",
+                       dir_name, strerror(errno));
+               exit(1);
        }
-       strcat(command, dir_name);
-       strcat(command, " > ");
-       strcat(command, file_name);
-       ret = system(command);
-
-       file = fopen(file_name, "r");
-       ret = fscanf(file, "%lld %s\n", &dir_size, path);
-       fclose(file);
-       remove(file_name);
-
-       dir_size *= sectorsize;
-       *size_of_data_ret = dir_size;
 
        num_of_meta_chunks = (dir_size / 2) / default_chunk_size;
        if (((dir_size / 2) % default_chunk_size) != 0)