Commit f4881bc7a83eff263789dd524b7c269d138d4af5

Authored by Josef Bacik
1 parent adaa4b8e4d

Btrfs: fix space leak when we fail to reserve metadata space

Dave reported a warning when running xfstest 275.  We have been leaking delalloc
metadata space when our reservations fail.  This is because we were improperly
calculating how much space to free for our checksum reservations.  The problem
is we would sometimes free up space that had already been freed in another
thread and we would end up with negative usage for the delalloc space.  This
patch fixes the problem by calculating how much space the other threads would
have already freed, and then calculate how much space we need to free had we not
done the reservation at all, and then freeing any excess space.  This makes
xfstests 275 no longer have leaked space.  Thanks

Cc: stable@vger.kernel.org
Reported-by: David Sterba <dsterba@suse.cz>
Signed-off-by: Josef Bacik <jbacik@fusionio.com>

Showing 1 changed file with 41 additions and 6 deletions Side-by-side Diff

fs/btrfs/extent-tree.c
... ... @@ -4815,14 +4815,49 @@
4815 4815 * If the inodes csum_bytes is the same as the original
4816 4816 * csum_bytes then we know we haven't raced with any free()ers
4817 4817 * so we can just reduce our inodes csum bytes and carry on.
4818   - * Otherwise we have to do the normal free thing to account for
4819   - * the case that the free side didn't free up its reserve
4820   - * because of this outstanding reservation.
4821 4818 */
4822   - if (BTRFS_I(inode)->csum_bytes == csum_bytes)
  4819 + if (BTRFS_I(inode)->csum_bytes == csum_bytes) {
4823 4820 calc_csum_metadata_size(inode, num_bytes, 0);
4824   - else
4825   - to_free = calc_csum_metadata_size(inode, num_bytes, 0);
  4821 + } else {
  4822 + u64 orig_csum_bytes = BTRFS_I(inode)->csum_bytes;
  4823 + u64 bytes;
  4824 +
  4825 + /*
  4826 + * This is tricky, but first we need to figure out how much we
  4827 + * free'd from any free-ers that occured during this
  4828 + * reservation, so we reset ->csum_bytes to the csum_bytes
  4829 + * before we dropped our lock, and then call the free for the
  4830 + * number of bytes that were freed while we were trying our
  4831 + * reservation.
  4832 + */
  4833 + bytes = csum_bytes - BTRFS_I(inode)->csum_bytes;
  4834 + BTRFS_I(inode)->csum_bytes = csum_bytes;
  4835 + to_free = calc_csum_metadata_size(inode, bytes, 0);
  4836 +
  4837 +
  4838 + /*
  4839 + * Now we need to see how much we would have freed had we not
  4840 + * been making this reservation and our ->csum_bytes were not
  4841 + * artificially inflated.
  4842 + */
  4843 + BTRFS_I(inode)->csum_bytes = csum_bytes - num_bytes;
  4844 + bytes = csum_bytes - orig_csum_bytes;
  4845 + bytes = calc_csum_metadata_size(inode, bytes, 0);
  4846 +
  4847 + /*
  4848 + * Now reset ->csum_bytes to what it should be. If bytes is
  4849 + * more than to_free then we would have free'd more space had we
  4850 + * not had an artificially high ->csum_bytes, so we need to free
  4851 + * the remainder. If bytes is the same or less then we don't
  4852 + * need to do anything, the other free-ers did the correct
  4853 + * thing.
  4854 + */
  4855 + BTRFS_I(inode)->csum_bytes = orig_csum_bytes - num_bytes;
  4856 + if (bytes > to_free)
  4857 + to_free = bytes - to_free;
  4858 + else
  4859 + to_free = 0;
  4860 + }
4826 4861 spin_unlock(&BTRFS_I(inode)->lock);
4827 4862 if (dropped)
4828 4863 to_free += btrfs_calc_trans_metadata_size(root, dropped);