Merge tag 'arc-v3.11-rc1-part1' of git://git.kernel.org/pub/scm/linux/kernel/git...
[firefly-linux-kernel-4.4.55.git] / fs / ext4 / ioctl.c
index 721f4d33e148adf63872544dd8e4cedbbbb249f7..9491ac0590f746b9abe56e484e7646022a0bd1c3 100644 (file)
 #include <asm/uaccess.h>
 #include "ext4_jbd2.h"
 #include "ext4.h"
+#include "ext4_extents.h"
 
 #define MAX_32_NUM ((((unsigned long long) 1) << 32) - 1)
 
+/**
+ * Swap memory between @a and @b for @len bytes.
+ *
+ * @a:          pointer to first memory area
+ * @b:          pointer to second memory area
+ * @len:        number of bytes to swap
+ *
+ */
+static void memswap(void *a, void *b, size_t len)
+{
+       unsigned char *ap, *bp;
+       unsigned char tmp;
+
+       ap = (unsigned char *)a;
+       bp = (unsigned char *)b;
+       while (len-- > 0) {
+               tmp = *ap;
+               *ap = *bp;
+               *bp = tmp;
+               ap++;
+               bp++;
+       }
+}
+
+/**
+ * Swap i_data and associated attributes between @inode1 and @inode2.
+ * This function is used for the primary swap between inode1 and inode2
+ * and also to revert this primary swap in case of errors.
+ *
+ * Therefore you have to make sure, that calling this method twice
+ * will revert all changes.
+ *
+ * @inode1:     pointer to first inode
+ * @inode2:     pointer to second inode
+ */
+static void swap_inode_data(struct inode *inode1, struct inode *inode2)
+{
+       loff_t isize;
+       struct ext4_inode_info *ei1;
+       struct ext4_inode_info *ei2;
+
+       ei1 = EXT4_I(inode1);
+       ei2 = EXT4_I(inode2);
+
+       memswap(&inode1->i_flags, &inode2->i_flags, sizeof(inode1->i_flags));
+       memswap(&inode1->i_version, &inode2->i_version,
+                 sizeof(inode1->i_version));
+       memswap(&inode1->i_blocks, &inode2->i_blocks,
+                 sizeof(inode1->i_blocks));
+       memswap(&inode1->i_bytes, &inode2->i_bytes, sizeof(inode1->i_bytes));
+       memswap(&inode1->i_atime, &inode2->i_atime, sizeof(inode1->i_atime));
+       memswap(&inode1->i_mtime, &inode2->i_mtime, sizeof(inode1->i_mtime));
+
+       memswap(ei1->i_data, ei2->i_data, sizeof(ei1->i_data));
+       memswap(&ei1->i_flags, &ei2->i_flags, sizeof(ei1->i_flags));
+       memswap(&ei1->i_disksize, &ei2->i_disksize, sizeof(ei1->i_disksize));
+       memswap(&ei1->i_es_tree, &ei2->i_es_tree, sizeof(ei1->i_es_tree));
+       memswap(&ei1->i_es_lru_nr, &ei2->i_es_lru_nr, sizeof(ei1->i_es_lru_nr));
+
+       isize = i_size_read(inode1);
+       i_size_write(inode1, i_size_read(inode2));
+       i_size_write(inode2, isize);
+}
+
+/**
+ * Swap the information from the given @inode and the inode
+ * EXT4_BOOT_LOADER_INO. It will basically swap i_data and all other
+ * important fields of the inodes.
+ *
+ * @sb:         the super block of the filesystem
+ * @inode:      the inode to swap with EXT4_BOOT_LOADER_INO
+ *
+ */
+static long swap_inode_boot_loader(struct super_block *sb,
+                               struct inode *inode)
+{
+       handle_t *handle;
+       int err;
+       struct inode *inode_bl;
+       struct ext4_inode_info *ei;
+       struct ext4_inode_info *ei_bl;
+       struct ext4_sb_info *sbi;
+
+       if (inode->i_nlink != 1 || !S_ISREG(inode->i_mode)) {
+               err = -EINVAL;
+               goto swap_boot_out;
+       }
+
+       if (!inode_owner_or_capable(inode) || !capable(CAP_SYS_ADMIN)) {
+               err = -EPERM;
+               goto swap_boot_out;
+       }
+
+       sbi = EXT4_SB(sb);
+       ei = EXT4_I(inode);
+
+       inode_bl = ext4_iget(sb, EXT4_BOOT_LOADER_INO);
+       if (IS_ERR(inode_bl)) {
+               err = PTR_ERR(inode_bl);
+               goto swap_boot_out;
+       }
+       ei_bl = EXT4_I(inode_bl);
+
+       filemap_flush(inode->i_mapping);
+       filemap_flush(inode_bl->i_mapping);
+
+       /* Protect orig inodes against a truncate and make sure,
+        * that only 1 swap_inode_boot_loader is running. */
+       ext4_inode_double_lock(inode, inode_bl);
+
+       truncate_inode_pages(&inode->i_data, 0);
+       truncate_inode_pages(&inode_bl->i_data, 0);
+
+       /* Wait for all existing dio workers */
+       ext4_inode_block_unlocked_dio(inode);
+       ext4_inode_block_unlocked_dio(inode_bl);
+       inode_dio_wait(inode);
+       inode_dio_wait(inode_bl);
+
+       handle = ext4_journal_start(inode_bl, EXT4_HT_MOVE_EXTENTS, 2);
+       if (IS_ERR(handle)) {
+               err = -EINVAL;
+               goto swap_boot_out;
+       }
+
+       /* Protect extent tree against block allocations via delalloc */
+       ext4_double_down_write_data_sem(inode, inode_bl);
+
+       if (inode_bl->i_nlink == 0) {
+               /* this inode has never been used as a BOOT_LOADER */
+               set_nlink(inode_bl, 1);
+               i_uid_write(inode_bl, 0);
+               i_gid_write(inode_bl, 0);
+               inode_bl->i_flags = 0;
+               ei_bl->i_flags = 0;
+               inode_bl->i_version = 1;
+               i_size_write(inode_bl, 0);
+               inode_bl->i_mode = S_IFREG;
+               if (EXT4_HAS_INCOMPAT_FEATURE(sb,
+                                             EXT4_FEATURE_INCOMPAT_EXTENTS)) {
+                       ext4_set_inode_flag(inode_bl, EXT4_INODE_EXTENTS);
+                       ext4_ext_tree_init(handle, inode_bl);
+               } else
+                       memset(ei_bl->i_data, 0, sizeof(ei_bl->i_data));
+       }
+
+       swap_inode_data(inode, inode_bl);
+
+       inode->i_ctime = inode_bl->i_ctime = ext4_current_time(inode);
+
+       spin_lock(&sbi->s_next_gen_lock);
+       inode->i_generation = sbi->s_next_generation++;
+       inode_bl->i_generation = sbi->s_next_generation++;
+       spin_unlock(&sbi->s_next_gen_lock);
+
+       ext4_discard_preallocations(inode);
+
+       err = ext4_mark_inode_dirty(handle, inode);
+       if (err < 0) {
+               ext4_warning(inode->i_sb,
+                       "couldn't mark inode #%lu dirty (err %d)",
+                       inode->i_ino, err);
+               /* Revert all changes: */
+               swap_inode_data(inode, inode_bl);
+       } else {
+               err = ext4_mark_inode_dirty(handle, inode_bl);
+               if (err < 0) {
+                       ext4_warning(inode_bl->i_sb,
+                               "couldn't mark inode #%lu dirty (err %d)",
+                               inode_bl->i_ino, err);
+                       /* Revert all changes: */
+                       swap_inode_data(inode, inode_bl);
+                       ext4_mark_inode_dirty(handle, inode);
+               }
+       }
+
+       ext4_journal_stop(handle);
+
+       ext4_double_up_write_data_sem(inode, inode_bl);
+
+       ext4_inode_resume_unlocked_dio(inode);
+       ext4_inode_resume_unlocked_dio(inode_bl);
+
+       ext4_inode_double_unlock(inode, inode_bl);
+
+       iput(inode_bl);
+
+swap_boot_out:
+       return err;
+}
+
 long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
        struct inode *inode = file_inode(filp);
