Merge branch 'for-linus' of git://github.com/chrismason/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 12 Sep 2011 18:47:49 +0000 (11:47 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 12 Sep 2011 18:47:49 +0000 (11:47 -0700)
* 'for-linus' of git://github.com/chrismason/linux:
  Btrfs: add dummy extent if dst offset excceeds file end in
  Btrfs: calc file extent num_bytes correctly in file clone
  btrfs: xattr: fix attribute removal
  Btrfs: fix wrong nbytes information of the inode
  Btrfs: fix the file extent gap when doing direct IO
  Btrfs: fix unclosed transaction handle in btrfs_cont_expand
  Btrfs: fix misuse of trans block rsv
  Btrfs: reset to appropriate block rsv after orphan operations
  Btrfs: skip locking if searching the commit root in csum lookup
  btrfs: fix warning in iput for bad-inode
  Btrfs: fix an oops when deleting snapshots

1  2 
fs/btrfs/file.c
fs/btrfs/inode.c
fs/btrfs/ioctl.c

diff --combined fs/btrfs/file.c
@@@ -1075,12 -1075,6 +1075,6 @@@ static noinline int prepare_pages(struc
        start_pos = pos & ~((u64)root->sectorsize - 1);
        last_pos = ((u64)index + num_pages) << PAGE_CACHE_SHIFT;
  
-       if (start_pos > inode->i_size) {
-               err = btrfs_cont_expand(inode, i_size_read(inode), start_pos);
-               if (err)
-                       return err;
-       }
  again:
        for (i = 0; i < num_pages; i++) {
                pages[i] = find_or_create_page(inode->i_mapping, index + i,
@@@ -1338,6 -1332,7 +1332,7 @@@ static ssize_t btrfs_file_aio_write(str
        struct inode *inode = fdentry(file)->d_inode;
        struct btrfs_root *root = BTRFS_I(inode)->root;
        loff_t *ppos = &iocb->ki_pos;
+       u64 start_pos;
        ssize_t num_written = 0;
        ssize_t err = 0;
        size_t count, ocount;
        file_update_time(file);
        BTRFS_I(inode)->sequence++;
  
+       start_pos = round_down(pos, root->sectorsize);
+       if (start_pos > i_size_read(inode)) {
+               err = btrfs_cont_expand(inode, i_size_read(inode), start_pos);
+               if (err) {
+                       mutex_unlock(&inode->i_mutex);
+                       goto out;
+               }
+       }
        if (unlikely(file->f_flags & O_DIRECT)) {
                num_written = __btrfs_direct_write(iocb, iov, nr_segs,
                                                   pos, ppos, count, ocount);
@@@ -1454,7 -1458,7 +1458,7 @@@ int btrfs_release_file(struct inode *in
   * important optimization for directories because holding the mutex prevents
   * new operations on the dir while we write to disk.
   */
 -int btrfs_sync_file(struct file *file, int datasync)
 +int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
  {
        struct dentry *dentry = file->f_path.dentry;
        struct inode *inode = dentry->d_inode;
  
        trace_btrfs_sync_file(file, datasync);
  
 +      ret = filemap_write_and_wait_range(inode->i_mapping, start, end);
 +      if (ret)
 +              return ret;
 +      mutex_lock(&inode->i_mutex);
 +
        /* we wait first, since the writeback may change the inode */
        root->log_batch++;
 -      /* the VFS called filemap_fdatawrite for us */
        btrfs_wait_ordered_range(inode, 0, (u64)-1);
        root->log_batch++;
  
         * check the transaction that last modified this inode
         * and see if its already been committed
         */
 -      if (!BTRFS_I(inode)->last_trans)
 +      if (!BTRFS_I(inode)->last_trans) {
 +              mutex_unlock(&inode->i_mutex);
                goto out;
 +      }
  
        /*
         * if the last transaction that changed this file was before
        if (BTRFS_I(inode)->last_trans <=
            root->fs_info->last_trans_committed) {
                BTRFS_I(inode)->last_trans = 0;
 +              mutex_unlock(&inode->i_mutex);
                goto out;
        }
  
        trans = btrfs_start_transaction(root, 0);
        if (IS_ERR(trans)) {
                ret = PTR_ERR(trans);
 +              mutex_unlock(&inode->i_mutex);
                goto out;
        }
  
        ret = btrfs_log_dentry_safe(trans, root, dentry);
 -      if (ret < 0)
 +      if (ret < 0) {
 +              mutex_unlock(&inode->i_mutex);
                goto out;
 +      }
  
        /* we've logged all the items and now have a consistent
         * version of the file in the log.  It is possible that
         * file again, but that will end up using the synchronization
         * inside btrfs_sync_log to keep things safe.
         */
 -      mutex_unlock(&dentry->d_inode->i_mutex);
 +      mutex_unlock(&inode->i_mutex);
  
        if (ret != BTRFS_NO_LOG_SYNC) {
                if (ret > 0) {
        } else {
                ret = btrfs_end_transaction(trans, root);
        }
 -      mutex_lock(&dentry->d_inode->i_mutex);
  out:
        return ret > 0 ? -EIO : ret;
  }
        return ret;
  }
  
 +static int find_desired_extent(struct inode *inode, loff_t *offset, int origin)
 +{
 +      struct btrfs_root *root = BTRFS_I(inode)->root;
 +      struct extent_map *em;
 +      struct extent_state *cached_state = NULL;
 +      u64 lockstart = *offset;
 +      u64 lockend = i_size_read(inode);
 +      u64 start = *offset;
 +      u64 orig_start = *offset;
 +      u64 len = i_size_read(inode);
 +      u64 last_end = 0;
 +      int ret = 0;
 +
 +      lockend = max_t(u64, root->sectorsize, lockend);
 +      if (lockend <= lockstart)
 +              lockend = lockstart + root->sectorsize;
 +
 +      len = lockend - lockstart + 1;
 +
 +      len = max_t(u64, len, root->sectorsize);
 +      if (inode->i_size == 0)
 +              return -ENXIO;
 +
 +      lock_extent_bits(&BTRFS_I(inode)->io_tree, lockstart, lockend, 0,
 +                       &cached_state, GFP_NOFS);
 +
 +      /*
 +       * Delalloc is such a pain.  If we have a hole and we have pending
 +       * delalloc for a portion of the hole we will get back a hole that
 +       * exists for the entire range since it hasn't been actually written
 +       * yet.  So to take care of this case we need to look for an extent just
 +       * before the position we want in case there is outstanding delalloc
 +       * going on here.
 +       */
 +      if (origin == SEEK_HOLE && start != 0) {
 +              if (start <= root->sectorsize)
 +                      em = btrfs_get_extent_fiemap(inode, NULL, 0, 0,
 +                                                   root->sectorsize, 0);
 +              else
 +                      em = btrfs_get_extent_fiemap(inode, NULL, 0,
 +                                                   start - root->sectorsize,
 +                                                   root->sectorsize, 0);
 +              if (IS_ERR(em)) {
 +                      ret = -ENXIO;
 +                      goto out;
 +              }
 +              last_end = em->start + em->len;
 +              if (em->block_start == EXTENT_MAP_DELALLOC)
 +                      last_end = min_t(u64, last_end, inode->i_size);
 +              free_extent_map(em);
 +      }
 +
 +      while (1) {
 +              em = btrfs_get_extent_fiemap(inode, NULL, 0, start, len, 0);
 +              if (IS_ERR(em)) {
 +                      ret = -ENXIO;
 +                      break;
 +              }
 +
 +              if (em->block_start == EXTENT_MAP_HOLE) {
 +                      if (test_bit(EXTENT_FLAG_VACANCY, &em->flags)) {
 +                              if (last_end <= orig_start) {
 +                                      free_extent_map(em);
 +                                      ret = -ENXIO;
 +                                      break;
 +                              }
 +                      }
 +
 +                      if (origin == SEEK_HOLE) {
 +                              *offset = start;
 +                              free_extent_map(em);
 +                              break;
 +                      }
 +              } else {
 +                      if (origin == SEEK_DATA) {
 +                              if (em->block_start == EXTENT_MAP_DELALLOC) {
 +                                      if (start >= inode->i_size) {
 +                                              free_extent_map(em);
 +                                              ret = -ENXIO;
 +                                              break;
 +                                      }
 +                              }
 +
 +                              *offset = start;
 +                              free_extent_map(em);
 +                              break;
 +                      }
 +              }
 +
 +              start = em->start + em->len;
 +              last_end = em->start + em->len;
 +
 +              if (em->block_start == EXTENT_MAP_DELALLOC)
 +                      last_end = min_t(u64, last_end, inode->i_size);
 +
 +              if (test_bit(EXTENT_FLAG_VACANCY, &em->flags)) {
 +                      free_extent_map(em);
 +                      ret = -ENXIO;
 +                      break;
 +              }
 +              free_extent_map(em);
 +              cond_resched();
 +      }
 +      if (!ret)
 +              *offset = min(*offset, inode->i_size);
 +out:
 +      unlock_extent_cached(&BTRFS_I(inode)->io_tree, lockstart, lockend,
 +                           &cached_state, GFP_NOFS);
 +      return ret;
 +}
 +
 +static loff_t btrfs_file_llseek(struct file *file, loff_t offset, int origin)
 +{
 +      struct inode *inode = file->f_mapping->host;
 +      int ret;
 +
 +      mutex_lock(&inode->i_mutex);
 +      switch (origin) {
 +      case SEEK_END:
 +      case SEEK_CUR:
 +              offset = generic_file_llseek_unlocked(file, offset, origin);
 +              goto out;
 +      case SEEK_DATA:
 +      case SEEK_HOLE:
 +              ret = find_desired_extent(inode, &offset, origin);
 +              if (ret) {
 +                      mutex_unlock(&inode->i_mutex);
 +                      return ret;
 +              }
 +      }
 +
 +      if (offset < 0 && !(file->f_mode & FMODE_UNSIGNED_OFFSET)) {
 +              ret = -EINVAL;
 +              goto out;
 +      }
 +      if (offset > inode->i_sb->s_maxbytes) {
 +              ret = -EINVAL;
 +              goto out;
 +      }
 +
 +      /* Special lock needed here? */
 +      if (offset != file->f_pos) {
 +              file->f_pos = offset;
 +              file->f_version = 0;
 +      }
 +out:
 +      mutex_unlock(&inode->i_mutex);
 +      return offset;
 +}
 +
  const struct file_operations btrfs_file_operations = {
 -      .llseek         = generic_file_llseek,
 +      .llseek         = btrfs_file_llseek,
        .read           = do_sync_read,
        .write          = do_sync_write,
        .aio_read       = generic_file_aio_read,
diff --combined fs/btrfs/inode.c
@@@ -1786,7 -1786,7 +1786,7 @@@ static int btrfs_finish_ordered_io(stru
                          &ordered_extent->list);
  
        ret = btrfs_ordered_update_i_size(inode, 0, ordered_extent);
-       if (!ret) {
+       if (!ret || !test_bit(BTRFS_ORDERED_PREALLOC, &ordered_extent->flags)) {
                ret = btrfs_update_inode(trans, root, inode);
                BUG_ON(ret);
        }
@@@ -3510,15 -3510,19 +3510,19 @@@ int btrfs_cont_expand(struct inode *ino
                        err = btrfs_drop_extents(trans, inode, cur_offset,
                                                 cur_offset + hole_size,
                                                 &hint_byte, 1);
-                       if (err)
+                       if (err) {
+                               btrfs_end_transaction(trans, root);
                                break;
+                       }
  
                        err = btrfs_insert_file_extent(trans, root,
                                        btrfs_ino(inode), cur_offset, 0,
                                        0, hole_size, 0, hole_size,
                                        0, 0, 0);
-                       if (err)
+                       if (err) {
+                               btrfs_end_transaction(trans, root);
                                break;
+                       }
  
                        btrfs_drop_extent_cache(inode, hole_start,
                                        last_byte - 1, 0);
@@@ -3952,7 -3956,6 +3956,6 @@@ struct inode *btrfs_iget(struct super_b
                         struct btrfs_root *root, int *new)
  {
        struct inode *inode;
-       int bad_inode = 0;
  
        inode = btrfs_iget_locked(s, location->objectid, root);
        if (!inode)
                        if (new)
                                *new = 1;
                } else {
-                       bad_inode = 1;
+                       unlock_new_inode(inode);
+                       iput(inode);
+                       inode = ERR_PTR(-ESTALE);
                }
        }
  
-       if (bad_inode) {
-               iput(inode);
-               inode = ERR_PTR(-ESTALE);
-       }
        return inode;
  }
  
@@@ -4009,19 -4009,12 +4009,19 @@@ struct inode *btrfs_lookup_dentry(struc
        struct btrfs_root *sub_root = root;
        struct btrfs_key location;
        int index;
 -      int ret;
 +      int ret = 0;
  
        if (dentry->d_name.len > BTRFS_NAME_LEN)
                return ERR_PTR(-ENAMETOOLONG);
  
 -      ret = btrfs_inode_by_name(dir, dentry, &location);
 +      if (unlikely(d_need_lookup(dentry))) {
 +              memcpy(&location, dentry->d_fsdata, sizeof(struct btrfs_key));
 +              kfree(dentry->d_fsdata);
 +              dentry->d_fsdata = NULL;
 +              d_clear_need_lookup(dentry);
 +      } else {
 +              ret = btrfs_inode_by_name(dir, dentry, &location);
 +      }
  
        if (ret < 0)
                return ERR_PTR(ret);
@@@ -4076,16 -4069,16 +4076,16 @@@ static int btrfs_dentry_delete(const st
        return 0;
  }
  
 +static void btrfs_dentry_release(struct dentry *dentry)
 +{
 +      if (dentry->d_fsdata)
 +              kfree(dentry->d_fsdata);
 +}
 +
  static struct dentry *btrfs_lookup(struct inode *dir, struct dentry *dentry,
                                   struct nameidata *nd)
  {
 -      struct inode *inode;
 -
 -      inode = btrfs_lookup_dentry(dir, dentry);
 -      if (IS_ERR(inode))
 -              return ERR_CAST(inode);
 -
 -      return d_splice_alias(inode, dentry);
 +      return d_splice_alias(btrfs_lookup_dentry(dir, dentry), dentry);
  }
  
  unsigned char btrfs_filetype_table[] = {
@@@ -4104,7 -4097,6 +4104,7 @@@ static int btrfs_real_readdir(struct fi
        struct btrfs_path *path;
        struct list_head ins_list;
        struct list_head del_list;
 +      struct qstr q;
        int ret;
        struct extent_buffer *leaf;
        int slot;
  
                while (di_cur < di_total) {
                        struct btrfs_key location;
 +                      struct dentry *tmp;
  
                        if (verify_dir_item(root, leaf, di))
                                break;
                        d_type = btrfs_filetype_table[btrfs_dir_type(leaf, di)];
                        btrfs_dir_item_key_to_cpu(leaf, di, &location);
  
 +                      q.name = name_ptr;
 +                      q.len = name_len;
 +                      q.hash = full_name_hash(q.name, q.len);
 +                      tmp = d_lookup(filp->f_dentry, &q);
 +                      if (!tmp) {
 +                              struct btrfs_key *newkey;
 +
 +                              newkey = kzalloc(sizeof(struct btrfs_key),
 +                                               GFP_NOFS);
 +                              if (!newkey)
 +                                      goto no_dentry;
 +                              tmp = d_alloc(filp->f_dentry, &q);
 +                              if (!tmp) {
 +                                      kfree(newkey);
 +                                      dput(tmp);
 +                                      goto no_dentry;
 +                              }
 +                              memcpy(newkey, &location,
 +                                     sizeof(struct btrfs_key));
 +                              tmp->d_fsdata = newkey;
 +                              tmp->d_flags |= DCACHE_NEED_LOOKUP;
 +                              d_rehash(tmp);
 +                              dput(tmp);
 +                      } else {
 +                              dput(tmp);
 +                      }
 +no_dentry:
                        /* is this a reference to our own snapshot? If so
                         * skip it
                         */
@@@ -4503,7 -4467,7 +4503,7 @@@ static struct inode *btrfs_new_inode(st
        inode->i_generation = BTRFS_I(inode)->generation;
        btrfs_set_inode_space_info(root, inode);
  
 -      if (mode & S_IFDIR)
 +      if (S_ISDIR(mode))
                owner = 0;
        else
                owner = 1;
  
        btrfs_inherit_iflags(inode, dir);
  
 -      if ((mode & S_IFREG)) {
 +      if (S_ISREG(mode)) {
                if (btrfs_test_opt(root, NODATASUM))
                        BTRFS_I(inode)->flags |= BTRFS_INODE_NODATASUM;
                if (btrfs_test_opt(root, NODATACOW) ||
@@@ -4802,10 -4766,11 +4802,10 @@@ static int btrfs_link(struct dentry *ol
        if (err) {
                drop_inode = 1;
        } else {
 -              struct dentry *parent = dget_parent(dentry);
 +              struct dentry *parent = dentry->d_parent;
                err = btrfs_update_inode(trans, root, inode);
                BUG_ON(err);
                btrfs_log_new_name(trans, inode, NULL, parent);
 -              dput(parent);
        }
  
        nr = trans->blocks_used;
@@@ -5823,7 -5788,7 +5823,7 @@@ again
  
        add_pending_csums(trans, inode, ordered->file_offset, &ordered->list);
        ret = btrfs_ordered_update_i_size(inode, 0, ordered);
-       if (!ret)
+       if (!ret || !test_bit(BTRFS_ORDERED_PREALLOC, &ordered->flags))
                btrfs_update_inode(trans, root, inode);
        ret = 0;
  out_unlock:
@@@ -6917,7 -6882,7 +6917,7 @@@ static int btrfs_getattr(struct vfsmoun
  {
        struct inode *inode = dentry->d_inode;
        generic_fillattr(inode, stat);
 -      stat->dev = BTRFS_I(inode)->root->anon_super.s_dev;
 +      stat->dev = BTRFS_I(inode)->root->anon_dev;
        stat->blksize = PAGE_CACHE_SIZE;
        stat->blocks = (inode_get_bytes(inode) +
                        BTRFS_I(inode)->delalloc_bytes) >> 9;
@@@ -7085,8 -7050,9 +7085,8 @@@ static int btrfs_rename(struct inode *o
        BUG_ON(ret);
  
        if (old_ino != BTRFS_FIRST_FREE_OBJECTID) {
 -              struct dentry *parent = dget_parent(new_dentry);
 +              struct dentry *parent = new_dentry->d_parent;
                btrfs_log_new_name(trans, old_inode, old_dir, parent);
 -              dput(parent);
                btrfs_end_log_trans(root);
        }
  out_fail:
@@@ -7351,19 -7317,15 +7351,19 @@@ static int btrfs_set_page_dirty(struct 
        return __set_page_dirty_nobuffers(page);
  }
  
 -static int btrfs_permission(struct inode *inode, int mask, unsigned int flags)
 +static int btrfs_permission(struct inode *inode, int mask)
  {
        struct btrfs_root *root = BTRFS_I(inode)->root;
 +      umode_t mode = inode->i_mode;
  
 -      if (btrfs_root_readonly(root) && (mask & MAY_WRITE))
 -              return -EROFS;
 -      if ((BTRFS_I(inode)->flags & BTRFS_INODE_READONLY) && (mask & MAY_WRITE))
 -              return -EACCES;
 -      return generic_permission(inode, mask, flags, btrfs_check_acl);
 +      if (mask & MAY_WRITE &&
 +          (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) {
 +              if (btrfs_root_readonly(root))
 +                      return -EROFS;
 +              if (BTRFS_I(inode)->flags & BTRFS_INODE_READONLY)
 +                      return -EACCES;
 +      }
 +      return generic_permission(inode, mask);
  }
  
  static const struct inode_operations btrfs_dir_inode_operations = {
        .listxattr      = btrfs_listxattr,
        .removexattr    = btrfs_removexattr,
        .permission     = btrfs_permission,
 +      .get_acl        = btrfs_get_acl,
  };
  static const struct inode_operations btrfs_dir_ro_inode_operations = {
        .lookup         = btrfs_lookup,
        .permission     = btrfs_permission,
 +      .get_acl        = btrfs_get_acl,
  };
  
  static const struct file_operations btrfs_dir_file_operations = {
@@@ -7457,7 -7417,6 +7457,7 @@@ static const struct inode_operations bt
        .removexattr    = btrfs_removexattr,
        .permission     = btrfs_permission,
        .fiemap         = btrfs_fiemap,
 +      .get_acl        = btrfs_get_acl,
  };
  static const struct inode_operations btrfs_special_inode_operations = {
        .getattr        = btrfs_getattr,
        .getxattr       = btrfs_getxattr,
        .listxattr      = btrfs_listxattr,
        .removexattr    = btrfs_removexattr,
 +      .get_acl        = btrfs_get_acl,
  };
  static const struct inode_operations btrfs_symlink_inode_operations = {
        .readlink       = generic_readlink,
        .getxattr       = btrfs_getxattr,
        .listxattr      = btrfs_listxattr,
        .removexattr    = btrfs_removexattr,
 +      .get_acl        = btrfs_get_acl,
  };
  
  const struct dentry_operations btrfs_dentry_operations = {
        .d_delete       = btrfs_dentry_delete,
 +      .d_release      = btrfs_dentry_release,
  };
diff --combined fs/btrfs/ioctl.c
@@@ -323,7 -323,7 +323,7 @@@ static noinline int create_subvol(struc
        struct btrfs_inode_item *inode_item;
        struct extent_buffer *leaf;
        struct btrfs_root *new_root;
 -      struct dentry *parent = dget_parent(dentry);
 +      struct dentry *parent = dentry->d_parent;
        struct inode *dir;
        int ret;
        int err;
        u64 index = 0;
  
        ret = btrfs_find_free_objectid(root->fs_info->tree_root, &objectid);
 -      if (ret) {
 -              dput(parent);
 +      if (ret)
                return ret;
 -      }
  
        dir = parent->d_inode;
  
         * 2 - dir items
         */
        trans = btrfs_start_transaction(root, 6);
 -      if (IS_ERR(trans)) {
 -              dput(parent);
 +      if (IS_ERR(trans))
                return PTR_ERR(trans);
 -      }
  
        leaf = btrfs_alloc_free_block(trans, root, root->leafsize,
                                      0, objectid, NULL, 0, 0, 0);
  
        d_instantiate(dentry, btrfs_lookup_dentry(dir, dentry));
  fail:
 -      dput(parent);
        if (async_transid) {
                *async_transid = trans->transid;
                err = btrfs_commit_transaction_async(trans, root, 1);
@@@ -451,6 -456,7 +451,6 @@@ static int create_snapshot(struct btrfs
                           bool readonly)
  {
        struct inode *inode;
 -      struct dentry *parent;
        struct btrfs_pending_snapshot *pending_snapshot;
        struct btrfs_trans_handle *trans;
        int ret;
        if (ret)
                goto fail;
  
 -      parent = dget_parent(dentry);
 -      inode = btrfs_lookup_dentry(parent->d_inode, dentry);
 -      dput(parent);
 +      inode = btrfs_lookup_dentry(dentry->d_parent->d_inode, dentry);
        if (IS_ERR(inode)) {
                ret = PTR_ERR(inode);
                goto fail;
@@@ -2220,6 -2228,12 +2220,12 @@@ static noinline long btrfs_ioctl_clone(
            !IS_ALIGNED(destoff, bs))
                goto out_unlock;
  
+       if (destoff > inode->i_size) {
+               ret = btrfs_cont_expand(inode, inode->i_size, destoff);
+               if (ret)
+                       goto out_unlock;
+       }
        /* do any pending delalloc/csum calc on src, one way or
           another, and lock file content */
        while (1) {
  
                        if (type == BTRFS_FILE_EXTENT_REG ||
                            type == BTRFS_FILE_EXTENT_PREALLOC) {
+                               /*
+                                *    a  | --- range to clone ---|  b
+                                * | ------------- extent ------------- |
+                                */
+                               /* substract range b */
+                               if (key.offset + datal > off + len)
+                                       datal = off + len - key.offset;
+                               /* substract range a */
                                if (off > key.offset) {
                                        datao += off - key.offset;
                                        datal -= off - key.offset;
                                }
  
-                               if (key.offset + datal > off + len)
-                                       datal = off + len - key.offset;
                                ret = btrfs_drop_extents(trans, inode,
                                                         new_key.offset,
                                                         new_key.offset + datal,