Permalink
Browse files

e4snap: add snap clone file support

snapclone file is used to support writable snapshot.
If an user wants to modify a snapshot, a snapclone file
is created and mounted via loop, the snapclone file stores
the diff between original snapshot and written snapshot.
  • Loading branch information...
1 parent 90bcb2c commit 33afdaf61072dc91a555b68e813681f54c7cb647 @YANGYongqiang committed Aug 16, 2012
Showing with 463 additions and 48 deletions.
  1. +9 −0 fs/ext4/Kconfig
  2. +0 −1 fs/ext4/balloc.c
  3. +14 −4 fs/ext4/ext4.h
  4. +3 −2 fs/ext4/file.c
  5. +1 −1 fs/ext4/ialloc.c
  6. +72 −9 fs/ext4/inode.c
  7. +4 −3 fs/ext4/ioctl.c
  8. +2 −1 fs/ext4/mballoc.c
  9. +7 −6 fs/ext4/namei.c
  10. +32 −0 fs/ext4/snapshot.h
  11. +311 −16 fs/ext4/snapshot_ctl.c
  12. +8 −5 fs/ext4/snapshot_inode.c
View
@@ -185,6 +185,15 @@ config EXT4_FS_SNAPSHOT_FILE
Snapshot files are marked with the snapfile flag and have special
read-only address space ops.
+config EXT4_FS_SNAPCLONE_FILE
+ bool "snapclone file"
+ depends on EXT4_FS_SNAPSHOT
+ default y
+ help
+ Ext4 snapclone implementation as a file inside the file system.
+ Snapshot clones are marked with the snapclone flag and have special
+ address space ops supporting writable snapshot.
+
config EXT4_FS_SNAPSHOT_FILE_READ
bool "snapshot file - read through to block device"
depends on EXT4_FS_SNAPSHOT_FILE
View
@@ -735,7 +735,6 @@ ext4_fsblk_t ext4_count_free_clusters(struct super_block *sb)
continue;
desc_count += ext4_free_group_clusters(sb, gdp);
}
-
return desc_count;
#endif
}
View
@@ -474,21 +474,22 @@ struct flex_groups {
#define EXT4_SNAPFILE_FL 0x01000000 /* snapshot file */
#define EXT4_SNAPFILE_DELETED_FL 0x04000000 /* snapshot is deleted */
#define EXT4_SNAPFILE_SHRUNK_FL 0x08000000 /* snapshot was shrunk */
+#define EXT4_SNAPFILE_CLONE_FL 0x10000000 /* snapclone file */
/* end of snapshot flags */
#endif
#define EXT4_RESERVED_FL 0x80000000 /* reserved for ext4 lib */
#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE
-
-#define EXT4_FL_USER_VISIBLE 0x014BDFFF /* User visible flags */
-#define EXT4_FL_USER_MODIFIABLE 0x014B80FF /* User modifiable flags */
+#define EXT4_FL_USER_VISIBLE 0x114BDFFF /* User visible flags */
+#define EXT4_FL_USER_MODIFIABLE 0x114B80FF /* User modifiable flags */
/* Flags that should be inherited by new inodes from their parent. */
#define EXT4_FL_INHERITED (EXT4_SECRM_FL | EXT4_UNRM_FL | EXT4_COMPR_FL |\
EXT4_SYNC_FL | EXT4_IMMUTABLE_FL | EXT4_APPEND_FL |\
EXT4_NODUMP_FL | EXT4_NOATIME_FL |\
EXT4_NOCOMPR_FL | EXT4_JOURNAL_DATA_FL |\
- EXT4_NOTAIL_FL | EXT4_DIRSYNC_FL | EXT4_SNAPFILE_FL)
+ EXT4_NOTAIL_FL | EXT4_DIRSYNC_FL |\
+ EXT4_SNAPFILE_FL | EXT4_SNAPFILE_CLONE_FL)
#else
#define EXT4_FL_USER_VISIBLE 0x004BDFFF /* User visible flags */
#define EXT4_FL_USER_MODIFIABLE 0x004B80FF /* User modifiable flags */
@@ -550,6 +551,7 @@ enum {
EXT4_INODE_SNAPFILE_DELETED = 26, /* Snapshot is deleted */
EXT4_INODE_SNAPFILE_SHRUNK = 27, /* Snapshot was shrunk */
#endif
+ EXT4_INODE_SNAPCLONE = 28, /* Snapclone file/dir */
EXT4_INODE_RESERVED = 31, /* reserved for ext4 lib */
};
@@ -1081,6 +1083,9 @@ struct ext4_inode_info {
#define EXT4_FLAGS_IS_SNAPSHOT 0x0010 /* Is a snapshot image */
#define EXT4_FLAGS_FIX_SNAPSHOT 0x0020 /* Corrupted snapshot */
#define EXT4_FLAGS_FIX_EXCLUDE 0x0040 /* Bad exclude bitmap */
+#ifdef CONFIG_EXT4_FS_SNAPCLONE_FILE
+#define EXT4_FLAGS_IS_SNAPCLONE 0x0080 /* Is a snapclone image */
+#endif
#define EXT4_SET_FLAGS(sb, mask) \
do { \
@@ -1511,6 +1516,7 @@ enum {
EXT4_SNAPSTATE_SHRUNK = 5, /* snapshot was shrunk (h) */
EXT4_SNAPSTATE_OPEN = 6, /* snapshot is mounted (o) */
EXT4_SNAPSTATE_TAGGED = 7, /* snapshot is tagged (t) */
+ EXT4_SNAPSTATE_CLONED = 8, /* snapshot is cloned (c) */
EXT4_SNAPSTATE_LAST
#endif
};
@@ -2231,6 +2237,10 @@ extern int ext4_orphan_add(handle_t *, struct inode *);
extern int ext4_orphan_del(handle_t *, struct inode *);
extern int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
__u32 start_minor_hash, __u32 *next_hash);
+#ifdef CONFIG_EXT4_FS_SNAPCLONE_FILE
+extern int ext4_add_nondir(handle_t *handle,
+ struct dentry *dentry, struct inode *inode);
+#endif
/* resize.c */
extern int ext4_group_add(struct super_block *sb,
View
@@ -50,6 +50,7 @@ static int ext4_release_file(struct inode *inode, struct file *filp)
ext4_discard_preallocations(inode);
up_write(&EXT4_I(inode)->i_data_sem);
}
+
if (is_dx(inode) && filp->private_data)
ext4_htree_free_dir_info(filp->private_data);
@@ -169,8 +170,8 @@ static int ext4_file_open(struct inode * inode, struct file * filp)
char buf[64], *cp;
#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE_PERM
- if (ext4_snapshot_file(inode) &&
- (filp->f_flags & O_ACCMODE) != O_RDONLY)
+ if (ext4_snapshot_file(inode) && !ext4_snapclone_file(inode) &&
+ (filp->f_flags & O_ACCMODE) != O_RDONLY)
/*
* allow only read-only access to snapshot files
*/
View
@@ -934,7 +934,7 @@ struct inode *ext4_new_inode(handle_t *handle, struct inode *dir, umode_t mode,
* and normal symlink
*/
if ((S_ISREG(mode) && !ext4_snapshot_file(inode)) ||
- S_ISDIR(mode) || S_ISLNK(mode)) {
+ S_ISDIR(mode) || S_ISLNK(mode)) {
#else
/* set extent flag only for directory, file and normal symlink*/
if (S_ISDIR(mode) || S_ISREG(mode) || S_ISLNK(mode)) {
View
@@ -142,6 +142,9 @@ void ext4_evict_inode(struct inode *inode)
ext4_ioend_wait(inode);
+ if (ext4_snapclone_file(inode))
+ ext4_snapclone_destroy(inode);
+
if (inode->i_nlink) {
/*
* When journalling data dirty buffers are tracked only in the
@@ -676,13 +679,22 @@ static int ext4_partial_write_begin(struct inode *inode, sector_t iblock,
map.m_lblk = iblock;
map.m_len = 1;
- ret = ext4_map_blocks(NULL, inode, &map, 0);
+ if (ext4_snapclone_file(inode)) {
+ ret = ext4_snapshot_get_block(inode, iblock, bh, 0);
+ if (ret == 0) {
+ BUG_ON(!buffer_mapped(bh));
+ ret = 1;
+ }
+ } else {
+ ret = ext4_map_blocks(NULL, inode, &map, 0);
+ }
if (ret <= 0)
return ret;
if (!buffer_uptodate(bh) && !buffer_unwritten(bh)) {
/* map existing block for read */
- map_bh(bh, inode->i_sb, map.m_pblk);
+ if (!ext4_snapclone_file(inode))
+ map_bh(bh, inode->i_sb, map.m_pblk);
ll_rw_block(READ, 1, &bh);
wait_on_buffer(bh);
/* clear existing block mapping */
@@ -728,8 +740,9 @@ static int _ext4_get_block(struct inode *inode, sector_t iblock,
}
#ifdef CONFIG_EXT4_FS_SNAPSHOT_HOOKS_DATA
- if ((flags & EXT4_GET_BLOCKS_MOVE_ON_WRITE) &&
- buffer_partial_write(bh)) {
+ if (((flags & EXT4_GET_BLOCKS_MOVE_ON_WRITE) ||
+ ext4_snapclone_file(inode)) &&
+ buffer_partial_write(bh)) {
/* Read existing block data before moving it to snapshot */
ret = ext4_partial_write_begin(inode, iblock, bh);
if (ret < 0)
@@ -1000,8 +1013,9 @@ static void ext4_snapshot_write_begin(struct inode *inode,
* guarantee this we have to know that the transaction is not restarted.
* Can we count on that?
*/
- if (!EXT4_SNAPSHOTS(inode->i_sb) ||
- !ext4_snapshot_should_move_data(inode))
+ if ((!EXT4_SNAPSHOTS(inode->i_sb) ||
+ !ext4_snapshot_should_move_data(inode)) &&
+ !(ext4_snapclone_file(inode)))
return;
if (!page_has_buffers(page))
@@ -1011,14 +1025,15 @@ static void ext4_snapshot_write_begin(struct inode *inode,
/*
* make sure that get_block() is called even if the buffer is
* mapped, but not if it is already a part of any transaction.
- * in data=ordered,the only mode supported by ext4, all dirty
+ * in data=ordered,the only mode supported by ext4 snapshot, all dirty
* data buffers are flushed on snapshot take via freeze_fs()
* API.
*/
if (!buffer_jbd(bh) && !buffer_delay(bh)) {
clear_buffer_mapped(bh);
/* explicitly request move-on-write */
- if (!delay && len < PAGE_CACHE_SIZE)
+ if ((!delay || ext4_snapclone_file(inode)) &&
+ len < PAGE_CACHE_SIZE)
/* read block before moving it to snapshot */
set_buffer_partial_write(bh);
}
@@ -3408,6 +3423,43 @@ static const struct address_space_operations ext4_da_aops = {
.is_partially_uptodate = block_is_partially_uptodate,
.error_remove_page = generic_error_remove_page,
};
+#ifdef CONFIG_EXT4_FS_SNAPCLONE_FILE
+static const struct address_space_operations ext4_snapclone_da_aops = {
+#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE_READ
+ .readpage = ext4_snapshot_readpage,
+#else
+ .readpage = ext4_readpage,
+ .readpages = ext4_readpages,
+#endif
+ .writepage = ext4_writepage,
+ .writepages = ext4_da_writepages,
+ .write_begin = ext4_da_write_begin,
+ .write_end = ext4_da_write_end,
+ .bmap = ext4_bmap,
+ .invalidatepage = ext4_da_invalidatepage,
+ .releasepage = ext4_releasepage,
+};
+
+#endif
+
+#ifdef CONFIG_EXT4_FS_SNAPCLONE_FILE
+static const struct address_space_operations ext4_snapclone_aops = {
+#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE_READ
+ .readpage = ext4_snapshot_readpage,
+#else
+ .readpage = ext4_readpage,
+ .readpages = ext4_readpages,
+#endif
+ .writepage = ext4_writepage,
+ .write_begin = ext4_write_begin,
+ .write_end = ext4_ordered_write_end,
+ .bmap = ext4_bmap,
+ .invalidatepage = ext4_invalidatepage,
+ .releasepage = ext4_releasepage,
+};
+
+#endif
+
#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE
static int ext4_no_writepage(struct page *page,
struct writeback_control *wbc)
@@ -3443,6 +3495,13 @@ static const struct address_space_operations ext4_snapfile_aops = {
void ext4_set_aops(struct inode *inode)
{
+ /* We can not change order of snapclone and snapshot. */
+ if (ext4_snapclone_file(inode) &&
+ test_opt(inode->i_sb, DELALLOC))
+ inode->i_mapping->a_ops = &ext4_snapclone_da_aops;
+ else if (ext4_snapclone_file(inode))
+ inode->i_mapping->a_ops = &ext4_snapclone_aops;
+ else
#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE
if (ext4_snapshot_file(inode))
inode->i_mapping->a_ops = &ext4_snapfile_aops;
@@ -4090,7 +4149,11 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
}
#endif
INIT_LIST_HEAD(&ei->i_orphan);
-
+ if (ext4_snapclone_file(inode)) {
+ ret = ext4_snapclone_load(inode);
+ if (ret)
+ goto bad_inode;
+ }
/*
* Set transaction id's of transactions that have to be committed
* to finish f[data]sync. We set them to currently running transaction
View
@@ -87,7 +87,6 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
goto flags_out;
}
#ifdef CONFIG_EXT4_FS_SNAPSHOT_CTL
-
/*
* The SNAPFILE flag can only be changed on directories by
* the relevant capability.
@@ -236,8 +235,10 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
err = ext4_reserve_inode_write(handle, inode, &iloc);
if (err)
goto snapflags_err;
-
- err = ext4_snapshot_set_flags(handle, inode, flags);
+ if (ext4_snapclone_file(inode))
+ err = ext4_snapclone_set_flags(handle, inode, flags);
+ else
+ err = ext4_snapshot_set_flags(handle, inode, flags);
if (err)
goto snapflags_err;
View
@@ -928,7 +928,8 @@ static int ext4_mb_init_cache(struct page *page, char *incore)
if (bh[i] && !buffer_uptodate(bh[i]))
goto out;
- if (!(sb->s_flags & EXT4_FLAGS_IS_SNAPSHOT))
+ if (!(sb->s_flags & (EXT4_FLAGS_IS_SNAPSHOT |
+ EXT4_FLAGS_IS_SNAPCLONE)))
goto bg_fixed;
for (i = 0; i < groups_per_page; i++) {
struct ext4_group_desc *desc;
View
@@ -1712,8 +1712,7 @@ static void ext4_dec_count(handle_t *handle, struct inode *inode)
drop_nlink(inode);
}
-
-static int ext4_add_nondir(handle_t *handle,
+int ext4_add_nondir(handle_t *handle,
struct dentry *dentry, struct inode *inode)
{
int err = ext4_add_entry(handle, dentry, inode);
@@ -2216,16 +2215,13 @@ static int ext4_unlink(struct inode *dir, struct dentry *dentry)
inode->i_ino, inode->i_nlink);
set_nlink(inode, 1);
}
-#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE_PERM
- /* prevent unlink of files on snapshot list */
- if (inode->i_nlink == 1 &&
+ if (inode->i_nlink >= 1 &&
ext4_snapshot_list(inode)) {
snapshot_debug(1, "snapshot (%u) cannot be unlinked!\n",
inode->i_generation);
retval = -EPERM;
goto end_unlink;
}
-#endif
retval = ext4_delete_entry(handle, dir, de, bh);
if (retval)
goto end_unlink;
@@ -2363,6 +2359,11 @@ static int ext4_link(struct dentry *old_dentry,
if (inode->i_nlink >= EXT4_LINK_MAX)
return -EMLINK;
+#ifdef CONFIG_EXT4_FS_SNAPCLONE_FILE
+ if (ext4_snapshot_file(inode))
+ return ext4_snapclone_take(old_dentry, dir, dentry);
+#endif
+
dquot_initialize(dir);
retry:
View
@@ -421,6 +421,15 @@ extern int ext4_snapshot_update(struct super_block *sb, int cleanup,
extern void ext4_snapshot_destroy(struct super_block *sb);
#endif
+#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE
+extern int ext4_snapclone_take(struct dentry *old_dentry, struct inode *dir,
+ struct dentry *dentry);
+extern long ext4_snapclone_load(struct inode *);
+extern void ext4_snapclone_destroy(struct inode *);
+extern int ext4_snapclone_set_flags(handle_t *handle, struct inode *inode,
+ unsigned int flags);
+#endif
+
static inline int init_ext4_snapshot(void)
{
#ifdef CONFIG_EXT4_FS_SNAPSHOT_JOURNAL_CACHE
@@ -446,6 +455,11 @@ extern int ext4_snapshot_merge_blocks(handle_t *handle,
ext4_lblk_t iblock, unsigned long maxblocks);
#endif
+#ifdef CONFIG_EXT4_FS_SNAPCLONE_FILE
+extern int ext4_snapshot_get_block(struct inode *inode, sector_t iblock,
+ struct buffer_head *bh_result, int create);
+#endif
+
#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE
/* tests if @inode is a snapshot file */
static inline int ext4_snapshot_file(struct inode *inode)
@@ -462,6 +476,24 @@ static inline int ext4_snapshot_list(struct inode *inode)
return ext4_test_inode_snapstate(inode, EXT4_SNAPSTATE_LIST);
}
#endif
+#ifdef CONFIG_EXT4_FS_SNAPCLONE_FILE
+/* tests if @inode is cloned */
+static inline int ext4_snapshot_cloned(struct inode *inode)
+{
+ return ext4_test_inode_snapstate(inode, EXT4_SNAPSTATE_CLONED);
+}
+
+/* tests if @inode is a snapclone file */
+static inline int ext4_snapclone_file(struct inode *inode)
+{
+ if (!S_ISREG(inode->i_mode))
+ /* a snapclone directory */
+ return 0;
+ return ext4_test_inode_flag(inode, EXT4_INODE_SNAPCLONE);
+}
+#else
+#define ext4_snapclone_file(inode) (0)
+#endif
#ifdef CONFIG_EXT4_FS_SNAPSHOT_FILE
/*
Oops, something went wrong.

0 comments on commit 33afdaf

Please sign in to comment.