@@ -83,17 +275,8 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                        if (!capable(CAP_SYS_RESOURCE))
                                goto flags_out;
                }
-               if (oldflags & EXT4_EXTENTS_FL) {
-                       /* We don't support clearning extent flags */
-                       if (!(flags & EXT4_EXTENTS_FL)) {
-                               err = -EOPNOTSUPP;
-                               goto flags_out;
-                       }
-               } else if (flags & EXT4_EXTENTS_FL) {
-                       /* migrate the file */
+               if ((flags ^ oldflags) & EXT4_EXTENTS_FL)
                        migrate = 1;
-                       flags &= ~EXT4_EXTENTS_FL;
-               }
 
                if (flags & EXT4_EOFBLOCKS_FL) {
                        /* we don't support adding EOFBLOCKS flag */
@@ -137,8 +320,13 @@ flags_err:
                        err = ext4_change_inode_journal_flag(inode, jflag);
                if (err)
                        goto flags_out;
-               if (migrate)
-                       err = ext4_ext_migrate(inode);
+               if (migrate) {
+                       if (flags & EXT4_EXTENTS_FL)
+                               err = ext4_ext_migrate(inode);
+                       else
+                               err = ext4_ind_migrate(inode);
+               }
+
 flags_out:
                mutex_unlock(&inode->i_mutex);
                mnt_drop_write_file(filp);
@@ -357,9 +545,13 @@ group_add_out:
                return err;
        }
 
+       case EXT4_IOC_SWAP_BOOT:
+               if (!(filp->f_mode & FMODE_WRITE))
+                       return -EBADF;
+               return swap_inode_boot_loader(sb, inode);
+
        case EXT4_IOC_RESIZE_FS: {
                ext4_fsblk_t n_blocks_count;
-               struct super_block *sb = inode->i_sb;
                int err = 0, err2 = 0;
                ext4_group_t o_group = EXT4_SB(sb)->s_groups_count;