Whamcloud - gitweb
libext2fs: handle inline data in dir iterator function
authorZheng Liu <wenqing.lz@taobao.com>
Fri, 6 Dec 2013 09:57:58 +0000 (17:57 +0800)
committerTheodore Ts'o <tytso@mit.edu>
Tue, 4 Mar 2014 13:46:08 +0000 (08:46 -0500)
Inline_data is handled in dir iterator because a lot of commands use
this function to traverse directory entries in debugfs.  We need to
handle inline_data individually because inline_data is saved in two
places.  One is in i_block, and another is in ibody extended attribute.

After applied this commit, the following commands in debugfs can
support the inline_data feature:
- cd
- chroot
- link*
- ls
- ncheck
- pwd
- unlink

* TODO: Inline_data doesn't expand to ibody extended attribute because
  link command doesn't handle DIR_NO_SPACE error until now.  But if we
  have already expanded inline data to ibody ea area, link command can
  occupy this space.

Signed-off-by: Zheng Liu <wenqing.lz@taobao.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
lib/ext2fs/Makefile.in
lib/ext2fs/Makefile.pq
lib/ext2fs/dir_iterate.c
lib/ext2fs/ext2_err.et.in
lib/ext2fs/ext2_fs.h
lib/ext2fs/ext2fs.h
lib/ext2fs/ext2fsP.h
lib/ext2fs/inline_data.c [new file with mode: 0644]
lib/ext2fs/swapfs.c

index 88808a3..9de8925 100644 (file)
@@ -59,6 +59,7 @@ OBJS= $(DEBUGFS_LIB_OBJS) $(RESIZE_LIB_OBJS) $(E2IMAGE_LIB_OBJS) \
        ind_block.o \
        initialize.o \
        inline.o \
+       inline_data.o \
        inode.o \
        io_manager.o \
        ismounted.o \
@@ -133,6 +134,7 @@ SRCS= ext2_err.c \
        $(srcdir)/ind_block.c \
        $(srcdir)/initialize.c \
        $(srcdir)/inline.c \
+       $(srcdir)/inline_data.c \
        $(srcdir)/inode.c \
        $(srcdir)/inode_io.c \
        $(srcdir)/imager.c \
@@ -767,6 +769,12 @@ inline.o: $(srcdir)/inline.c $(top_builddir)/lib/config.h \
  $(srcdir)/ext2_fs.h $(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
  $(srcdir)/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
  $(srcdir)/ext2_ext_attr.h $(srcdir)/bitops.h
+inline_data.o: $(srcdir)/inline_data.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/ext2_fs.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(srcdir)/ext2fs.h \
+ $(srcdir)/ext2_fs.h $(srcdir)/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \
+ $(srcdir)/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \
+ $(srcdir)/ext2_ext_attr.h $(srcdir)/bitops.h
 inode.o: $(srcdir)/inode.c $(top_builddir)/lib/config.h \
  $(top_builddir)/lib/dirpaths.h $(srcdir)/ext2_fs.h \
  $(top_builddir)/lib/ext2fs/ext2_types.h $(srcdir)/ext2fsP.h \
index 2f7b654..89082a7 100644 (file)
@@ -27,6 +27,7 @@ OBJS=         alloc.obj \
        icount.obj \
        initialize.obj \
        inline.obj \
+       inline_data.obj \
        inode.obj \
        ismounted.obj \
        link.obj \
index 306ffb0..8cb6740 100644 (file)
@@ -127,6 +127,11 @@ errcode_t ext2fs_dir_iterate2(ext2_filsys fs,
                                       ext2fs_process_dir_block, &ctx);
        if (!block_buf)
                ext2fs_free_mem(&ctx.buf);
+       if (retval == EXT2_ET_INLINE_DATA_CANT_ITERATE) {
+               ctx.flags |= DIRENT_FLAG_INCLUDE_INLINE_DATA;
+               (void) ext2fs_inline_data_dir_iterate(fs, dir, &ctx);
+               retval = 0;
+       }
        if (retval)
                return retval;
        return ctx.errcode;
@@ -189,30 +194,40 @@ int ext2fs_process_dir_block(ext2_filsys fs,
        int             ret = 0;
        int             changed = 0;
        int             do_abort = 0;
-       unsigned int    rec_len, size;
+       unsigned int    rec_len, size, buflen;
        int             entry;
        struct ext2_dir_entry *dirent;
        int             csum_size = 0;
+       int             inline_data;
+       errcode_t       retval = 0;
 
        if (blockcnt < 0)
                return 0;
 
        entry = blockcnt ? DIRENT_OTHER_FILE : DIRENT_DOT_FILE;
 
-       ctx->errcode = ext2fs_read_dir_block4(fs, *blocknr, ctx->buf, 0,
-                                             ctx->dir);
-       if (ctx->errcode)
-               return BLOCK_ABORT;
+       /* If a dir has inline data, we don't need to read block */
+       inline_data = !!(ctx->flags & DIRENT_FLAG_INCLUDE_INLINE_DATA);
+       if (!inline_data) {
+               ctx->errcode = ext2fs_read_dir_block4(fs, *blocknr, ctx->buf, 0,
+                                                     ctx->dir);
+               if (ctx->errcode)
+                       return BLOCK_ABORT;
+               /* If we handle a normal dir, we traverse the entire block */
+               buflen = fs->blocksize;
+       } else {
+               buflen = ctx->buflen;
+       }
 
        if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
                                        EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
                csum_size = sizeof(struct ext2_dir_entry_tail);
 
-       while (offset < fs->blocksize) {
+       while (offset < buflen) {
                dirent = (struct ext2_dir_entry *) (ctx->buf + offset);
                if (ext2fs_get_rec_len(fs, dirent, &rec_len))
                        return BLOCK_ABORT;
-               if (((offset + rec_len) > fs->blocksize) ||
+               if (((offset + rec_len) > buflen) ||
                    (rec_len < 8) ||
                    ((rec_len % 4) != 0) ||
                    ((ext2fs_dirent_name_len(dirent)+8) > rec_len)) {
@@ -220,7 +235,13 @@ int ext2fs_process_dir_block(ext2_filsys fs,
                        return BLOCK_ABORT;
                }
                if (!dirent->inode) {
-                       if ((offset == fs->blocksize - csum_size) &&
+                       /*
+                        * We just need to check metadata_csum when this
+                        * dir hasn't inline data.  That means that 'buflen'
+                        * should be blocksize.
+                        */
+                       if (!inline_data &&
+                           (offset == buflen - csum_size) &&
                            (dirent->rec_len == csum_size) &&
                            (dirent->name_len == EXT2_DIR_NAME_LEN_CSUM)) {
                                if (!(ctx->flags & DIRENT_FLAG_INCLUDE_CSUM))
@@ -234,7 +255,7 @@ int ext2fs_process_dir_block(ext2_filsys fs,
                                  (next_real_entry > offset) ?
                                  DIRENT_DELETED_FILE : entry,
                                  dirent, offset,
-                                 fs->blocksize, ctx->buf,
+                                 buflen, ctx->buf,
                                  ctx->priv_data);
                if (entry < DIRENT_OTHER_FILE)
                        entry++;
@@ -272,13 +293,21 @@ next:
        }
 
        if (changed) {
-               ctx->errcode = ext2fs_write_dir_block4(fs, *blocknr, ctx->buf,
-                                                      0, ctx->dir);
-               if (ctx->errcode)
-                       return BLOCK_ABORT;
+               if (!inline_data) {
+                       ctx->errcode = ext2fs_write_dir_block4(fs, *blocknr,
+                                                              ctx->buf,
+                                                              0, ctx->dir);
+                       if (ctx->errcode)
+                               return BLOCK_ABORT;
+               } else {
+                       /*
+                        * return BLOCK_INLINE_DATA_CHANGED to notify caller
+                        * that inline data has been changed.
+                        */
+                       retval = BLOCK_INLINE_DATA_CHANGED;
+               }
        }
        if (do_abort)
-               return BLOCK_ABORT;
-       return 0;
+               return retval | BLOCK_ABORT;
+       return retval;
 }
-
index 0a69aa3..a7c8902 100644 (file)
@@ -503,4 +503,7 @@ ec  EXT2_ET_EA_NO_SPACE,
 ec     EXT2_ET_MISSING_EA_FEATURE,
        "Filesystem is missing ext_attr or inline_data feature"
 
+ec     EXT2_ET_NO_INLINE_DATA,
+       "Inode doesn't have inline data"
+
        end
index ac46ba8..21a8187 100644 (file)
@@ -916,4 +916,14 @@ struct mmp_struct {
  */
 #define EXT4_MMP_MIN_CHECK_INTERVAL     5
 
+/*
+ * Minimum size of inline data.
+ */
+#define EXT4_MIN_INLINE_DATA_SIZE      ((sizeof(__u32) * EXT2_N_BLOCKS))
+
+/*
+ * Size of a parent inode in inline data directory.
+ */
+#define EXT4_INLINE_DATA_DOTDOT_SIZE   (4)
+
 #endif /* _LINUX_EXT2_FS_H */
index b526f5c..ad34291 100644 (file)
@@ -298,9 +298,10 @@ struct struct_ext2_filsys {
 /*
  * Return flags for the block iterator functions
  */
-#define BLOCK_CHANGED  1
-#define BLOCK_ABORT    2
-#define BLOCK_ERROR    4
+#define BLOCK_CHANGED                  1
+#define BLOCK_ABORT                    2
+#define BLOCK_ERROR                    4
+#define BLOCK_INLINE_DATA_CHANGED      8
 
 /*
  * Block interate flags
@@ -438,6 +439,7 @@ struct ext2_extent_info {
 #define DIRENT_FLAG_INCLUDE_EMPTY      1
 #define DIRENT_FLAG_INCLUDE_REMOVED    2
 #define DIRENT_FLAG_INCLUDE_CSUM       4
+#define DIRENT_FLAG_INCLUDE_INLINE_DATA 8
 
 #define DIRENT_DOT_FILE                1
 #define DIRENT_DOT_DOT_FILE    2
index 319e9b0..b2afd15 100644 (file)
@@ -50,6 +50,7 @@ struct dir_context {
        ext2_ino_t              dir;
        int             flags;
        char            *buf;
+       unsigned int    buflen;
        int (*func)(ext2_ino_t  dir,
                    int entry,
                    struct ext2_dir_entry *dirent,
@@ -87,6 +88,10 @@ extern int ext2fs_process_dir_block(ext2_filsys      fs,
                                    int                 ref_offset,
                                    void                *priv_data);
 
+extern int ext2fs_inline_data_dir_iterate(ext2_filsys fs,
+                                         ext2_ino_t ino,
+                                         void *priv_data);
+
 /* Generic numeric progress meter */
 
 struct ext2fs_numeric_progress_struct {
diff --git a/lib/ext2fs/inline_data.c b/lib/ext2fs/inline_data.c
new file mode 100644 (file)
index 0000000..9bfc8ef
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * inline_data.c --- data in inode
+ *
+ * Copyright (C) 2012 Zheng Liu <wenqing.lz@taobao.com>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU library
+ * General Public License, version 2.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <time.h>
+
+#include "ext2_fs.h"
+#include "ext2_ext_attr.h"
+
+#include "ext2fs.h"
+#include "ext2fsP.h"
+
+struct ext2_inline_data {
+       ext2_filsys fs;
+       ext2_ino_t ino;
+       size_t ea_size; /* the size of inline data in ea area */
+       void *ea_data;
+};
+
+static errcode_t ext2fs_inline_data_ea_set(struct ext2_inline_data *data)
+{
+       struct ext2_xattr_handle *handle;
+       errcode_t retval;
+
+       retval = ext2fs_xattrs_open(data->fs, data->ino, &handle);
+       if (retval)
+               return retval;
+
+       retval = ext2fs_xattrs_read(handle);
+       if (retval)
+               goto err;
+
+       retval = ext2fs_xattr_set(handle, "system.data",
+                                 data->ea_data, data->ea_size);
+       if (retval)
+               goto err;
+
+       retval = ext2fs_xattrs_write(handle);
+
+err:
+       (void) ext2fs_xattrs_close(&handle);
+       return retval;
+}
+
+static errcode_t ext2fs_inline_data_ea_get(struct ext2_inline_data *data)
+{
+       struct ext2_xattr_handle *handle;
+       errcode_t retval;
+
+       data->ea_size = 0;
+       data->ea_data = 0;
+
+       retval = ext2fs_xattrs_open(data->fs, data->ino, &handle);
+       if (retval)
+               return retval;
+
+       retval = ext2fs_xattrs_read(handle);
+       if (retval)
+               goto err;
+
+       retval = ext2fs_xattr_get(handle, "system.data",
+                                 (void **)&data->ea_data, &data->ea_size);
+       if (retval)
+               goto err;
+
+err:
+       (void) ext2fs_xattrs_close(&handle);
+       return retval;
+}
+
+
+int ext2fs_inline_data_dir_iterate(ext2_filsys fs, ext2_ino_t ino,
+                                  void *priv_data)
+{
+       struct dir_context *ctx;
+       struct ext2_inode inode;
+       struct ext2_dir_entry dirent;
+       struct ext2_inline_data data;
+       int ret = BLOCK_ABORT;
+       e2_blkcnt_t blockcnt = 0;
+
+       ctx = (struct dir_context *)priv_data;
+
+       ctx->errcode = ext2fs_read_inode(fs, ino, &inode);
+       if (ctx->errcode)
+               goto out;
+
+       if (!(inode.i_flags & EXT4_INLINE_DATA_FL)) {
+               ctx->errcode = EXT2_ET_NO_INLINE_DATA;
+               goto out;
+       }
+
+       if (!LINUX_S_ISDIR(inode.i_mode)) {
+               ctx->errcode = EXT2_ET_NO_DIRECTORY;
+               goto out;
+       }
+       ret = 0;
+
+       /* we first check '.' and '..' dir */
+       dirent.inode = ino;
+       dirent.name_len = 1;
+       ext2fs_set_rec_len(fs, EXT2_DIR_REC_LEN(2), &dirent);
+       dirent.name[0] = '.';
+       dirent.name[1] = '\0';
+       ctx->buf = (char *)&dirent;
+       ext2fs_get_rec_len(fs, &dirent, &ctx->buflen);
+       ret |= ext2fs_process_dir_block(fs, 0, blockcnt++, 0, 0, priv_data);
+       if (ret & BLOCK_ABORT)
+               goto out;
+
+       dirent.inode = ext2fs_le32_to_cpu(inode.i_block[0]);
+       dirent.name_len = 2;
+       ext2fs_set_rec_len(fs, EXT2_DIR_REC_LEN(3), &dirent);
+       dirent.name[0] = '.';
+       dirent.name[1] = '.';
+       dirent.name[2] = '\0';
+       ctx->buf = (char *)&dirent;
+       ext2fs_get_rec_len(fs, &dirent, &ctx->buflen);
+       ret |= ext2fs_process_dir_block(fs, 0, blockcnt++, 0, 0, priv_data);
+       if (ret & BLOCK_INLINE_DATA_CHANGED) {
+               errcode_t err;
+
+               inode.i_block[0] = ext2fs_cpu_to_le32(dirent.inode);
+               err = ext2fs_write_inode(fs, ino, &inode);
+               if (err)
+                       goto out;
+               ret &= ~BLOCK_INLINE_DATA_CHANGED;
+       }
+       if (ret & BLOCK_ABORT)
+               goto out;
+
+       ctx->buf = (char *)inode.i_block + EXT4_INLINE_DATA_DOTDOT_SIZE;
+       ctx->buflen = EXT4_MIN_INLINE_DATA_SIZE - EXT4_INLINE_DATA_DOTDOT_SIZE;
+#ifdef WORDS_BIGENDIAN
+       ctx->errcode = ext2fs_dirent_swab_in2(fs, ctx->buf, ctx->buflen, 0);
+       if (ctx->errcode) {
+               ret |= BLOCK_ABORT;
+               goto out;
+       }
+#endif
+       ret |= ext2fs_process_dir_block(fs, 0, blockcnt++, 0, 0, priv_data);
+       if (ret & BLOCK_INLINE_DATA_CHANGED) {
+#ifdef WORDS_BIGENDIAN
+               ctx->errcode = ext2fs_dirent_swab_out2(fs, ctx->buf,
+                                                      ctx->buflen, 0);
+               if (ctx->errcode) {
+                       ret |= BLOCK_ABORT;
+                       goto out;
+               }
+#endif
+               ctx->errcode = ext2fs_write_inode(fs, ino, &inode);
+               if (ctx->errcode)
+                       ret |= BLOCK_ABORT;
+               ret &= ~BLOCK_INLINE_DATA_CHANGED;
+       }
+       if (ret & BLOCK_ABORT)
+               goto out;
+
+       data.fs = fs;
+       data.ino = ino;
+       ctx->errcode = ext2fs_inline_data_ea_get(&data);
+       if (ctx->errcode) {
+               ret |= BLOCK_ABORT;
+               goto out;
+       }
+       if (data.ea_size <= 0)
+               goto out;
+
+       ctx->buf = data.ea_data;
+       ctx->buflen = data.ea_size;
+#ifdef WORDS_BIGENDIAN
+       ctx.errcode = ext2fs_dirent_swab_in2(fs, ctx->buf, ctx->buflen, 0);
+       if (ctx.errcode) {
+               ret |= BLOCK_ABORT;
+               goto out;
+       }
+#endif
+
+       ret |= ext2fs_process_dir_block(fs, 0, blockcnt++, 0, 0, priv_data);
+       if (ret & BLOCK_INLINE_DATA_CHANGED) {
+#ifdef WORDS_BIGENDIAN
+               ctx->errcode = ext2fs_dirent_swab_out2(fs, ctx->buf,
+                                                     ctx->buflen, 0);
+               if (ctx->errcode) {
+                       ret |= BLOCK_ABORT;
+                       goto out1;
+               }
+#endif
+               ctx->errcode = ext2fs_inline_data_ea_set(&data);
+               if (ctx->errcode)
+                       ret |= BLOCK_ABORT;
+       }
+
+out1:
+       ext2fs_free_mem(&data.ea_data);
+       ctx->buf = 0;
+
+out:
+       ret &= ~(BLOCK_ABORT | BLOCK_INLINE_DATA_CHANGED);
+       return ret;
+}
index 760f25f..e3628b3 100644 (file)
@@ -209,6 +209,7 @@ void ext2fs_swap_inode_full(ext2_filsys fs, struct ext2_inode_large *t,
 {
        unsigned i, has_data_blocks, extra_isize, attr_magic;
        int has_extents = 0;
+       int has_inline_data = 0;
        int islnk = 0;
        __u32 *eaf, *eat;
 
@@ -235,12 +236,18 @@ void ext2fs_swap_inode_full(ext2_filsys fs, struct ext2_inode_large *t,
                                           (struct ext2_inode *) t);
        if (hostorder && (f->i_flags & EXT4_EXTENTS_FL))
                has_extents = 1;
+       if (hostorder && (f->i_flags & EXT4_INLINE_DATA_FL))
+               has_inline_data = 1;
        t->i_flags = ext2fs_swab32(f->i_flags);
        if (!hostorder && (t->i_flags & EXT4_EXTENTS_FL))
                has_extents = 1;
+       if (!hostorder && (f->i_flags & EXT4_INLINE_DATA_FL))
+               has_inline_data = 1;
        t->i_dir_acl = ext2fs_swab32(f->i_dir_acl);
-       /* extent data are swapped on access, not here */
-       if (!has_extents && (!islnk || has_data_blocks)) {
+       /*
+        * Extent data and inline data are swapped on access, not here
+        */
+       if (!has_extents && !has_inline_data && (!islnk || has_data_blocks)) {
                for (i = 0; i < EXT2_N_BLOCKS; i++)
                        t->i_block[i] = ext2fs_swab32(f->i_block[i]);
        } else if (t != f) {