mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-24 20:54:10 +08:00
Merge git://git.kernel.org/pub/scm/linux/kernel/git/mason/btrfs-unstable
* git://git.kernel.org/pub/scm/linux/kernel/git/mason/btrfs-unstable: (864 commits) Btrfs: explicitly mark the tree log root for writeback Btrfs: Drop the hardware crc32c asm code Btrfs: Add Documentation/filesystem/btrfs.txt, remove old COPYING Btrfs: kmap_atomic(KM_USER0) is safe for btrfs_readpage_end_io_hook Btrfs: Don't use kmap_atomic(..., KM_IRQ0) during checksum verifies Btrfs: tree logging checksum fixes Btrfs: don't change file extent's ram_bytes in btrfs_drop_extents Btrfs: Use btrfs_join_transaction to avoid deadlocks during snapshot creation Btrfs: drop remaining LINUX_KERNEL_VERSION checks and compat code Btrfs: drop EXPORT symbols from extent_io.c Btrfs: Fix checkpatch.pl warnings Btrfs: Fix free block discard calls down to the block layer Btrfs: avoid orphan inode caused by log replay Btrfs: avoid potential super block corruption Btrfs: do not call kfree if kmalloc failed in btrfs_sysfs_add_super Btrfs: fix a memory leak in btrfs_get_sb Btrfs: Fix typo in clear_state_cb Btrfs: Fix memset length in btrfs_file_write Btrfs: update directory's size when creating subvol/snapshot Btrfs: add permission checks to the ioctls ...
This commit is contained in:
commit
73d59314e6
91
Documentation/filesystems/btrfs.txt
Normal file
91
Documentation/filesystems/btrfs.txt
Normal file
@ -0,0 +1,91 @@
|
||||
|
||||
BTRFS
|
||||
=====
|
||||
|
||||
Btrfs is a new copy on write filesystem for Linux aimed at
|
||||
implementing advanced features while focusing on fault tolerance,
|
||||
repair and easy administration. Initially developed by Oracle, Btrfs
|
||||
is licensed under the GPL and open for contribution from anyone.
|
||||
|
||||
Linux has a wealth of filesystems to choose from, but we are facing a
|
||||
number of challenges with scaling to the large storage subsystems that
|
||||
are becoming common in today's data centers. Filesystems need to scale
|
||||
in their ability to address and manage large storage, and also in
|
||||
their ability to detect, repair and tolerate errors in the data stored
|
||||
on disk. Btrfs is under heavy development, and is not suitable for
|
||||
any uses other than benchmarking and review. The Btrfs disk format is
|
||||
not yet finalized.
|
||||
|
||||
The main Btrfs features include:
|
||||
|
||||
* Extent based file storage (2^64 max file size)
|
||||
* Space efficient packing of small files
|
||||
* Space efficient indexed directories
|
||||
* Dynamic inode allocation
|
||||
* Writable snapshots
|
||||
* Subvolumes (separate internal filesystem roots)
|
||||
* Object level mirroring and striping
|
||||
* Checksums on data and metadata (multiple algorithms available)
|
||||
* Compression
|
||||
* Integrated multiple device support, with several raid algorithms
|
||||
* Online filesystem check (not yet implemented)
|
||||
* Very fast offline filesystem check
|
||||
* Efficient incremental backup and FS mirroring (not yet implemented)
|
||||
* Online filesystem defragmentation
|
||||
|
||||
|
||||
|
||||
MAILING LIST
|
||||
============
|
||||
|
||||
There is a Btrfs mailing list hosted on vger.kernel.org. You can
|
||||
find details on how to subscribe here:
|
||||
|
||||
http://vger.kernel.org/vger-lists.html#linux-btrfs
|
||||
|
||||
Mailing list archives are available from gmane:
|
||||
|
||||
http://dir.gmane.org/gmane.comp.file-systems.btrfs
|
||||
|
||||
|
||||
|
||||
IRC
|
||||
===
|
||||
|
||||
Discussion of Btrfs also occurs on the #btrfs channel of the Freenode
|
||||
IRC network.
|
||||
|
||||
|
||||
|
||||
UTILITIES
|
||||
=========
|
||||
|
||||
Userspace tools for creating and manipulating Btrfs file systems are
|
||||
available from the git repository at the following location:
|
||||
|
||||
http://git.kernel.org/?p=linux/kernel/git/mason/btrfs-progs-unstable.git
|
||||
git://git.kernel.org/pub/scm/linux/kernel/git/mason/btrfs-progs-unstable.git
|
||||
|
||||
These include the following tools:
|
||||
|
||||
mkfs.btrfs: create a filesystem
|
||||
|
||||
btrfsctl: control program to create snapshots and subvolumes:
|
||||
|
||||
mount /dev/sda2 /mnt
|
||||
btrfsctl -s new_subvol_name /mnt
|
||||
btrfsctl -s snapshot_of_default /mnt/default
|
||||
btrfsctl -s snapshot_of_new_subvol /mnt/new_subvol_name
|
||||
btrfsctl -s snapshot_of_a_snapshot /mnt/snapshot_of_new_subvol
|
||||
ls /mnt
|
||||
default snapshot_of_a_snapshot snapshot_of_new_subvol
|
||||
new_subvol_name snapshot_of_default
|
||||
|
||||
Snapshots and subvolumes cannot be deleted right now, but you can
|
||||
rm -rf all the files and directories inside them.
|
||||
|
||||
btrfsck: do a limited check of the FS extent trees.
|
||||
|
||||
btrfs-debug-tree: print all of the FS metadata in text form. Example:
|
||||
|
||||
btrfs-debug-tree /dev/sda2 >& big_output_file
|
19
fs/Kconfig
19
fs/Kconfig
@ -269,6 +269,25 @@ config OCFS2_FS_POSIX_ACL
|
||||
Posix Access Control Lists (ACLs) support permissions for users and
|
||||
groups beyond the owner/group/world scheme.
|
||||
|
||||
config BTRFS_FS
|
||||
tristate "Btrfs filesystem (EXPERIMENTAL) Unstable disk format"
|
||||
depends on EXPERIMENTAL
|
||||
select LIBCRC32C
|
||||
select ZLIB_INFLATE
|
||||
select ZLIB_DEFLATE
|
||||
help
|
||||
Btrfs is a new filesystem with extents, writable snapshotting,
|
||||
support for multiple devices and many more features.
|
||||
|
||||
Btrfs is highly experimental, and THE DISK FORMAT IS NOT YET
|
||||
FINALIZED. You should say N here unless you are interested in
|
||||
testing Btrfs with non-critical data.
|
||||
|
||||
To compile this file system support as a module, choose M here. The
|
||||
module will be called btrfs.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endif # BLOCK
|
||||
|
||||
source "fs/notify/Kconfig"
|
||||
|
@ -119,4 +119,5 @@ obj-$(CONFIG_HOSTFS) += hostfs/
|
||||
obj-$(CONFIG_HPPFS) += hppfs/
|
||||
obj-$(CONFIG_DEBUG_FS) += debugfs/
|
||||
obj-$(CONFIG_OCFS2_FS) += ocfs2/
|
||||
obj-$(CONFIG_BTRFS_FS) += btrfs/
|
||||
obj-$(CONFIG_GFS2_FS) += gfs2/
|
||||
|
25
fs/btrfs/Makefile
Normal file
25
fs/btrfs/Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
ifneq ($(KERNELRELEASE),)
|
||||
# kbuild part of makefile
|
||||
|
||||
obj-$(CONFIG_BTRFS_FS) := btrfs.o
|
||||
btrfs-y := super.o ctree.o extent-tree.o print-tree.o root-tree.o dir-item.o \
|
||||
file-item.o inode-item.o inode-map.o disk-io.o \
|
||||
transaction.o inode.o file.o tree-defrag.o \
|
||||
extent_map.o sysfs.o struct-funcs.o xattr.o ordered-data.o \
|
||||
extent_io.o volumes.o async-thread.o ioctl.o locking.o orphan.o \
|
||||
ref-cache.o export.o tree-log.o acl.o free-space-cache.o zlib.o \
|
||||
compression.o
|
||||
else
|
||||
|
||||
# Normal Makefile
|
||||
|
||||
KERNELDIR := /lib/modules/`uname -r`/build
|
||||
all:
|
||||
$(MAKE) -C $(KERNELDIR) M=`pwd` CONFIG_BTRFS_FS=m modules
|
||||
|
||||
modules_install:
|
||||
$(MAKE) -C $(KERNELDIR) M=`pwd` modules_install
|
||||
clean:
|
||||
$(MAKE) -C $(KERNELDIR) M=`pwd` clean
|
||||
|
||||
endif
|
351
fs/btrfs/acl.c
Normal file
351
fs/btrfs/acl.c
Normal file
@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Red Hat. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/posix_acl_xattr.h>
|
||||
#include <linux/posix_acl.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include "ctree.h"
|
||||
#include "btrfs_inode.h"
|
||||
#include "xattr.h"
|
||||
|
||||
#ifdef CONFIG_FS_POSIX_ACL
|
||||
|
||||
static void btrfs_update_cached_acl(struct inode *inode,
|
||||
struct posix_acl **p_acl,
|
||||
struct posix_acl *acl)
|
||||
{
|
||||
spin_lock(&inode->i_lock);
|
||||
if (*p_acl && *p_acl != BTRFS_ACL_NOT_CACHED)
|
||||
posix_acl_release(*p_acl);
|
||||
*p_acl = posix_acl_dup(acl);
|
||||
spin_unlock(&inode->i_lock);
|
||||
}
|
||||
|
||||
static struct posix_acl *btrfs_get_acl(struct inode *inode, int type)
|
||||
{
|
||||
int size;
|
||||
const char *name;
|
||||
char *value = NULL;
|
||||
struct posix_acl *acl = NULL, **p_acl;
|
||||
|
||||
switch (type) {
|
||||
case ACL_TYPE_ACCESS:
|
||||
name = POSIX_ACL_XATTR_ACCESS;
|
||||
p_acl = &BTRFS_I(inode)->i_acl;
|
||||
break;
|
||||
case ACL_TYPE_DEFAULT:
|
||||
name = POSIX_ACL_XATTR_DEFAULT;
|
||||
p_acl = &BTRFS_I(inode)->i_default_acl;
|
||||
break;
|
||||
default:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
spin_lock(&inode->i_lock);
|
||||
if (*p_acl != BTRFS_ACL_NOT_CACHED)
|
||||
acl = posix_acl_dup(*p_acl);
|
||||
spin_unlock(&inode->i_lock);
|
||||
|
||||
if (acl)
|
||||
return acl;
|
||||
|
||||
|
||||
size = __btrfs_getxattr(inode, name, "", 0);
|
||||
if (size > 0) {
|
||||
value = kzalloc(size, GFP_NOFS);
|
||||
if (!value)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
size = __btrfs_getxattr(inode, name, value, size);
|
||||
if (size > 0) {
|
||||
acl = posix_acl_from_xattr(value, size);
|
||||
btrfs_update_cached_acl(inode, p_acl, acl);
|
||||
}
|
||||
kfree(value);
|
||||
} else if (size == -ENOENT) {
|
||||
acl = NULL;
|
||||
btrfs_update_cached_acl(inode, p_acl, acl);
|
||||
}
|
||||
|
||||
return acl;
|
||||
}
|
||||
|
||||
static int btrfs_xattr_get_acl(struct inode *inode, int type,
|
||||
void *value, size_t size)
|
||||
{
|
||||
struct posix_acl *acl;
|
||||
int ret = 0;
|
||||
|
||||
acl = btrfs_get_acl(inode, type);
|
||||
|
||||
if (IS_ERR(acl))
|
||||
return PTR_ERR(acl);
|
||||
if (acl == NULL)
|
||||
return -ENODATA;
|
||||
ret = posix_acl_to_xattr(acl, value, size);
|
||||
posix_acl_release(acl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Needs to be called with fs_mutex held
|
||||
*/
|
||||
static int btrfs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
|
||||
{
|
||||
int ret, size = 0;
|
||||
const char *name;
|
||||
struct posix_acl **p_acl;
|
||||
char *value = NULL;
|
||||
mode_t mode;
|
||||
|
||||
if (acl) {
|
||||
ret = posix_acl_valid(acl);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case ACL_TYPE_ACCESS:
|
||||
mode = inode->i_mode;
|
||||
ret = posix_acl_equiv_mode(acl, &mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = 0;
|
||||
inode->i_mode = mode;
|
||||
name = POSIX_ACL_XATTR_ACCESS;
|
||||
p_acl = &BTRFS_I(inode)->i_acl;
|
||||
break;
|
||||
case ACL_TYPE_DEFAULT:
|
||||
if (!S_ISDIR(inode->i_mode))
|
||||
return acl ? -EINVAL : 0;
|
||||
name = POSIX_ACL_XATTR_DEFAULT;
|
||||
p_acl = &BTRFS_I(inode)->i_default_acl;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (acl) {
|
||||
size = posix_acl_xattr_size(acl->a_count);
|
||||
value = kmalloc(size, GFP_NOFS);
|
||||
if (!value) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = posix_acl_to_xattr(acl, value, size);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = __btrfs_setxattr(inode, name, value, size, 0);
|
||||
|
||||
out:
|
||||
kfree(value);
|
||||
|
||||
if (!ret)
|
||||
btrfs_update_cached_acl(inode, p_acl, acl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int btrfs_xattr_set_acl(struct inode *inode, int type,
|
||||
const void *value, size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct posix_acl *acl = NULL;
|
||||
|
||||
if (value) {
|
||||
acl = posix_acl_from_xattr(value, size);
|
||||
if (acl == NULL) {
|
||||
value = NULL;
|
||||
size = 0;
|
||||
} else if (IS_ERR(acl)) {
|
||||
return PTR_ERR(acl);
|
||||
}
|
||||
}
|
||||
|
||||
ret = btrfs_set_acl(inode, acl, type);
|
||||
|
||||
posix_acl_release(acl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int btrfs_xattr_acl_access_get(struct inode *inode, const char *name,
|
||||
void *value, size_t size)
|
||||
{
|
||||
return btrfs_xattr_get_acl(inode, ACL_TYPE_ACCESS, value, size);
|
||||
}
|
||||
|
||||
static int btrfs_xattr_acl_access_set(struct inode *inode, const char *name,
|
||||
const void *value, size_t size, int flags)
|
||||
{
|
||||
return btrfs_xattr_set_acl(inode, ACL_TYPE_ACCESS, value, size);
|
||||
}
|
||||
|
||||
static int btrfs_xattr_acl_default_get(struct inode *inode, const char *name,
|
||||
void *value, size_t size)
|
||||
{
|
||||
return btrfs_xattr_get_acl(inode, ACL_TYPE_DEFAULT, value, size);
|
||||
}
|
||||
|
||||
static int btrfs_xattr_acl_default_set(struct inode *inode, const char *name,
|
||||
const void *value, size_t size, int flags)
|
||||
{
|
||||
return btrfs_xattr_set_acl(inode, ACL_TYPE_DEFAULT, value, size);
|
||||
}
|
||||
|
||||
int btrfs_check_acl(struct inode *inode, int mask)
|
||||
{
|
||||
struct posix_acl *acl;
|
||||
int error = -EAGAIN;
|
||||
|
||||
acl = btrfs_get_acl(inode, ACL_TYPE_ACCESS);
|
||||
|
||||
if (IS_ERR(acl))
|
||||
return PTR_ERR(acl);
|
||||
if (acl) {
|
||||
error = posix_acl_permission(inode, acl, mask);
|
||||
posix_acl_release(acl);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* btrfs_init_acl is already generally called under fs_mutex, so the locking
|
||||
* stuff has been fixed to work with that. If the locking stuff changes, we
|
||||
* need to re-evaluate the acl locking stuff.
|
||||
*/
|
||||
int btrfs_init_acl(struct inode *inode, struct inode *dir)
|
||||
{
|
||||
struct posix_acl *acl = NULL;
|
||||
int ret = 0;
|
||||
|
||||
/* this happens with subvols */
|
||||
if (!dir)
|
||||
return 0;
|
||||
|
||||
if (!S_ISLNK(inode->i_mode)) {
|
||||
if (IS_POSIXACL(dir)) {
|
||||
acl = btrfs_get_acl(dir, ACL_TYPE_DEFAULT);
|
||||
if (IS_ERR(acl))
|
||||
return PTR_ERR(acl);
|
||||
}
|
||||
|
||||
if (!acl)
|
||||
inode->i_mode &= ~current->fs->umask;
|
||||
}
|
||||
|
||||
if (IS_POSIXACL(dir) && acl) {
|
||||
struct posix_acl *clone;
|
||||
mode_t mode;
|
||||
|
||||
if (S_ISDIR(inode->i_mode)) {
|
||||
ret = btrfs_set_acl(inode, acl, ACL_TYPE_DEFAULT);
|
||||
if (ret)
|
||||
goto failed;
|
||||
}
|
||||
clone = posix_acl_clone(acl, GFP_NOFS);
|
||||
ret = -ENOMEM;
|
||||
if (!clone)
|
||||
goto failed;
|
||||
|
||||
mode = inode->i_mode;
|
||||
ret = posix_acl_create_masq(clone, &mode);
|
||||
if (ret >= 0) {
|
||||
inode->i_mode = mode;
|
||||
if (ret > 0) {
|
||||
/* we need an acl */
|
||||
ret = btrfs_set_acl(inode, clone,
|
||||
ACL_TYPE_ACCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
failed:
|
||||
posix_acl_release(acl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_acl_chmod(struct inode *inode)
|
||||
{
|
||||
struct posix_acl *acl, *clone;
|
||||
int ret = 0;
|
||||
|
||||
if (S_ISLNK(inode->i_mode))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (!IS_POSIXACL(inode))
|
||||
return 0;
|
||||
|
||||
acl = btrfs_get_acl(inode, ACL_TYPE_ACCESS);
|
||||
if (IS_ERR(acl) || !acl)
|
||||
return PTR_ERR(acl);
|
||||
|
||||
clone = posix_acl_clone(acl, GFP_KERNEL);
|
||||
posix_acl_release(acl);
|
||||
if (!clone)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = posix_acl_chmod_masq(clone, inode->i_mode);
|
||||
if (!ret)
|
||||
ret = btrfs_set_acl(inode, clone, ACL_TYPE_ACCESS);
|
||||
|
||||
posix_acl_release(clone);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct xattr_handler btrfs_xattr_acl_default_handler = {
|
||||
.prefix = POSIX_ACL_XATTR_DEFAULT,
|
||||
.get = btrfs_xattr_acl_default_get,
|
||||
.set = btrfs_xattr_acl_default_set,
|
||||
};
|
||||
|
||||
struct xattr_handler btrfs_xattr_acl_access_handler = {
|
||||
.prefix = POSIX_ACL_XATTR_ACCESS,
|
||||
.get = btrfs_xattr_acl_access_get,
|
||||
.set = btrfs_xattr_acl_access_set,
|
||||
};
|
||||
|
||||
#else /* CONFIG_FS_POSIX_ACL */
|
||||
|
||||
int btrfs_acl_chmod(struct inode *inode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btrfs_init_acl(struct inode *inode, struct inode *dir)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btrfs_check_acl(struct inode *inode, int mask)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_FS_POSIX_ACL */
|
419
fs/btrfs/async-thread.c
Normal file
419
fs/btrfs/async-thread.c
Normal file
@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
# include <linux/freezer.h>
|
||||
#include "async-thread.h"
|
||||
|
||||
#define WORK_QUEUED_BIT 0
|
||||
#define WORK_DONE_BIT 1
|
||||
#define WORK_ORDER_DONE_BIT 2
|
||||
|
||||
/*
|
||||
* container for the kthread task pointer and the list of pending work
|
||||
* One of these is allocated per thread.
|
||||
*/
|
||||
struct btrfs_worker_thread {
|
||||
/* pool we belong to */
|
||||
struct btrfs_workers *workers;
|
||||
|
||||
/* list of struct btrfs_work that are waiting for service */
|
||||
struct list_head pending;
|
||||
|
||||
/* list of worker threads from struct btrfs_workers */
|
||||
struct list_head worker_list;
|
||||
|
||||
/* kthread */
|
||||
struct task_struct *task;
|
||||
|
||||
/* number of things on the pending list */
|
||||
atomic_t num_pending;
|
||||
|
||||
unsigned long sequence;
|
||||
|
||||
/* protects the pending list. */
|
||||
spinlock_t lock;
|
||||
|
||||
/* set to non-zero when this thread is already awake and kicking */
|
||||
int working;
|
||||
|
||||
/* are we currently idle */
|
||||
int idle;
|
||||
};
|
||||
|
||||
/*
|
||||
* helper function to move a thread onto the idle list after it
|
||||
* has finished some requests.
|
||||
*/
|
||||
static void check_idle_worker(struct btrfs_worker_thread *worker)
|
||||
{
|
||||
if (!worker->idle && atomic_read(&worker->num_pending) <
|
||||
worker->workers->idle_thresh / 2) {
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&worker->workers->lock, flags);
|
||||
worker->idle = 1;
|
||||
list_move(&worker->worker_list, &worker->workers->idle_list);
|
||||
spin_unlock_irqrestore(&worker->workers->lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* helper function to move a thread off the idle list after new
|
||||
* pending work is added.
|
||||
*/
|
||||
static void check_busy_worker(struct btrfs_worker_thread *worker)
|
||||
{
|
||||
if (worker->idle && atomic_read(&worker->num_pending) >=
|
||||
worker->workers->idle_thresh) {
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&worker->workers->lock, flags);
|
||||
worker->idle = 0;
|
||||
list_move_tail(&worker->worker_list,
|
||||
&worker->workers->worker_list);
|
||||
spin_unlock_irqrestore(&worker->workers->lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static noinline int run_ordered_completions(struct btrfs_workers *workers,
|
||||
struct btrfs_work *work)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (!workers->ordered)
|
||||
return 0;
|
||||
|
||||
set_bit(WORK_DONE_BIT, &work->flags);
|
||||
|
||||
spin_lock_irqsave(&workers->lock, flags);
|
||||
|
||||
while (!list_empty(&workers->order_list)) {
|
||||
work = list_entry(workers->order_list.next,
|
||||
struct btrfs_work, order_list);
|
||||
|
||||
if (!test_bit(WORK_DONE_BIT, &work->flags))
|
||||
break;
|
||||
|
||||
/* we are going to call the ordered done function, but
|
||||
* we leave the work item on the list as a barrier so
|
||||
* that later work items that are done don't have their
|
||||
* functions called before this one returns
|
||||
*/
|
||||
if (test_and_set_bit(WORK_ORDER_DONE_BIT, &work->flags))
|
||||
break;
|
||||
|
||||
spin_unlock_irqrestore(&workers->lock, flags);
|
||||
|
||||
work->ordered_func(work);
|
||||
|
||||
/* now take the lock again and call the freeing code */
|
||||
spin_lock_irqsave(&workers->lock, flags);
|
||||
list_del(&work->order_list);
|
||||
work->ordered_free(work);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&workers->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* main loop for servicing work items
|
||||
*/
|
||||
static int worker_loop(void *arg)
|
||||
{
|
||||
struct btrfs_worker_thread *worker = arg;
|
||||
struct list_head *cur;
|
||||
struct btrfs_work *work;
|
||||
do {
|
||||
spin_lock_irq(&worker->lock);
|
||||
while (!list_empty(&worker->pending)) {
|
||||
cur = worker->pending.next;
|
||||
work = list_entry(cur, struct btrfs_work, list);
|
||||
list_del(&work->list);
|
||||
clear_bit(WORK_QUEUED_BIT, &work->flags);
|
||||
|
||||
work->worker = worker;
|
||||
spin_unlock_irq(&worker->lock);
|
||||
|
||||
work->func(work);
|
||||
|
||||
atomic_dec(&worker->num_pending);
|
||||
/*
|
||||
* unless this is an ordered work queue,
|
||||
* 'work' was probably freed by func above.
|
||||
*/
|
||||
run_ordered_completions(worker->workers, work);
|
||||
|
||||
spin_lock_irq(&worker->lock);
|
||||
check_idle_worker(worker);
|
||||
|
||||
}
|
||||
worker->working = 0;
|
||||
if (freezing(current)) {
|
||||
refrigerator();
|
||||
} else {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
spin_unlock_irq(&worker->lock);
|
||||
if (!kthread_should_stop())
|
||||
schedule();
|
||||
__set_current_state(TASK_RUNNING);
|
||||
}
|
||||
} while (!kthread_should_stop());
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* this will wait for all the worker threads to shutdown
|
||||
*/
|
||||
int btrfs_stop_workers(struct btrfs_workers *workers)
|
||||
{
|
||||
struct list_head *cur;
|
||||
struct btrfs_worker_thread *worker;
|
||||
|
||||
list_splice_init(&workers->idle_list, &workers->worker_list);
|
||||
while (!list_empty(&workers->worker_list)) {
|
||||
cur = workers->worker_list.next;
|
||||
worker = list_entry(cur, struct btrfs_worker_thread,
|
||||
worker_list);
|
||||
kthread_stop(worker->task);
|
||||
list_del(&worker->worker_list);
|
||||
kfree(worker);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* simple init on struct btrfs_workers
|
||||
*/
|
||||
void btrfs_init_workers(struct btrfs_workers *workers, char *name, int max)
|
||||
{
|
||||
workers->num_workers = 0;
|
||||
INIT_LIST_HEAD(&workers->worker_list);
|
||||
INIT_LIST_HEAD(&workers->idle_list);
|
||||
INIT_LIST_HEAD(&workers->order_list);
|
||||
spin_lock_init(&workers->lock);
|
||||
workers->max_workers = max;
|
||||
workers->idle_thresh = 32;
|
||||
workers->name = name;
|
||||
workers->ordered = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* starts new worker threads. This does not enforce the max worker
|
||||
* count in case you need to temporarily go past it.
|
||||
*/
|
||||
int btrfs_start_workers(struct btrfs_workers *workers, int num_workers)
|
||||
{
|
||||
struct btrfs_worker_thread *worker;
|
||||
int ret = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_workers; i++) {
|
||||
worker = kzalloc(sizeof(*worker), GFP_NOFS);
|
||||
if (!worker) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&worker->pending);
|
||||
INIT_LIST_HEAD(&worker->worker_list);
|
||||
spin_lock_init(&worker->lock);
|
||||
atomic_set(&worker->num_pending, 0);
|
||||
worker->task = kthread_run(worker_loop, worker,
|
||||
"btrfs-%s-%d", workers->name,
|
||||
workers->num_workers + i);
|
||||
worker->workers = workers;
|
||||
if (IS_ERR(worker->task)) {
|
||||
kfree(worker);
|
||||
ret = PTR_ERR(worker->task);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
spin_lock_irq(&workers->lock);
|
||||
list_add_tail(&worker->worker_list, &workers->idle_list);
|
||||
worker->idle = 1;
|
||||
workers->num_workers++;
|
||||
spin_unlock_irq(&workers->lock);
|
||||
}
|
||||
return 0;
|
||||
fail:
|
||||
btrfs_stop_workers(workers);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* run through the list and find a worker thread that doesn't have a lot
|
||||
* to do right now. This can return null if we aren't yet at the thread
|
||||
* count limit and all of the threads are busy.
|
||||
*/
|
||||
static struct btrfs_worker_thread *next_worker(struct btrfs_workers *workers)
|
||||
{
|
||||
struct btrfs_worker_thread *worker;
|
||||
struct list_head *next;
|
||||
int enforce_min = workers->num_workers < workers->max_workers;
|
||||
|
||||
/*
|
||||
* if we find an idle thread, don't move it to the end of the
|
||||
* idle list. This improves the chance that the next submission
|
||||
* will reuse the same thread, and maybe catch it while it is still
|
||||
* working
|
||||
*/
|
||||
if (!list_empty(&workers->idle_list)) {
|
||||
next = workers->idle_list.next;
|
||||
worker = list_entry(next, struct btrfs_worker_thread,
|
||||
worker_list);
|
||||
return worker;
|
||||
}
|
||||
if (enforce_min || list_empty(&workers->worker_list))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* if we pick a busy task, move the task to the end of the list.
|
||||
* hopefully this will keep things somewhat evenly balanced.
|
||||
* Do the move in batches based on the sequence number. This groups
|
||||
* requests submitted at roughly the same time onto the same worker.
|
||||
*/
|
||||
next = workers->worker_list.next;
|
||||
worker = list_entry(next, struct btrfs_worker_thread, worker_list);
|
||||
atomic_inc(&worker->num_pending);
|
||||
worker->sequence++;
|
||||
|
||||
if (worker->sequence % workers->idle_thresh == 0)
|
||||
list_move_tail(next, &workers->worker_list);
|
||||
return worker;
|
||||
}
|
||||
|
||||
/*
|
||||
* selects a worker thread to take the next job. This will either find
|
||||
* an idle worker, start a new worker up to the max count, or just return
|
||||
* one of the existing busy workers.
|
||||
*/
|
||||
static struct btrfs_worker_thread *find_worker(struct btrfs_workers *workers)
|
||||
{
|
||||
struct btrfs_worker_thread *worker;
|
||||
unsigned long flags;
|
||||
|
||||
again:
|
||||
spin_lock_irqsave(&workers->lock, flags);
|
||||
worker = next_worker(workers);
|
||||
spin_unlock_irqrestore(&workers->lock, flags);
|
||||
|
||||
if (!worker) {
|
||||
spin_lock_irqsave(&workers->lock, flags);
|
||||
if (workers->num_workers >= workers->max_workers) {
|
||||
struct list_head *fallback = NULL;
|
||||
/*
|
||||
* we have failed to find any workers, just
|
||||
* return the force one
|
||||
*/
|
||||
if (!list_empty(&workers->worker_list))
|
||||
fallback = workers->worker_list.next;
|
||||
if (!list_empty(&workers->idle_list))
|
||||
fallback = workers->idle_list.next;
|
||||
BUG_ON(!fallback);
|
||||
worker = list_entry(fallback,
|
||||
struct btrfs_worker_thread, worker_list);
|
||||
spin_unlock_irqrestore(&workers->lock, flags);
|
||||
} else {
|
||||
spin_unlock_irqrestore(&workers->lock, flags);
|
||||
/* we're below the limit, start another worker */
|
||||
btrfs_start_workers(workers, 1);
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
return worker;
|
||||
}
|
||||
|
||||
/*
|
||||
* btrfs_requeue_work just puts the work item back on the tail of the list
|
||||
* it was taken from. It is intended for use with long running work functions
|
||||
* that make some progress and want to give the cpu up for others.
|
||||
*/
|
||||
int btrfs_requeue_work(struct btrfs_work *work)
|
||||
{
|
||||
struct btrfs_worker_thread *worker = work->worker;
|
||||
unsigned long flags;
|
||||
|
||||
if (test_and_set_bit(WORK_QUEUED_BIT, &work->flags))
|
||||
goto out;
|
||||
|
||||
spin_lock_irqsave(&worker->lock, flags);
|
||||
atomic_inc(&worker->num_pending);
|
||||
list_add_tail(&work->list, &worker->pending);
|
||||
|
||||
/* by definition we're busy, take ourselves off the idle
|
||||
* list
|
||||
*/
|
||||
if (worker->idle) {
|
||||
spin_lock_irqsave(&worker->workers->lock, flags);
|
||||
worker->idle = 0;
|
||||
list_move_tail(&worker->worker_list,
|
||||
&worker->workers->worker_list);
|
||||
spin_unlock_irqrestore(&worker->workers->lock, flags);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&worker->lock, flags);
|
||||
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* places a struct btrfs_work into the pending queue of one of the kthreads
|
||||
*/
|
||||
int btrfs_queue_worker(struct btrfs_workers *workers, struct btrfs_work *work)
|
||||
{
|
||||
struct btrfs_worker_thread *worker;
|
||||
unsigned long flags;
|
||||
int wake = 0;
|
||||
|
||||
/* don't requeue something already on a list */
|
||||
if (test_and_set_bit(WORK_QUEUED_BIT, &work->flags))
|
||||
goto out;
|
||||
|
||||
worker = find_worker(workers);
|
||||
if (workers->ordered) {
|
||||
spin_lock_irqsave(&workers->lock, flags);
|
||||
list_add_tail(&work->order_list, &workers->order_list);
|
||||
spin_unlock_irqrestore(&workers->lock, flags);
|
||||
} else {
|
||||
INIT_LIST_HEAD(&work->order_list);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&worker->lock, flags);
|
||||
atomic_inc(&worker->num_pending);
|
||||
check_busy_worker(worker);
|
||||
list_add_tail(&work->list, &worker->pending);
|
||||
|
||||
/*
|
||||
* avoid calling into wake_up_process if this thread has already
|
||||
* been kicked
|
||||
*/
|
||||
if (!worker->working)
|
||||
wake = 1;
|
||||
worker->working = 1;
|
||||
|
||||
spin_unlock_irqrestore(&worker->lock, flags);
|
||||
|
||||
if (wake)
|
||||
wake_up_process(worker->task);
|
||||
out:
|
||||
return 0;
|
||||
}
|
101
fs/btrfs/async-thread.h
Normal file
101
fs/btrfs/async-thread.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BTRFS_ASYNC_THREAD_
|
||||
#define __BTRFS_ASYNC_THREAD_
|
||||
|
||||
struct btrfs_worker_thread;
|
||||
|
||||
/*
|
||||
* This is similar to a workqueue, but it is meant to spread the operations
|
||||
* across all available cpus instead of just the CPU that was used to
|
||||
* queue the work. There is also some batching introduced to try and
|
||||
* cut down on context switches.
|
||||
*
|
||||
* By default threads are added on demand up to 2 * the number of cpus.
|
||||
* Changing struct btrfs_workers->max_workers is one way to prevent
|
||||
* demand creation of kthreads.
|
||||
*
|
||||
* the basic model of these worker threads is to embed a btrfs_work
|
||||
* structure in your own data struct, and use container_of in a
|
||||
* work function to get back to your data struct.
|
||||
*/
|
||||
struct btrfs_work {
|
||||
/*
|
||||
* func should be set to the function you want called
|
||||
* your work struct is passed as the only arg
|
||||
*
|
||||
* ordered_func must be set for work sent to an ordered work queue,
|
||||
* and it is called to complete a given work item in the same
|
||||
* order they were sent to the queue.
|
||||
*/
|
||||
void (*func)(struct btrfs_work *work);
|
||||
void (*ordered_func)(struct btrfs_work *work);
|
||||
void (*ordered_free)(struct btrfs_work *work);
|
||||
|
||||
/*
|
||||
* flags should be set to zero. It is used to make sure the
|
||||
* struct is only inserted once into the list.
|
||||
*/
|
||||
unsigned long flags;
|
||||
|
||||
/* don't touch these */
|
||||
struct btrfs_worker_thread *worker;
|
||||
struct list_head list;
|
||||
struct list_head order_list;
|
||||
};
|
||||
|
||||
struct btrfs_workers {
|
||||
/* current number of running workers */
|
||||
int num_workers;
|
||||
|
||||
/* max number of workers allowed. changed by btrfs_start_workers */
|
||||
int max_workers;
|
||||
|
||||
/* once a worker has this many requests or fewer, it is idle */
|
||||
int idle_thresh;
|
||||
|
||||
/* force completions in the order they were queued */
|
||||
int ordered;
|
||||
|
||||
/* list with all the work threads. The workers on the idle thread
|
||||
* may be actively servicing jobs, but they haven't yet hit the
|
||||
* idle thresh limit above.
|
||||
*/
|
||||
struct list_head worker_list;
|
||||
struct list_head idle_list;
|
||||
|
||||
/*
|
||||
* when operating in ordered mode, this maintains the list
|
||||
* of work items waiting for completion
|
||||
*/
|
||||
struct list_head order_list;
|
||||
|
||||
/* lock for finding the next worker thread to queue on */
|
||||
spinlock_t lock;
|
||||
|
||||
/* extra name for this worker, used for current->name */
|
||||
char *name;
|
||||
};
|
||||
|
||||
int btrfs_queue_worker(struct btrfs_workers *workers, struct btrfs_work *work);
|
||||
int btrfs_start_workers(struct btrfs_workers *workers, int num_workers);
|
||||
int btrfs_stop_workers(struct btrfs_workers *workers);
|
||||
void btrfs_init_workers(struct btrfs_workers *workers, char *name, int max);
|
||||
int btrfs_requeue_work(struct btrfs_work *work);
|
||||
#endif
|
131
fs/btrfs/btrfs_inode.h
Normal file
131
fs/btrfs/btrfs_inode.h
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BTRFS_I__
|
||||
#define __BTRFS_I__
|
||||
|
||||
#include "extent_map.h"
|
||||
#include "extent_io.h"
|
||||
#include "ordered-data.h"
|
||||
|
||||
/* in memory btrfs inode */
|
||||
struct btrfs_inode {
|
||||
/* which subvolume this inode belongs to */
|
||||
struct btrfs_root *root;
|
||||
|
||||
/* key used to find this inode on disk. This is used by the code
|
||||
* to read in roots of subvolumes
|
||||
*/
|
||||
struct btrfs_key location;
|
||||
|
||||
/* the extent_tree has caches of all the extent mappings to disk */
|
||||
struct extent_map_tree extent_tree;
|
||||
|
||||
/* the io_tree does range state (DIRTY, LOCKED etc) */
|
||||
struct extent_io_tree io_tree;
|
||||
|
||||
/* special utility tree used to record which mirrors have already been
|
||||
* tried when checksums fail for a given block
|
||||
*/
|
||||
struct extent_io_tree io_failure_tree;
|
||||
|
||||
/* held while inesrting or deleting extents from files */
|
||||
struct mutex extent_mutex;
|
||||
|
||||
/* held while logging the inode in tree-log.c */
|
||||
struct mutex log_mutex;
|
||||
|
||||
/* used to order data wrt metadata */
|
||||
struct btrfs_ordered_inode_tree ordered_tree;
|
||||
|
||||
/* standard acl pointers */
|
||||
struct posix_acl *i_acl;
|
||||
struct posix_acl *i_default_acl;
|
||||
|
||||
/* for keeping track of orphaned inodes */
|
||||
struct list_head i_orphan;
|
||||
|
||||
/* list of all the delalloc inodes in the FS. There are times we need
|
||||
* to write all the delalloc pages to disk, and this list is used
|
||||
* to walk them all.
|
||||
*/
|
||||
struct list_head delalloc_inodes;
|
||||
|
||||
/* full 64 bit generation number, struct vfs_inode doesn't have a big
|
||||
* enough field for this.
|
||||
*/
|
||||
u64 generation;
|
||||
|
||||
/* sequence number for NFS changes */
|
||||
u64 sequence;
|
||||
|
||||
/*
|
||||
* transid of the trans_handle that last modified this inode
|
||||
*/
|
||||
u64 last_trans;
|
||||
/*
|
||||
* transid that last logged this inode
|
||||
*/
|
||||
u64 logged_trans;
|
||||
|
||||
/*
|
||||
* trans that last made a change that should be fully fsync'd. This
|
||||
* gets reset to zero each time the inode is logged
|
||||
*/
|
||||
u64 log_dirty_trans;
|
||||
|
||||
/* total number of bytes pending delalloc, used by stat to calc the
|
||||
* real block usage of the file
|
||||
*/
|
||||
u64 delalloc_bytes;
|
||||
|
||||
/*
|
||||
* the size of the file stored in the metadata on disk. data=ordered
|
||||
* means the in-memory i_size might be larger than the size on disk
|
||||
* because not all the blocks are written yet.
|
||||
*/
|
||||
u64 disk_i_size;
|
||||
|
||||
/* flags field from the on disk inode */
|
||||
u32 flags;
|
||||
|
||||
/*
|
||||
* if this is a directory then index_cnt is the counter for the index
|
||||
* number for new files that are created
|
||||
*/
|
||||
u64 index_cnt;
|
||||
|
||||
/* the start of block group preferred for allocations. */
|
||||
u64 block_group;
|
||||
|
||||
struct inode vfs_inode;
|
||||
};
|
||||
|
||||
static inline struct btrfs_inode *BTRFS_I(struct inode *inode)
|
||||
{
|
||||
return container_of(inode, struct btrfs_inode, vfs_inode);
|
||||
}
|
||||
|
||||
static inline void btrfs_i_size_write(struct inode *inode, u64 size)
|
||||
{
|
||||
inode->i_size = size;
|
||||
BTRFS_I(inode)->disk_i_size = size;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
7
fs/btrfs/compat.h
Normal file
7
fs/btrfs/compat.h
Normal file
@ -0,0 +1,7 @@
|
||||
#ifndef _COMPAT_H_
|
||||
#define _COMPAT_H_
|
||||
|
||||
#define btrfs_drop_nlink(inode) drop_nlink(inode)
|
||||
#define btrfs_inc_nlink(inode) inc_nlink(inode)
|
||||
|
||||
#endif /* _COMPAT_H_ */
|
709
fs/btrfs/compression.c
Normal file
709
fs/btrfs/compression.c
Normal file
@ -0,0 +1,709 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/bio.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/backing-dev.h>
|
||||
#include <linux/mpage.h>
|
||||
#include <linux/swap.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/bit_spinlock.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/pagevec.h>
|
||||
#include "compat.h"
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "transaction.h"
|
||||
#include "btrfs_inode.h"
|
||||
#include "volumes.h"
|
||||
#include "ordered-data.h"
|
||||
#include "compression.h"
|
||||
#include "extent_io.h"
|
||||
#include "extent_map.h"
|
||||
|
||||
struct compressed_bio {
|
||||
/* number of bios pending for this compressed extent */
|
||||
atomic_t pending_bios;
|
||||
|
||||
/* the pages with the compressed data on them */
|
||||
struct page **compressed_pages;
|
||||
|
||||
/* inode that owns this data */
|
||||
struct inode *inode;
|
||||
|
||||
/* starting offset in the inode for our pages */
|
||||
u64 start;
|
||||
|
||||
/* number of bytes in the inode we're working on */
|
||||
unsigned long len;
|
||||
|
||||
/* number of bytes on disk */
|
||||
unsigned long compressed_len;
|
||||
|
||||
/* number of compressed pages in the array */
|
||||
unsigned long nr_pages;
|
||||
|
||||
/* IO errors */
|
||||
int errors;
|
||||
int mirror_num;
|
||||
|
||||
/* for reads, this is the bio we are copying the data into */
|
||||
struct bio *orig_bio;
|
||||
|
||||
/*
|
||||
* the start of a variable length array of checksums only
|
||||
* used by reads
|
||||
*/
|
||||
u32 sums;
|
||||
};
|
||||
|
||||
static inline int compressed_bio_size(struct btrfs_root *root,
|
||||
unsigned long disk_size)
|
||||
{
|
||||
u16 csum_size = btrfs_super_csum_size(&root->fs_info->super_copy);
|
||||
return sizeof(struct compressed_bio) +
|
||||
((disk_size + root->sectorsize - 1) / root->sectorsize) *
|
||||
csum_size;
|
||||
}
|
||||
|
||||
static struct bio *compressed_bio_alloc(struct block_device *bdev,
|
||||
u64 first_byte, gfp_t gfp_flags)
|
||||
{
|
||||
struct bio *bio;
|
||||
int nr_vecs;
|
||||
|
||||
nr_vecs = bio_get_nr_vecs(bdev);
|
||||
bio = bio_alloc(gfp_flags, nr_vecs);
|
||||
|
||||
if (bio == NULL && (current->flags & PF_MEMALLOC)) {
|
||||
while (!bio && (nr_vecs /= 2))
|
||||
bio = bio_alloc(gfp_flags, nr_vecs);
|
||||
}
|
||||
|
||||
if (bio) {
|
||||
bio->bi_size = 0;
|
||||
bio->bi_bdev = bdev;
|
||||
bio->bi_sector = first_byte >> 9;
|
||||
}
|
||||
return bio;
|
||||
}
|
||||
|
||||
static int check_compressed_csum(struct inode *inode,
|
||||
struct compressed_bio *cb,
|
||||
u64 disk_start)
|
||||
{
|
||||
int ret;
|
||||
struct btrfs_root *root = BTRFS_I(inode)->root;
|
||||
struct page *page;
|
||||
unsigned long i;
|
||||
char *kaddr;
|
||||
u32 csum;
|
||||
u32 *cb_sum = &cb->sums;
|
||||
|
||||
if (btrfs_test_flag(inode, NODATASUM))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < cb->nr_pages; i++) {
|
||||
page = cb->compressed_pages[i];
|
||||
csum = ~(u32)0;
|
||||
|
||||
kaddr = kmap_atomic(page, KM_USER0);
|
||||
csum = btrfs_csum_data(root, kaddr, csum, PAGE_CACHE_SIZE);
|
||||
btrfs_csum_final(csum, (char *)&csum);
|
||||
kunmap_atomic(kaddr, KM_USER0);
|
||||
|
||||
if (csum != *cb_sum) {
|
||||
printk(KERN_INFO "btrfs csum failed ino %lu "
|
||||
"extent %llu csum %u "
|
||||
"wanted %u mirror %d\n", inode->i_ino,
|
||||
(unsigned long long)disk_start,
|
||||
csum, *cb_sum, cb->mirror_num);
|
||||
ret = -EIO;
|
||||
goto fail;
|
||||
}
|
||||
cb_sum++;
|
||||
|
||||
}
|
||||
ret = 0;
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* when we finish reading compressed pages from the disk, we
|
||||
* decompress them and then run the bio end_io routines on the
|
||||
* decompressed pages (in the inode address space).
|
||||
*
|
||||
* This allows the checksumming and other IO error handling routines
|
||||
* to work normally
|
||||
*
|
||||
* The compressed pages are freed here, and it must be run
|
||||
* in process context
|
||||
*/
|
||||
static void end_compressed_bio_read(struct bio *bio, int err)
|
||||
{
|
||||
struct extent_io_tree *tree;
|
||||
struct compressed_bio *cb = bio->bi_private;
|
||||
struct inode *inode;
|
||||
struct page *page;
|
||||
unsigned long index;
|
||||
int ret;
|
||||
|
||||
if (err)
|
||||
cb->errors = 1;
|
||||
|
||||
/* if there are more bios still pending for this compressed
|
||||
* extent, just exit
|
||||
*/
|
||||
if (!atomic_dec_and_test(&cb->pending_bios))
|
||||
goto out;
|
||||
|
||||
inode = cb->inode;
|
||||
ret = check_compressed_csum(inode, cb, (u64)bio->bi_sector << 9);
|
||||
if (ret)
|
||||
goto csum_failed;
|
||||
|
||||
/* ok, we're the last bio for this extent, lets start
|
||||
* the decompression.
|
||||
*/
|
||||
tree = &BTRFS_I(inode)->io_tree;
|
||||
ret = btrfs_zlib_decompress_biovec(cb->compressed_pages,
|
||||
cb->start,
|
||||
cb->orig_bio->bi_io_vec,
|
||||
cb->orig_bio->bi_vcnt,
|
||||
cb->compressed_len);
|
||||
csum_failed:
|
||||
if (ret)
|
||||
cb->errors = 1;
|
||||
|
||||
/* release the compressed pages */
|
||||
index = 0;
|
||||
for (index = 0; index < cb->nr_pages; index++) {
|
||||
page = cb->compressed_pages[index];
|
||||
page->mapping = NULL;
|
||||
page_cache_release(page);
|
||||
}
|
||||
|
||||
/* do io completion on the original bio */
|
||||
if (cb->errors) {
|
||||
bio_io_error(cb->orig_bio);
|
||||
} else {
|
||||
int bio_index = 0;
|
||||
struct bio_vec *bvec = cb->orig_bio->bi_io_vec;
|
||||
|
||||
/*
|
||||
* we have verified the checksum already, set page
|
||||
* checked so the end_io handlers know about it
|
||||
*/
|
||||
while (bio_index < cb->orig_bio->bi_vcnt) {
|
||||
SetPageChecked(bvec->bv_page);
|
||||
bvec++;
|
||||
bio_index++;
|
||||
}
|
||||
bio_endio(cb->orig_bio, 0);
|
||||
}
|
||||
|
||||
/* finally free the cb struct */
|
||||
kfree(cb->compressed_pages);
|
||||
kfree(cb);
|
||||
out:
|
||||
bio_put(bio);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the writeback bits on all of the file
|
||||
* pages for a compressed write
|
||||
*/
|
||||
static noinline int end_compressed_writeback(struct inode *inode, u64 start,
|
||||
unsigned long ram_size)
|
||||
{
|
||||
unsigned long index = start >> PAGE_CACHE_SHIFT;
|
||||
unsigned long end_index = (start + ram_size - 1) >> PAGE_CACHE_SHIFT;
|
||||
struct page *pages[16];
|
||||
unsigned long nr_pages = end_index - index + 1;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
while (nr_pages > 0) {
|
||||
ret = find_get_pages_contig(inode->i_mapping, index,
|
||||
min_t(unsigned long,
|
||||
nr_pages, ARRAY_SIZE(pages)), pages);
|
||||
if (ret == 0) {
|
||||
nr_pages -= 1;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
for (i = 0; i < ret; i++) {
|
||||
end_page_writeback(pages[i]);
|
||||
page_cache_release(pages[i]);
|
||||
}
|
||||
nr_pages -= ret;
|
||||
index += ret;
|
||||
}
|
||||
/* the inode may be gone now */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* do the cleanup once all the compressed pages hit the disk.
|
||||
* This will clear writeback on the file pages and free the compressed
|
||||
* pages.
|
||||
*
|
||||
* This also calls the writeback end hooks for the file pages so that
|
||||
* metadata and checksums can be updated in the file.
|
||||
*/
|
||||
static void end_compressed_bio_write(struct bio *bio, int err)
|
||||
{
|
||||
struct extent_io_tree *tree;
|
||||
struct compressed_bio *cb = bio->bi_private;
|
||||
struct inode *inode;
|
||||
struct page *page;
|
||||
unsigned long index;
|
||||
|
||||
if (err)
|
||||
cb->errors = 1;
|
||||
|
||||
/* if there are more bios still pending for this compressed
|
||||
* extent, just exit
|
||||
*/
|
||||
if (!atomic_dec_and_test(&cb->pending_bios))
|
||||
goto out;
|
||||
|
||||
/* ok, we're the last bio for this extent, step one is to
|
||||
* call back into the FS and do all the end_io operations
|
||||
*/
|
||||
inode = cb->inode;
|
||||
tree = &BTRFS_I(inode)->io_tree;
|
||||
cb->compressed_pages[0]->mapping = cb->inode->i_mapping;
|
||||
tree->ops->writepage_end_io_hook(cb->compressed_pages[0],
|
||||
cb->start,
|
||||
cb->start + cb->len - 1,
|
||||
NULL, 1);
|
||||
cb->compressed_pages[0]->mapping = NULL;
|
||||
|
||||
end_compressed_writeback(inode, cb->start, cb->len);
|
||||
/* note, our inode could be gone now */
|
||||
|
||||
/*
|
||||
* release the compressed pages, these came from alloc_page and
|
||||
* are not attached to the inode at all
|
||||
*/
|
||||
index = 0;
|
||||
for (index = 0; index < cb->nr_pages; index++) {
|
||||
page = cb->compressed_pages[index];
|
||||
page->mapping = NULL;
|
||||
page_cache_release(page);
|
||||
}
|
||||
|
||||
/* finally free the cb struct */
|
||||
kfree(cb->compressed_pages);
|
||||
kfree(cb);
|
||||
out:
|
||||
bio_put(bio);
|
||||
}
|
||||
|
||||
/*
|
||||
* worker function to build and submit bios for previously compressed pages.
|
||||
* The corresponding pages in the inode should be marked for writeback
|
||||
* and the compressed pages should have a reference on them for dropping
|
||||
* when the IO is complete.
|
||||
*
|
||||
* This also checksums the file bytes and gets things ready for
|
||||
* the end io hooks.
|
||||
*/
|
||||
int btrfs_submit_compressed_write(struct inode *inode, u64 start,
|
||||
unsigned long len, u64 disk_start,
|
||||
unsigned long compressed_len,
|
||||
struct page **compressed_pages,
|
||||
unsigned long nr_pages)
|
||||
{
|
||||
struct bio *bio = NULL;
|
||||
struct btrfs_root *root = BTRFS_I(inode)->root;
|
||||
struct compressed_bio *cb;
|
||||
unsigned long bytes_left;
|
||||
struct extent_io_tree *io_tree = &BTRFS_I(inode)->io_tree;
|
||||
int page_index = 0;
|
||||
struct page *page;
|
||||
u64 first_byte = disk_start;
|
||||
struct block_device *bdev;
|
||||
int ret;
|
||||
|
||||
WARN_ON(start & ((u64)PAGE_CACHE_SIZE - 1));
|
||||
cb = kmalloc(compressed_bio_size(root, compressed_len), GFP_NOFS);
|
||||
atomic_set(&cb->pending_bios, 0);
|
||||
cb->errors = 0;
|
||||
cb->inode = inode;
|
||||
cb->start = start;
|
||||
cb->len = len;
|
||||
cb->mirror_num = 0;
|
||||
cb->compressed_pages = compressed_pages;
|
||||
cb->compressed_len = compressed_len;
|
||||
cb->orig_bio = NULL;
|
||||
cb->nr_pages = nr_pages;
|
||||
|
||||
bdev = BTRFS_I(inode)->root->fs_info->fs_devices->latest_bdev;
|
||||
|
||||
bio = compressed_bio_alloc(bdev, first_byte, GFP_NOFS);
|
||||
bio->bi_private = cb;
|
||||
bio->bi_end_io = end_compressed_bio_write;
|
||||
atomic_inc(&cb->pending_bios);
|
||||
|
||||
/* create and submit bios for the compressed pages */
|
||||
bytes_left = compressed_len;
|
||||
for (page_index = 0; page_index < cb->nr_pages; page_index++) {
|
||||
page = compressed_pages[page_index];
|
||||
page->mapping = inode->i_mapping;
|
||||
if (bio->bi_size)
|
||||
ret = io_tree->ops->merge_bio_hook(page, 0,
|
||||
PAGE_CACHE_SIZE,
|
||||
bio, 0);
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
page->mapping = NULL;
|
||||
if (ret || bio_add_page(bio, page, PAGE_CACHE_SIZE, 0) <
|
||||
PAGE_CACHE_SIZE) {
|
||||
bio_get(bio);
|
||||
|
||||
/*
|
||||
* inc the count before we submit the bio so
|
||||
* we know the end IO handler won't happen before
|
||||
* we inc the count. Otherwise, the cb might get
|
||||
* freed before we're done setting it up
|
||||
*/
|
||||
atomic_inc(&cb->pending_bios);
|
||||
ret = btrfs_bio_wq_end_io(root->fs_info, bio, 0);
|
||||
BUG_ON(ret);
|
||||
|
||||
ret = btrfs_csum_one_bio(root, inode, bio, start, 1);
|
||||
BUG_ON(ret);
|
||||
|
||||
ret = btrfs_map_bio(root, WRITE, bio, 0, 1);
|
||||
BUG_ON(ret);
|
||||
|
||||
bio_put(bio);
|
||||
|
||||
bio = compressed_bio_alloc(bdev, first_byte, GFP_NOFS);
|
||||
bio->bi_private = cb;
|
||||
bio->bi_end_io = end_compressed_bio_write;
|
||||
bio_add_page(bio, page, PAGE_CACHE_SIZE, 0);
|
||||
}
|
||||
if (bytes_left < PAGE_CACHE_SIZE) {
|
||||
printk("bytes left %lu compress len %lu nr %lu\n",
|
||||
bytes_left, cb->compressed_len, cb->nr_pages);
|
||||
}
|
||||
bytes_left -= PAGE_CACHE_SIZE;
|
||||
first_byte += PAGE_CACHE_SIZE;
|
||||
cond_resched();
|
||||
}
|
||||
bio_get(bio);
|
||||
|
||||
ret = btrfs_bio_wq_end_io(root->fs_info, bio, 0);
|
||||
BUG_ON(ret);
|
||||
|
||||
ret = btrfs_csum_one_bio(root, inode, bio, start, 1);
|
||||
BUG_ON(ret);
|
||||
|
||||
ret = btrfs_map_bio(root, WRITE, bio, 0, 1);
|
||||
BUG_ON(ret);
|
||||
|
||||
bio_put(bio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static noinline int add_ra_bio_pages(struct inode *inode,
|
||||
u64 compressed_end,
|
||||
struct compressed_bio *cb)
|
||||
{
|
||||
unsigned long end_index;
|
||||
unsigned long page_index;
|
||||
u64 last_offset;
|
||||
u64 isize = i_size_read(inode);
|
||||
int ret;
|
||||
struct page *page;
|
||||
unsigned long nr_pages = 0;
|
||||
struct extent_map *em;
|
||||
struct address_space *mapping = inode->i_mapping;
|
||||
struct pagevec pvec;
|
||||
struct extent_map_tree *em_tree;
|
||||
struct extent_io_tree *tree;
|
||||
u64 end;
|
||||
int misses = 0;
|
||||
|
||||
page = cb->orig_bio->bi_io_vec[cb->orig_bio->bi_vcnt - 1].bv_page;
|
||||
last_offset = (page_offset(page) + PAGE_CACHE_SIZE);
|
||||
em_tree = &BTRFS_I(inode)->extent_tree;
|
||||
tree = &BTRFS_I(inode)->io_tree;
|
||||
|
||||
if (isize == 0)
|
||||
return 0;
|
||||
|
||||
end_index = (i_size_read(inode) - 1) >> PAGE_CACHE_SHIFT;
|
||||
|
||||
pagevec_init(&pvec, 0);
|
||||
while (last_offset < compressed_end) {
|
||||
page_index = last_offset >> PAGE_CACHE_SHIFT;
|
||||
|
||||
if (page_index > end_index)
|
||||
break;
|
||||
|
||||
rcu_read_lock();
|
||||
page = radix_tree_lookup(&mapping->page_tree, page_index);
|
||||
rcu_read_unlock();
|
||||
if (page) {
|
||||
misses++;
|
||||
if (misses > 4)
|
||||
break;
|
||||
goto next;
|
||||
}
|
||||
|
||||
page = alloc_page(mapping_gfp_mask(mapping) | GFP_NOFS);
|
||||
if (!page)
|
||||
break;
|
||||
|
||||
page->index = page_index;
|
||||
/*
|
||||
* what we want to do here is call add_to_page_cache_lru,
|
||||
* but that isn't exported, so we reproduce it here
|
||||
*/
|
||||
if (add_to_page_cache(page, mapping,
|
||||
page->index, GFP_NOFS)) {
|
||||
page_cache_release(page);
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* open coding of lru_cache_add, also not exported */
|
||||
page_cache_get(page);
|
||||
if (!pagevec_add(&pvec, page))
|
||||
__pagevec_lru_add_file(&pvec);
|
||||
|
||||
end = last_offset + PAGE_CACHE_SIZE - 1;
|
||||
/*
|
||||
* at this point, we have a locked page in the page cache
|
||||
* for these bytes in the file. But, we have to make
|
||||
* sure they map to this compressed extent on disk.
|
||||
*/
|
||||
set_page_extent_mapped(page);
|
||||
lock_extent(tree, last_offset, end, GFP_NOFS);
|
||||
spin_lock(&em_tree->lock);
|
||||
em = lookup_extent_mapping(em_tree, last_offset,
|
||||
PAGE_CACHE_SIZE);
|
||||
spin_unlock(&em_tree->lock);
|
||||
|
||||
if (!em || last_offset < em->start ||
|
||||
(last_offset + PAGE_CACHE_SIZE > extent_map_end(em)) ||
|
||||
(em->block_start >> 9) != cb->orig_bio->bi_sector) {
|
||||
free_extent_map(em);
|
||||
unlock_extent(tree, last_offset, end, GFP_NOFS);
|
||||
unlock_page(page);
|
||||
page_cache_release(page);
|
||||
break;
|
||||
}
|
||||
free_extent_map(em);
|
||||
|
||||
if (page->index == end_index) {
|
||||
char *userpage;
|
||||
size_t zero_offset = isize & (PAGE_CACHE_SIZE - 1);
|
||||
|
||||
if (zero_offset) {
|
||||
int zeros;
|
||||
zeros = PAGE_CACHE_SIZE - zero_offset;
|
||||
userpage = kmap_atomic(page, KM_USER0);
|
||||
memset(userpage + zero_offset, 0, zeros);
|
||||
flush_dcache_page(page);
|
||||
kunmap_atomic(userpage, KM_USER0);
|
||||
}
|
||||
}
|
||||
|
||||
ret = bio_add_page(cb->orig_bio, page,
|
||||
PAGE_CACHE_SIZE, 0);
|
||||
|
||||
if (ret == PAGE_CACHE_SIZE) {
|
||||
nr_pages++;
|
||||
page_cache_release(page);
|
||||
} else {
|
||||
unlock_extent(tree, last_offset, end, GFP_NOFS);
|
||||
unlock_page(page);
|
||||
page_cache_release(page);
|
||||
break;
|
||||
}
|
||||
next:
|
||||
last_offset += PAGE_CACHE_SIZE;
|
||||
}
|
||||
if (pagevec_count(&pvec))
|
||||
__pagevec_lru_add_file(&pvec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* for a compressed read, the bio we get passed has all the inode pages
|
||||
* in it. We don't actually do IO on those pages but allocate new ones
|
||||
* to hold the compressed pages on disk.
|
||||
*
|
||||
* bio->bi_sector points to the compressed extent on disk
|
||||
* bio->bi_io_vec points to all of the inode pages
|
||||
* bio->bi_vcnt is a count of pages
|
||||
*
|
||||
* After the compressed pages are read, we copy the bytes into the
|
||||
* bio we were passed and then call the bio end_io calls
|
||||
*/
|
||||
int btrfs_submit_compressed_read(struct inode *inode, struct bio *bio,
|
||||
int mirror_num, unsigned long bio_flags)
|
||||
{
|
||||
struct extent_io_tree *tree;
|
||||
struct extent_map_tree *em_tree;
|
||||
struct compressed_bio *cb;
|
||||
struct btrfs_root *root = BTRFS_I(inode)->root;
|
||||
unsigned long uncompressed_len = bio->bi_vcnt * PAGE_CACHE_SIZE;
|
||||
unsigned long compressed_len;
|
||||
unsigned long nr_pages;
|
||||
unsigned long page_index;
|
||||
struct page *page;
|
||||
struct block_device *bdev;
|
||||
struct bio *comp_bio;
|
||||
u64 cur_disk_byte = (u64)bio->bi_sector << 9;
|
||||
u64 em_len;
|
||||
u64 em_start;
|
||||
struct extent_map *em;
|
||||
int ret;
|
||||
u32 *sums;
|
||||
|
||||
tree = &BTRFS_I(inode)->io_tree;
|
||||
em_tree = &BTRFS_I(inode)->extent_tree;
|
||||
|
||||
/* we need the actual starting offset of this extent in the file */
|
||||
spin_lock(&em_tree->lock);
|
||||
em = lookup_extent_mapping(em_tree,
|
||||
page_offset(bio->bi_io_vec->bv_page),
|
||||
PAGE_CACHE_SIZE);
|
||||
spin_unlock(&em_tree->lock);
|
||||
|
||||
compressed_len = em->block_len;
|
||||
cb = kmalloc(compressed_bio_size(root, compressed_len), GFP_NOFS);
|
||||
atomic_set(&cb->pending_bios, 0);
|
||||
cb->errors = 0;
|
||||
cb->inode = inode;
|
||||
cb->mirror_num = mirror_num;
|
||||
sums = &cb->sums;
|
||||
|
||||
cb->start = em->orig_start;
|
||||
em_len = em->len;
|
||||
em_start = em->start;
|
||||
|
||||
free_extent_map(em);
|
||||
em = NULL;
|
||||
|
||||
cb->len = uncompressed_len;
|
||||
cb->compressed_len = compressed_len;
|
||||
cb->orig_bio = bio;
|
||||
|
||||
nr_pages = (compressed_len + PAGE_CACHE_SIZE - 1) /
|
||||
PAGE_CACHE_SIZE;
|
||||
cb->compressed_pages = kmalloc(sizeof(struct page *) * nr_pages,
|
||||
GFP_NOFS);
|
||||
bdev = BTRFS_I(inode)->root->fs_info->fs_devices->latest_bdev;
|
||||
|
||||
for (page_index = 0; page_index < nr_pages; page_index++) {
|
||||
cb->compressed_pages[page_index] = alloc_page(GFP_NOFS |
|
||||
__GFP_HIGHMEM);
|
||||
}
|
||||
cb->nr_pages = nr_pages;
|
||||
|
||||
add_ra_bio_pages(inode, em_start + em_len, cb);
|
||||
|
||||
/* include any pages we added in add_ra-bio_pages */
|
||||
uncompressed_len = bio->bi_vcnt * PAGE_CACHE_SIZE;
|
||||
cb->len = uncompressed_len;
|
||||
|
||||
comp_bio = compressed_bio_alloc(bdev, cur_disk_byte, GFP_NOFS);
|
||||
comp_bio->bi_private = cb;
|
||||
comp_bio->bi_end_io = end_compressed_bio_read;
|
||||
atomic_inc(&cb->pending_bios);
|
||||
|
||||
for (page_index = 0; page_index < nr_pages; page_index++) {
|
||||
page = cb->compressed_pages[page_index];
|
||||
page->mapping = inode->i_mapping;
|
||||
page->index = em_start >> PAGE_CACHE_SHIFT;
|
||||
|
||||
if (comp_bio->bi_size)
|
||||
ret = tree->ops->merge_bio_hook(page, 0,
|
||||
PAGE_CACHE_SIZE,
|
||||
comp_bio, 0);
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
page->mapping = NULL;
|
||||
if (ret || bio_add_page(comp_bio, page, PAGE_CACHE_SIZE, 0) <
|
||||
PAGE_CACHE_SIZE) {
|
||||
bio_get(comp_bio);
|
||||
|
||||
ret = btrfs_bio_wq_end_io(root->fs_info, comp_bio, 0);
|
||||
BUG_ON(ret);
|
||||
|
||||
/*
|
||||
* inc the count before we submit the bio so
|
||||
* we know the end IO handler won't happen before
|
||||
* we inc the count. Otherwise, the cb might get
|
||||
* freed before we're done setting it up
|
||||
*/
|
||||
atomic_inc(&cb->pending_bios);
|
||||
|
||||
if (!btrfs_test_flag(inode, NODATASUM)) {
|
||||
btrfs_lookup_bio_sums(root, inode, comp_bio,
|
||||
sums);
|
||||
}
|
||||
sums += (comp_bio->bi_size + root->sectorsize - 1) /
|
||||
root->sectorsize;
|
||||
|
||||
ret = btrfs_map_bio(root, READ, comp_bio,
|
||||
mirror_num, 0);
|
||||
BUG_ON(ret);
|
||||
|
||||
bio_put(comp_bio);
|
||||
|
||||
comp_bio = compressed_bio_alloc(bdev, cur_disk_byte,
|
||||
GFP_NOFS);
|
||||
comp_bio->bi_private = cb;
|
||||
comp_bio->bi_end_io = end_compressed_bio_read;
|
||||
|
||||
bio_add_page(comp_bio, page, PAGE_CACHE_SIZE, 0);
|
||||
}
|
||||
cur_disk_byte += PAGE_CACHE_SIZE;
|
||||
}
|
||||
bio_get(comp_bio);
|
||||
|
||||
ret = btrfs_bio_wq_end_io(root->fs_info, comp_bio, 0);
|
||||
BUG_ON(ret);
|
||||
|
||||
if (!btrfs_test_flag(inode, NODATASUM))
|
||||
btrfs_lookup_bio_sums(root, inode, comp_bio, sums);
|
||||
|
||||
ret = btrfs_map_bio(root, READ, comp_bio, mirror_num, 0);
|
||||
BUG_ON(ret);
|
||||
|
||||
bio_put(comp_bio);
|
||||
return 0;
|
||||
}
|
47
fs/btrfs/compression.h
Normal file
47
fs/btrfs/compression.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BTRFS_COMPRESSION_
|
||||
#define __BTRFS_COMPRESSION_
|
||||
|
||||
int btrfs_zlib_decompress(unsigned char *data_in,
|
||||
struct page *dest_page,
|
||||
unsigned long start_byte,
|
||||
size_t srclen, size_t destlen);
|
||||
int btrfs_zlib_compress_pages(struct address_space *mapping,
|
||||
u64 start, unsigned long len,
|
||||
struct page **pages,
|
||||
unsigned long nr_dest_pages,
|
||||
unsigned long *out_pages,
|
||||
unsigned long *total_in,
|
||||
unsigned long *total_out,
|
||||
unsigned long max_out);
|
||||
int btrfs_zlib_decompress_biovec(struct page **pages_in,
|
||||
u64 disk_start,
|
||||
struct bio_vec *bvec,
|
||||
int vcnt,
|
||||
size_t srclen);
|
||||
void btrfs_zlib_exit(void);
|
||||
int btrfs_submit_compressed_write(struct inode *inode, u64 start,
|
||||
unsigned long len, u64 disk_start,
|
||||
unsigned long compressed_len,
|
||||
struct page **compressed_pages,
|
||||
unsigned long nr_pages);
|
||||
int btrfs_submit_compressed_read(struct inode *inode, struct bio *bio,
|
||||
int mirror_num, unsigned long bio_flags);
|
||||
#endif
|
29
fs/btrfs/crc32c.h
Normal file
29
fs/btrfs/crc32c.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BTRFS_CRC32C__
|
||||
#define __BTRFS_CRC32C__
|
||||
#include <linux/crc32c.h>
|
||||
|
||||
/*
|
||||
* this file used to do more for selecting the HW version of crc32c,
|
||||
* perhaps it will one day again soon.
|
||||
*/
|
||||
#define btrfs_crc32c(seed, data, length) crc32c(seed, data, length)
|
||||
#endif
|
||||
|
3953
fs/btrfs/ctree.c
Normal file
3953
fs/btrfs/ctree.c
Normal file
File diff suppressed because it is too large
Load Diff
2129
fs/btrfs/ctree.h
Normal file
2129
fs/btrfs/ctree.h
Normal file
File diff suppressed because it is too large
Load Diff
386
fs/btrfs/dir-item.c
Normal file
386
fs/btrfs/dir-item.c
Normal file
@ -0,0 +1,386 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "hash.h"
|
||||
#include "transaction.h"
|
||||
|
||||
/*
|
||||
* insert a name into a directory, doing overflow properly if there is a hash
|
||||
* collision. data_size indicates how big the item inserted should be. On
|
||||
* success a struct btrfs_dir_item pointer is returned, otherwise it is
|
||||
* an ERR_PTR.
|
||||
*
|
||||
* The name is not copied into the dir item, you have to do that yourself.
|
||||
*/
|
||||
static struct btrfs_dir_item *insert_with_overflow(struct btrfs_trans_handle
|
||||
*trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path,
|
||||
struct btrfs_key *cpu_key,
|
||||
u32 data_size,
|
||||
const char *name,
|
||||
int name_len)
|
||||
{
|
||||
int ret;
|
||||
char *ptr;
|
||||
struct btrfs_item *item;
|
||||
struct extent_buffer *leaf;
|
||||
|
||||
ret = btrfs_insert_empty_item(trans, root, path, cpu_key, data_size);
|
||||
if (ret == -EEXIST) {
|
||||
struct btrfs_dir_item *di;
|
||||
di = btrfs_match_dir_item_name(root, path, name, name_len);
|
||||
if (di)
|
||||
return ERR_PTR(-EEXIST);
|
||||
ret = btrfs_extend_item(trans, root, path, data_size);
|
||||
WARN_ON(ret > 0);
|
||||
}
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
WARN_ON(ret > 0);
|
||||
leaf = path->nodes[0];
|
||||
item = btrfs_item_nr(leaf, path->slots[0]);
|
||||
ptr = btrfs_item_ptr(leaf, path->slots[0], char);
|
||||
BUG_ON(data_size > btrfs_item_size(leaf, item));
|
||||
ptr += btrfs_item_size(leaf, item) - data_size;
|
||||
return (struct btrfs_dir_item *)ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* xattrs work a lot like directories, this inserts an xattr item
|
||||
* into the tree
|
||||
*/
|
||||
int btrfs_insert_xattr_item(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, const char *name,
|
||||
u16 name_len, const void *data, u16 data_len,
|
||||
u64 dir)
|
||||
{
|
||||
int ret = 0;
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_dir_item *dir_item;
|
||||
unsigned long name_ptr, data_ptr;
|
||||
struct btrfs_key key, location;
|
||||
struct btrfs_disk_key disk_key;
|
||||
struct extent_buffer *leaf;
|
||||
u32 data_size;
|
||||
|
||||
key.objectid = dir;
|
||||
btrfs_set_key_type(&key, BTRFS_XATTR_ITEM_KEY);
|
||||
key.offset = btrfs_name_hash(name, name_len);
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
if (name_len + data_len + sizeof(struct btrfs_dir_item) >
|
||||
BTRFS_LEAF_DATA_SIZE(root) - sizeof(struct btrfs_item))
|
||||
return -ENOSPC;
|
||||
|
||||
data_size = sizeof(*dir_item) + name_len + data_len;
|
||||
dir_item = insert_with_overflow(trans, root, path, &key, data_size,
|
||||
name, name_len);
|
||||
/*
|
||||
* FIXME: at some point we should handle xattr's that are larger than
|
||||
* what we can fit in our leaf. We set location to NULL b/c we arent
|
||||
* pointing at anything else, that will change if we store the xattr
|
||||
* data in a separate inode.
|
||||
*/
|
||||
BUG_ON(IS_ERR(dir_item));
|
||||
memset(&location, 0, sizeof(location));
|
||||
|
||||
leaf = path->nodes[0];
|
||||
btrfs_cpu_key_to_disk(&disk_key, &location);
|
||||
btrfs_set_dir_item_key(leaf, dir_item, &disk_key);
|
||||
btrfs_set_dir_type(leaf, dir_item, BTRFS_FT_XATTR);
|
||||
btrfs_set_dir_name_len(leaf, dir_item, name_len);
|
||||
btrfs_set_dir_transid(leaf, dir_item, trans->transid);
|
||||
btrfs_set_dir_data_len(leaf, dir_item, data_len);
|
||||
name_ptr = (unsigned long)(dir_item + 1);
|
||||
data_ptr = (unsigned long)((char *)name_ptr + name_len);
|
||||
|
||||
write_extent_buffer(leaf, name, name_ptr, name_len);
|
||||
write_extent_buffer(leaf, data, data_ptr, data_len);
|
||||
btrfs_mark_buffer_dirty(path->nodes[0]);
|
||||
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* insert a directory item in the tree, doing all the magic for
|
||||
* both indexes. 'dir' indicates which objectid to insert it into,
|
||||
* 'location' is the key to stuff into the directory item, 'type' is the
|
||||
* type of the inode we're pointing to, and 'index' is the sequence number
|
||||
* to use for the second index (if one is created).
|
||||
*/
|
||||
int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, struct btrfs_root
|
||||
*root, const char *name, int name_len, u64 dir,
|
||||
struct btrfs_key *location, u8 type, u64 index)
|
||||
{
|
||||
int ret = 0;
|
||||
int ret2 = 0;
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_dir_item *dir_item;
|
||||
struct extent_buffer *leaf;
|
||||
unsigned long name_ptr;
|
||||
struct btrfs_key key;
|
||||
struct btrfs_disk_key disk_key;
|
||||
u32 data_size;
|
||||
|
||||
key.objectid = dir;
|
||||
btrfs_set_key_type(&key, BTRFS_DIR_ITEM_KEY);
|
||||
key.offset = btrfs_name_hash(name, name_len);
|
||||
path = btrfs_alloc_path();
|
||||
data_size = sizeof(*dir_item) + name_len;
|
||||
dir_item = insert_with_overflow(trans, root, path, &key, data_size,
|
||||
name, name_len);
|
||||
if (IS_ERR(dir_item)) {
|
||||
ret = PTR_ERR(dir_item);
|
||||
if (ret == -EEXIST)
|
||||
goto second_insert;
|
||||
goto out;
|
||||
}
|
||||
|
||||
leaf = path->nodes[0];
|
||||
btrfs_cpu_key_to_disk(&disk_key, location);
|
||||
btrfs_set_dir_item_key(leaf, dir_item, &disk_key);
|
||||
btrfs_set_dir_type(leaf, dir_item, type);
|
||||
btrfs_set_dir_data_len(leaf, dir_item, 0);
|
||||
btrfs_set_dir_name_len(leaf, dir_item, name_len);
|
||||
btrfs_set_dir_transid(leaf, dir_item, trans->transid);
|
||||
name_ptr = (unsigned long)(dir_item + 1);
|
||||
|
||||
write_extent_buffer(leaf, name, name_ptr, name_len);
|
||||
btrfs_mark_buffer_dirty(leaf);
|
||||
|
||||
second_insert:
|
||||
/* FIXME, use some real flag for selecting the extra index */
|
||||
if (root == root->fs_info->tree_root) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
btrfs_release_path(root, path);
|
||||
|
||||
btrfs_set_key_type(&key, BTRFS_DIR_INDEX_KEY);
|
||||
key.offset = index;
|
||||
dir_item = insert_with_overflow(trans, root, path, &key, data_size,
|
||||
name, name_len);
|
||||
if (IS_ERR(dir_item)) {
|
||||
ret2 = PTR_ERR(dir_item);
|
||||
goto out;
|
||||
}
|
||||
leaf = path->nodes[0];
|
||||
btrfs_cpu_key_to_disk(&disk_key, location);
|
||||
btrfs_set_dir_item_key(leaf, dir_item, &disk_key);
|
||||
btrfs_set_dir_type(leaf, dir_item, type);
|
||||
btrfs_set_dir_data_len(leaf, dir_item, 0);
|
||||
btrfs_set_dir_name_len(leaf, dir_item, name_len);
|
||||
btrfs_set_dir_transid(leaf, dir_item, trans->transid);
|
||||
name_ptr = (unsigned long)(dir_item + 1);
|
||||
write_extent_buffer(leaf, name, name_ptr, name_len);
|
||||
btrfs_mark_buffer_dirty(leaf);
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (ret2)
|
||||
return ret2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* lookup a directory item based on name. 'dir' is the objectid
|
||||
* we're searching in, and 'mod' tells us if you plan on deleting the
|
||||
* item (use mod < 0) or changing the options (use mod > 0)
|
||||
*/
|
||||
struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path, u64 dir,
|
||||
const char *name, int name_len,
|
||||
int mod)
|
||||
{
|
||||
int ret;
|
||||
struct btrfs_key key;
|
||||
int ins_len = mod < 0 ? -1 : 0;
|
||||
int cow = mod != 0;
|
||||
struct btrfs_key found_key;
|
||||
struct extent_buffer *leaf;
|
||||
|
||||
key.objectid = dir;
|
||||
btrfs_set_key_type(&key, BTRFS_DIR_ITEM_KEY);
|
||||
|
||||
key.offset = btrfs_name_hash(name, name_len);
|
||||
|
||||
ret = btrfs_search_slot(trans, root, &key, path, ins_len, cow);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
if (ret > 0) {
|
||||
if (path->slots[0] == 0)
|
||||
return NULL;
|
||||
path->slots[0]--;
|
||||
}
|
||||
|
||||
leaf = path->nodes[0];
|
||||
btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
|
||||
|
||||
if (found_key.objectid != dir ||
|
||||
btrfs_key_type(&found_key) != BTRFS_DIR_ITEM_KEY ||
|
||||
found_key.offset != key.offset)
|
||||
return NULL;
|
||||
|
||||
return btrfs_match_dir_item_name(root, path, name, name_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* lookup a directory item based on index. 'dir' is the objectid
|
||||
* we're searching in, and 'mod' tells us if you plan on deleting the
|
||||
* item (use mod < 0) or changing the options (use mod > 0)
|
||||
*
|
||||
* The name is used to make sure the index really points to the name you were
|
||||
* looking for.
|
||||
*/
|
||||
struct btrfs_dir_item *
|
||||
btrfs_lookup_dir_index_item(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path, u64 dir,
|
||||
u64 objectid, const char *name, int name_len,
|
||||
int mod)
|
||||
{
|
||||
int ret;
|
||||
struct btrfs_key key;
|
||||
int ins_len = mod < 0 ? -1 : 0;
|
||||
int cow = mod != 0;
|
||||
|
||||
key.objectid = dir;
|
||||
btrfs_set_key_type(&key, BTRFS_DIR_INDEX_KEY);
|
||||
key.offset = objectid;
|
||||
|
||||
ret = btrfs_search_slot(trans, root, &key, path, ins_len, cow);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
if (ret > 0)
|
||||
return ERR_PTR(-ENOENT);
|
||||
return btrfs_match_dir_item_name(root, path, name, name_len);
|
||||
}
|
||||
|
||||
struct btrfs_dir_item *btrfs_lookup_xattr(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path, u64 dir,
|
||||
const char *name, u16 name_len,
|
||||
int mod)
|
||||
{
|
||||
int ret;
|
||||
struct btrfs_key key;
|
||||
int ins_len = mod < 0 ? -1 : 0;
|
||||
int cow = mod != 0;
|
||||
struct btrfs_key found_key;
|
||||
struct extent_buffer *leaf;
|
||||
|
||||
key.objectid = dir;
|
||||
btrfs_set_key_type(&key, BTRFS_XATTR_ITEM_KEY);
|
||||
key.offset = btrfs_name_hash(name, name_len);
|
||||
ret = btrfs_search_slot(trans, root, &key, path, ins_len, cow);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
if (ret > 0) {
|
||||
if (path->slots[0] == 0)
|
||||
return NULL;
|
||||
path->slots[0]--;
|
||||
}
|
||||
|
||||
leaf = path->nodes[0];
|
||||
btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
|
||||
|
||||
if (found_key.objectid != dir ||
|
||||
btrfs_key_type(&found_key) != BTRFS_XATTR_ITEM_KEY ||
|
||||
found_key.offset != key.offset)
|
||||
return NULL;
|
||||
|
||||
return btrfs_match_dir_item_name(root, path, name, name_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* helper function to look at the directory item pointed to by 'path'
|
||||
* this walks through all the entries in a dir item and finds one
|
||||
* for a specific name.
|
||||
*/
|
||||
struct btrfs_dir_item *btrfs_match_dir_item_name(struct btrfs_root *root,
|
||||
struct btrfs_path *path,
|
||||
const char *name, int name_len)
|
||||
{
|
||||
struct btrfs_dir_item *dir_item;
|
||||
unsigned long name_ptr;
|
||||
u32 total_len;
|
||||
u32 cur = 0;
|
||||
u32 this_len;
|
||||
struct extent_buffer *leaf;
|
||||
|
||||
leaf = path->nodes[0];
|
||||
dir_item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dir_item);
|
||||
total_len = btrfs_item_size_nr(leaf, path->slots[0]);
|
||||
while (cur < total_len) {
|
||||
this_len = sizeof(*dir_item) +
|
||||
btrfs_dir_name_len(leaf, dir_item) +
|
||||
btrfs_dir_data_len(leaf, dir_item);
|
||||
name_ptr = (unsigned long)(dir_item + 1);
|
||||
|
||||
if (btrfs_dir_name_len(leaf, dir_item) == name_len &&
|
||||
memcmp_extent_buffer(leaf, name, name_ptr, name_len) == 0)
|
||||
return dir_item;
|
||||
|
||||
cur += this_len;
|
||||
dir_item = (struct btrfs_dir_item *)((char *)dir_item +
|
||||
this_len);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* given a pointer into a directory item, delete it. This
|
||||
* handles items that have more than one entry in them.
|
||||
*/
|
||||
int btrfs_delete_one_dir_name(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path,
|
||||
struct btrfs_dir_item *di)
|
||||
{
|
||||
|
||||
struct extent_buffer *leaf;
|
||||
u32 sub_item_len;
|
||||
u32 item_len;
|
||||
int ret = 0;
|
||||
|
||||
leaf = path->nodes[0];
|
||||
sub_item_len = sizeof(*di) + btrfs_dir_name_len(leaf, di) +
|
||||
btrfs_dir_data_len(leaf, di);
|
||||
item_len = btrfs_item_size_nr(leaf, path->slots[0]);
|
||||
if (sub_item_len == item_len) {
|
||||
ret = btrfs_del_item(trans, root, path);
|
||||
} else {
|
||||
/* MARKER */
|
||||
unsigned long ptr = (unsigned long)di;
|
||||
unsigned long start;
|
||||
|
||||
start = btrfs_item_ptr_offset(leaf, path->slots[0]);
|
||||
memmove_extent_buffer(leaf, ptr, ptr + sub_item_len,
|
||||
item_len - (ptr + sub_item_len - start));
|
||||
ret = btrfs_truncate_item(trans, root, path,
|
||||
item_len - sub_item_len, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
2343
fs/btrfs/disk-io.c
Normal file
2343
fs/btrfs/disk-io.c
Normal file
File diff suppressed because it is too large
Load Diff
102
fs/btrfs/disk-io.h
Normal file
102
fs/btrfs/disk-io.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __DISKIO__
|
||||
#define __DISKIO__
|
||||
|
||||
#define BTRFS_SUPER_INFO_OFFSET (64 * 1024)
|
||||
#define BTRFS_SUPER_INFO_SIZE 4096
|
||||
|
||||
#define BTRFS_SUPER_MIRROR_MAX 3
|
||||
#define BTRFS_SUPER_MIRROR_SHIFT 12
|
||||
|
||||
static inline u64 btrfs_sb_offset(int mirror)
|
||||
{
|
||||
u64 start = 16 * 1024;
|
||||
if (mirror)
|
||||
return start << (BTRFS_SUPER_MIRROR_SHIFT * mirror);
|
||||
return BTRFS_SUPER_INFO_OFFSET;
|
||||
}
|
||||
|
||||
struct btrfs_device;
|
||||
struct btrfs_fs_devices;
|
||||
|
||||
struct extent_buffer *read_tree_block(struct btrfs_root *root, u64 bytenr,
|
||||
u32 blocksize, u64 parent_transid);
|
||||
int readahead_tree_block(struct btrfs_root *root, u64 bytenr, u32 blocksize,
|
||||
u64 parent_transid);
|
||||
struct extent_buffer *btrfs_find_create_tree_block(struct btrfs_root *root,
|
||||
u64 bytenr, u32 blocksize);
|
||||
int clean_tree_block(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, struct extent_buffer *buf);
|
||||
struct btrfs_root *open_ctree(struct super_block *sb,
|
||||
struct btrfs_fs_devices *fs_devices,
|
||||
char *options);
|
||||
int close_ctree(struct btrfs_root *root);
|
||||
int write_ctree_super(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, int max_mirrors);
|
||||
struct buffer_head *btrfs_read_dev_super(struct block_device *bdev);
|
||||
int btrfs_commit_super(struct btrfs_root *root);
|
||||
struct extent_buffer *btrfs_find_tree_block(struct btrfs_root *root,
|
||||
u64 bytenr, u32 blocksize);
|
||||
struct btrfs_root *btrfs_lookup_fs_root(struct btrfs_fs_info *fs_info,
|
||||
u64 root_objectid);
|
||||
struct btrfs_root *btrfs_read_fs_root(struct btrfs_fs_info *fs_info,
|
||||
struct btrfs_key *location,
|
||||
const char *name, int namelen);
|
||||
struct btrfs_root *btrfs_read_fs_root_no_radix(struct btrfs_root *tree_root,
|
||||
struct btrfs_key *location);
|
||||
struct btrfs_root *btrfs_read_fs_root_no_name(struct btrfs_fs_info *fs_info,
|
||||
struct btrfs_key *location);
|
||||
int btrfs_cleanup_fs_roots(struct btrfs_fs_info *fs_info);
|
||||
int btrfs_insert_dev_radix(struct btrfs_root *root,
|
||||
struct block_device *bdev,
|
||||
u64 device_id,
|
||||
u64 block_start,
|
||||
u64 num_blocks);
|
||||
void btrfs_btree_balance_dirty(struct btrfs_root *root, unsigned long nr);
|
||||
int btrfs_free_fs_root(struct btrfs_fs_info *fs_info, struct btrfs_root *root);
|
||||
void btrfs_mark_buffer_dirty(struct extent_buffer *buf);
|
||||
int btrfs_buffer_uptodate(struct extent_buffer *buf, u64 parent_transid);
|
||||
int btrfs_set_buffer_uptodate(struct extent_buffer *buf);
|
||||
int wait_on_tree_block_writeback(struct btrfs_root *root,
|
||||
struct extent_buffer *buf);
|
||||
int btrfs_read_buffer(struct extent_buffer *buf, u64 parent_transid);
|
||||
u32 btrfs_csum_data(struct btrfs_root *root, char *data, u32 seed, size_t len);
|
||||
void btrfs_csum_final(u32 crc, char *result);
|
||||
int btrfs_open_device(struct btrfs_device *dev);
|
||||
int btrfs_verify_block_csum(struct btrfs_root *root,
|
||||
struct extent_buffer *buf);
|
||||
int btrfs_bio_wq_end_io(struct btrfs_fs_info *info, struct bio *bio,
|
||||
int metadata);
|
||||
int btrfs_wq_submit_bio(struct btrfs_fs_info *fs_info, struct inode *inode,
|
||||
int rw, struct bio *bio, int mirror_num,
|
||||
unsigned long bio_flags,
|
||||
extent_submit_bio_hook_t *submit_bio_start,
|
||||
extent_submit_bio_hook_t *submit_bio_done);
|
||||
|
||||
int btrfs_congested_async(struct btrfs_fs_info *info, int iodone);
|
||||
unsigned long btrfs_async_submit_limit(struct btrfs_fs_info *info);
|
||||
int btrfs_write_tree_block(struct extent_buffer *buf);
|
||||
int btrfs_wait_tree_block_writeback(struct extent_buffer *buf);
|
||||
int btrfs_free_log_root_tree(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_fs_info *fs_info);
|
||||
int btrfs_init_log_root_tree(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_fs_info *fs_info);
|
||||
int btree_lock_page_hook(struct page *page);
|
||||
#endif
|
203
fs/btrfs/export.c
Normal file
203
fs/btrfs/export.c
Normal file
@ -0,0 +1,203 @@
|
||||
#include <linux/fs.h>
|
||||
#include <linux/types.h>
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "btrfs_inode.h"
|
||||
#include "print-tree.h"
|
||||
#include "export.h"
|
||||
#include "compat.h"
|
||||
|
||||
#define BTRFS_FID_SIZE_NON_CONNECTABLE (offsetof(struct btrfs_fid, \
|
||||
parent_objectid) / 4)
|
||||
#define BTRFS_FID_SIZE_CONNECTABLE (offsetof(struct btrfs_fid, \
|
||||
parent_root_objectid) / 4)
|
||||
#define BTRFS_FID_SIZE_CONNECTABLE_ROOT (sizeof(struct btrfs_fid) / 4)
|
||||
|
||||
static int btrfs_encode_fh(struct dentry *dentry, u32 *fh, int *max_len,
|
||||
int connectable)
|
||||
{
|
||||
struct btrfs_fid *fid = (struct btrfs_fid *)fh;
|
||||
struct inode *inode = dentry->d_inode;
|
||||
int len = *max_len;
|
||||
int type;
|
||||
|
||||
if ((len < BTRFS_FID_SIZE_NON_CONNECTABLE) ||
|
||||
(connectable && len < BTRFS_FID_SIZE_CONNECTABLE))
|
||||
return 255;
|
||||
|
||||
len = BTRFS_FID_SIZE_NON_CONNECTABLE;
|
||||
type = FILEID_BTRFS_WITHOUT_PARENT;
|
||||
|
||||
fid->objectid = BTRFS_I(inode)->location.objectid;
|
||||
fid->root_objectid = BTRFS_I(inode)->root->objectid;
|
||||
fid->gen = inode->i_generation;
|
||||
|
||||
if (connectable && !S_ISDIR(inode->i_mode)) {
|
||||
struct inode *parent;
|
||||
u64 parent_root_id;
|
||||
|
||||
spin_lock(&dentry->d_lock);
|
||||
|
||||
parent = dentry->d_parent->d_inode;
|
||||
fid->parent_objectid = BTRFS_I(parent)->location.objectid;
|
||||
fid->parent_gen = parent->i_generation;
|
||||
parent_root_id = BTRFS_I(parent)->root->objectid;
|
||||
|
||||
spin_unlock(&dentry->d_lock);
|
||||
|
||||
if (parent_root_id != fid->root_objectid) {
|
||||
fid->parent_root_objectid = parent_root_id;
|
||||
len = BTRFS_FID_SIZE_CONNECTABLE_ROOT;
|
||||
type = FILEID_BTRFS_WITH_PARENT_ROOT;
|
||||
} else {
|
||||
len = BTRFS_FID_SIZE_CONNECTABLE;
|
||||
type = FILEID_BTRFS_WITH_PARENT;
|
||||
}
|
||||
}
|
||||
|
||||
*max_len = len;
|
||||
return type;
|
||||
}
|
||||
|
||||
static struct dentry *btrfs_get_dentry(struct super_block *sb, u64 objectid,
|
||||
u64 root_objectid, u32 generation)
|
||||
{
|
||||
struct btrfs_root *root;
|
||||
struct inode *inode;
|
||||
struct btrfs_key key;
|
||||
|
||||
key.objectid = root_objectid;
|
||||
btrfs_set_key_type(&key, BTRFS_ROOT_ITEM_KEY);
|
||||
key.offset = (u64)-1;
|
||||
|
||||
root = btrfs_read_fs_root_no_name(btrfs_sb(sb)->fs_info, &key);
|
||||
if (IS_ERR(root))
|
||||
return ERR_CAST(root);
|
||||
|
||||
key.objectid = objectid;
|
||||
btrfs_set_key_type(&key, BTRFS_INODE_ITEM_KEY);
|
||||
key.offset = 0;
|
||||
|
||||
inode = btrfs_iget(sb, &key, root, NULL);
|
||||
if (IS_ERR(inode))
|
||||
return (void *)inode;
|
||||
|
||||
if (generation != inode->i_generation) {
|
||||
iput(inode);
|
||||
return ERR_PTR(-ESTALE);
|
||||
}
|
||||
|
||||
return d_obtain_alias(inode);
|
||||
}
|
||||
|
||||
static struct dentry *btrfs_fh_to_parent(struct super_block *sb, struct fid *fh,
|
||||
int fh_len, int fh_type)
|
||||
{
|
||||
struct btrfs_fid *fid = (struct btrfs_fid *) fh;
|
||||
u64 objectid, root_objectid;
|
||||
u32 generation;
|
||||
|
||||
if (fh_type == FILEID_BTRFS_WITH_PARENT) {
|
||||
if (fh_len != BTRFS_FID_SIZE_CONNECTABLE)
|
||||
return NULL;
|
||||
root_objectid = fid->root_objectid;
|
||||
} else if (fh_type == FILEID_BTRFS_WITH_PARENT_ROOT) {
|
||||
if (fh_len != BTRFS_FID_SIZE_CONNECTABLE_ROOT)
|
||||
return NULL;
|
||||
root_objectid = fid->parent_root_objectid;
|
||||
} else
|
||||
return NULL;
|
||||
|
||||
objectid = fid->parent_objectid;
|
||||
generation = fid->parent_gen;
|
||||
|
||||
return btrfs_get_dentry(sb, objectid, root_objectid, generation);
|
||||
}
|
||||
|
||||
static struct dentry *btrfs_fh_to_dentry(struct super_block *sb, struct fid *fh,
|
||||
int fh_len, int fh_type)
|
||||
{
|
||||
struct btrfs_fid *fid = (struct btrfs_fid *) fh;
|
||||
u64 objectid, root_objectid;
|
||||
u32 generation;
|
||||
|
||||
if ((fh_type != FILEID_BTRFS_WITH_PARENT ||
|
||||
fh_len != BTRFS_FID_SIZE_CONNECTABLE) &&
|
||||
(fh_type != FILEID_BTRFS_WITH_PARENT_ROOT ||
|
||||
fh_len != BTRFS_FID_SIZE_CONNECTABLE_ROOT) &&
|
||||
(fh_type != FILEID_BTRFS_WITHOUT_PARENT ||
|
||||
fh_len != BTRFS_FID_SIZE_NON_CONNECTABLE))
|
||||
return NULL;
|
||||
|
||||
objectid = fid->objectid;
|
||||
root_objectid = fid->root_objectid;
|
||||
generation = fid->gen;
|
||||
|
||||
return btrfs_get_dentry(sb, objectid, root_objectid, generation);
|
||||
}
|
||||
|
||||
static struct dentry *btrfs_get_parent(struct dentry *child)
|
||||
{
|
||||
struct inode *dir = child->d_inode;
|
||||
struct btrfs_root *root = BTRFS_I(dir)->root;
|
||||
struct btrfs_key key;
|
||||
struct btrfs_path *path;
|
||||
struct extent_buffer *leaf;
|
||||
int slot;
|
||||
u64 objectid;
|
||||
int ret;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
|
||||
key.objectid = dir->i_ino;
|
||||
btrfs_set_key_type(&key, BTRFS_INODE_REF_KEY);
|
||||
key.offset = (u64)-1;
|
||||
|
||||
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
|
||||
if (ret < 0) {
|
||||
/* Error */
|
||||
btrfs_free_path(path);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
leaf = path->nodes[0];
|
||||
slot = path->slots[0];
|
||||
if (ret) {
|
||||
/* btrfs_search_slot() returns the slot where we'd want to
|
||||
insert a backref for parent inode #0xFFFFFFFFFFFFFFFF.
|
||||
The _real_ backref, telling us what the parent inode
|
||||
_actually_ is, will be in the slot _before_ the one
|
||||
that btrfs_search_slot() returns. */
|
||||
if (!slot) {
|
||||
/* Unless there is _no_ key in the tree before... */
|
||||
btrfs_free_path(path);
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
slot--;
|
||||
}
|
||||
|
||||
btrfs_item_key_to_cpu(leaf, &key, slot);
|
||||
btrfs_free_path(path);
|
||||
|
||||
if (key.objectid != dir->i_ino || key.type != BTRFS_INODE_REF_KEY)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
objectid = key.offset;
|
||||
|
||||
/* If we are already at the root of a subvol, return the real root */
|
||||
if (objectid == dir->i_ino)
|
||||
return dget(dir->i_sb->s_root);
|
||||
|
||||
/* Build a new key for the inode item */
|
||||
key.objectid = objectid;
|
||||
btrfs_set_key_type(&key, BTRFS_INODE_ITEM_KEY);
|
||||
key.offset = 0;
|
||||
|
||||
return d_obtain_alias(btrfs_iget(root->fs_info->sb, &key, root, NULL));
|
||||
}
|
||||
|
||||
const struct export_operations btrfs_export_ops = {
|
||||
.encode_fh = btrfs_encode_fh,
|
||||
.fh_to_dentry = btrfs_fh_to_dentry,
|
||||
.fh_to_parent = btrfs_fh_to_parent,
|
||||
.get_parent = btrfs_get_parent,
|
||||
};
|
19
fs/btrfs/export.h
Normal file
19
fs/btrfs/export.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef BTRFS_EXPORT_H
|
||||
#define BTRFS_EXPORT_H
|
||||
|
||||
#include <linux/exportfs.h>
|
||||
|
||||
extern const struct export_operations btrfs_export_ops;
|
||||
|
||||
struct btrfs_fid {
|
||||
u64 objectid;
|
||||
u64 root_objectid;
|
||||
u32 gen;
|
||||
|
||||
u64 parent_objectid;
|
||||
u32 parent_gen;
|
||||
|
||||
u64 parent_root_objectid;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
#endif
|
5986
fs/btrfs/extent-tree.c
Normal file
5986
fs/btrfs/extent-tree.c
Normal file
File diff suppressed because it is too large
Load Diff
3717
fs/btrfs/extent_io.c
Normal file
3717
fs/btrfs/extent_io.c
Normal file
File diff suppressed because it is too large
Load Diff
269
fs/btrfs/extent_io.h
Normal file
269
fs/btrfs/extent_io.h
Normal file
@ -0,0 +1,269 @@
|
||||
#ifndef __EXTENTIO__
|
||||
#define __EXTENTIO__
|
||||
|
||||
#include <linux/rbtree.h>
|
||||
|
||||
/* bits for the extent state */
|
||||
#define EXTENT_DIRTY 1
|
||||
#define EXTENT_WRITEBACK (1 << 1)
|
||||
#define EXTENT_UPTODATE (1 << 2)
|
||||
#define EXTENT_LOCKED (1 << 3)
|
||||
#define EXTENT_NEW (1 << 4)
|
||||
#define EXTENT_DELALLOC (1 << 5)
|
||||
#define EXTENT_DEFRAG (1 << 6)
|
||||
#define EXTENT_DEFRAG_DONE (1 << 7)
|
||||
#define EXTENT_BUFFER_FILLED (1 << 8)
|
||||
#define EXTENT_ORDERED (1 << 9)
|
||||
#define EXTENT_ORDERED_METADATA (1 << 10)
|
||||
#define EXTENT_BOUNDARY (1 << 11)
|
||||
#define EXTENT_NODATASUM (1 << 12)
|
||||
#define EXTENT_IOBITS (EXTENT_LOCKED | EXTENT_WRITEBACK)
|
||||
|
||||
/* flags for bio submission */
|
||||
#define EXTENT_BIO_COMPRESSED 1
|
||||
|
||||
/*
|
||||
* page->private values. Every page that is controlled by the extent
|
||||
* map has page->private set to one.
|
||||
*/
|
||||
#define EXTENT_PAGE_PRIVATE 1
|
||||
#define EXTENT_PAGE_PRIVATE_FIRST_PAGE 3
|
||||
|
||||
struct extent_state;
|
||||
|
||||
typedef int (extent_submit_bio_hook_t)(struct inode *inode, int rw,
|
||||
struct bio *bio, int mirror_num,
|
||||
unsigned long bio_flags);
|
||||
struct extent_io_ops {
|
||||
int (*fill_delalloc)(struct inode *inode, struct page *locked_page,
|
||||
u64 start, u64 end, int *page_started,
|
||||
unsigned long *nr_written);
|
||||
int (*writepage_start_hook)(struct page *page, u64 start, u64 end);
|
||||
int (*writepage_io_hook)(struct page *page, u64 start, u64 end);
|
||||
extent_submit_bio_hook_t *submit_bio_hook;
|
||||
int (*merge_bio_hook)(struct page *page, unsigned long offset,
|
||||
size_t size, struct bio *bio,
|
||||
unsigned long bio_flags);
|
||||
int (*readpage_io_hook)(struct page *page, u64 start, u64 end);
|
||||
int (*readpage_io_failed_hook)(struct bio *bio, struct page *page,
|
||||
u64 start, u64 end,
|
||||
struct extent_state *state);
|
||||
int (*writepage_io_failed_hook)(struct bio *bio, struct page *page,
|
||||
u64 start, u64 end,
|
||||
struct extent_state *state);
|
||||
int (*readpage_end_io_hook)(struct page *page, u64 start, u64 end,
|
||||
struct extent_state *state);
|
||||
int (*writepage_end_io_hook)(struct page *page, u64 start, u64 end,
|
||||
struct extent_state *state, int uptodate);
|
||||
int (*set_bit_hook)(struct inode *inode, u64 start, u64 end,
|
||||
unsigned long old, unsigned long bits);
|
||||
int (*clear_bit_hook)(struct inode *inode, u64 start, u64 end,
|
||||
unsigned long old, unsigned long bits);
|
||||
int (*write_cache_pages_lock_hook)(struct page *page);
|
||||
};
|
||||
|
||||
struct extent_io_tree {
|
||||
struct rb_root state;
|
||||
struct rb_root buffer;
|
||||
struct address_space *mapping;
|
||||
u64 dirty_bytes;
|
||||
spinlock_t lock;
|
||||
spinlock_t buffer_lock;
|
||||
struct extent_io_ops *ops;
|
||||
};
|
||||
|
||||
struct extent_state {
|
||||
u64 start;
|
||||
u64 end; /* inclusive */
|
||||
struct rb_node rb_node;
|
||||
struct extent_io_tree *tree;
|
||||
wait_queue_head_t wq;
|
||||
atomic_t refs;
|
||||
unsigned long state;
|
||||
|
||||
/* for use by the FS */
|
||||
u64 private;
|
||||
|
||||
struct list_head leak_list;
|
||||
};
|
||||
|
||||
struct extent_buffer {
|
||||
u64 start;
|
||||
unsigned long len;
|
||||
char *map_token;
|
||||
char *kaddr;
|
||||
unsigned long map_start;
|
||||
unsigned long map_len;
|
||||
struct page *first_page;
|
||||
atomic_t refs;
|
||||
int flags;
|
||||
struct list_head leak_list;
|
||||
struct rb_node rb_node;
|
||||
struct mutex mutex;
|
||||
};
|
||||
|
||||
struct extent_map_tree;
|
||||
|
||||
static inline struct extent_state *extent_state_next(struct extent_state *state)
|
||||
{
|
||||
struct rb_node *node;
|
||||
node = rb_next(&state->rb_node);
|
||||
if (!node)
|
||||
return NULL;
|
||||
return rb_entry(node, struct extent_state, rb_node);
|
||||
}
|
||||
|
||||
typedef struct extent_map *(get_extent_t)(struct inode *inode,
|
||||
struct page *page,
|
||||
size_t page_offset,
|
||||
u64 start, u64 len,
|
||||
int create);
|
||||
|
||||
void extent_io_tree_init(struct extent_io_tree *tree,
|
||||
struct address_space *mapping, gfp_t mask);
|
||||
int try_release_extent_mapping(struct extent_map_tree *map,
|
||||
struct extent_io_tree *tree, struct page *page,
|
||||
gfp_t mask);
|
||||
int try_release_extent_buffer(struct extent_io_tree *tree, struct page *page);
|
||||
int try_release_extent_state(struct extent_map_tree *map,
|
||||
struct extent_io_tree *tree, struct page *page,
|
||||
gfp_t mask);
|
||||
int lock_extent(struct extent_io_tree *tree, u64 start, u64 end, gfp_t mask);
|
||||
int unlock_extent(struct extent_io_tree *tree, u64 start, u64 end, gfp_t mask);
|
||||
int try_lock_extent(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
gfp_t mask);
|
||||
int extent_read_full_page(struct extent_io_tree *tree, struct page *page,
|
||||
get_extent_t *get_extent);
|
||||
int __init extent_io_init(void);
|
||||
void extent_io_exit(void);
|
||||
|
||||
u64 count_range_bits(struct extent_io_tree *tree,
|
||||
u64 *start, u64 search_end,
|
||||
u64 max_bytes, unsigned long bits);
|
||||
|
||||
int test_range_bit(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
int bits, int filled);
|
||||
int clear_extent_bits(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
int bits, gfp_t mask);
|
||||
int clear_extent_bit(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
int bits, int wake, int delete, gfp_t mask);
|
||||
int set_extent_bits(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
int bits, gfp_t mask);
|
||||
int set_extent_uptodate(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
gfp_t mask);
|
||||
int set_extent_new(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
gfp_t mask);
|
||||
int set_extent_dirty(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
gfp_t mask);
|
||||
int clear_extent_dirty(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
gfp_t mask);
|
||||
int clear_extent_ordered(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
gfp_t mask);
|
||||
int clear_extent_ordered_metadata(struct extent_io_tree *tree, u64 start,
|
||||
u64 end, gfp_t mask);
|
||||
int set_extent_delalloc(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
gfp_t mask);
|
||||
int set_extent_ordered(struct extent_io_tree *tree, u64 start, u64 end,
|
||||
gfp_t mask);
|
||||
int find_first_extent_bit(struct extent_io_tree *tree, u64 start,
|
||||
u64 *start_ret, u64 *end_ret, int bits);
|
||||
struct extent_state *find_first_extent_bit_state(struct extent_io_tree *tree,
|
||||
u64 start, int bits);
|
||||
int extent_invalidatepage(struct extent_io_tree *tree,
|
||||
struct page *page, unsigned long offset);
|
||||
int extent_write_full_page(struct extent_io_tree *tree, struct page *page,
|
||||
get_extent_t *get_extent,
|
||||
struct writeback_control *wbc);
|
||||
int extent_write_locked_range(struct extent_io_tree *tree, struct inode *inode,
|
||||
u64 start, u64 end, get_extent_t *get_extent,
|
||||
int mode);
|
||||
int extent_writepages(struct extent_io_tree *tree,
|
||||
struct address_space *mapping,
|
||||
get_extent_t *get_extent,
|
||||
struct writeback_control *wbc);
|
||||
int extent_readpages(struct extent_io_tree *tree,
|
||||
struct address_space *mapping,
|
||||
struct list_head *pages, unsigned nr_pages,
|
||||
get_extent_t get_extent);
|
||||
int extent_prepare_write(struct extent_io_tree *tree,
|
||||
struct inode *inode, struct page *page,
|
||||
unsigned from, unsigned to, get_extent_t *get_extent);
|
||||
int extent_commit_write(struct extent_io_tree *tree,
|
||||
struct inode *inode, struct page *page,
|
||||
unsigned from, unsigned to);
|
||||
sector_t extent_bmap(struct address_space *mapping, sector_t iblock,
|
||||
get_extent_t *get_extent);
|
||||
int set_range_dirty(struct extent_io_tree *tree, u64 start, u64 end);
|
||||
int set_state_private(struct extent_io_tree *tree, u64 start, u64 private);
|
||||
int get_state_private(struct extent_io_tree *tree, u64 start, u64 *private);
|
||||
void set_page_extent_mapped(struct page *page);
|
||||
|
||||
struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree,
|
||||
u64 start, unsigned long len,
|
||||
struct page *page0,
|
||||
gfp_t mask);
|
||||
struct extent_buffer *find_extent_buffer(struct extent_io_tree *tree,
|
||||
u64 start, unsigned long len,
|
||||
gfp_t mask);
|
||||
void free_extent_buffer(struct extent_buffer *eb);
|
||||
int read_extent_buffer_pages(struct extent_io_tree *tree,
|
||||
struct extent_buffer *eb, u64 start, int wait,
|
||||
get_extent_t *get_extent, int mirror_num);
|
||||
|
||||
static inline void extent_buffer_get(struct extent_buffer *eb)
|
||||
{
|
||||
atomic_inc(&eb->refs);
|
||||
}
|
||||
|
||||
int memcmp_extent_buffer(struct extent_buffer *eb, const void *ptrv,
|
||||
unsigned long start,
|
||||
unsigned long len);
|
||||
void read_extent_buffer(struct extent_buffer *eb, void *dst,
|
||||
unsigned long start,
|
||||
unsigned long len);
|
||||
void write_extent_buffer(struct extent_buffer *eb, const void *src,
|
||||
unsigned long start, unsigned long len);
|
||||
void copy_extent_buffer(struct extent_buffer *dst, struct extent_buffer *src,
|
||||
unsigned long dst_offset, unsigned long src_offset,
|
||||
unsigned long len);
|
||||
void memcpy_extent_buffer(struct extent_buffer *dst, unsigned long dst_offset,
|
||||
unsigned long src_offset, unsigned long len);
|
||||
void memmove_extent_buffer(struct extent_buffer *dst, unsigned long dst_offset,
|
||||
unsigned long src_offset, unsigned long len);
|
||||
void memset_extent_buffer(struct extent_buffer *eb, char c,
|
||||
unsigned long start, unsigned long len);
|
||||
int wait_on_extent_buffer_writeback(struct extent_io_tree *tree,
|
||||
struct extent_buffer *eb);
|
||||
int wait_on_extent_writeback(struct extent_io_tree *tree, u64 start, u64 end);
|
||||
int wait_extent_bit(struct extent_io_tree *tree, u64 start, u64 end, int bits);
|
||||
int clear_extent_buffer_dirty(struct extent_io_tree *tree,
|
||||
struct extent_buffer *eb);
|
||||
int set_extent_buffer_dirty(struct extent_io_tree *tree,
|
||||
struct extent_buffer *eb);
|
||||
int set_extent_buffer_uptodate(struct extent_io_tree *tree,
|
||||
struct extent_buffer *eb);
|
||||
int clear_extent_buffer_uptodate(struct extent_io_tree *tree,
|
||||
struct extent_buffer *eb);
|
||||
int extent_buffer_uptodate(struct extent_io_tree *tree,
|
||||
struct extent_buffer *eb);
|
||||
int map_extent_buffer(struct extent_buffer *eb, unsigned long offset,
|
||||
unsigned long min_len, char **token, char **map,
|
||||
unsigned long *map_start,
|
||||
unsigned long *map_len, int km);
|
||||
int map_private_extent_buffer(struct extent_buffer *eb, unsigned long offset,
|
||||
unsigned long min_len, char **token, char **map,
|
||||
unsigned long *map_start,
|
||||
unsigned long *map_len, int km);
|
||||
void unmap_extent_buffer(struct extent_buffer *eb, char *token, int km);
|
||||
int release_extent_buffer_tail_pages(struct extent_buffer *eb);
|
||||
int extent_range_uptodate(struct extent_io_tree *tree,
|
||||
u64 start, u64 end);
|
||||
int extent_clear_unlock_delalloc(struct inode *inode,
|
||||
struct extent_io_tree *tree,
|
||||
u64 start, u64 end, struct page *locked_page,
|
||||
int unlock_page,
|
||||
int clear_unlock,
|
||||
int clear_delalloc, int clear_dirty,
|
||||
int set_writeback,
|
||||
int end_writeback);
|
||||
#endif
|
351
fs/btrfs/extent_map.c
Normal file
351
fs/btrfs/extent_map.c
Normal file
@ -0,0 +1,351 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/hardirq.h>
|
||||
#include "extent_map.h"
|
||||
|
||||
/* temporary define until extent_map moves out of btrfs */
|
||||
struct kmem_cache *btrfs_cache_create(const char *name, size_t size,
|
||||
unsigned long extra_flags,
|
||||
void (*ctor)(void *, struct kmem_cache *,
|
||||
unsigned long));
|
||||
|
||||
static struct kmem_cache *extent_map_cache;
|
||||
|
||||
int __init extent_map_init(void)
|
||||
{
|
||||
extent_map_cache = btrfs_cache_create("extent_map",
|
||||
sizeof(struct extent_map), 0,
|
||||
NULL);
|
||||
if (!extent_map_cache)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void extent_map_exit(void)
|
||||
{
|
||||
if (extent_map_cache)
|
||||
kmem_cache_destroy(extent_map_cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* extent_map_tree_init - initialize extent map tree
|
||||
* @tree: tree to initialize
|
||||
* @mask: flags for memory allocations during tree operations
|
||||
*
|
||||
* Initialize the extent tree @tree. Should be called for each new inode
|
||||
* or other user of the extent_map interface.
|
||||
*/
|
||||
void extent_map_tree_init(struct extent_map_tree *tree, gfp_t mask)
|
||||
{
|
||||
tree->map.rb_node = NULL;
|
||||
spin_lock_init(&tree->lock);
|
||||
}
|
||||
EXPORT_SYMBOL(extent_map_tree_init);
|
||||
|
||||
/**
|
||||
* alloc_extent_map - allocate new extent map structure
|
||||
* @mask: memory allocation flags
|
||||
*
|
||||
* Allocate a new extent_map structure. The new structure is
|
||||
* returned with a reference count of one and needs to be
|
||||
* freed using free_extent_map()
|
||||
*/
|
||||
struct extent_map *alloc_extent_map(gfp_t mask)
|
||||
{
|
||||
struct extent_map *em;
|
||||
em = kmem_cache_alloc(extent_map_cache, mask);
|
||||
if (!em || IS_ERR(em))
|
||||
return em;
|
||||
em->in_tree = 0;
|
||||
em->flags = 0;
|
||||
atomic_set(&em->refs, 1);
|
||||
return em;
|
||||
}
|
||||
EXPORT_SYMBOL(alloc_extent_map);
|
||||
|
||||
/**
|
||||
* free_extent_map - drop reference count of an extent_map
|
||||
* @em: extent map beeing releasead
|
||||
*
|
||||
* Drops the reference out on @em by one and free the structure
|
||||
* if the reference count hits zero.
|
||||
*/
|
||||
void free_extent_map(struct extent_map *em)
|
||||
{
|
||||
if (!em)
|
||||
return;
|
||||
WARN_ON(atomic_read(&em->refs) == 0);
|
||||
if (atomic_dec_and_test(&em->refs)) {
|
||||
WARN_ON(em->in_tree);
|
||||
kmem_cache_free(extent_map_cache, em);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(free_extent_map);
|
||||
|
||||
static struct rb_node *tree_insert(struct rb_root *root, u64 offset,
|
||||
struct rb_node *node)
|
||||
{
|
||||
struct rb_node **p = &root->rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
struct extent_map *entry;
|
||||
|
||||
while (*p) {
|
||||
parent = *p;
|
||||
entry = rb_entry(parent, struct extent_map, rb_node);
|
||||
|
||||
WARN_ON(!entry->in_tree);
|
||||
|
||||
if (offset < entry->start)
|
||||
p = &(*p)->rb_left;
|
||||
else if (offset >= extent_map_end(entry))
|
||||
p = &(*p)->rb_right;
|
||||
else
|
||||
return parent;
|
||||
}
|
||||
|
||||
entry = rb_entry(node, struct extent_map, rb_node);
|
||||
entry->in_tree = 1;
|
||||
rb_link_node(node, parent, p);
|
||||
rb_insert_color(node, root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* search through the tree for an extent_map with a given offset. If
|
||||
* it can't be found, try to find some neighboring extents
|
||||
*/
|
||||
static struct rb_node *__tree_search(struct rb_root *root, u64 offset,
|
||||
struct rb_node **prev_ret,
|
||||
struct rb_node **next_ret)
|
||||
{
|
||||
struct rb_node *n = root->rb_node;
|
||||
struct rb_node *prev = NULL;
|
||||
struct rb_node *orig_prev = NULL;
|
||||
struct extent_map *entry;
|
||||
struct extent_map *prev_entry = NULL;
|
||||
|
||||
while (n) {
|
||||
entry = rb_entry(n, struct extent_map, rb_node);
|
||||
prev = n;
|
||||
prev_entry = entry;
|
||||
|
||||
WARN_ON(!entry->in_tree);
|
||||
|
||||
if (offset < entry->start)
|
||||
n = n->rb_left;
|
||||
else if (offset >= extent_map_end(entry))
|
||||
n = n->rb_right;
|
||||
else
|
||||
return n;
|
||||
}
|
||||
|
||||
if (prev_ret) {
|
||||
orig_prev = prev;
|
||||
while (prev && offset >= extent_map_end(prev_entry)) {
|
||||
prev = rb_next(prev);
|
||||
prev_entry = rb_entry(prev, struct extent_map, rb_node);
|
||||
}
|
||||
*prev_ret = prev;
|
||||
prev = orig_prev;
|
||||
}
|
||||
|
||||
if (next_ret) {
|
||||
prev_entry = rb_entry(prev, struct extent_map, rb_node);
|
||||
while (prev && offset < prev_entry->start) {
|
||||
prev = rb_prev(prev);
|
||||
prev_entry = rb_entry(prev, struct extent_map, rb_node);
|
||||
}
|
||||
*next_ret = prev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* look for an offset in the tree, and if it can't be found, return
|
||||
* the first offset we can find smaller than 'offset'.
|
||||
*/
|
||||
static inline struct rb_node *tree_search(struct rb_root *root, u64 offset)
|
||||
{
|
||||
struct rb_node *prev;
|
||||
struct rb_node *ret;
|
||||
ret = __tree_search(root, offset, &prev, NULL);
|
||||
if (!ret)
|
||||
return prev;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check to see if two extent_map structs are adjacent and safe to merge */
|
||||
static int mergable_maps(struct extent_map *prev, struct extent_map *next)
|
||||
{
|
||||
if (test_bit(EXTENT_FLAG_PINNED, &prev->flags))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* don't merge compressed extents, we need to know their
|
||||
* actual size
|
||||
*/
|
||||
if (test_bit(EXTENT_FLAG_COMPRESSED, &prev->flags))
|
||||
return 0;
|
||||
|
||||
if (extent_map_end(prev) == next->start &&
|
||||
prev->flags == next->flags &&
|
||||
prev->bdev == next->bdev &&
|
||||
((next->block_start == EXTENT_MAP_HOLE &&
|
||||
prev->block_start == EXTENT_MAP_HOLE) ||
|
||||
(next->block_start == EXTENT_MAP_INLINE &&
|
||||
prev->block_start == EXTENT_MAP_INLINE) ||
|
||||
(next->block_start == EXTENT_MAP_DELALLOC &&
|
||||
prev->block_start == EXTENT_MAP_DELALLOC) ||
|
||||
(next->block_start < EXTENT_MAP_LAST_BYTE - 1 &&
|
||||
next->block_start == extent_map_block_end(prev)))) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* add_extent_mapping - add new extent map to the extent tree
|
||||
* @tree: tree to insert new map in
|
||||
* @em: map to insert
|
||||
*
|
||||
* Insert @em into @tree or perform a simple forward/backward merge with
|
||||
* existing mappings. The extent_map struct passed in will be inserted
|
||||
* into the tree directly, with an additional reference taken, or a
|
||||
* reference dropped if the merge attempt was sucessfull.
|
||||
*/
|
||||
int add_extent_mapping(struct extent_map_tree *tree,
|
||||
struct extent_map *em)
|
||||
{
|
||||
int ret = 0;
|
||||
struct extent_map *merge = NULL;
|
||||
struct rb_node *rb;
|
||||
struct extent_map *exist;
|
||||
|
||||
exist = lookup_extent_mapping(tree, em->start, em->len);
|
||||
if (exist) {
|
||||
free_extent_map(exist);
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
assert_spin_locked(&tree->lock);
|
||||
rb = tree_insert(&tree->map, em->start, &em->rb_node);
|
||||
if (rb) {
|
||||
ret = -EEXIST;
|
||||
free_extent_map(merge);
|
||||
goto out;
|
||||
}
|
||||
atomic_inc(&em->refs);
|
||||
if (em->start != 0) {
|
||||
rb = rb_prev(&em->rb_node);
|
||||
if (rb)
|
||||
merge = rb_entry(rb, struct extent_map, rb_node);
|
||||
if (rb && mergable_maps(merge, em)) {
|
||||
em->start = merge->start;
|
||||
em->len += merge->len;
|
||||
em->block_len += merge->block_len;
|
||||
em->block_start = merge->block_start;
|
||||
merge->in_tree = 0;
|
||||
rb_erase(&merge->rb_node, &tree->map);
|
||||
free_extent_map(merge);
|
||||
}
|
||||
}
|
||||
rb = rb_next(&em->rb_node);
|
||||
if (rb)
|
||||
merge = rb_entry(rb, struct extent_map, rb_node);
|
||||
if (rb && mergable_maps(em, merge)) {
|
||||
em->len += merge->len;
|
||||
em->block_len += merge->len;
|
||||
rb_erase(&merge->rb_node, &tree->map);
|
||||
merge->in_tree = 0;
|
||||
free_extent_map(merge);
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(add_extent_mapping);
|
||||
|
||||
/* simple helper to do math around the end of an extent, handling wrap */
|
||||
static u64 range_end(u64 start, u64 len)
|
||||
{
|
||||
if (start + len < start)
|
||||
return (u64)-1;
|
||||
return start + len;
|
||||
}
|
||||
|
||||
/**
|
||||
* lookup_extent_mapping - lookup extent_map
|
||||
* @tree: tree to lookup in
|
||||
* @start: byte offset to start the search
|
||||
* @len: length of the lookup range
|
||||
*
|
||||
* Find and return the first extent_map struct in @tree that intersects the
|
||||
* [start, len] range. There may be additional objects in the tree that
|
||||
* intersect, so check the object returned carefully to make sure that no
|
||||
* additional lookups are needed.
|
||||
*/
|
||||
struct extent_map *lookup_extent_mapping(struct extent_map_tree *tree,
|
||||
u64 start, u64 len)
|
||||
{
|
||||
struct extent_map *em;
|
||||
struct rb_node *rb_node;
|
||||
struct rb_node *prev = NULL;
|
||||
struct rb_node *next = NULL;
|
||||
u64 end = range_end(start, len);
|
||||
|
||||
assert_spin_locked(&tree->lock);
|
||||
rb_node = __tree_search(&tree->map, start, &prev, &next);
|
||||
if (!rb_node && prev) {
|
||||
em = rb_entry(prev, struct extent_map, rb_node);
|
||||
if (end > em->start && start < extent_map_end(em))
|
||||
goto found;
|
||||
}
|
||||
if (!rb_node && next) {
|
||||
em = rb_entry(next, struct extent_map, rb_node);
|
||||
if (end > em->start && start < extent_map_end(em))
|
||||
goto found;
|
||||
}
|
||||
if (!rb_node) {
|
||||
em = NULL;
|
||||
goto out;
|
||||
}
|
||||
if (IS_ERR(rb_node)) {
|
||||
em = ERR_PTR(PTR_ERR(rb_node));
|
||||
goto out;
|
||||
}
|
||||
em = rb_entry(rb_node, struct extent_map, rb_node);
|
||||
if (end > em->start && start < extent_map_end(em))
|
||||
goto found;
|
||||
|
||||
em = NULL;
|
||||
goto out;
|
||||
|
||||
found:
|
||||
atomic_inc(&em->refs);
|
||||
out:
|
||||
return em;
|
||||
}
|
||||
EXPORT_SYMBOL(lookup_extent_mapping);
|
||||
|
||||
/**
|
||||
* remove_extent_mapping - removes an extent_map from the extent tree
|
||||
* @tree: extent tree to remove from
|
||||
* @em: extent map beeing removed
|
||||
*
|
||||
* Removes @em from @tree. No reference counts are dropped, and no checks
|
||||
* are done to see if the range is in use
|
||||
*/
|
||||
int remove_extent_mapping(struct extent_map_tree *tree, struct extent_map *em)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
WARN_ON(test_bit(EXTENT_FLAG_PINNED, &em->flags));
|
||||
assert_spin_locked(&tree->lock);
|
||||
rb_erase(&em->rb_node, &tree->map);
|
||||
em->in_tree = 0;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(remove_extent_mapping);
|
62
fs/btrfs/extent_map.h
Normal file
62
fs/btrfs/extent_map.h
Normal file
@ -0,0 +1,62 @@
|
||||
#ifndef __EXTENTMAP__
|
||||
#define __EXTENTMAP__
|
||||
|
||||
#include <linux/rbtree.h>
|
||||
|
||||
#define EXTENT_MAP_LAST_BYTE (u64)-4
|
||||
#define EXTENT_MAP_HOLE (u64)-3
|
||||
#define EXTENT_MAP_INLINE (u64)-2
|
||||
#define EXTENT_MAP_DELALLOC (u64)-1
|
||||
|
||||
/* bits for the flags field */
|
||||
#define EXTENT_FLAG_PINNED 0 /* this entry not yet on disk, don't free it */
|
||||
#define EXTENT_FLAG_COMPRESSED 1
|
||||
#define EXTENT_FLAG_VACANCY 2 /* no file extent item found */
|
||||
#define EXTENT_FLAG_PREALLOC 3 /* pre-allocated extent */
|
||||
|
||||
struct extent_map {
|
||||
struct rb_node rb_node;
|
||||
|
||||
/* all of these are in bytes */
|
||||
u64 start;
|
||||
u64 len;
|
||||
u64 orig_start;
|
||||
u64 block_start;
|
||||
u64 block_len;
|
||||
unsigned long flags;
|
||||
struct block_device *bdev;
|
||||
atomic_t refs;
|
||||
int in_tree;
|
||||
};
|
||||
|
||||
struct extent_map_tree {
|
||||
struct rb_root map;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static inline u64 extent_map_end(struct extent_map *em)
|
||||
{
|
||||
if (em->start + em->len < em->start)
|
||||
return (u64)-1;
|
||||
return em->start + em->len;
|
||||
}
|
||||
|
||||
static inline u64 extent_map_block_end(struct extent_map *em)
|
||||
{
|
||||
if (em->block_start + em->block_len < em->block_start)
|
||||
return (u64)-1;
|
||||
return em->block_start + em->block_len;
|
||||
}
|
||||
|
||||
void extent_map_tree_init(struct extent_map_tree *tree, gfp_t mask);
|
||||
struct extent_map *lookup_extent_mapping(struct extent_map_tree *tree,
|
||||
u64 start, u64 len);
|
||||
int add_extent_mapping(struct extent_map_tree *tree,
|
||||
struct extent_map *em);
|
||||
int remove_extent_mapping(struct extent_map_tree *tree, struct extent_map *em);
|
||||
|
||||
struct extent_map *alloc_extent_map(gfp_t mask);
|
||||
void free_extent_map(struct extent_map *em);
|
||||
int __init extent_map_init(void);
|
||||
void extent_map_exit(void);
|
||||
#endif
|
831
fs/btrfs/file-item.c
Normal file
831
fs/btrfs/file-item.c
Normal file
@ -0,0 +1,831 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/bio.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/highmem.h>
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "transaction.h"
|
||||
#include "print-tree.h"
|
||||
|
||||
#define MAX_CSUM_ITEMS(r, size) ((((BTRFS_LEAF_DATA_SIZE(r) - \
|
||||
sizeof(struct btrfs_item) * 2) / \
|
||||
size) - 1))
|
||||
|
||||
#define MAX_ORDERED_SUM_BYTES(r) ((PAGE_SIZE - \
|
||||
sizeof(struct btrfs_ordered_sum)) / \
|
||||
sizeof(struct btrfs_sector_sum) * \
|
||||
(r)->sectorsize - (r)->sectorsize)
|
||||
|
||||
int btrfs_insert_file_extent(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
u64 objectid, u64 pos,
|
||||
u64 disk_offset, u64 disk_num_bytes,
|
||||
u64 num_bytes, u64 offset, u64 ram_bytes,
|
||||
u8 compression, u8 encryption, u16 other_encoding)
|
||||
{
|
||||
int ret = 0;
|
||||
struct btrfs_file_extent_item *item;
|
||||
struct btrfs_key file_key;
|
||||
struct btrfs_path *path;
|
||||
struct extent_buffer *leaf;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
file_key.objectid = objectid;
|
||||
file_key.offset = pos;
|
||||
btrfs_set_key_type(&file_key, BTRFS_EXTENT_DATA_KEY);
|
||||
|
||||
ret = btrfs_insert_empty_item(trans, root, path, &file_key,
|
||||
sizeof(*item));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
BUG_ON(ret);
|
||||
leaf = path->nodes[0];
|
||||
item = btrfs_item_ptr(leaf, path->slots[0],
|
||||
struct btrfs_file_extent_item);
|
||||
btrfs_set_file_extent_disk_bytenr(leaf, item, disk_offset);
|
||||
btrfs_set_file_extent_disk_num_bytes(leaf, item, disk_num_bytes);
|
||||
btrfs_set_file_extent_offset(leaf, item, offset);
|
||||
btrfs_set_file_extent_num_bytes(leaf, item, num_bytes);
|
||||
btrfs_set_file_extent_ram_bytes(leaf, item, ram_bytes);
|
||||
btrfs_set_file_extent_generation(leaf, item, trans->transid);
|
||||
btrfs_set_file_extent_type(leaf, item, BTRFS_FILE_EXTENT_REG);
|
||||
btrfs_set_file_extent_compression(leaf, item, compression);
|
||||
btrfs_set_file_extent_encryption(leaf, item, encryption);
|
||||
btrfs_set_file_extent_other_encoding(leaf, item, other_encoding);
|
||||
|
||||
btrfs_mark_buffer_dirty(leaf);
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct btrfs_csum_item *btrfs_lookup_csum(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path,
|
||||
u64 bytenr, int cow)
|
||||
{
|
||||
int ret;
|
||||
struct btrfs_key file_key;
|
||||
struct btrfs_key found_key;
|
||||
struct btrfs_csum_item *item;
|
||||
struct extent_buffer *leaf;
|
||||
u64 csum_offset = 0;
|
||||
u16 csum_size =
|
||||
btrfs_super_csum_size(&root->fs_info->super_copy);
|
||||
int csums_in_item;
|
||||
|
||||
file_key.objectid = BTRFS_EXTENT_CSUM_OBJECTID;
|
||||
file_key.offset = bytenr;
|
||||
btrfs_set_key_type(&file_key, BTRFS_EXTENT_CSUM_KEY);
|
||||
ret = btrfs_search_slot(trans, root, &file_key, path, 0, cow);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
leaf = path->nodes[0];
|
||||
if (ret > 0) {
|
||||
ret = 1;
|
||||
if (path->slots[0] == 0)
|
||||
goto fail;
|
||||
path->slots[0]--;
|
||||
btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
|
||||
if (btrfs_key_type(&found_key) != BTRFS_EXTENT_CSUM_KEY)
|
||||
goto fail;
|
||||
|
||||
csum_offset = (bytenr - found_key.offset) >>
|
||||
root->fs_info->sb->s_blocksize_bits;
|
||||
csums_in_item = btrfs_item_size_nr(leaf, path->slots[0]);
|
||||
csums_in_item /= csum_size;
|
||||
|
||||
if (csum_offset >= csums_in_item) {
|
||||
ret = -EFBIG;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_csum_item);
|
||||
item = (struct btrfs_csum_item *)((unsigned char *)item +
|
||||
csum_offset * csum_size);
|
||||
return item;
|
||||
fail:
|
||||
if (ret > 0)
|
||||
ret = -ENOENT;
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
|
||||
int btrfs_lookup_file_extent(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path, u64 objectid,
|
||||
u64 offset, int mod)
|
||||
{
|
||||
int ret;
|
||||
struct btrfs_key file_key;
|
||||
int ins_len = mod < 0 ? -1 : 0;
|
||||
int cow = mod != 0;
|
||||
|
||||
file_key.objectid = objectid;
|
||||
file_key.offset = offset;
|
||||
btrfs_set_key_type(&file_key, BTRFS_EXTENT_DATA_KEY);
|
||||
ret = btrfs_search_slot(trans, root, &file_key, path, ins_len, cow);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int btrfs_lookup_bio_sums(struct btrfs_root *root, struct inode *inode,
|
||||
struct bio *bio, u32 *dst)
|
||||
{
|
||||
u32 sum;
|
||||
struct bio_vec *bvec = bio->bi_io_vec;
|
||||
int bio_index = 0;
|
||||
u64 offset;
|
||||
u64 item_start_offset = 0;
|
||||
u64 item_last_offset = 0;
|
||||
u64 disk_bytenr;
|
||||
u32 diff;
|
||||
u16 csum_size =
|
||||
btrfs_super_csum_size(&root->fs_info->super_copy);
|
||||
int ret;
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_csum_item *item = NULL;
|
||||
struct extent_io_tree *io_tree = &BTRFS_I(inode)->io_tree;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (bio->bi_size > PAGE_CACHE_SIZE * 8)
|
||||
path->reada = 2;
|
||||
|
||||
WARN_ON(bio->bi_vcnt <= 0);
|
||||
|
||||
disk_bytenr = (u64)bio->bi_sector << 9;
|
||||
while (bio_index < bio->bi_vcnt) {
|
||||
offset = page_offset(bvec->bv_page) + bvec->bv_offset;
|
||||
ret = btrfs_find_ordered_sum(inode, offset, disk_bytenr, &sum);
|
||||
if (ret == 0)
|
||||
goto found;
|
||||
|
||||
if (!item || disk_bytenr < item_start_offset ||
|
||||
disk_bytenr >= item_last_offset) {
|
||||
struct btrfs_key found_key;
|
||||
u32 item_size;
|
||||
|
||||
if (item)
|
||||
btrfs_release_path(root, path);
|
||||
item = btrfs_lookup_csum(NULL, root->fs_info->csum_root,
|
||||
path, disk_bytenr, 0);
|
||||
if (IS_ERR(item)) {
|
||||
ret = PTR_ERR(item);
|
||||
if (ret == -ENOENT || ret == -EFBIG)
|
||||
ret = 0;
|
||||
sum = 0;
|
||||
if (BTRFS_I(inode)->root->root_key.objectid ==
|
||||
BTRFS_DATA_RELOC_TREE_OBJECTID) {
|
||||
set_extent_bits(io_tree, offset,
|
||||
offset + bvec->bv_len - 1,
|
||||
EXTENT_NODATASUM, GFP_NOFS);
|
||||
} else {
|
||||
printk(KERN_INFO "btrfs no csum found "
|
||||
"for inode %lu start %llu\n",
|
||||
inode->i_ino,
|
||||
(unsigned long long)offset);
|
||||
}
|
||||
item = NULL;
|
||||
btrfs_release_path(root, path);
|
||||
goto found;
|
||||
}
|
||||
btrfs_item_key_to_cpu(path->nodes[0], &found_key,
|
||||
path->slots[0]);
|
||||
|
||||
item_start_offset = found_key.offset;
|
||||
item_size = btrfs_item_size_nr(path->nodes[0],
|
||||
path->slots[0]);
|
||||
item_last_offset = item_start_offset +
|
||||
(item_size / csum_size) *
|
||||
root->sectorsize;
|
||||
item = btrfs_item_ptr(path->nodes[0], path->slots[0],
|
||||
struct btrfs_csum_item);
|
||||
}
|
||||
/*
|
||||
* this byte range must be able to fit inside
|
||||
* a single leaf so it will also fit inside a u32
|
||||
*/
|
||||
diff = disk_bytenr - item_start_offset;
|
||||
diff = diff / root->sectorsize;
|
||||
diff = diff * csum_size;
|
||||
|
||||
read_extent_buffer(path->nodes[0], &sum,
|
||||
((unsigned long)item) + diff,
|
||||
csum_size);
|
||||
found:
|
||||
if (dst)
|
||||
*dst++ = sum;
|
||||
else
|
||||
set_state_private(io_tree, offset, sum);
|
||||
disk_bytenr += bvec->bv_len;
|
||||
bio_index++;
|
||||
bvec++;
|
||||
}
|
||||
btrfs_free_path(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btrfs_lookup_csums_range(struct btrfs_root *root, u64 start, u64 end,
|
||||
struct list_head *list)
|
||||
{
|
||||
struct btrfs_key key;
|
||||
struct btrfs_path *path;
|
||||
struct extent_buffer *leaf;
|
||||
struct btrfs_ordered_sum *sums;
|
||||
struct btrfs_sector_sum *sector_sum;
|
||||
struct btrfs_csum_item *item;
|
||||
unsigned long offset;
|
||||
int ret;
|
||||
size_t size;
|
||||
u64 csum_end;
|
||||
u16 csum_size = btrfs_super_csum_size(&root->fs_info->super_copy);
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
|
||||
key.objectid = BTRFS_EXTENT_CSUM_OBJECTID;
|
||||
key.offset = start;
|
||||
key.type = BTRFS_EXTENT_CSUM_KEY;
|
||||
|
||||
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
if (ret > 0 && path->slots[0] > 0) {
|
||||
leaf = path->nodes[0];
|
||||
btrfs_item_key_to_cpu(leaf, &key, path->slots[0] - 1);
|
||||
if (key.objectid == BTRFS_EXTENT_CSUM_OBJECTID &&
|
||||
key.type == BTRFS_EXTENT_CSUM_KEY) {
|
||||
offset = (start - key.offset) >>
|
||||
root->fs_info->sb->s_blocksize_bits;
|
||||
if (offset * csum_size <
|
||||
btrfs_item_size_nr(leaf, path->slots[0] - 1))
|
||||
path->slots[0]--;
|
||||
}
|
||||
}
|
||||
|
||||
while (start <= end) {
|
||||
leaf = path->nodes[0];
|
||||
if (path->slots[0] >= btrfs_header_nritems(leaf)) {
|
||||
ret = btrfs_next_leaf(root, path);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
if (ret > 0)
|
||||
break;
|
||||
leaf = path->nodes[0];
|
||||
}
|
||||
|
||||
btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
|
||||
if (key.objectid != BTRFS_EXTENT_CSUM_OBJECTID ||
|
||||
key.type != BTRFS_EXTENT_CSUM_KEY)
|
||||
break;
|
||||
|
||||
btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
|
||||
if (key.offset > end)
|
||||
break;
|
||||
|
||||
if (key.offset > start)
|
||||
start = key.offset;
|
||||
|
||||
size = btrfs_item_size_nr(leaf, path->slots[0]);
|
||||
csum_end = key.offset + (size / csum_size) * root->sectorsize;
|
||||
if (csum_end <= start) {
|
||||
path->slots[0]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
csum_end = min(csum_end, end + 1);
|
||||
item = btrfs_item_ptr(path->nodes[0], path->slots[0],
|
||||
struct btrfs_csum_item);
|
||||
while (start < csum_end) {
|
||||
size = min_t(size_t, csum_end - start,
|
||||
MAX_ORDERED_SUM_BYTES(root));
|
||||
sums = kzalloc(btrfs_ordered_sum_size(root, size),
|
||||
GFP_NOFS);
|
||||
BUG_ON(!sums);
|
||||
|
||||
sector_sum = sums->sums;
|
||||
sums->bytenr = start;
|
||||
sums->len = size;
|
||||
|
||||
offset = (start - key.offset) >>
|
||||
root->fs_info->sb->s_blocksize_bits;
|
||||
offset *= csum_size;
|
||||
|
||||
while (size > 0) {
|
||||
read_extent_buffer(path->nodes[0],
|
||||
§or_sum->sum,
|
||||
((unsigned long)item) +
|
||||
offset, csum_size);
|
||||
sector_sum->bytenr = start;
|
||||
|
||||
size -= root->sectorsize;
|
||||
start += root->sectorsize;
|
||||
offset += csum_size;
|
||||
sector_sum++;
|
||||
}
|
||||
list_add_tail(&sums->list, list);
|
||||
}
|
||||
path->slots[0]++;
|
||||
}
|
||||
ret = 0;
|
||||
fail:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_csum_one_bio(struct btrfs_root *root, struct inode *inode,
|
||||
struct bio *bio, u64 file_start, int contig)
|
||||
{
|
||||
struct btrfs_ordered_sum *sums;
|
||||
struct btrfs_sector_sum *sector_sum;
|
||||
struct btrfs_ordered_extent *ordered;
|
||||
char *data;
|
||||
struct bio_vec *bvec = bio->bi_io_vec;
|
||||
int bio_index = 0;
|
||||
unsigned long total_bytes = 0;
|
||||
unsigned long this_sum_bytes = 0;
|
||||
u64 offset;
|
||||
u64 disk_bytenr;
|
||||
|
||||
WARN_ON(bio->bi_vcnt <= 0);
|
||||
sums = kzalloc(btrfs_ordered_sum_size(root, bio->bi_size), GFP_NOFS);
|
||||
if (!sums)
|
||||
return -ENOMEM;
|
||||
|
||||
sector_sum = sums->sums;
|
||||
disk_bytenr = (u64)bio->bi_sector << 9;
|
||||
sums->len = bio->bi_size;
|
||||
INIT_LIST_HEAD(&sums->list);
|
||||
|
||||
if (contig)
|
||||
offset = file_start;
|
||||
else
|
||||
offset = page_offset(bvec->bv_page) + bvec->bv_offset;
|
||||
|
||||
ordered = btrfs_lookup_ordered_extent(inode, offset);
|
||||
BUG_ON(!ordered);
|
||||
sums->bytenr = ordered->start;
|
||||
|
||||
while (bio_index < bio->bi_vcnt) {
|
||||
if (!contig)
|
||||
offset = page_offset(bvec->bv_page) + bvec->bv_offset;
|
||||
|
||||
if (!contig && (offset >= ordered->file_offset + ordered->len ||
|
||||
offset < ordered->file_offset)) {
|
||||
unsigned long bytes_left;
|
||||
sums->len = this_sum_bytes;
|
||||
this_sum_bytes = 0;
|
||||
btrfs_add_ordered_sum(inode, ordered, sums);
|
||||
btrfs_put_ordered_extent(ordered);
|
||||
|
||||
bytes_left = bio->bi_size - total_bytes;
|
||||
|
||||
sums = kzalloc(btrfs_ordered_sum_size(root, bytes_left),
|
||||
GFP_NOFS);
|
||||
BUG_ON(!sums);
|
||||
sector_sum = sums->sums;
|
||||
sums->len = bytes_left;
|
||||
ordered = btrfs_lookup_ordered_extent(inode, offset);
|
||||
BUG_ON(!ordered);
|
||||
sums->bytenr = ordered->start;
|
||||
}
|
||||
|
||||
data = kmap_atomic(bvec->bv_page, KM_USER0);
|
||||
sector_sum->sum = ~(u32)0;
|
||||
sector_sum->sum = btrfs_csum_data(root,
|
||||
data + bvec->bv_offset,
|
||||
sector_sum->sum,
|
||||
bvec->bv_len);
|
||||
kunmap_atomic(data, KM_USER0);
|
||||
btrfs_csum_final(sector_sum->sum,
|
||||
(char *)§or_sum->sum);
|
||||
sector_sum->bytenr = disk_bytenr;
|
||||
|
||||
sector_sum++;
|
||||
bio_index++;
|
||||
total_bytes += bvec->bv_len;
|
||||
this_sum_bytes += bvec->bv_len;
|
||||
disk_bytenr += bvec->bv_len;
|
||||
offset += bvec->bv_len;
|
||||
bvec++;
|
||||
}
|
||||
this_sum_bytes = 0;
|
||||
btrfs_add_ordered_sum(inode, ordered, sums);
|
||||
btrfs_put_ordered_extent(ordered);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* helper function for csum removal, this expects the
|
||||
* key to describe the csum pointed to by the path, and it expects
|
||||
* the csum to overlap the range [bytenr, len]
|
||||
*
|
||||
* The csum should not be entirely contained in the range and the
|
||||
* range should not be entirely contained in the csum.
|
||||
*
|
||||
* This calls btrfs_truncate_item with the correct args based on the
|
||||
* overlap, and fixes up the key as required.
|
||||
*/
|
||||
static noinline int truncate_one_csum(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path,
|
||||
struct btrfs_key *key,
|
||||
u64 bytenr, u64 len)
|
||||
{
|
||||
struct extent_buffer *leaf;
|
||||
u16 csum_size =
|
||||
btrfs_super_csum_size(&root->fs_info->super_copy);
|
||||
u64 csum_end;
|
||||
u64 end_byte = bytenr + len;
|
||||
u32 blocksize_bits = root->fs_info->sb->s_blocksize_bits;
|
||||
int ret;
|
||||
|
||||
leaf = path->nodes[0];
|
||||
csum_end = btrfs_item_size_nr(leaf, path->slots[0]) / csum_size;
|
||||
csum_end <<= root->fs_info->sb->s_blocksize_bits;
|
||||
csum_end += key->offset;
|
||||
|
||||
if (key->offset < bytenr && csum_end <= end_byte) {
|
||||
/*
|
||||
* [ bytenr - len ]
|
||||
* [ ]
|
||||
* [csum ]
|
||||
* A simple truncate off the end of the item
|
||||
*/
|
||||
u32 new_size = (bytenr - key->offset) >> blocksize_bits;
|
||||
new_size *= csum_size;
|
||||
ret = btrfs_truncate_item(trans, root, path, new_size, 1);
|
||||
BUG_ON(ret);
|
||||
} else if (key->offset >= bytenr && csum_end > end_byte &&
|
||||
end_byte > key->offset) {
|
||||
/*
|
||||
* [ bytenr - len ]
|
||||
* [ ]
|
||||
* [csum ]
|
||||
* we need to truncate from the beginning of the csum
|
||||
*/
|
||||
u32 new_size = (csum_end - end_byte) >> blocksize_bits;
|
||||
new_size *= csum_size;
|
||||
|
||||
ret = btrfs_truncate_item(trans, root, path, new_size, 0);
|
||||
BUG_ON(ret);
|
||||
|
||||
key->offset = end_byte;
|
||||
ret = btrfs_set_item_key_safe(trans, root, path, key);
|
||||
BUG_ON(ret);
|
||||
} else {
|
||||
BUG();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* deletes the csum items from the csum tree for a given
|
||||
* range of bytes.
|
||||
*/
|
||||
int btrfs_del_csums(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, u64 bytenr, u64 len)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_key key;
|
||||
u64 end_byte = bytenr + len;
|
||||
u64 csum_end;
|
||||
struct extent_buffer *leaf;
|
||||
int ret;
|
||||
u16 csum_size =
|
||||
btrfs_super_csum_size(&root->fs_info->super_copy);
|
||||
int blocksize_bits = root->fs_info->sb->s_blocksize_bits;
|
||||
|
||||
root = root->fs_info->csum_root;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
|
||||
while (1) {
|
||||
key.objectid = BTRFS_EXTENT_CSUM_OBJECTID;
|
||||
key.offset = end_byte - 1;
|
||||
key.type = BTRFS_EXTENT_CSUM_KEY;
|
||||
|
||||
ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
|
||||
if (ret > 0) {
|
||||
if (path->slots[0] == 0)
|
||||
goto out;
|
||||
path->slots[0]--;
|
||||
}
|
||||
leaf = path->nodes[0];
|
||||
btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
|
||||
|
||||
if (key.objectid != BTRFS_EXTENT_CSUM_OBJECTID ||
|
||||
key.type != BTRFS_EXTENT_CSUM_KEY) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (key.offset >= end_byte)
|
||||
break;
|
||||
|
||||
csum_end = btrfs_item_size_nr(leaf, path->slots[0]) / csum_size;
|
||||
csum_end <<= blocksize_bits;
|
||||
csum_end += key.offset;
|
||||
|
||||
/* this csum ends before we start, we're done */
|
||||
if (csum_end <= bytenr)
|
||||
break;
|
||||
|
||||
/* delete the entire item, it is inside our range */
|
||||
if (key.offset >= bytenr && csum_end <= end_byte) {
|
||||
ret = btrfs_del_item(trans, root, path);
|
||||
BUG_ON(ret);
|
||||
if (key.offset == bytenr)
|
||||
break;
|
||||
} else if (key.offset < bytenr && csum_end > end_byte) {
|
||||
unsigned long offset;
|
||||
unsigned long shift_len;
|
||||
unsigned long item_offset;
|
||||
/*
|
||||
* [ bytenr - len ]
|
||||
* [csum ]
|
||||
*
|
||||
* Our bytes are in the middle of the csum,
|
||||
* we need to split this item and insert a new one.
|
||||
*
|
||||
* But we can't drop the path because the
|
||||
* csum could change, get removed, extended etc.
|
||||
*
|
||||
* The trick here is the max size of a csum item leaves
|
||||
* enough room in the tree block for a single
|
||||
* item header. So, we split the item in place,
|
||||
* adding a new header pointing to the existing
|
||||
* bytes. Then we loop around again and we have
|
||||
* a nicely formed csum item that we can neatly
|
||||
* truncate.
|
||||
*/
|
||||
offset = (bytenr - key.offset) >> blocksize_bits;
|
||||
offset *= csum_size;
|
||||
|
||||
shift_len = (len >> blocksize_bits) * csum_size;
|
||||
|
||||
item_offset = btrfs_item_ptr_offset(leaf,
|
||||
path->slots[0]);
|
||||
|
||||
memset_extent_buffer(leaf, 0, item_offset + offset,
|
||||
shift_len);
|
||||
key.offset = bytenr;
|
||||
|
||||
/*
|
||||
* btrfs_split_item returns -EAGAIN when the
|
||||
* item changed size or key
|
||||
*/
|
||||
ret = btrfs_split_item(trans, root, path, &key, offset);
|
||||
BUG_ON(ret && ret != -EAGAIN);
|
||||
|
||||
key.offset = end_byte - 1;
|
||||
} else {
|
||||
ret = truncate_one_csum(trans, root, path,
|
||||
&key, bytenr, len);
|
||||
BUG_ON(ret);
|
||||
if (key.offset < bytenr)
|
||||
break;
|
||||
}
|
||||
btrfs_release_path(root, path);
|
||||
}
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btrfs_csum_file_blocks(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_ordered_sum *sums)
|
||||
{
|
||||
u64 bytenr;
|
||||
int ret;
|
||||
struct btrfs_key file_key;
|
||||
struct btrfs_key found_key;
|
||||
u64 next_offset;
|
||||
u64 total_bytes = 0;
|
||||
int found_next;
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_csum_item *item;
|
||||
struct btrfs_csum_item *item_end;
|
||||
struct extent_buffer *leaf = NULL;
|
||||
u64 csum_offset;
|
||||
struct btrfs_sector_sum *sector_sum;
|
||||
u32 nritems;
|
||||
u32 ins_size;
|
||||
char *eb_map;
|
||||
char *eb_token;
|
||||
unsigned long map_len;
|
||||
unsigned long map_start;
|
||||
u16 csum_size =
|
||||
btrfs_super_csum_size(&root->fs_info->super_copy);
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
sector_sum = sums->sums;
|
||||
again:
|
||||
next_offset = (u64)-1;
|
||||
found_next = 0;
|
||||
file_key.objectid = BTRFS_EXTENT_CSUM_OBJECTID;
|
||||
file_key.offset = sector_sum->bytenr;
|
||||
bytenr = sector_sum->bytenr;
|
||||
btrfs_set_key_type(&file_key, BTRFS_EXTENT_CSUM_KEY);
|
||||
|
||||
item = btrfs_lookup_csum(trans, root, path, sector_sum->bytenr, 1);
|
||||
if (!IS_ERR(item)) {
|
||||
leaf = path->nodes[0];
|
||||
ret = 0;
|
||||
goto found;
|
||||
}
|
||||
ret = PTR_ERR(item);
|
||||
if (ret == -EFBIG) {
|
||||
u32 item_size;
|
||||
/* we found one, but it isn't big enough yet */
|
||||
leaf = path->nodes[0];
|
||||
item_size = btrfs_item_size_nr(leaf, path->slots[0]);
|
||||
if ((item_size / csum_size) >=
|
||||
MAX_CSUM_ITEMS(root, csum_size)) {
|
||||
/* already at max size, make a new one */
|
||||
goto insert;
|
||||
}
|
||||
} else {
|
||||
int slot = path->slots[0] + 1;
|
||||
/* we didn't find a csum item, insert one */
|
||||
nritems = btrfs_header_nritems(path->nodes[0]);
|
||||
if (path->slots[0] >= nritems - 1) {
|
||||
ret = btrfs_next_leaf(root, path);
|
||||
if (ret == 1)
|
||||
found_next = 1;
|
||||
if (ret != 0)
|
||||
goto insert;
|
||||
slot = 0;
|
||||
}
|
||||
btrfs_item_key_to_cpu(path->nodes[0], &found_key, slot);
|
||||
if (found_key.objectid != BTRFS_EXTENT_CSUM_OBJECTID ||
|
||||
found_key.type != BTRFS_EXTENT_CSUM_KEY) {
|
||||
found_next = 1;
|
||||
goto insert;
|
||||
}
|
||||
next_offset = found_key.offset;
|
||||
found_next = 1;
|
||||
goto insert;
|
||||
}
|
||||
|
||||
/*
|
||||
* at this point, we know the tree has an item, but it isn't big
|
||||
* enough yet to put our csum in. Grow it
|
||||
*/
|
||||
btrfs_release_path(root, path);
|
||||
ret = btrfs_search_slot(trans, root, &file_key, path,
|
||||
csum_size, 1);
|
||||
if (ret < 0)
|
||||
goto fail_unlock;
|
||||
|
||||
if (ret > 0) {
|
||||
if (path->slots[0] == 0)
|
||||
goto insert;
|
||||
path->slots[0]--;
|
||||
}
|
||||
|
||||
leaf = path->nodes[0];
|
||||
btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
|
||||
csum_offset = (bytenr - found_key.offset) >>
|
||||
root->fs_info->sb->s_blocksize_bits;
|
||||
|
||||
if (btrfs_key_type(&found_key) != BTRFS_EXTENT_CSUM_KEY ||
|
||||
found_key.objectid != BTRFS_EXTENT_CSUM_OBJECTID ||
|
||||
csum_offset >= MAX_CSUM_ITEMS(root, csum_size)) {
|
||||
goto insert;
|
||||
}
|
||||
|
||||
if (csum_offset >= btrfs_item_size_nr(leaf, path->slots[0]) /
|
||||
csum_size) {
|
||||
u32 diff = (csum_offset + 1) * csum_size;
|
||||
|
||||
/*
|
||||
* is the item big enough already? we dropped our lock
|
||||
* before and need to recheck
|
||||
*/
|
||||
if (diff < btrfs_item_size_nr(leaf, path->slots[0]))
|
||||
goto csum;
|
||||
|
||||
diff = diff - btrfs_item_size_nr(leaf, path->slots[0]);
|
||||
if (diff != csum_size)
|
||||
goto insert;
|
||||
|
||||
ret = btrfs_extend_item(trans, root, path, diff);
|
||||
BUG_ON(ret);
|
||||
goto csum;
|
||||
}
|
||||
|
||||
insert:
|
||||
btrfs_release_path(root, path);
|
||||
csum_offset = 0;
|
||||
if (found_next) {
|
||||
u64 tmp = total_bytes + root->sectorsize;
|
||||
u64 next_sector = sector_sum->bytenr;
|
||||
struct btrfs_sector_sum *next = sector_sum + 1;
|
||||
|
||||
while (tmp < sums->len) {
|
||||
if (next_sector + root->sectorsize != next->bytenr)
|
||||
break;
|
||||
tmp += root->sectorsize;
|
||||
next_sector = next->bytenr;
|
||||
next++;
|
||||
}
|
||||
tmp = min(tmp, next_offset - file_key.offset);
|
||||
tmp >>= root->fs_info->sb->s_blocksize_bits;
|
||||
tmp = max((u64)1, tmp);
|
||||
tmp = min(tmp, (u64)MAX_CSUM_ITEMS(root, csum_size));
|
||||
ins_size = csum_size * tmp;
|
||||
} else {
|
||||
ins_size = csum_size;
|
||||
}
|
||||
ret = btrfs_insert_empty_item(trans, root, path, &file_key,
|
||||
ins_size);
|
||||
if (ret < 0)
|
||||
goto fail_unlock;
|
||||
if (ret != 0) {
|
||||
WARN_ON(1);
|
||||
goto fail_unlock;
|
||||
}
|
||||
csum:
|
||||
leaf = path->nodes[0];
|
||||
item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_csum_item);
|
||||
ret = 0;
|
||||
item = (struct btrfs_csum_item *)((unsigned char *)item +
|
||||
csum_offset * csum_size);
|
||||
found:
|
||||
item_end = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_csum_item);
|
||||
item_end = (struct btrfs_csum_item *)((unsigned char *)item_end +
|
||||
btrfs_item_size_nr(leaf, path->slots[0]));
|
||||
eb_token = NULL;
|
||||
cond_resched();
|
||||
next_sector:
|
||||
|
||||
if (!eb_token ||
|
||||
(unsigned long)item + csum_size >= map_start + map_len) {
|
||||
int err;
|
||||
|
||||
if (eb_token)
|
||||
unmap_extent_buffer(leaf, eb_token, KM_USER1);
|
||||
eb_token = NULL;
|
||||
err = map_private_extent_buffer(leaf, (unsigned long)item,
|
||||
csum_size,
|
||||
&eb_token, &eb_map,
|
||||
&map_start, &map_len, KM_USER1);
|
||||
if (err)
|
||||
eb_token = NULL;
|
||||
}
|
||||
if (eb_token) {
|
||||
memcpy(eb_token + ((unsigned long)item & (PAGE_CACHE_SIZE - 1)),
|
||||
§or_sum->sum, csum_size);
|
||||
} else {
|
||||
write_extent_buffer(leaf, §or_sum->sum,
|
||||
(unsigned long)item, csum_size);
|
||||
}
|
||||
|
||||
total_bytes += root->sectorsize;
|
||||
sector_sum++;
|
||||
if (total_bytes < sums->len) {
|
||||
item = (struct btrfs_csum_item *)((char *)item +
|
||||
csum_size);
|
||||
if (item < item_end && bytenr + PAGE_CACHE_SIZE ==
|
||||
sector_sum->bytenr) {
|
||||
bytenr = sector_sum->bytenr;
|
||||
goto next_sector;
|
||||
}
|
||||
}
|
||||
if (eb_token) {
|
||||
unmap_extent_buffer(leaf, eb_token, KM_USER1);
|
||||
eb_token = NULL;
|
||||
}
|
||||
btrfs_mark_buffer_dirty(path->nodes[0]);
|
||||
cond_resched();
|
||||
if (total_bytes < sums->len) {
|
||||
btrfs_release_path(root, path);
|
||||
goto again;
|
||||
}
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
|
||||
fail_unlock:
|
||||
goto out;
|
||||
}
|
1288
fs/btrfs/file.c
Normal file
1288
fs/btrfs/file.c
Normal file
File diff suppressed because it is too large
Load Diff
495
fs/btrfs/free-space-cache.c
Normal file
495
fs/btrfs/free-space-cache.c
Normal file
@ -0,0 +1,495 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Red Hat. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include "ctree.h"
|
||||
|
||||
static int tree_insert_offset(struct rb_root *root, u64 offset,
|
||||
struct rb_node *node)
|
||||
{
|
||||
struct rb_node **p = &root->rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
struct btrfs_free_space *info;
|
||||
|
||||
while (*p) {
|
||||
parent = *p;
|
||||
info = rb_entry(parent, struct btrfs_free_space, offset_index);
|
||||
|
||||
if (offset < info->offset)
|
||||
p = &(*p)->rb_left;
|
||||
else if (offset > info->offset)
|
||||
p = &(*p)->rb_right;
|
||||
else
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
rb_link_node(node, parent, p);
|
||||
rb_insert_color(node, root);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tree_insert_bytes(struct rb_root *root, u64 bytes,
|
||||
struct rb_node *node)
|
||||
{
|
||||
struct rb_node **p = &root->rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
struct btrfs_free_space *info;
|
||||
|
||||
while (*p) {
|
||||
parent = *p;
|
||||
info = rb_entry(parent, struct btrfs_free_space, bytes_index);
|
||||
|
||||
if (bytes < info->bytes)
|
||||
p = &(*p)->rb_left;
|
||||
else
|
||||
p = &(*p)->rb_right;
|
||||
}
|
||||
|
||||
rb_link_node(node, parent, p);
|
||||
rb_insert_color(node, root);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* searches the tree for the given offset. If contains is set we will return
|
||||
* the free space that contains the given offset. If contains is not set we
|
||||
* will return the free space that starts at or after the given offset and is
|
||||
* at least bytes long.
|
||||
*/
|
||||
static struct btrfs_free_space *tree_search_offset(struct rb_root *root,
|
||||
u64 offset, u64 bytes,
|
||||
int contains)
|
||||
{
|
||||
struct rb_node *n = root->rb_node;
|
||||
struct btrfs_free_space *entry, *ret = NULL;
|
||||
|
||||
while (n) {
|
||||
entry = rb_entry(n, struct btrfs_free_space, offset_index);
|
||||
|
||||
if (offset < entry->offset) {
|
||||
if (!contains &&
|
||||
(!ret || entry->offset < ret->offset) &&
|
||||
(bytes <= entry->bytes))
|
||||
ret = entry;
|
||||
n = n->rb_left;
|
||||
} else if (offset > entry->offset) {
|
||||
if ((entry->offset + entry->bytes - 1) >= offset &&
|
||||
bytes <= entry->bytes) {
|
||||
ret = entry;
|
||||
break;
|
||||
}
|
||||
n = n->rb_right;
|
||||
} else {
|
||||
if (bytes > entry->bytes) {
|
||||
n = n->rb_right;
|
||||
continue;
|
||||
}
|
||||
ret = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* return a chunk at least bytes size, as close to offset that we can get.
|
||||
*/
|
||||
static struct btrfs_free_space *tree_search_bytes(struct rb_root *root,
|
||||
u64 offset, u64 bytes)
|
||||
{
|
||||
struct rb_node *n = root->rb_node;
|
||||
struct btrfs_free_space *entry, *ret = NULL;
|
||||
|
||||
while (n) {
|
||||
entry = rb_entry(n, struct btrfs_free_space, bytes_index);
|
||||
|
||||
if (bytes < entry->bytes) {
|
||||
/*
|
||||
* We prefer to get a hole size as close to the size we
|
||||
* are asking for so we don't take small slivers out of
|
||||
* huge holes, but we also want to get as close to the
|
||||
* offset as possible so we don't have a whole lot of
|
||||
* fragmentation.
|
||||
*/
|
||||
if (offset <= entry->offset) {
|
||||
if (!ret)
|
||||
ret = entry;
|
||||
else if (entry->bytes < ret->bytes)
|
||||
ret = entry;
|
||||
else if (entry->offset < ret->offset)
|
||||
ret = entry;
|
||||
}
|
||||
n = n->rb_left;
|
||||
} else if (bytes > entry->bytes) {
|
||||
n = n->rb_right;
|
||||
} else {
|
||||
/*
|
||||
* Ok we may have multiple chunks of the wanted size,
|
||||
* so we don't want to take the first one we find, we
|
||||
* want to take the one closest to our given offset, so
|
||||
* keep searching just in case theres a better match.
|
||||
*/
|
||||
n = n->rb_right;
|
||||
if (offset > entry->offset)
|
||||
continue;
|
||||
else if (!ret || entry->offset < ret->offset)
|
||||
ret = entry;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void unlink_free_space(struct btrfs_block_group_cache *block_group,
|
||||
struct btrfs_free_space *info)
|
||||
{
|
||||
rb_erase(&info->offset_index, &block_group->free_space_offset);
|
||||
rb_erase(&info->bytes_index, &block_group->free_space_bytes);
|
||||
}
|
||||
|
||||
static int link_free_space(struct btrfs_block_group_cache *block_group,
|
||||
struct btrfs_free_space *info)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
|
||||
ret = tree_insert_offset(&block_group->free_space_offset, info->offset,
|
||||
&info->offset_index);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = tree_insert_bytes(&block_group->free_space_bytes, info->bytes,
|
||||
&info->bytes_index);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __btrfs_add_free_space(struct btrfs_block_group_cache *block_group,
|
||||
u64 offset, u64 bytes)
|
||||
{
|
||||
struct btrfs_free_space *right_info;
|
||||
struct btrfs_free_space *left_info;
|
||||
struct btrfs_free_space *info = NULL;
|
||||
struct btrfs_free_space *alloc_info;
|
||||
int ret = 0;
|
||||
|
||||
alloc_info = kzalloc(sizeof(struct btrfs_free_space), GFP_NOFS);
|
||||
if (!alloc_info)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* first we want to see if there is free space adjacent to the range we
|
||||
* are adding, if there is remove that struct and add a new one to
|
||||
* cover the entire range
|
||||
*/
|
||||
right_info = tree_search_offset(&block_group->free_space_offset,
|
||||
offset+bytes, 0, 1);
|
||||
left_info = tree_search_offset(&block_group->free_space_offset,
|
||||
offset-1, 0, 1);
|
||||
|
||||
if (right_info && right_info->offset == offset+bytes) {
|
||||
unlink_free_space(block_group, right_info);
|
||||
info = right_info;
|
||||
info->offset = offset;
|
||||
info->bytes += bytes;
|
||||
} else if (right_info && right_info->offset != offset+bytes) {
|
||||
printk(KERN_ERR "btrfs adding space in the middle of an "
|
||||
"existing free space area. existing: "
|
||||
"offset=%llu, bytes=%llu. new: offset=%llu, "
|
||||
"bytes=%llu\n", (unsigned long long)right_info->offset,
|
||||
(unsigned long long)right_info->bytes,
|
||||
(unsigned long long)offset,
|
||||
(unsigned long long)bytes);
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (left_info) {
|
||||
unlink_free_space(block_group, left_info);
|
||||
|
||||
if (unlikely((left_info->offset + left_info->bytes) !=
|
||||
offset)) {
|
||||
printk(KERN_ERR "btrfs free space to the left "
|
||||
"of new free space isn't "
|
||||
"quite right. existing: offset=%llu, "
|
||||
"bytes=%llu. new: offset=%llu, bytes=%llu\n",
|
||||
(unsigned long long)left_info->offset,
|
||||
(unsigned long long)left_info->bytes,
|
||||
(unsigned long long)offset,
|
||||
(unsigned long long)bytes);
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (info) {
|
||||
info->offset = left_info->offset;
|
||||
info->bytes += left_info->bytes;
|
||||
kfree(left_info);
|
||||
} else {
|
||||
info = left_info;
|
||||
info->bytes += bytes;
|
||||
}
|
||||
}
|
||||
|
||||
if (info) {
|
||||
ret = link_free_space(block_group, info);
|
||||
if (!ret)
|
||||
info = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
info = alloc_info;
|
||||
alloc_info = NULL;
|
||||
info->offset = offset;
|
||||
info->bytes = bytes;
|
||||
|
||||
ret = link_free_space(block_group, info);
|
||||
if (ret)
|
||||
kfree(info);
|
||||
out:
|
||||
if (ret) {
|
||||
printk(KERN_ERR "btrfs: unable to add free space :%d\n", ret);
|
||||
if (ret == -EEXIST)
|
||||
BUG();
|
||||
}
|
||||
|
||||
kfree(alloc_info);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
__btrfs_remove_free_space(struct btrfs_block_group_cache *block_group,
|
||||
u64 offset, u64 bytes)
|
||||
{
|
||||
struct btrfs_free_space *info;
|
||||
int ret = 0;
|
||||
|
||||
info = tree_search_offset(&block_group->free_space_offset, offset, 0,
|
||||
1);
|
||||
|
||||
if (info && info->offset == offset) {
|
||||
if (info->bytes < bytes) {
|
||||
printk(KERN_ERR "Found free space at %llu, size %llu,"
|
||||
"trying to use %llu\n",
|
||||
(unsigned long long)info->offset,
|
||||
(unsigned long long)info->bytes,
|
||||
(unsigned long long)bytes);
|
||||
WARN_ON(1);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
unlink_free_space(block_group, info);
|
||||
|
||||
if (info->bytes == bytes) {
|
||||
kfree(info);
|
||||
goto out;
|
||||
}
|
||||
|
||||
info->offset += bytes;
|
||||
info->bytes -= bytes;
|
||||
|
||||
ret = link_free_space(block_group, info);
|
||||
BUG_ON(ret);
|
||||
} else if (info && info->offset < offset &&
|
||||
info->offset + info->bytes >= offset + bytes) {
|
||||
u64 old_start = info->offset;
|
||||
/*
|
||||
* we're freeing space in the middle of the info,
|
||||
* this can happen during tree log replay
|
||||
*
|
||||
* first unlink the old info and then
|
||||
* insert it again after the hole we're creating
|
||||
*/
|
||||
unlink_free_space(block_group, info);
|
||||
if (offset + bytes < info->offset + info->bytes) {
|
||||
u64 old_end = info->offset + info->bytes;
|
||||
|
||||
info->offset = offset + bytes;
|
||||
info->bytes = old_end - info->offset;
|
||||
ret = link_free_space(block_group, info);
|
||||
BUG_ON(ret);
|
||||
} else {
|
||||
/* the hole we're creating ends at the end
|
||||
* of the info struct, just free the info
|
||||
*/
|
||||
kfree(info);
|
||||
}
|
||||
|
||||
/* step two, insert a new info struct to cover anything
|
||||
* before the hole
|
||||
*/
|
||||
ret = __btrfs_add_free_space(block_group, old_start,
|
||||
offset - old_start);
|
||||
BUG_ON(ret);
|
||||
} else {
|
||||
WARN_ON(1);
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_add_free_space(struct btrfs_block_group_cache *block_group,
|
||||
u64 offset, u64 bytes)
|
||||
{
|
||||
int ret;
|
||||
struct btrfs_free_space *sp;
|
||||
|
||||
mutex_lock(&block_group->alloc_mutex);
|
||||
ret = __btrfs_add_free_space(block_group, offset, bytes);
|
||||
sp = tree_search_offset(&block_group->free_space_offset, offset, 0, 1);
|
||||
BUG_ON(!sp);
|
||||
mutex_unlock(&block_group->alloc_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_add_free_space_lock(struct btrfs_block_group_cache *block_group,
|
||||
u64 offset, u64 bytes)
|
||||
{
|
||||
int ret;
|
||||
struct btrfs_free_space *sp;
|
||||
|
||||
ret = __btrfs_add_free_space(block_group, offset, bytes);
|
||||
sp = tree_search_offset(&block_group->free_space_offset, offset, 0, 1);
|
||||
BUG_ON(!sp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_remove_free_space(struct btrfs_block_group_cache *block_group,
|
||||
u64 offset, u64 bytes)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&block_group->alloc_mutex);
|
||||
ret = __btrfs_remove_free_space(block_group, offset, bytes);
|
||||
mutex_unlock(&block_group->alloc_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_remove_free_space_lock(struct btrfs_block_group_cache *block_group,
|
||||
u64 offset, u64 bytes)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = __btrfs_remove_free_space(block_group, offset, bytes);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void btrfs_dump_free_space(struct btrfs_block_group_cache *block_group,
|
||||
u64 bytes)
|
||||
{
|
||||
struct btrfs_free_space *info;
|
||||
struct rb_node *n;
|
||||
int count = 0;
|
||||
|
||||
for (n = rb_first(&block_group->free_space_offset); n; n = rb_next(n)) {
|
||||
info = rb_entry(n, struct btrfs_free_space, offset_index);
|
||||
if (info->bytes >= bytes)
|
||||
count++;
|
||||
}
|
||||
printk(KERN_INFO "%d blocks of free space at or bigger than bytes is"
|
||||
"\n", count);
|
||||
}
|
||||
|
||||
u64 btrfs_block_group_free_space(struct btrfs_block_group_cache *block_group)
|
||||
{
|
||||
struct btrfs_free_space *info;
|
||||
struct rb_node *n;
|
||||
u64 ret = 0;
|
||||
|
||||
for (n = rb_first(&block_group->free_space_offset); n;
|
||||
n = rb_next(n)) {
|
||||
info = rb_entry(n, struct btrfs_free_space, offset_index);
|
||||
ret += info->bytes;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void btrfs_remove_free_space_cache(struct btrfs_block_group_cache *block_group)
|
||||
{
|
||||
struct btrfs_free_space *info;
|
||||
struct rb_node *node;
|
||||
|
||||
mutex_lock(&block_group->alloc_mutex);
|
||||
while ((node = rb_last(&block_group->free_space_bytes)) != NULL) {
|
||||
info = rb_entry(node, struct btrfs_free_space, bytes_index);
|
||||
unlink_free_space(block_group, info);
|
||||
kfree(info);
|
||||
if (need_resched()) {
|
||||
mutex_unlock(&block_group->alloc_mutex);
|
||||
cond_resched();
|
||||
mutex_lock(&block_group->alloc_mutex);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&block_group->alloc_mutex);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static struct btrfs_free_space *btrfs_find_free_space_offset(struct
|
||||
btrfs_block_group_cache
|
||||
*block_group, u64 offset,
|
||||
u64 bytes)
|
||||
{
|
||||
struct btrfs_free_space *ret;
|
||||
|
||||
mutex_lock(&block_group->alloc_mutex);
|
||||
ret = tree_search_offset(&block_group->free_space_offset, offset,
|
||||
bytes, 0);
|
||||
mutex_unlock(&block_group->alloc_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct btrfs_free_space *btrfs_find_free_space_bytes(struct
|
||||
btrfs_block_group_cache
|
||||
*block_group, u64 offset,
|
||||
u64 bytes)
|
||||
{
|
||||
struct btrfs_free_space *ret;
|
||||
|
||||
mutex_lock(&block_group->alloc_mutex);
|
||||
|
||||
ret = tree_search_bytes(&block_group->free_space_bytes, offset, bytes);
|
||||
mutex_unlock(&block_group->alloc_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct btrfs_free_space *btrfs_find_free_space(struct btrfs_block_group_cache
|
||||
*block_group, u64 offset,
|
||||
u64 bytes)
|
||||
{
|
||||
struct btrfs_free_space *ret = NULL;
|
||||
|
||||
ret = tree_search_offset(&block_group->free_space_offset, offset,
|
||||
bytes, 0);
|
||||
if (!ret)
|
||||
ret = tree_search_bytes(&block_group->free_space_bytes,
|
||||
offset, bytes);
|
||||
|
||||
return ret;
|
||||
}
|
27
fs/btrfs/hash.h
Normal file
27
fs/btrfs/hash.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __HASH__
|
||||
#define __HASH__
|
||||
|
||||
#include "crc32c.h"
|
||||
static inline u64 btrfs_name_hash(const char *name, int len)
|
||||
{
|
||||
return btrfs_crc32c((u32)~1, name, len);
|
||||
}
|
||||
#endif
|
206
fs/btrfs/inode-item.c
Normal file
206
fs/btrfs/inode-item.c
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "transaction.h"
|
||||
|
||||
static int find_name_in_backref(struct btrfs_path *path, const char *name,
|
||||
int name_len, struct btrfs_inode_ref **ref_ret)
|
||||
{
|
||||
struct extent_buffer *leaf;
|
||||
struct btrfs_inode_ref *ref;
|
||||
unsigned long ptr;
|
||||
unsigned long name_ptr;
|
||||
u32 item_size;
|
||||
u32 cur_offset = 0;
|
||||
int len;
|
||||
|
||||
leaf = path->nodes[0];
|
||||
item_size = btrfs_item_size_nr(leaf, path->slots[0]);
|
||||
ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
|
||||
while (cur_offset < item_size) {
|
||||
ref = (struct btrfs_inode_ref *)(ptr + cur_offset);
|
||||
len = btrfs_inode_ref_name_len(leaf, ref);
|
||||
name_ptr = (unsigned long)(ref + 1);
|
||||
cur_offset += len + sizeof(*ref);
|
||||
if (len != name_len)
|
||||
continue;
|
||||
if (memcmp_extent_buffer(leaf, name, name_ptr, name_len) == 0) {
|
||||
*ref_ret = ref;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btrfs_del_inode_ref(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
const char *name, int name_len,
|
||||
u64 inode_objectid, u64 ref_objectid, u64 *index)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_key key;
|
||||
struct btrfs_inode_ref *ref;
|
||||
struct extent_buffer *leaf;
|
||||
unsigned long ptr;
|
||||
unsigned long item_start;
|
||||
u32 item_size;
|
||||
u32 sub_item_len;
|
||||
int ret;
|
||||
int del_len = name_len + sizeof(*ref);
|
||||
|
||||
key.objectid = inode_objectid;
|
||||
key.offset = ref_objectid;
|
||||
btrfs_set_key_type(&key, BTRFS_INODE_REF_KEY);
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
|
||||
if (ret > 0) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
} else if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
if (!find_name_in_backref(path, name, name_len, &ref)) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
leaf = path->nodes[0];
|
||||
item_size = btrfs_item_size_nr(leaf, path->slots[0]);
|
||||
|
||||
if (index)
|
||||
*index = btrfs_inode_ref_index(leaf, ref);
|
||||
|
||||
if (del_len == item_size) {
|
||||
ret = btrfs_del_item(trans, root, path);
|
||||
goto out;
|
||||
}
|
||||
ptr = (unsigned long)ref;
|
||||
sub_item_len = name_len + sizeof(*ref);
|
||||
item_start = btrfs_item_ptr_offset(leaf, path->slots[0]);
|
||||
memmove_extent_buffer(leaf, ptr, ptr + sub_item_len,
|
||||
item_size - (ptr + sub_item_len - item_start));
|
||||
ret = btrfs_truncate_item(trans, root, path,
|
||||
item_size - sub_item_len, 1);
|
||||
BUG_ON(ret);
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_insert_inode_ref(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
const char *name, int name_len,
|
||||
u64 inode_objectid, u64 ref_objectid, u64 index)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_key key;
|
||||
struct btrfs_inode_ref *ref;
|
||||
unsigned long ptr;
|
||||
int ret;
|
||||
int ins_len = name_len + sizeof(*ref);
|
||||
|
||||
key.objectid = inode_objectid;
|
||||
key.offset = ref_objectid;
|
||||
btrfs_set_key_type(&key, BTRFS_INODE_REF_KEY);
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = btrfs_insert_empty_item(trans, root, path, &key,
|
||||
ins_len);
|
||||
if (ret == -EEXIST) {
|
||||
u32 old_size;
|
||||
|
||||
if (find_name_in_backref(path, name, name_len, &ref))
|
||||
goto out;
|
||||
|
||||
old_size = btrfs_item_size_nr(path->nodes[0], path->slots[0]);
|
||||
ret = btrfs_extend_item(trans, root, path, ins_len);
|
||||
BUG_ON(ret);
|
||||
ref = btrfs_item_ptr(path->nodes[0], path->slots[0],
|
||||
struct btrfs_inode_ref);
|
||||
ref = (struct btrfs_inode_ref *)((unsigned long)ref + old_size);
|
||||
btrfs_set_inode_ref_name_len(path->nodes[0], ref, name_len);
|
||||
btrfs_set_inode_ref_index(path->nodes[0], ref, index);
|
||||
ptr = (unsigned long)(ref + 1);
|
||||
ret = 0;
|
||||
} else if (ret < 0) {
|
||||
goto out;
|
||||
} else {
|
||||
ref = btrfs_item_ptr(path->nodes[0], path->slots[0],
|
||||
struct btrfs_inode_ref);
|
||||
btrfs_set_inode_ref_name_len(path->nodes[0], ref, name_len);
|
||||
btrfs_set_inode_ref_index(path->nodes[0], ref, index);
|
||||
ptr = (unsigned long)(ref + 1);
|
||||
}
|
||||
write_extent_buffer(path->nodes[0], name, ptr, name_len);
|
||||
btrfs_mark_buffer_dirty(path->nodes[0]);
|
||||
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_insert_empty_inode(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_path *path, u64 objectid)
|
||||
{
|
||||
struct btrfs_key key;
|
||||
int ret;
|
||||
key.objectid = objectid;
|
||||
btrfs_set_key_type(&key, BTRFS_INODE_ITEM_KEY);
|
||||
key.offset = 0;
|
||||
|
||||
ret = btrfs_insert_empty_item(trans, root, path, &key,
|
||||
sizeof(struct btrfs_inode_item));
|
||||
if (ret == 0 && objectid > root->highest_inode)
|
||||
root->highest_inode = objectid;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_lookup_inode(struct btrfs_trans_handle *trans, struct btrfs_root
|
||||
*root, struct btrfs_path *path,
|
||||
struct btrfs_key *location, int mod)
|
||||
{
|
||||
int ins_len = mod < 0 ? -1 : 0;
|
||||
int cow = mod != 0;
|
||||
int ret;
|
||||
int slot;
|
||||
struct extent_buffer *leaf;
|
||||
struct btrfs_key found_key;
|
||||
|
||||
ret = btrfs_search_slot(trans, root, location, path, ins_len, cow);
|
||||
if (ret > 0 && btrfs_key_type(location) == BTRFS_ROOT_ITEM_KEY &&
|
||||
location->offset == (u64)-1 && path->slots[0] != 0) {
|
||||
slot = path->slots[0] - 1;
|
||||
leaf = path->nodes[0];
|
||||
btrfs_item_key_to_cpu(leaf, &found_key, slot);
|
||||
if (found_key.objectid == location->objectid &&
|
||||
btrfs_key_type(&found_key) == btrfs_key_type(location)) {
|
||||
path->slots[0]--;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
144
fs/btrfs/inode-map.c
Normal file
144
fs/btrfs/inode-map.c
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "transaction.h"
|
||||
|
||||
int btrfs_find_highest_inode(struct btrfs_root *root, u64 *objectid)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
int ret;
|
||||
struct extent_buffer *l;
|
||||
struct btrfs_key search_key;
|
||||
struct btrfs_key found_key;
|
||||
int slot;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
|
||||
search_key.objectid = BTRFS_LAST_FREE_OBJECTID;
|
||||
search_key.type = -1;
|
||||
search_key.offset = (u64)-1;
|
||||
ret = btrfs_search_slot(NULL, root, &search_key, path, 0, 0);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
BUG_ON(ret == 0);
|
||||
if (path->slots[0] > 0) {
|
||||
slot = path->slots[0] - 1;
|
||||
l = path->nodes[0];
|
||||
btrfs_item_key_to_cpu(l, &found_key, slot);
|
||||
*objectid = found_key.objectid;
|
||||
} else {
|
||||
*objectid = BTRFS_FIRST_FREE_OBJECTID;
|
||||
}
|
||||
ret = 0;
|
||||
error:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* walks the btree of allocated inodes and find a hole.
|
||||
*/
|
||||
int btrfs_find_free_objectid(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
u64 dirid, u64 *objectid)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_key key;
|
||||
int ret;
|
||||
int slot = 0;
|
||||
u64 last_ino = 0;
|
||||
int start_found;
|
||||
struct extent_buffer *l;
|
||||
struct btrfs_key search_key;
|
||||
u64 search_start = dirid;
|
||||
|
||||
mutex_lock(&root->objectid_mutex);
|
||||
if (root->last_inode_alloc >= BTRFS_FIRST_FREE_OBJECTID &&
|
||||
root->last_inode_alloc < BTRFS_LAST_FREE_OBJECTID) {
|
||||
*objectid = ++root->last_inode_alloc;
|
||||
mutex_unlock(&root->objectid_mutex);
|
||||
return 0;
|
||||
}
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
search_start = max(search_start, BTRFS_FIRST_FREE_OBJECTID);
|
||||
search_key.objectid = search_start;
|
||||
search_key.type = 0;
|
||||
search_key.offset = 0;
|
||||
|
||||
btrfs_init_path(path);
|
||||
start_found = 0;
|
||||
ret = btrfs_search_slot(trans, root, &search_key, path, 0, 0);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
while (1) {
|
||||
l = path->nodes[0];
|
||||
slot = path->slots[0];
|
||||
if (slot >= btrfs_header_nritems(l)) {
|
||||
ret = btrfs_next_leaf(root, path);
|
||||
if (ret == 0)
|
||||
continue;
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
if (!start_found) {
|
||||
*objectid = search_start;
|
||||
start_found = 1;
|
||||
goto found;
|
||||
}
|
||||
*objectid = last_ino > search_start ?
|
||||
last_ino : search_start;
|
||||
goto found;
|
||||
}
|
||||
btrfs_item_key_to_cpu(l, &key, slot);
|
||||
if (key.objectid >= search_start) {
|
||||
if (start_found) {
|
||||
if (last_ino < search_start)
|
||||
last_ino = search_start;
|
||||
if (key.objectid > last_ino) {
|
||||
*objectid = last_ino;
|
||||
goto found;
|
||||
}
|
||||
} else if (key.objectid > search_start) {
|
||||
*objectid = search_start;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
if (key.objectid >= BTRFS_LAST_FREE_OBJECTID)
|
||||
break;
|
||||
|
||||
start_found = 1;
|
||||
last_ino = key.objectid + 1;
|
||||
path->slots[0]++;
|
||||
}
|
||||
BUG_ON(1);
|
||||
found:
|
||||
btrfs_release_path(root, path);
|
||||
btrfs_free_path(path);
|
||||
BUG_ON(*objectid < search_start);
|
||||
mutex_unlock(&root->objectid_mutex);
|
||||
return 0;
|
||||
error:
|
||||
btrfs_release_path(root, path);
|
||||
btrfs_free_path(path);
|
||||
mutex_unlock(&root->objectid_mutex);
|
||||
return ret;
|
||||
}
|
5035
fs/btrfs/inode.c
Normal file
5035
fs/btrfs/inode.c
Normal file
File diff suppressed because it is too large
Load Diff
1132
fs/btrfs/ioctl.c
Normal file
1132
fs/btrfs/ioctl.c
Normal file
File diff suppressed because it is too large
Load Diff
67
fs/btrfs/ioctl.h
Normal file
67
fs/btrfs/ioctl.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __IOCTL_
|
||||
#define __IOCTL_
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#define BTRFS_IOCTL_MAGIC 0x94
|
||||
#define BTRFS_VOL_NAME_MAX 255
|
||||
#define BTRFS_PATH_NAME_MAX 3072
|
||||
|
||||
struct btrfs_ioctl_vol_args {
|
||||
__s64 fd;
|
||||
char name[BTRFS_PATH_NAME_MAX + 1];
|
||||
};
|
||||
|
||||
#define BTRFS_IOC_SNAP_CREATE _IOW(BTRFS_IOCTL_MAGIC, 1, \
|
||||
struct btrfs_ioctl_vol_args)
|
||||
#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, \
|
||||
struct btrfs_ioctl_vol_args)
|
||||
#define BTRFS_IOC_RESIZE _IOW(BTRFS_IOCTL_MAGIC, 3, \
|
||||
struct btrfs_ioctl_vol_args)
|
||||
#define BTRFS_IOC_SCAN_DEV _IOW(BTRFS_IOCTL_MAGIC, 4, \
|
||||
struct btrfs_ioctl_vol_args)
|
||||
/* trans start and trans end are dangerous, and only for
|
||||
* use by applications that know how to avoid the
|
||||
* resulting deadlocks
|
||||
*/
|
||||
#define BTRFS_IOC_TRANS_START _IO(BTRFS_IOCTL_MAGIC, 6)
|
||||
#define BTRFS_IOC_TRANS_END _IO(BTRFS_IOCTL_MAGIC, 7)
|
||||
#define BTRFS_IOC_SYNC _IO(BTRFS_IOCTL_MAGIC, 8)
|
||||
|
||||
#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int)
|
||||
#define BTRFS_IOC_ADD_DEV _IOW(BTRFS_IOCTL_MAGIC, 10, \
|
||||
struct btrfs_ioctl_vol_args)
|
||||
#define BTRFS_IOC_RM_DEV _IOW(BTRFS_IOCTL_MAGIC, 11, \
|
||||
struct btrfs_ioctl_vol_args)
|
||||
#define BTRFS_IOC_BALANCE _IOW(BTRFS_IOCTL_MAGIC, 12, \
|
||||
struct btrfs_ioctl_vol_args)
|
||||
struct btrfs_ioctl_clone_range_args {
|
||||
__s64 src_fd;
|
||||
__u64 src_offset, src_length;
|
||||
__u64 dest_offset;
|
||||
};
|
||||
|
||||
#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \
|
||||
struct btrfs_ioctl_clone_range_args)
|
||||
|
||||
#define BTRFS_IOC_SUBVOL_CREATE _IOW(BTRFS_IOCTL_MAGIC, 14, \
|
||||
struct btrfs_ioctl_vol_args)
|
||||
|
||||
#endif
|
88
fs/btrfs/locking.c
Normal file
88
fs/btrfs/locking.c
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
#include <linux/sched.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/page-flags.h>
|
||||
#include <asm/bug.h>
|
||||
#include "ctree.h"
|
||||
#include "extent_io.h"
|
||||
#include "locking.h"
|
||||
|
||||
/*
|
||||
* locks the per buffer mutex in an extent buffer. This uses adaptive locks
|
||||
* and the spin is not tuned very extensively. The spinning does make a big
|
||||
* difference in almost every workload, but spinning for the right amount of
|
||||
* time needs some help.
|
||||
*
|
||||
* In general, we want to spin as long as the lock holder is doing btree
|
||||
* searches, and we should give up if they are in more expensive code.
|
||||
*/
|
||||
|
||||
int btrfs_tree_lock(struct extent_buffer *eb)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (mutex_trylock(&eb->mutex))
|
||||
return 0;
|
||||
for (i = 0; i < 512; i++) {
|
||||
cpu_relax();
|
||||
if (mutex_trylock(&eb->mutex))
|
||||
return 0;
|
||||
}
|
||||
cpu_relax();
|
||||
mutex_lock_nested(&eb->mutex, BTRFS_MAX_LEVEL - btrfs_header_level(eb));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btrfs_try_tree_lock(struct extent_buffer *eb)
|
||||
{
|
||||
return mutex_trylock(&eb->mutex);
|
||||
}
|
||||
|
||||
int btrfs_tree_unlock(struct extent_buffer *eb)
|
||||
{
|
||||
mutex_unlock(&eb->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btrfs_tree_locked(struct extent_buffer *eb)
|
||||
{
|
||||
return mutex_is_locked(&eb->mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* btrfs_search_slot uses this to decide if it should drop its locks
|
||||
* before doing something expensive like allocating free blocks for cow.
|
||||
*/
|
||||
int btrfs_path_lock_waiting(struct btrfs_path *path, int level)
|
||||
{
|
||||
int i;
|
||||
struct extent_buffer *eb;
|
||||
for (i = level; i <= level + 1 && i < BTRFS_MAX_LEVEL; i++) {
|
||||
eb = path->nodes[i];
|
||||
if (!eb)
|
||||
break;
|
||||
smp_mb();
|
||||
if (!list_empty(&eb->mutex.wait_list))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
27
fs/btrfs/locking.h
Normal file
27
fs/btrfs/locking.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BTRFS_LOCKING_
|
||||
#define __BTRFS_LOCKING_
|
||||
|
||||
int btrfs_tree_lock(struct extent_buffer *eb);
|
||||
int btrfs_tree_unlock(struct extent_buffer *eb);
|
||||
int btrfs_tree_locked(struct extent_buffer *eb);
|
||||
int btrfs_try_tree_lock(struct extent_buffer *eb);
|
||||
int btrfs_path_lock_waiting(struct btrfs_path *path, int level);
|
||||
#endif
|
730
fs/btrfs/ordered-data.c
Normal file
730
fs/btrfs/ordered-data.c
Normal file
@ -0,0 +1,730 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/pagevec.h>
|
||||
#include "ctree.h"
|
||||
#include "transaction.h"
|
||||
#include "btrfs_inode.h"
|
||||
#include "extent_io.h"
|
||||
|
||||
static u64 entry_end(struct btrfs_ordered_extent *entry)
|
||||
{
|
||||
if (entry->file_offset + entry->len < entry->file_offset)
|
||||
return (u64)-1;
|
||||
return entry->file_offset + entry->len;
|
||||
}
|
||||
|
||||
/* returns NULL if the insertion worked, or it returns the node it did find
|
||||
* in the tree
|
||||
*/
|
||||
static struct rb_node *tree_insert(struct rb_root *root, u64 file_offset,
|
||||
struct rb_node *node)
|
||||
{
|
||||
struct rb_node **p = &root->rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
struct btrfs_ordered_extent *entry;
|
||||
|
||||
while (*p) {
|
||||
parent = *p;
|
||||
entry = rb_entry(parent, struct btrfs_ordered_extent, rb_node);
|
||||
|
||||
if (file_offset < entry->file_offset)
|
||||
p = &(*p)->rb_left;
|
||||
else if (file_offset >= entry_end(entry))
|
||||
p = &(*p)->rb_right;
|
||||
else
|
||||
return parent;
|
||||
}
|
||||
|
||||
rb_link_node(node, parent, p);
|
||||
rb_insert_color(node, root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* look for a given offset in the tree, and if it can't be found return the
|
||||
* first lesser offset
|
||||
*/
|
||||
static struct rb_node *__tree_search(struct rb_root *root, u64 file_offset,
|
||||
struct rb_node **prev_ret)
|
||||
{
|
||||
struct rb_node *n = root->rb_node;
|
||||
struct rb_node *prev = NULL;
|
||||
struct rb_node *test;
|
||||
struct btrfs_ordered_extent *entry;
|
||||
struct btrfs_ordered_extent *prev_entry = NULL;
|
||||
|
||||
while (n) {
|
||||
entry = rb_entry(n, struct btrfs_ordered_extent, rb_node);
|
||||
prev = n;
|
||||
prev_entry = entry;
|
||||
|
||||
if (file_offset < entry->file_offset)
|
||||
n = n->rb_left;
|
||||
else if (file_offset >= entry_end(entry))
|
||||
n = n->rb_right;
|
||||
else
|
||||
return n;
|
||||
}
|
||||
if (!prev_ret)
|
||||
return NULL;
|
||||
|
||||
while (prev && file_offset >= entry_end(prev_entry)) {
|
||||
test = rb_next(prev);
|
||||
if (!test)
|
||||
break;
|
||||
prev_entry = rb_entry(test, struct btrfs_ordered_extent,
|
||||
rb_node);
|
||||
if (file_offset < entry_end(prev_entry))
|
||||
break;
|
||||
|
||||
prev = test;
|
||||
}
|
||||
if (prev)
|
||||
prev_entry = rb_entry(prev, struct btrfs_ordered_extent,
|
||||
rb_node);
|
||||
while (prev && file_offset < entry_end(prev_entry)) {
|
||||
test = rb_prev(prev);
|
||||
if (!test)
|
||||
break;
|
||||
prev_entry = rb_entry(test, struct btrfs_ordered_extent,
|
||||
rb_node);
|
||||
prev = test;
|
||||
}
|
||||
*prev_ret = prev;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* helper to check if a given offset is inside a given entry
|
||||
*/
|
||||
static int offset_in_entry(struct btrfs_ordered_extent *entry, u64 file_offset)
|
||||
{
|
||||
if (file_offset < entry->file_offset ||
|
||||
entry->file_offset + entry->len <= file_offset)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* look find the first ordered struct that has this offset, otherwise
|
||||
* the first one less than this offset
|
||||
*/
|
||||
static inline struct rb_node *tree_search(struct btrfs_ordered_inode_tree *tree,
|
||||
u64 file_offset)
|
||||
{
|
||||
struct rb_root *root = &tree->tree;
|
||||
struct rb_node *prev;
|
||||
struct rb_node *ret;
|
||||
struct btrfs_ordered_extent *entry;
|
||||
|
||||
if (tree->last) {
|
||||
entry = rb_entry(tree->last, struct btrfs_ordered_extent,
|
||||
rb_node);
|
||||
if (offset_in_entry(entry, file_offset))
|
||||
return tree->last;
|
||||
}
|
||||
ret = __tree_search(root, file_offset, &prev);
|
||||
if (!ret)
|
||||
ret = prev;
|
||||
if (ret)
|
||||
tree->last = ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* allocate and add a new ordered_extent into the per-inode tree.
|
||||
* file_offset is the logical offset in the file
|
||||
*
|
||||
* start is the disk block number of an extent already reserved in the
|
||||
* extent allocation tree
|
||||
*
|
||||
* len is the length of the extent
|
||||
*
|
||||
* This also sets the EXTENT_ORDERED bit on the range in the inode.
|
||||
*
|
||||
* The tree is given a single reference on the ordered extent that was
|
||||
* inserted.
|
||||
*/
|
||||
int btrfs_add_ordered_extent(struct inode *inode, u64 file_offset,
|
||||
u64 start, u64 len, u64 disk_len, int type)
|
||||
{
|
||||
struct btrfs_ordered_inode_tree *tree;
|
||||
struct rb_node *node;
|
||||
struct btrfs_ordered_extent *entry;
|
||||
|
||||
tree = &BTRFS_I(inode)->ordered_tree;
|
||||
entry = kzalloc(sizeof(*entry), GFP_NOFS);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&tree->mutex);
|
||||
entry->file_offset = file_offset;
|
||||
entry->start = start;
|
||||
entry->len = len;
|
||||
entry->disk_len = disk_len;
|
||||
entry->inode = inode;
|
||||
if (type != BTRFS_ORDERED_IO_DONE && type != BTRFS_ORDERED_COMPLETE)
|
||||
set_bit(type, &entry->flags);
|
||||
|
||||
/* one ref for the tree */
|
||||
atomic_set(&entry->refs, 1);
|
||||
init_waitqueue_head(&entry->wait);
|
||||
INIT_LIST_HEAD(&entry->list);
|
||||
INIT_LIST_HEAD(&entry->root_extent_list);
|
||||
|
||||
node = tree_insert(&tree->tree, file_offset,
|
||||
&entry->rb_node);
|
||||
BUG_ON(node);
|
||||
|
||||
set_extent_ordered(&BTRFS_I(inode)->io_tree, file_offset,
|
||||
entry_end(entry) - 1, GFP_NOFS);
|
||||
|
||||
spin_lock(&BTRFS_I(inode)->root->fs_info->ordered_extent_lock);
|
||||
list_add_tail(&entry->root_extent_list,
|
||||
&BTRFS_I(inode)->root->fs_info->ordered_extents);
|
||||
spin_unlock(&BTRFS_I(inode)->root->fs_info->ordered_extent_lock);
|
||||
|
||||
mutex_unlock(&tree->mutex);
|
||||
BUG_ON(node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a struct btrfs_ordered_sum into the list of checksums to be inserted
|
||||
* when an ordered extent is finished. If the list covers more than one
|
||||
* ordered extent, it is split across multiples.
|
||||
*/
|
||||
int btrfs_add_ordered_sum(struct inode *inode,
|
||||
struct btrfs_ordered_extent *entry,
|
||||
struct btrfs_ordered_sum *sum)
|
||||
{
|
||||
struct btrfs_ordered_inode_tree *tree;
|
||||
|
||||
tree = &BTRFS_I(inode)->ordered_tree;
|
||||
mutex_lock(&tree->mutex);
|
||||
list_add_tail(&sum->list, &entry->list);
|
||||
mutex_unlock(&tree->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* this is used to account for finished IO across a given range
|
||||
* of the file. The IO should not span ordered extents. If
|
||||
* a given ordered_extent is completely done, 1 is returned, otherwise
|
||||
* 0.
|
||||
*
|
||||
* test_and_set_bit on a flag in the struct btrfs_ordered_extent is used
|
||||
* to make sure this function only returns 1 once for a given ordered extent.
|
||||
*/
|
||||
int btrfs_dec_test_ordered_pending(struct inode *inode,
|
||||
u64 file_offset, u64 io_size)
|
||||
{
|
||||
struct btrfs_ordered_inode_tree *tree;
|
||||
struct rb_node *node;
|
||||
struct btrfs_ordered_extent *entry;
|
||||
struct extent_io_tree *io_tree = &BTRFS_I(inode)->io_tree;
|
||||
int ret;
|
||||
|
||||
tree = &BTRFS_I(inode)->ordered_tree;
|
||||
mutex_lock(&tree->mutex);
|
||||
clear_extent_ordered(io_tree, file_offset, file_offset + io_size - 1,
|
||||
GFP_NOFS);
|
||||
node = tree_search(tree, file_offset);
|
||||
if (!node) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
entry = rb_entry(node, struct btrfs_ordered_extent, rb_node);
|
||||
if (!offset_in_entry(entry, file_offset)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = test_range_bit(io_tree, entry->file_offset,
|
||||
entry->file_offset + entry->len - 1,
|
||||
EXTENT_ORDERED, 0);
|
||||
if (ret == 0)
|
||||
ret = test_and_set_bit(BTRFS_ORDERED_IO_DONE, &entry->flags);
|
||||
out:
|
||||
mutex_unlock(&tree->mutex);
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* used to drop a reference on an ordered extent. This will free
|
||||
* the extent if the last reference is dropped
|
||||
*/
|
||||
int btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
|
||||
{
|
||||
struct list_head *cur;
|
||||
struct btrfs_ordered_sum *sum;
|
||||
|
||||
if (atomic_dec_and_test(&entry->refs)) {
|
||||
while (!list_empty(&entry->list)) {
|
||||
cur = entry->list.next;
|
||||
sum = list_entry(cur, struct btrfs_ordered_sum, list);
|
||||
list_del(&sum->list);
|
||||
kfree(sum);
|
||||
}
|
||||
kfree(entry);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* remove an ordered extent from the tree. No references are dropped
|
||||
* but, anyone waiting on this extent is woken up.
|
||||
*/
|
||||
int btrfs_remove_ordered_extent(struct inode *inode,
|
||||
struct btrfs_ordered_extent *entry)
|
||||
{
|
||||
struct btrfs_ordered_inode_tree *tree;
|
||||
struct rb_node *node;
|
||||
|
||||
tree = &BTRFS_I(inode)->ordered_tree;
|
||||
mutex_lock(&tree->mutex);
|
||||
node = &entry->rb_node;
|
||||
rb_erase(node, &tree->tree);
|
||||
tree->last = NULL;
|
||||
set_bit(BTRFS_ORDERED_COMPLETE, &entry->flags);
|
||||
|
||||
spin_lock(&BTRFS_I(inode)->root->fs_info->ordered_extent_lock);
|
||||
list_del_init(&entry->root_extent_list);
|
||||
spin_unlock(&BTRFS_I(inode)->root->fs_info->ordered_extent_lock);
|
||||
|
||||
mutex_unlock(&tree->mutex);
|
||||
wake_up(&entry->wait);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* wait for all the ordered extents in a root. This is done when balancing
|
||||
* space between drives.
|
||||
*/
|
||||
int btrfs_wait_ordered_extents(struct btrfs_root *root, int nocow_only)
|
||||
{
|
||||
struct list_head splice;
|
||||
struct list_head *cur;
|
||||
struct btrfs_ordered_extent *ordered;
|
||||
struct inode *inode;
|
||||
|
||||
INIT_LIST_HEAD(&splice);
|
||||
|
||||
spin_lock(&root->fs_info->ordered_extent_lock);
|
||||
list_splice_init(&root->fs_info->ordered_extents, &splice);
|
||||
while (!list_empty(&splice)) {
|
||||
cur = splice.next;
|
||||
ordered = list_entry(cur, struct btrfs_ordered_extent,
|
||||
root_extent_list);
|
||||
if (nocow_only &&
|
||||
!test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags) &&
|
||||
!test_bit(BTRFS_ORDERED_PREALLOC, &ordered->flags)) {
|
||||
list_move(&ordered->root_extent_list,
|
||||
&root->fs_info->ordered_extents);
|
||||
cond_resched_lock(&root->fs_info->ordered_extent_lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
list_del_init(&ordered->root_extent_list);
|
||||
atomic_inc(&ordered->refs);
|
||||
|
||||
/*
|
||||
* the inode may be getting freed (in sys_unlink path).
|
||||
*/
|
||||
inode = igrab(ordered->inode);
|
||||
|
||||
spin_unlock(&root->fs_info->ordered_extent_lock);
|
||||
|
||||
if (inode) {
|
||||
btrfs_start_ordered_extent(inode, ordered, 1);
|
||||
btrfs_put_ordered_extent(ordered);
|
||||
iput(inode);
|
||||
} else {
|
||||
btrfs_put_ordered_extent(ordered);
|
||||
}
|
||||
|
||||
spin_lock(&root->fs_info->ordered_extent_lock);
|
||||
}
|
||||
spin_unlock(&root->fs_info->ordered_extent_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to start IO or wait for a given ordered extent to finish.
|
||||
*
|
||||
* If wait is one, this effectively waits on page writeback for all the pages
|
||||
* in the extent, and it waits on the io completion code to insert
|
||||
* metadata into the btree corresponding to the extent
|
||||
*/
|
||||
void btrfs_start_ordered_extent(struct inode *inode,
|
||||
struct btrfs_ordered_extent *entry,
|
||||
int wait)
|
||||
{
|
||||
u64 start = entry->file_offset;
|
||||
u64 end = start + entry->len - 1;
|
||||
|
||||
/*
|
||||
* pages in the range can be dirty, clean or writeback. We
|
||||
* start IO on any dirty ones so the wait doesn't stall waiting
|
||||
* for pdflush to find them
|
||||
*/
|
||||
btrfs_fdatawrite_range(inode->i_mapping, start, end, WB_SYNC_ALL);
|
||||
if (wait) {
|
||||
wait_event(entry->wait, test_bit(BTRFS_ORDERED_COMPLETE,
|
||||
&entry->flags));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to wait on ordered extents across a large range of bytes.
|
||||
*/
|
||||
int btrfs_wait_ordered_range(struct inode *inode, u64 start, u64 len)
|
||||
{
|
||||
u64 end;
|
||||
u64 orig_end;
|
||||
u64 wait_end;
|
||||
struct btrfs_ordered_extent *ordered;
|
||||
|
||||
if (start + len < start) {
|
||||
orig_end = INT_LIMIT(loff_t);
|
||||
} else {
|
||||
orig_end = start + len - 1;
|
||||
if (orig_end > INT_LIMIT(loff_t))
|
||||
orig_end = INT_LIMIT(loff_t);
|
||||
}
|
||||
wait_end = orig_end;
|
||||
again:
|
||||
/* start IO across the range first to instantiate any delalloc
|
||||
* extents
|
||||
*/
|
||||
btrfs_fdatawrite_range(inode->i_mapping, start, orig_end, WB_SYNC_NONE);
|
||||
|
||||
/* The compression code will leave pages locked but return from
|
||||
* writepage without setting the page writeback. Starting again
|
||||
* with WB_SYNC_ALL will end up waiting for the IO to actually start.
|
||||
*/
|
||||
btrfs_fdatawrite_range(inode->i_mapping, start, orig_end, WB_SYNC_ALL);
|
||||
|
||||
btrfs_wait_on_page_writeback_range(inode->i_mapping,
|
||||
start >> PAGE_CACHE_SHIFT,
|
||||
orig_end >> PAGE_CACHE_SHIFT);
|
||||
|
||||
end = orig_end;
|
||||
while (1) {
|
||||
ordered = btrfs_lookup_first_ordered_extent(inode, end);
|
||||
if (!ordered)
|
||||
break;
|
||||
if (ordered->file_offset > orig_end) {
|
||||
btrfs_put_ordered_extent(ordered);
|
||||
break;
|
||||
}
|
||||
if (ordered->file_offset + ordered->len < start) {
|
||||
btrfs_put_ordered_extent(ordered);
|
||||
break;
|
||||
}
|
||||
btrfs_start_ordered_extent(inode, ordered, 1);
|
||||
end = ordered->file_offset;
|
||||
btrfs_put_ordered_extent(ordered);
|
||||
if (end == 0 || end == start)
|
||||
break;
|
||||
end--;
|
||||
}
|
||||
if (test_range_bit(&BTRFS_I(inode)->io_tree, start, orig_end,
|
||||
EXTENT_ORDERED | EXTENT_DELALLOC, 0)) {
|
||||
schedule_timeout(1);
|
||||
goto again;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* find an ordered extent corresponding to file_offset. return NULL if
|
||||
* nothing is found, otherwise take a reference on the extent and return it
|
||||
*/
|
||||
struct btrfs_ordered_extent *btrfs_lookup_ordered_extent(struct inode *inode,
|
||||
u64 file_offset)
|
||||
{
|
||||
struct btrfs_ordered_inode_tree *tree;
|
||||
struct rb_node *node;
|
||||
struct btrfs_ordered_extent *entry = NULL;
|
||||
|
||||
tree = &BTRFS_I(inode)->ordered_tree;
|
||||
mutex_lock(&tree->mutex);
|
||||
node = tree_search(tree, file_offset);
|
||||
if (!node)
|
||||
goto out;
|
||||
|
||||
entry = rb_entry(node, struct btrfs_ordered_extent, rb_node);
|
||||
if (!offset_in_entry(entry, file_offset))
|
||||
entry = NULL;
|
||||
if (entry)
|
||||
atomic_inc(&entry->refs);
|
||||
out:
|
||||
mutex_unlock(&tree->mutex);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* lookup and return any extent before 'file_offset'. NULL is returned
|
||||
* if none is found
|
||||
*/
|
||||
struct btrfs_ordered_extent *
|
||||
btrfs_lookup_first_ordered_extent(struct inode *inode, u64 file_offset)
|
||||
{
|
||||
struct btrfs_ordered_inode_tree *tree;
|
||||
struct rb_node *node;
|
||||
struct btrfs_ordered_extent *entry = NULL;
|
||||
|
||||
tree = &BTRFS_I(inode)->ordered_tree;
|
||||
mutex_lock(&tree->mutex);
|
||||
node = tree_search(tree, file_offset);
|
||||
if (!node)
|
||||
goto out;
|
||||
|
||||
entry = rb_entry(node, struct btrfs_ordered_extent, rb_node);
|
||||
atomic_inc(&entry->refs);
|
||||
out:
|
||||
mutex_unlock(&tree->mutex);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* After an extent is done, call this to conditionally update the on disk
|
||||
* i_size. i_size is updated to cover any fully written part of the file.
|
||||
*/
|
||||
int btrfs_ordered_update_i_size(struct inode *inode,
|
||||
struct btrfs_ordered_extent *ordered)
|
||||
{
|
||||
struct btrfs_ordered_inode_tree *tree = &BTRFS_I(inode)->ordered_tree;
|
||||
struct extent_io_tree *io_tree = &BTRFS_I(inode)->io_tree;
|
||||
u64 disk_i_size;
|
||||
u64 new_i_size;
|
||||
u64 i_size_test;
|
||||
struct rb_node *node;
|
||||
struct btrfs_ordered_extent *test;
|
||||
|
||||
mutex_lock(&tree->mutex);
|
||||
disk_i_size = BTRFS_I(inode)->disk_i_size;
|
||||
|
||||
/*
|
||||
* if the disk i_size is already at the inode->i_size, or
|
||||
* this ordered extent is inside the disk i_size, we're done
|
||||
*/
|
||||
if (disk_i_size >= inode->i_size ||
|
||||
ordered->file_offset + ordered->len <= disk_i_size) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* we can't update the disk_isize if there are delalloc bytes
|
||||
* between disk_i_size and this ordered extent
|
||||
*/
|
||||
if (test_range_bit(io_tree, disk_i_size,
|
||||
ordered->file_offset + ordered->len - 1,
|
||||
EXTENT_DELALLOC, 0)) {
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* walk backward from this ordered extent to disk_i_size.
|
||||
* if we find an ordered extent then we can't update disk i_size
|
||||
* yet
|
||||
*/
|
||||
node = &ordered->rb_node;
|
||||
while (1) {
|
||||
node = rb_prev(node);
|
||||
if (!node)
|
||||
break;
|
||||
test = rb_entry(node, struct btrfs_ordered_extent, rb_node);
|
||||
if (test->file_offset + test->len <= disk_i_size)
|
||||
break;
|
||||
if (test->file_offset >= inode->i_size)
|
||||
break;
|
||||
if (test->file_offset >= disk_i_size)
|
||||
goto out;
|
||||
}
|
||||
new_i_size = min_t(u64, entry_end(ordered), i_size_read(inode));
|
||||
|
||||
/*
|
||||
* at this point, we know we can safely update i_size to at least
|
||||
* the offset from this ordered extent. But, we need to
|
||||
* walk forward and see if ios from higher up in the file have
|
||||
* finished.
|
||||
*/
|
||||
node = rb_next(&ordered->rb_node);
|
||||
i_size_test = 0;
|
||||
if (node) {
|
||||
/*
|
||||
* do we have an area where IO might have finished
|
||||
* between our ordered extent and the next one.
|
||||
*/
|
||||
test = rb_entry(node, struct btrfs_ordered_extent, rb_node);
|
||||
if (test->file_offset > entry_end(ordered))
|
||||
i_size_test = test->file_offset;
|
||||
} else {
|
||||
i_size_test = i_size_read(inode);
|
||||
}
|
||||
|
||||
/*
|
||||
* i_size_test is the end of a region after this ordered
|
||||
* extent where there are no ordered extents. As long as there
|
||||
* are no delalloc bytes in this area, it is safe to update
|
||||
* disk_i_size to the end of the region.
|
||||
*/
|
||||
if (i_size_test > entry_end(ordered) &&
|
||||
!test_range_bit(io_tree, entry_end(ordered), i_size_test - 1,
|
||||
EXTENT_DELALLOC, 0)) {
|
||||
new_i_size = min_t(u64, i_size_test, i_size_read(inode));
|
||||
}
|
||||
BTRFS_I(inode)->disk_i_size = new_i_size;
|
||||
out:
|
||||
mutex_unlock(&tree->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* search the ordered extents for one corresponding to 'offset' and
|
||||
* try to find a checksum. This is used because we allow pages to
|
||||
* be reclaimed before their checksum is actually put into the btree
|
||||
*/
|
||||
int btrfs_find_ordered_sum(struct inode *inode, u64 offset, u64 disk_bytenr,
|
||||
u32 *sum)
|
||||
{
|
||||
struct btrfs_ordered_sum *ordered_sum;
|
||||
struct btrfs_sector_sum *sector_sums;
|
||||
struct btrfs_ordered_extent *ordered;
|
||||
struct btrfs_ordered_inode_tree *tree = &BTRFS_I(inode)->ordered_tree;
|
||||
struct list_head *cur;
|
||||
unsigned long num_sectors;
|
||||
unsigned long i;
|
||||
u32 sectorsize = BTRFS_I(inode)->root->sectorsize;
|
||||
int ret = 1;
|
||||
|
||||
ordered = btrfs_lookup_ordered_extent(inode, offset);
|
||||
if (!ordered)
|
||||
return 1;
|
||||
|
||||
mutex_lock(&tree->mutex);
|
||||
list_for_each_prev(cur, &ordered->list) {
|
||||
ordered_sum = list_entry(cur, struct btrfs_ordered_sum, list);
|
||||
if (disk_bytenr >= ordered_sum->bytenr) {
|
||||
num_sectors = ordered_sum->len / sectorsize;
|
||||
sector_sums = ordered_sum->sums;
|
||||
for (i = 0; i < num_sectors; i++) {
|
||||
if (sector_sums[i].bytenr == disk_bytenr) {
|
||||
*sum = sector_sums[i].sum;
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&tree->mutex);
|
||||
btrfs_put_ordered_extent(ordered);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* taken from mm/filemap.c because it isn't exported
|
||||
*
|
||||
* __filemap_fdatawrite_range - start writeback on mapping dirty pages in range
|
||||
* @mapping: address space structure to write
|
||||
* @start: offset in bytes where the range starts
|
||||
* @end: offset in bytes where the range ends (inclusive)
|
||||
* @sync_mode: enable synchronous operation
|
||||
*
|
||||
* Start writeback against all of a mapping's dirty pages that lie
|
||||
* within the byte offsets <start, end> inclusive.
|
||||
*
|
||||
* If sync_mode is WB_SYNC_ALL then this is a "data integrity" operation, as
|
||||
* opposed to a regular memory cleansing writeback. The difference between
|
||||
* these two operations is that if a dirty page/buffer is encountered, it must
|
||||
* be waited upon, and not just skipped over.
|
||||
*/
|
||||
int btrfs_fdatawrite_range(struct address_space *mapping, loff_t start,
|
||||
loff_t end, int sync_mode)
|
||||
{
|
||||
struct writeback_control wbc = {
|
||||
.sync_mode = sync_mode,
|
||||
.nr_to_write = mapping->nrpages * 2,
|
||||
.range_start = start,
|
||||
.range_end = end,
|
||||
.for_writepages = 1,
|
||||
};
|
||||
return btrfs_writepages(mapping, &wbc);
|
||||
}
|
||||
|
||||
/**
|
||||
* taken from mm/filemap.c because it isn't exported
|
||||
*
|
||||
* wait_on_page_writeback_range - wait for writeback to complete
|
||||
* @mapping: target address_space
|
||||
* @start: beginning page index
|
||||
* @end: ending page index
|
||||
*
|
||||
* Wait for writeback to complete against pages indexed by start->end
|
||||
* inclusive
|
||||
*/
|
||||
int btrfs_wait_on_page_writeback_range(struct address_space *mapping,
|
||||
pgoff_t start, pgoff_t end)
|
||||
{
|
||||
struct pagevec pvec;
|
||||
int nr_pages;
|
||||
int ret = 0;
|
||||
pgoff_t index;
|
||||
|
||||
if (end < start)
|
||||
return 0;
|
||||
|
||||
pagevec_init(&pvec, 0);
|
||||
index = start;
|
||||
while ((index <= end) &&
|
||||
(nr_pages = pagevec_lookup_tag(&pvec, mapping, &index,
|
||||
PAGECACHE_TAG_WRITEBACK,
|
||||
min(end - index, (pgoff_t)PAGEVEC_SIZE-1) + 1)) != 0) {
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < nr_pages; i++) {
|
||||
struct page *page = pvec.pages[i];
|
||||
|
||||
/* until radix tree lookup accepts end_index */
|
||||
if (page->index > end)
|
||||
continue;
|
||||
|
||||
wait_on_page_writeback(page);
|
||||
if (PageError(page))
|
||||
ret = -EIO;
|
||||
}
|
||||
pagevec_release(&pvec);
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
/* Check for outstanding write errors */
|
||||
if (test_and_clear_bit(AS_ENOSPC, &mapping->flags))
|
||||
ret = -ENOSPC;
|
||||
if (test_and_clear_bit(AS_EIO, &mapping->flags))
|
||||
ret = -EIO;
|
||||
|
||||
return ret;
|
||||
}
|
158
fs/btrfs/ordered-data.h
Normal file
158
fs/btrfs/ordered-data.h
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BTRFS_ORDERED_DATA__
|
||||
#define __BTRFS_ORDERED_DATA__
|
||||
|
||||
/* one of these per inode */
|
||||
struct btrfs_ordered_inode_tree {
|
||||
struct mutex mutex;
|
||||
struct rb_root tree;
|
||||
struct rb_node *last;
|
||||
};
|
||||
|
||||
/*
|
||||
* these are used to collect checksums done just before bios submission.
|
||||
* They are attached via a list into the ordered extent, and
|
||||
* checksum items are inserted into the tree after all the blocks in
|
||||
* the ordered extent are on disk
|
||||
*/
|
||||
struct btrfs_sector_sum {
|
||||
/* bytenr on disk */
|
||||
u64 bytenr;
|
||||
u32 sum;
|
||||
};
|
||||
|
||||
struct btrfs_ordered_sum {
|
||||
/* bytenr is the start of this extent on disk */
|
||||
u64 bytenr;
|
||||
|
||||
/*
|
||||
* this is the length in bytes covered by the sums array below.
|
||||
*/
|
||||
unsigned long len;
|
||||
struct list_head list;
|
||||
/* last field is a variable length array of btrfs_sector_sums */
|
||||
struct btrfs_sector_sum sums[];
|
||||
};
|
||||
|
||||
/*
|
||||
* bits for the flags field:
|
||||
*
|
||||
* BTRFS_ORDERED_IO_DONE is set when all of the blocks are written.
|
||||
* It is used to make sure metadata is inserted into the tree only once
|
||||
* per extent.
|
||||
*
|
||||
* BTRFS_ORDERED_COMPLETE is set when the extent is removed from the
|
||||
* rbtree, just before waking any waiters. It is used to indicate the
|
||||
* IO is done and any metadata is inserted into the tree.
|
||||
*/
|
||||
#define BTRFS_ORDERED_IO_DONE 0 /* set when all the pages are written */
|
||||
|
||||
#define BTRFS_ORDERED_COMPLETE 1 /* set when removed from the tree */
|
||||
|
||||
#define BTRFS_ORDERED_NOCOW 2 /* set when we want to write in place */
|
||||
|
||||
#define BTRFS_ORDERED_COMPRESSED 3 /* writing a compressed extent */
|
||||
|
||||
#define BTRFS_ORDERED_PREALLOC 4 /* set when writing to prealloced extent */
|
||||
|
||||
struct btrfs_ordered_extent {
|
||||
/* logical offset in the file */
|
||||
u64 file_offset;
|
||||
|
||||
/* disk byte number */
|
||||
u64 start;
|
||||
|
||||
/* ram length of the extent in bytes */
|
||||
u64 len;
|
||||
|
||||
/* extent length on disk */
|
||||
u64 disk_len;
|
||||
|
||||
/* flags (described above) */
|
||||
unsigned long flags;
|
||||
|
||||
/* reference count */
|
||||
atomic_t refs;
|
||||
|
||||
/* the inode we belong to */
|
||||
struct inode *inode;
|
||||
|
||||
/* list of checksums for insertion when the extent io is done */
|
||||
struct list_head list;
|
||||
|
||||
/* used to wait for the BTRFS_ORDERED_COMPLETE bit */
|
||||
wait_queue_head_t wait;
|
||||
|
||||
/* our friendly rbtree entry */
|
||||
struct rb_node rb_node;
|
||||
|
||||
/* a per root list of all the pending ordered extents */
|
||||
struct list_head root_extent_list;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* calculates the total size you need to allocate for an ordered sum
|
||||
* structure spanning 'bytes' in the file
|
||||
*/
|
||||
static inline int btrfs_ordered_sum_size(struct btrfs_root *root,
|
||||
unsigned long bytes)
|
||||
{
|
||||
unsigned long num_sectors = (bytes + root->sectorsize - 1) /
|
||||
root->sectorsize;
|
||||
num_sectors++;
|
||||
return sizeof(struct btrfs_ordered_sum) +
|
||||
num_sectors * sizeof(struct btrfs_sector_sum);
|
||||
}
|
||||
|
||||
static inline void
|
||||
btrfs_ordered_inode_tree_init(struct btrfs_ordered_inode_tree *t)
|
||||
{
|
||||
mutex_init(&t->mutex);
|
||||
t->tree.rb_node = NULL;
|
||||
t->last = NULL;
|
||||
}
|
||||
|
||||
int btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry);
|
||||
int btrfs_remove_ordered_extent(struct inode *inode,
|
||||
struct btrfs_ordered_extent *entry);
|
||||
int btrfs_dec_test_ordered_pending(struct inode *inode,
|
||||
u64 file_offset, u64 io_size);
|
||||
int btrfs_add_ordered_extent(struct inode *inode, u64 file_offset,
|
||||
u64 start, u64 len, u64 disk_len, int tyep);
|
||||
int btrfs_add_ordered_sum(struct inode *inode,
|
||||
struct btrfs_ordered_extent *entry,
|
||||
struct btrfs_ordered_sum *sum);
|
||||
struct btrfs_ordered_extent *btrfs_lookup_ordered_extent(struct inode *inode,
|
||||
u64 file_offset);
|
||||
void btrfs_start_ordered_extent(struct inode *inode,
|
||||
struct btrfs_ordered_extent *entry, int wait);
|
||||
int btrfs_wait_ordered_range(struct inode *inode, u64 start, u64 len);
|
||||
struct btrfs_ordered_extent *
|
||||
btrfs_lookup_first_ordered_extent(struct inode * inode, u64 file_offset);
|
||||
int btrfs_ordered_update_i_size(struct inode *inode,
|
||||
struct btrfs_ordered_extent *ordered);
|
||||
int btrfs_find_ordered_sum(struct inode *inode, u64 offset, u64 disk_bytenr, u32 *sum);
|
||||
int btrfs_wait_on_page_writeback_range(struct address_space *mapping,
|
||||
pgoff_t start, pgoff_t end);
|
||||
int btrfs_fdatawrite_range(struct address_space *mapping, loff_t start,
|
||||
loff_t end, int sync_mode);
|
||||
int btrfs_wait_ordered_extents(struct btrfs_root *root, int nocow_only);
|
||||
#endif
|
67
fs/btrfs/orphan.c
Normal file
67
fs/btrfs/orphan.c
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Red Hat. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
|
||||
int btrfs_insert_orphan_item(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, u64 offset)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_key key;
|
||||
int ret = 0;
|
||||
|
||||
key.objectid = BTRFS_ORPHAN_OBJECTID;
|
||||
btrfs_set_key_type(&key, BTRFS_ORPHAN_ITEM_KEY);
|
||||
key.offset = offset;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = btrfs_insert_empty_item(trans, root, path, &key, 0);
|
||||
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_del_orphan_item(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, u64 offset)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_key key;
|
||||
int ret = 0;
|
||||
|
||||
key.objectid = BTRFS_ORPHAN_OBJECTID;
|
||||
btrfs_set_key_type(&key, BTRFS_ORPHAN_ITEM_KEY);
|
||||
key.offset = offset;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = btrfs_del_item(trans, root, path);
|
||||
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
216
fs/btrfs/print-tree.c
Normal file
216
fs/btrfs/print-tree.c
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "print-tree.h"
|
||||
|
||||
static void print_chunk(struct extent_buffer *eb, struct btrfs_chunk *chunk)
|
||||
{
|
||||
int num_stripes = btrfs_chunk_num_stripes(eb, chunk);
|
||||
int i;
|
||||
printk(KERN_INFO "\t\tchunk length %llu owner %llu type %llu "
|
||||
"num_stripes %d\n",
|
||||
(unsigned long long)btrfs_chunk_length(eb, chunk),
|
||||
(unsigned long long)btrfs_chunk_owner(eb, chunk),
|
||||
(unsigned long long)btrfs_chunk_type(eb, chunk),
|
||||
num_stripes);
|
||||
for (i = 0 ; i < num_stripes ; i++) {
|
||||
printk(KERN_INFO "\t\t\tstripe %d devid %llu offset %llu\n", i,
|
||||
(unsigned long long)btrfs_stripe_devid_nr(eb, chunk, i),
|
||||
(unsigned long long)btrfs_stripe_offset_nr(eb, chunk, i));
|
||||
}
|
||||
}
|
||||
static void print_dev_item(struct extent_buffer *eb,
|
||||
struct btrfs_dev_item *dev_item)
|
||||
{
|
||||
printk(KERN_INFO "\t\tdev item devid %llu "
|
||||
"total_bytes %llu bytes used %llu\n",
|
||||
(unsigned long long)btrfs_device_id(eb, dev_item),
|
||||
(unsigned long long)btrfs_device_total_bytes(eb, dev_item),
|
||||
(unsigned long long)btrfs_device_bytes_used(eb, dev_item));
|
||||
}
|
||||
void btrfs_print_leaf(struct btrfs_root *root, struct extent_buffer *l)
|
||||
{
|
||||
int i;
|
||||
u32 nr = btrfs_header_nritems(l);
|
||||
struct btrfs_item *item;
|
||||
struct btrfs_extent_item *ei;
|
||||
struct btrfs_root_item *ri;
|
||||
struct btrfs_dir_item *di;
|
||||
struct btrfs_inode_item *ii;
|
||||
struct btrfs_block_group_item *bi;
|
||||
struct btrfs_file_extent_item *fi;
|
||||
struct btrfs_key key;
|
||||
struct btrfs_key found_key;
|
||||
struct btrfs_extent_ref *ref;
|
||||
struct btrfs_dev_extent *dev_extent;
|
||||
u32 type;
|
||||
|
||||
printk(KERN_INFO "leaf %llu total ptrs %d free space %d\n",
|
||||
(unsigned long long)btrfs_header_bytenr(l), nr,
|
||||
btrfs_leaf_free_space(root, l));
|
||||
for (i = 0 ; i < nr ; i++) {
|
||||
item = btrfs_item_nr(l, i);
|
||||
btrfs_item_key_to_cpu(l, &key, i);
|
||||
type = btrfs_key_type(&key);
|
||||
printk(KERN_INFO "\titem %d key (%llu %x %llu) itemoff %d "
|
||||
"itemsize %d\n",
|
||||
i,
|
||||
(unsigned long long)key.objectid, type,
|
||||
(unsigned long long)key.offset,
|
||||
btrfs_item_offset(l, item), btrfs_item_size(l, item));
|
||||
switch (type) {
|
||||
case BTRFS_INODE_ITEM_KEY:
|
||||
ii = btrfs_item_ptr(l, i, struct btrfs_inode_item);
|
||||
printk(KERN_INFO "\t\tinode generation %llu size %llu "
|
||||
"mode %o\n",
|
||||
(unsigned long long)
|
||||
btrfs_inode_generation(l, ii),
|
||||
(unsigned long long)btrfs_inode_size(l, ii),
|
||||
btrfs_inode_mode(l, ii));
|
||||
break;
|
||||
case BTRFS_DIR_ITEM_KEY:
|
||||
di = btrfs_item_ptr(l, i, struct btrfs_dir_item);
|
||||
btrfs_dir_item_key_to_cpu(l, di, &found_key);
|
||||
printk(KERN_INFO "\t\tdir oid %llu type %u\n",
|
||||
(unsigned long long)found_key.objectid,
|
||||
btrfs_dir_type(l, di));
|
||||
break;
|
||||
case BTRFS_ROOT_ITEM_KEY:
|
||||
ri = btrfs_item_ptr(l, i, struct btrfs_root_item);
|
||||
printk(KERN_INFO "\t\troot data bytenr %llu refs %u\n",
|
||||
(unsigned long long)
|
||||
btrfs_disk_root_bytenr(l, ri),
|
||||
btrfs_disk_root_refs(l, ri));
|
||||
break;
|
||||
case BTRFS_EXTENT_ITEM_KEY:
|
||||
ei = btrfs_item_ptr(l, i, struct btrfs_extent_item);
|
||||
printk(KERN_INFO "\t\textent data refs %u\n",
|
||||
btrfs_extent_refs(l, ei));
|
||||
break;
|
||||
case BTRFS_EXTENT_REF_KEY:
|
||||
ref = btrfs_item_ptr(l, i, struct btrfs_extent_ref);
|
||||
printk(KERN_INFO "\t\textent back ref root %llu "
|
||||
"gen %llu owner %llu num_refs %lu\n",
|
||||
(unsigned long long)btrfs_ref_root(l, ref),
|
||||
(unsigned long long)btrfs_ref_generation(l, ref),
|
||||
(unsigned long long)btrfs_ref_objectid(l, ref),
|
||||
(unsigned long)btrfs_ref_num_refs(l, ref));
|
||||
break;
|
||||
|
||||
case BTRFS_EXTENT_DATA_KEY:
|
||||
fi = btrfs_item_ptr(l, i,
|
||||
struct btrfs_file_extent_item);
|
||||
if (btrfs_file_extent_type(l, fi) ==
|
||||
BTRFS_FILE_EXTENT_INLINE) {
|
||||
printk(KERN_INFO "\t\tinline extent data "
|
||||
"size %u\n",
|
||||
btrfs_file_extent_inline_len(l, fi));
|
||||
break;
|
||||
}
|
||||
printk(KERN_INFO "\t\textent data disk bytenr %llu "
|
||||
"nr %llu\n",
|
||||
(unsigned long long)
|
||||
btrfs_file_extent_disk_bytenr(l, fi),
|
||||
(unsigned long long)
|
||||
btrfs_file_extent_disk_num_bytes(l, fi));
|
||||
printk(KERN_INFO "\t\textent data offset %llu "
|
||||
"nr %llu ram %llu\n",
|
||||
(unsigned long long)
|
||||
btrfs_file_extent_offset(l, fi),
|
||||
(unsigned long long)
|
||||
btrfs_file_extent_num_bytes(l, fi),
|
||||
(unsigned long long)
|
||||
btrfs_file_extent_ram_bytes(l, fi));
|
||||
break;
|
||||
case BTRFS_BLOCK_GROUP_ITEM_KEY:
|
||||
bi = btrfs_item_ptr(l, i,
|
||||
struct btrfs_block_group_item);
|
||||
printk(KERN_INFO "\t\tblock group used %llu\n",
|
||||
(unsigned long long)
|
||||
btrfs_disk_block_group_used(l, bi));
|
||||
break;
|
||||
case BTRFS_CHUNK_ITEM_KEY:
|
||||
print_chunk(l, btrfs_item_ptr(l, i,
|
||||
struct btrfs_chunk));
|
||||
break;
|
||||
case BTRFS_DEV_ITEM_KEY:
|
||||
print_dev_item(l, btrfs_item_ptr(l, i,
|
||||
struct btrfs_dev_item));
|
||||
break;
|
||||
case BTRFS_DEV_EXTENT_KEY:
|
||||
dev_extent = btrfs_item_ptr(l, i,
|
||||
struct btrfs_dev_extent);
|
||||
printk(KERN_INFO "\t\tdev extent chunk_tree %llu\n"
|
||||
"\t\tchunk objectid %llu chunk offset %llu "
|
||||
"length %llu\n",
|
||||
(unsigned long long)
|
||||
btrfs_dev_extent_chunk_tree(l, dev_extent),
|
||||
(unsigned long long)
|
||||
btrfs_dev_extent_chunk_objectid(l, dev_extent),
|
||||
(unsigned long long)
|
||||
btrfs_dev_extent_chunk_offset(l, dev_extent),
|
||||
(unsigned long long)
|
||||
btrfs_dev_extent_length(l, dev_extent));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void btrfs_print_tree(struct btrfs_root *root, struct extent_buffer *c)
|
||||
{
|
||||
int i; u32 nr;
|
||||
struct btrfs_key key;
|
||||
int level;
|
||||
|
||||
if (!c)
|
||||
return;
|
||||
nr = btrfs_header_nritems(c);
|
||||
level = btrfs_header_level(c);
|
||||
if (level == 0) {
|
||||
btrfs_print_leaf(root, c);
|
||||
return;
|
||||
}
|
||||
printk(KERN_INFO "node %llu level %d total ptrs %d free spc %u\n",
|
||||
(unsigned long long)btrfs_header_bytenr(c),
|
||||
btrfs_header_level(c), nr,
|
||||
(u32)BTRFS_NODEPTRS_PER_BLOCK(root) - nr);
|
||||
for (i = 0; i < nr; i++) {
|
||||
btrfs_node_key_to_cpu(c, &key, i);
|
||||
printk(KERN_INFO "\tkey %d (%llu %u %llu) block %llu\n",
|
||||
i,
|
||||
(unsigned long long)key.objectid,
|
||||
key.type,
|
||||
(unsigned long long)key.offset,
|
||||
(unsigned long long)btrfs_node_blockptr(c, i));
|
||||
}
|
||||
for (i = 0; i < nr; i++) {
|
||||
struct extent_buffer *next = read_tree_block(root,
|
||||
btrfs_node_blockptr(c, i),
|
||||
btrfs_level_size(root, level - 1),
|
||||
btrfs_node_ptr_generation(c, i));
|
||||
if (btrfs_is_leaf(next) &&
|
||||
btrfs_header_level(c) != 1)
|
||||
BUG();
|
||||
if (btrfs_header_level(next) !=
|
||||
btrfs_header_level(c) - 1)
|
||||
BUG();
|
||||
btrfs_print_tree(root, next);
|
||||
free_extent_buffer(next);
|
||||
}
|
||||
}
|
23
fs/btrfs/print-tree.h
Normal file
23
fs/btrfs/print-tree.h
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __PRINT_TREE_
|
||||
#define __PRINT_TREE_
|
||||
void btrfs_print_leaf(struct btrfs_root *root, struct extent_buffer *l);
|
||||
void btrfs_print_tree(struct btrfs_root *root, struct extent_buffer *t);
|
||||
#endif
|
230
fs/btrfs/ref-cache.c
Normal file
230
fs/btrfs/ref-cache.c
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include "ctree.h"
|
||||
#include "ref-cache.h"
|
||||
#include "transaction.h"
|
||||
|
||||
/*
|
||||
* leaf refs are used to cache the information about which extents
|
||||
* a given leaf has references on. This allows us to process that leaf
|
||||
* in btrfs_drop_snapshot without needing to read it back from disk.
|
||||
*/
|
||||
|
||||
/*
|
||||
* kmalloc a leaf reference struct and update the counters for the
|
||||
* total ref cache size
|
||||
*/
|
||||
struct btrfs_leaf_ref *btrfs_alloc_leaf_ref(struct btrfs_root *root,
|
||||
int nr_extents)
|
||||
{
|
||||
struct btrfs_leaf_ref *ref;
|
||||
size_t size = btrfs_leaf_ref_size(nr_extents);
|
||||
|
||||
ref = kmalloc(size, GFP_NOFS);
|
||||
if (ref) {
|
||||
spin_lock(&root->fs_info->ref_cache_lock);
|
||||
root->fs_info->total_ref_cache_size += size;
|
||||
spin_unlock(&root->fs_info->ref_cache_lock);
|
||||
|
||||
memset(ref, 0, sizeof(*ref));
|
||||
atomic_set(&ref->usage, 1);
|
||||
INIT_LIST_HEAD(&ref->list);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
/*
|
||||
* free a leaf reference struct and update the counters for the
|
||||
* total ref cache size
|
||||
*/
|
||||
void btrfs_free_leaf_ref(struct btrfs_root *root, struct btrfs_leaf_ref *ref)
|
||||
{
|
||||
if (!ref)
|
||||
return;
|
||||
WARN_ON(atomic_read(&ref->usage) == 0);
|
||||
if (atomic_dec_and_test(&ref->usage)) {
|
||||
size_t size = btrfs_leaf_ref_size(ref->nritems);
|
||||
|
||||
BUG_ON(ref->in_tree);
|
||||
kfree(ref);
|
||||
|
||||
spin_lock(&root->fs_info->ref_cache_lock);
|
||||
root->fs_info->total_ref_cache_size -= size;
|
||||
spin_unlock(&root->fs_info->ref_cache_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static struct rb_node *tree_insert(struct rb_root *root, u64 bytenr,
|
||||
struct rb_node *node)
|
||||
{
|
||||
struct rb_node **p = &root->rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
struct btrfs_leaf_ref *entry;
|
||||
|
||||
while (*p) {
|
||||
parent = *p;
|
||||
entry = rb_entry(parent, struct btrfs_leaf_ref, rb_node);
|
||||
|
||||
if (bytenr < entry->bytenr)
|
||||
p = &(*p)->rb_left;
|
||||
else if (bytenr > entry->bytenr)
|
||||
p = &(*p)->rb_right;
|
||||
else
|
||||
return parent;
|
||||
}
|
||||
|
||||
entry = rb_entry(node, struct btrfs_leaf_ref, rb_node);
|
||||
rb_link_node(node, parent, p);
|
||||
rb_insert_color(node, root);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct rb_node *tree_search(struct rb_root *root, u64 bytenr)
|
||||
{
|
||||
struct rb_node *n = root->rb_node;
|
||||
struct btrfs_leaf_ref *entry;
|
||||
|
||||
while (n) {
|
||||
entry = rb_entry(n, struct btrfs_leaf_ref, rb_node);
|
||||
WARN_ON(!entry->in_tree);
|
||||
|
||||
if (bytenr < entry->bytenr)
|
||||
n = n->rb_left;
|
||||
else if (bytenr > entry->bytenr)
|
||||
n = n->rb_right;
|
||||
else
|
||||
return n;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int btrfs_remove_leaf_refs(struct btrfs_root *root, u64 max_root_gen,
|
||||
int shared)
|
||||
{
|
||||
struct btrfs_leaf_ref *ref = NULL;
|
||||
struct btrfs_leaf_ref_tree *tree = root->ref_tree;
|
||||
|
||||
if (shared)
|
||||
tree = &root->fs_info->shared_ref_tree;
|
||||
if (!tree)
|
||||
return 0;
|
||||
|
||||
spin_lock(&tree->lock);
|
||||
while (!list_empty(&tree->list)) {
|
||||
ref = list_entry(tree->list.next, struct btrfs_leaf_ref, list);
|
||||
BUG_ON(ref->tree != tree);
|
||||
if (ref->root_gen > max_root_gen)
|
||||
break;
|
||||
if (!xchg(&ref->in_tree, 0)) {
|
||||
cond_resched_lock(&tree->lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
rb_erase(&ref->rb_node, &tree->root);
|
||||
list_del_init(&ref->list);
|
||||
|
||||
spin_unlock(&tree->lock);
|
||||
btrfs_free_leaf_ref(root, ref);
|
||||
cond_resched();
|
||||
spin_lock(&tree->lock);
|
||||
}
|
||||
spin_unlock(&tree->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* find the leaf ref for a given extent. This returns the ref struct with
|
||||
* a usage reference incremented
|
||||
*/
|
||||
struct btrfs_leaf_ref *btrfs_lookup_leaf_ref(struct btrfs_root *root,
|
||||
u64 bytenr)
|
||||
{
|
||||
struct rb_node *rb;
|
||||
struct btrfs_leaf_ref *ref = NULL;
|
||||
struct btrfs_leaf_ref_tree *tree = root->ref_tree;
|
||||
again:
|
||||
if (tree) {
|
||||
spin_lock(&tree->lock);
|
||||
rb = tree_search(&tree->root, bytenr);
|
||||
if (rb)
|
||||
ref = rb_entry(rb, struct btrfs_leaf_ref, rb_node);
|
||||
if (ref)
|
||||
atomic_inc(&ref->usage);
|
||||
spin_unlock(&tree->lock);
|
||||
if (ref)
|
||||
return ref;
|
||||
}
|
||||
if (tree != &root->fs_info->shared_ref_tree) {
|
||||
tree = &root->fs_info->shared_ref_tree;
|
||||
goto again;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* add a fully filled in leaf ref struct
|
||||
* remove all the refs older than a given root generation
|
||||
*/
|
||||
int btrfs_add_leaf_ref(struct btrfs_root *root, struct btrfs_leaf_ref *ref,
|
||||
int shared)
|
||||
{
|
||||
int ret = 0;
|
||||
struct rb_node *rb;
|
||||
struct btrfs_leaf_ref_tree *tree = root->ref_tree;
|
||||
|
||||
if (shared)
|
||||
tree = &root->fs_info->shared_ref_tree;
|
||||
|
||||
spin_lock(&tree->lock);
|
||||
rb = tree_insert(&tree->root, ref->bytenr, &ref->rb_node);
|
||||
if (rb) {
|
||||
ret = -EEXIST;
|
||||
} else {
|
||||
atomic_inc(&ref->usage);
|
||||
ref->tree = tree;
|
||||
ref->in_tree = 1;
|
||||
list_add_tail(&ref->list, &tree->list);
|
||||
}
|
||||
spin_unlock(&tree->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* remove a single leaf ref from the tree. This drops the ref held by the tree
|
||||
* only
|
||||
*/
|
||||
int btrfs_remove_leaf_ref(struct btrfs_root *root, struct btrfs_leaf_ref *ref)
|
||||
{
|
||||
struct btrfs_leaf_ref_tree *tree;
|
||||
|
||||
if (!xchg(&ref->in_tree, 0))
|
||||
return 0;
|
||||
|
||||
tree = ref->tree;
|
||||
spin_lock(&tree->lock);
|
||||
|
||||
rb_erase(&ref->rb_node, &tree->root);
|
||||
list_del_init(&ref->list);
|
||||
|
||||
spin_unlock(&tree->lock);
|
||||
|
||||
btrfs_free_leaf_ref(root, ref);
|
||||
return 0;
|
||||
}
|
77
fs/btrfs/ref-cache.h
Normal file
77
fs/btrfs/ref-cache.h
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
#ifndef __REFCACHE__
|
||||
#define __REFCACHE__
|
||||
|
||||
struct btrfs_extent_info {
|
||||
/* bytenr and num_bytes find the extent in the extent allocation tree */
|
||||
u64 bytenr;
|
||||
u64 num_bytes;
|
||||
|
||||
/* objectid and offset find the back reference for the file */
|
||||
u64 objectid;
|
||||
u64 offset;
|
||||
};
|
||||
|
||||
struct btrfs_leaf_ref {
|
||||
struct rb_node rb_node;
|
||||
struct btrfs_leaf_ref_tree *tree;
|
||||
int in_tree;
|
||||
atomic_t usage;
|
||||
|
||||
u64 root_gen;
|
||||
u64 bytenr;
|
||||
u64 owner;
|
||||
u64 generation;
|
||||
int nritems;
|
||||
|
||||
struct list_head list;
|
||||
struct btrfs_extent_info extents[];
|
||||
};
|
||||
|
||||
static inline size_t btrfs_leaf_ref_size(int nr_extents)
|
||||
{
|
||||
return sizeof(struct btrfs_leaf_ref) +
|
||||
sizeof(struct btrfs_extent_info) * nr_extents;
|
||||
}
|
||||
|
||||
static inline void btrfs_leaf_ref_tree_init(struct btrfs_leaf_ref_tree *tree)
|
||||
{
|
||||
tree->root.rb_node = NULL;
|
||||
INIT_LIST_HEAD(&tree->list);
|
||||
spin_lock_init(&tree->lock);
|
||||
}
|
||||
|
||||
static inline int btrfs_leaf_ref_tree_empty(struct btrfs_leaf_ref_tree *tree)
|
||||
{
|
||||
return RB_EMPTY_ROOT(&tree->root);
|
||||
}
|
||||
|
||||
void btrfs_leaf_ref_tree_init(struct btrfs_leaf_ref_tree *tree);
|
||||
struct btrfs_leaf_ref *btrfs_alloc_leaf_ref(struct btrfs_root *root,
|
||||
int nr_extents);
|
||||
void btrfs_free_leaf_ref(struct btrfs_root *root, struct btrfs_leaf_ref *ref);
|
||||
struct btrfs_leaf_ref *btrfs_lookup_leaf_ref(struct btrfs_root *root,
|
||||
u64 bytenr);
|
||||
int btrfs_add_leaf_ref(struct btrfs_root *root, struct btrfs_leaf_ref *ref,
|
||||
int shared);
|
||||
int btrfs_remove_leaf_refs(struct btrfs_root *root, u64 max_root_gen,
|
||||
int shared);
|
||||
int btrfs_remove_leaf_ref(struct btrfs_root *root, struct btrfs_leaf_ref *ref);
|
||||
|
||||
#endif
|
366
fs/btrfs/root-tree.c
Normal file
366
fs/btrfs/root-tree.c
Normal file
@ -0,0 +1,366 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include "ctree.h"
|
||||
#include "transaction.h"
|
||||
#include "disk-io.h"
|
||||
#include "print-tree.h"
|
||||
|
||||
/*
|
||||
* search forward for a root, starting with objectid 'search_start'
|
||||
* if a root key is found, the objectid we find is filled into 'found_objectid'
|
||||
* and 0 is returned. < 0 is returned on error, 1 if there is nothing
|
||||
* left in the tree.
|
||||
*/
|
||||
int btrfs_search_root(struct btrfs_root *root, u64 search_start,
|
||||
u64 *found_objectid)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_key search_key;
|
||||
int ret;
|
||||
|
||||
root = root->fs_info->tree_root;
|
||||
search_key.objectid = search_start;
|
||||
search_key.type = (u8)-1;
|
||||
search_key.offset = (u64)-1;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
again:
|
||||
ret = btrfs_search_slot(NULL, root, &search_key, path, 0, 0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
if (ret == 0) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) {
|
||||
ret = btrfs_next_leaf(root, path);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
btrfs_item_key_to_cpu(path->nodes[0], &search_key, path->slots[0]);
|
||||
if (search_key.type != BTRFS_ROOT_ITEM_KEY) {
|
||||
search_key.offset++;
|
||||
btrfs_release_path(root, path);
|
||||
goto again;
|
||||
}
|
||||
ret = 0;
|
||||
*found_objectid = search_key.objectid;
|
||||
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* lookup the root with the highest offset for a given objectid. The key we do
|
||||
* find is copied into 'key'. If we find something return 0, otherwise 1, < 0
|
||||
* on error.
|
||||
*/
|
||||
int btrfs_find_last_root(struct btrfs_root *root, u64 objectid,
|
||||
struct btrfs_root_item *item, struct btrfs_key *key)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_key search_key;
|
||||
struct btrfs_key found_key;
|
||||
struct extent_buffer *l;
|
||||
int ret;
|
||||
int slot;
|
||||
|
||||
search_key.objectid = objectid;
|
||||
search_key.type = BTRFS_ROOT_ITEM_KEY;
|
||||
search_key.offset = (u64)-1;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
ret = btrfs_search_slot(NULL, root, &search_key, path, 0, 0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
BUG_ON(ret == 0);
|
||||
l = path->nodes[0];
|
||||
BUG_ON(path->slots[0] == 0);
|
||||
slot = path->slots[0] - 1;
|
||||
btrfs_item_key_to_cpu(l, &found_key, slot);
|
||||
if (found_key.objectid != objectid) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
read_extent_buffer(l, item, btrfs_item_ptr_offset(l, slot),
|
||||
sizeof(*item));
|
||||
memcpy(key, &found_key, sizeof(found_key));
|
||||
ret = 0;
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* copy the data in 'item' into the btree
|
||||
*/
|
||||
int btrfs_update_root(struct btrfs_trans_handle *trans, struct btrfs_root
|
||||
*root, struct btrfs_key *key, struct btrfs_root_item
|
||||
*item)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
struct extent_buffer *l;
|
||||
int ret;
|
||||
int slot;
|
||||
unsigned long ptr;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
ret = btrfs_search_slot(trans, root, key, path, 0, 1);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (ret != 0) {
|
||||
btrfs_print_leaf(root, path->nodes[0]);
|
||||
printk(KERN_CRIT "unable to update root key %llu %u %llu\n",
|
||||
(unsigned long long)key->objectid, key->type,
|
||||
(unsigned long long)key->offset);
|
||||
BUG_ON(1);
|
||||
}
|
||||
|
||||
l = path->nodes[0];
|
||||
slot = path->slots[0];
|
||||
ptr = btrfs_item_ptr_offset(l, slot);
|
||||
write_extent_buffer(l, item, ptr, sizeof(*item));
|
||||
btrfs_mark_buffer_dirty(path->nodes[0]);
|
||||
out:
|
||||
btrfs_release_path(root, path);
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btrfs_insert_root(struct btrfs_trans_handle *trans, struct btrfs_root
|
||||
*root, struct btrfs_key *key, struct btrfs_root_item
|
||||
*item)
|
||||
{
|
||||
int ret;
|
||||
ret = btrfs_insert_item(trans, root, key, item, sizeof(*item));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* at mount time we want to find all the old transaction snapshots that were in
|
||||
* the process of being deleted if we crashed. This is any root item with an
|
||||
* offset lower than the latest root. They need to be queued for deletion to
|
||||
* finish what was happening when we crashed.
|
||||
*/
|
||||
int btrfs_find_dead_roots(struct btrfs_root *root, u64 objectid,
|
||||
struct btrfs_root *latest)
|
||||
{
|
||||
struct btrfs_root *dead_root;
|
||||
struct btrfs_item *item;
|
||||
struct btrfs_root_item *ri;
|
||||
struct btrfs_key key;
|
||||
struct btrfs_key found_key;
|
||||
struct btrfs_path *path;
|
||||
int ret;
|
||||
u32 nritems;
|
||||
struct extent_buffer *leaf;
|
||||
int slot;
|
||||
|
||||
key.objectid = objectid;
|
||||
btrfs_set_key_type(&key, BTRFS_ROOT_ITEM_KEY);
|
||||
key.offset = 0;
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
again:
|
||||
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
while (1) {
|
||||
leaf = path->nodes[0];
|
||||
nritems = btrfs_header_nritems(leaf);
|
||||
slot = path->slots[0];
|
||||
if (slot >= nritems) {
|
||||
ret = btrfs_next_leaf(root, path);
|
||||
if (ret)
|
||||
break;
|
||||
leaf = path->nodes[0];
|
||||
nritems = btrfs_header_nritems(leaf);
|
||||
slot = path->slots[0];
|
||||
}
|
||||
item = btrfs_item_nr(leaf, slot);
|
||||
btrfs_item_key_to_cpu(leaf, &key, slot);
|
||||
if (btrfs_key_type(&key) != BTRFS_ROOT_ITEM_KEY)
|
||||
goto next;
|
||||
|
||||
if (key.objectid < objectid)
|
||||
goto next;
|
||||
|
||||
if (key.objectid > objectid)
|
||||
break;
|
||||
|
||||
ri = btrfs_item_ptr(leaf, slot, struct btrfs_root_item);
|
||||
if (btrfs_disk_root_refs(leaf, ri) != 0)
|
||||
goto next;
|
||||
|
||||
memcpy(&found_key, &key, sizeof(key));
|
||||
key.offset++;
|
||||
btrfs_release_path(root, path);
|
||||
dead_root =
|
||||
btrfs_read_fs_root_no_radix(root->fs_info->tree_root,
|
||||
&found_key);
|
||||
if (IS_ERR(dead_root)) {
|
||||
ret = PTR_ERR(dead_root);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (objectid == BTRFS_TREE_RELOC_OBJECTID)
|
||||
ret = btrfs_add_dead_reloc_root(dead_root);
|
||||
else
|
||||
ret = btrfs_add_dead_root(dead_root, latest);
|
||||
if (ret)
|
||||
goto err;
|
||||
goto again;
|
||||
next:
|
||||
slot++;
|
||||
path->slots[0]++;
|
||||
}
|
||||
ret = 0;
|
||||
err:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* drop the root item for 'key' from 'root' */
|
||||
int btrfs_del_root(struct btrfs_trans_handle *trans, struct btrfs_root *root,
|
||||
struct btrfs_key *key)
|
||||
{
|
||||
struct btrfs_path *path;
|
||||
int ret;
|
||||
u32 refs;
|
||||
struct btrfs_root_item *ri;
|
||||
struct extent_buffer *leaf;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
BUG_ON(!path);
|
||||
ret = btrfs_search_slot(trans, root, key, path, -1, 1);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
BUG_ON(ret != 0);
|
||||
leaf = path->nodes[0];
|
||||
ri = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_root_item);
|
||||
|
||||
refs = btrfs_disk_root_refs(leaf, ri);
|
||||
BUG_ON(refs != 0);
|
||||
ret = btrfs_del_item(trans, root, path);
|
||||
out:
|
||||
btrfs_release_path(root, path);
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if 0 /* this will get used when snapshot deletion is implemented */
|
||||
int btrfs_del_root_ref(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *tree_root,
|
||||
u64 root_id, u8 type, u64 ref_id)
|
||||
{
|
||||
struct btrfs_key key;
|
||||
int ret;
|
||||
struct btrfs_path *path;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
|
||||
key.objectid = root_id;
|
||||
key.type = type;
|
||||
key.offset = ref_id;
|
||||
|
||||
ret = btrfs_search_slot(trans, tree_root, &key, path, -1, 1);
|
||||
BUG_ON(ret);
|
||||
|
||||
ret = btrfs_del_item(trans, tree_root, path);
|
||||
BUG_ON(ret);
|
||||
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
int btrfs_find_root_ref(struct btrfs_root *tree_root,
|
||||
struct btrfs_path *path,
|
||||
u64 root_id, u64 ref_id)
|
||||
{
|
||||
struct btrfs_key key;
|
||||
int ret;
|
||||
|
||||
key.objectid = root_id;
|
||||
key.type = BTRFS_ROOT_REF_KEY;
|
||||
key.offset = ref_id;
|
||||
|
||||
ret = btrfs_search_slot(NULL, tree_root, &key, path, 0, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* add a btrfs_root_ref item. type is either BTRFS_ROOT_REF_KEY
|
||||
* or BTRFS_ROOT_BACKREF_KEY.
|
||||
*
|
||||
* The dirid, sequence, name and name_len refer to the directory entry
|
||||
* that is referencing the root.
|
||||
*
|
||||
* For a forward ref, the root_id is the id of the tree referencing
|
||||
* the root and ref_id is the id of the subvol or snapshot.
|
||||
*
|
||||
* For a back ref the root_id is the id of the subvol or snapshot and
|
||||
* ref_id is the id of the tree referencing it.
|
||||
*/
|
||||
int btrfs_add_root_ref(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *tree_root,
|
||||
u64 root_id, u8 type, u64 ref_id,
|
||||
u64 dirid, u64 sequence,
|
||||
const char *name, int name_len)
|
||||
{
|
||||
struct btrfs_key key;
|
||||
int ret;
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_root_ref *ref;
|
||||
struct extent_buffer *leaf;
|
||||
unsigned long ptr;
|
||||
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
|
||||
key.objectid = root_id;
|
||||
key.type = type;
|
||||
key.offset = ref_id;
|
||||
|
||||
ret = btrfs_insert_empty_item(trans, tree_root, path, &key,
|
||||
sizeof(*ref) + name_len);
|
||||
BUG_ON(ret);
|
||||
|
||||
leaf = path->nodes[0];
|
||||
ref = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_root_ref);
|
||||
btrfs_set_root_ref_dirid(leaf, ref, dirid);
|
||||
btrfs_set_root_ref_sequence(leaf, ref, sequence);
|
||||
btrfs_set_root_ref_name_len(leaf, ref, name_len);
|
||||
ptr = (unsigned long)(ref + 1);
|
||||
write_extent_buffer(leaf, name, ptr, name_len);
|
||||
btrfs_mark_buffer_dirty(leaf);
|
||||
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
139
fs/btrfs/struct-funcs.c
Normal file
139
fs/btrfs/struct-funcs.c
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/highmem.h>
|
||||
|
||||
/* this is some deeply nasty code. ctree.h has a different
|
||||
* definition for this BTRFS_SETGET_FUNCS macro, behind a #ifndef
|
||||
*
|
||||
* The end result is that anyone who #includes ctree.h gets a
|
||||
* declaration for the btrfs_set_foo functions and btrfs_foo functions
|
||||
*
|
||||
* This file declares the macros and then #includes ctree.h, which results
|
||||
* in cpp creating the function here based on the template below.
|
||||
*
|
||||
* These setget functions do all the extent_buffer related mapping
|
||||
* required to efficiently read and write specific fields in the extent
|
||||
* buffers. Every pointer to metadata items in btrfs is really just
|
||||
* an unsigned long offset into the extent buffer which has been
|
||||
* cast to a specific type. This gives us all the gcc type checking.
|
||||
*
|
||||
* The extent buffer api is used to do all the kmapping and page
|
||||
* spanning work required to get extent buffers in highmem and have
|
||||
* a metadata blocksize different from the page size.
|
||||
*
|
||||
* The macro starts with a simple function prototype declaration so that
|
||||
* sparse won't complain about it being static.
|
||||
*/
|
||||
|
||||
#define BTRFS_SETGET_FUNCS(name, type, member, bits) \
|
||||
u##bits btrfs_##name(struct extent_buffer *eb, type *s); \
|
||||
void btrfs_set_##name(struct extent_buffer *eb, type *s, u##bits val); \
|
||||
u##bits btrfs_##name(struct extent_buffer *eb, \
|
||||
type *s) \
|
||||
{ \
|
||||
unsigned long part_offset = (unsigned long)s; \
|
||||
unsigned long offset = part_offset + offsetof(type, member); \
|
||||
type *p; \
|
||||
/* ugly, but we want the fast path here */ \
|
||||
if (eb->map_token && offset >= eb->map_start && \
|
||||
offset + sizeof(((type *)0)->member) <= eb->map_start + \
|
||||
eb->map_len) { \
|
||||
p = (type *)(eb->kaddr + part_offset - eb->map_start); \
|
||||
return le##bits##_to_cpu(p->member); \
|
||||
} \
|
||||
{ \
|
||||
int err; \
|
||||
char *map_token; \
|
||||
char *kaddr; \
|
||||
int unmap_on_exit = (eb->map_token == NULL); \
|
||||
unsigned long map_start; \
|
||||
unsigned long map_len; \
|
||||
u##bits res; \
|
||||
err = map_extent_buffer(eb, offset, \
|
||||
sizeof(((type *)0)->member), \
|
||||
&map_token, &kaddr, \
|
||||
&map_start, &map_len, KM_USER1); \
|
||||
if (err) { \
|
||||
__le##bits leres; \
|
||||
read_eb_member(eb, s, type, member, &leres); \
|
||||
return le##bits##_to_cpu(leres); \
|
||||
} \
|
||||
p = (type *)(kaddr + part_offset - map_start); \
|
||||
res = le##bits##_to_cpu(p->member); \
|
||||
if (unmap_on_exit) \
|
||||
unmap_extent_buffer(eb, map_token, KM_USER1); \
|
||||
return res; \
|
||||
} \
|
||||
} \
|
||||
void btrfs_set_##name(struct extent_buffer *eb, \
|
||||
type *s, u##bits val) \
|
||||
{ \
|
||||
unsigned long part_offset = (unsigned long)s; \
|
||||
unsigned long offset = part_offset + offsetof(type, member); \
|
||||
type *p; \
|
||||
/* ugly, but we want the fast path here */ \
|
||||
if (eb->map_token && offset >= eb->map_start && \
|
||||
offset + sizeof(((type *)0)->member) <= eb->map_start + \
|
||||
eb->map_len) { \
|
||||
p = (type *)(eb->kaddr + part_offset - eb->map_start); \
|
||||
p->member = cpu_to_le##bits(val); \
|
||||
return; \
|
||||
} \
|
||||
{ \
|
||||
int err; \
|
||||
char *map_token; \
|
||||
char *kaddr; \
|
||||
int unmap_on_exit = (eb->map_token == NULL); \
|
||||
unsigned long map_start; \
|
||||
unsigned long map_len; \
|
||||
err = map_extent_buffer(eb, offset, \
|
||||
sizeof(((type *)0)->member), \
|
||||
&map_token, &kaddr, \
|
||||
&map_start, &map_len, KM_USER1); \
|
||||
if (err) { \
|
||||
__le##bits val2; \
|
||||
val2 = cpu_to_le##bits(val); \
|
||||
write_eb_member(eb, s, type, member, &val2); \
|
||||
return; \
|
||||
} \
|
||||
p = (type *)(kaddr + part_offset - map_start); \
|
||||
p->member = cpu_to_le##bits(val); \
|
||||
if (unmap_on_exit) \
|
||||
unmap_extent_buffer(eb, map_token, KM_USER1); \
|
||||
} \
|
||||
}
|
||||
|
||||
#include "ctree.h"
|
||||
|
||||
void btrfs_node_key(struct extent_buffer *eb,
|
||||
struct btrfs_disk_key *disk_key, int nr)
|
||||
{
|
||||
unsigned long ptr = btrfs_node_key_ptr_offset(nr);
|
||||
if (eb->map_token && ptr >= eb->map_start &&
|
||||
ptr + sizeof(*disk_key) <= eb->map_start + eb->map_len) {
|
||||
memcpy(disk_key, eb->kaddr + ptr - eb->map_start,
|
||||
sizeof(*disk_key));
|
||||
return;
|
||||
} else if (eb->map_token) {
|
||||
unmap_extent_buffer(eb, eb->map_token, KM_USER1);
|
||||
eb->map_token = NULL;
|
||||
}
|
||||
read_eb_member(eb, (struct btrfs_key_ptr *)ptr,
|
||||
struct btrfs_key_ptr, key, disk_key);
|
||||
}
|
720
fs/btrfs/super.c
Normal file
720
fs/btrfs/super.c
Normal file
@ -0,0 +1,720 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/backing-dev.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/mpage.h>
|
||||
#include <linux/swap.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/statfs.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/parser.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/version.h>
|
||||
#include "compat.h"
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "transaction.h"
|
||||
#include "btrfs_inode.h"
|
||||
#include "ioctl.h"
|
||||
#include "print-tree.h"
|
||||
#include "xattr.h"
|
||||
#include "volumes.h"
|
||||
#include "version.h"
|
||||
#include "export.h"
|
||||
#include "compression.h"
|
||||
|
||||
#define BTRFS_SUPER_MAGIC 0x9123683E
|
||||
|
||||
static struct super_operations btrfs_super_ops;
|
||||
|
||||
static void btrfs_put_super(struct super_block *sb)
|
||||
{
|
||||
struct btrfs_root *root = btrfs_sb(sb);
|
||||
int ret;
|
||||
|
||||
ret = close_ctree(root);
|
||||
sb->s_fs_info = NULL;
|
||||
}
|
||||
|
||||
enum {
|
||||
Opt_degraded, Opt_subvol, Opt_device, Opt_nodatasum, Opt_nodatacow,
|
||||
Opt_max_extent, Opt_max_inline, Opt_alloc_start, Opt_nobarrier,
|
||||
Opt_ssd, Opt_thread_pool, Opt_noacl, Opt_compress, Opt_err,
|
||||
};
|
||||
|
||||
static match_table_t tokens = {
|
||||
{Opt_degraded, "degraded"},
|
||||
{Opt_subvol, "subvol=%s"},
|
||||
{Opt_device, "device=%s"},
|
||||
{Opt_nodatasum, "nodatasum"},
|
||||
{Opt_nodatacow, "nodatacow"},
|
||||
{Opt_nobarrier, "nobarrier"},
|
||||
{Opt_max_extent, "max_extent=%s"},
|
||||
{Opt_max_inline, "max_inline=%s"},
|
||||
{Opt_alloc_start, "alloc_start=%s"},
|
||||
{Opt_thread_pool, "thread_pool=%d"},
|
||||
{Opt_compress, "compress"},
|
||||
{Opt_ssd, "ssd"},
|
||||
{Opt_noacl, "noacl"},
|
||||
{Opt_err, NULL},
|
||||
};
|
||||
|
||||
u64 btrfs_parse_size(char *str)
|
||||
{
|
||||
u64 res;
|
||||
int mult = 1;
|
||||
char *end;
|
||||
char last;
|
||||
|
||||
res = simple_strtoul(str, &end, 10);
|
||||
|
||||
last = end[0];
|
||||
if (isalpha(last)) {
|
||||
last = tolower(last);
|
||||
switch (last) {
|
||||
case 'g':
|
||||
mult *= 1024;
|
||||
case 'm':
|
||||
mult *= 1024;
|
||||
case 'k':
|
||||
mult *= 1024;
|
||||
}
|
||||
res = res * mult;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Regular mount options parser. Everything that is needed only when
|
||||
* reading in a new superblock is parsed here.
|
||||
*/
|
||||
int btrfs_parse_options(struct btrfs_root *root, char *options)
|
||||
{
|
||||
struct btrfs_fs_info *info = root->fs_info;
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
char *p, *num;
|
||||
int intarg;
|
||||
|
||||
if (!options)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* strsep changes the string, duplicate it because parse_options
|
||||
* gets called twice
|
||||
*/
|
||||
options = kstrdup(options, GFP_NOFS);
|
||||
if (!options)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
while ((p = strsep(&options, ",")) != NULL) {
|
||||
int token;
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
token = match_token(p, tokens, args);
|
||||
switch (token) {
|
||||
case Opt_degraded:
|
||||
printk(KERN_INFO "btrfs: allowing degraded mounts\n");
|
||||
btrfs_set_opt(info->mount_opt, DEGRADED);
|
||||
break;
|
||||
case Opt_subvol:
|
||||
case Opt_device:
|
||||
/*
|
||||
* These are parsed by btrfs_parse_early_options
|
||||
* and can be happily ignored here.
|
||||
*/
|
||||
break;
|
||||
case Opt_nodatasum:
|
||||
printk(KERN_INFO "btrfs: setting nodatacsum\n");
|
||||
btrfs_set_opt(info->mount_opt, NODATASUM);
|
||||
break;
|
||||
case Opt_nodatacow:
|
||||
printk(KERN_INFO "btrfs: setting nodatacow\n");
|
||||
btrfs_set_opt(info->mount_opt, NODATACOW);
|
||||
btrfs_set_opt(info->mount_opt, NODATASUM);
|
||||
break;
|
||||
case Opt_compress:
|
||||
printk(KERN_INFO "btrfs: use compression\n");
|
||||
btrfs_set_opt(info->mount_opt, COMPRESS);
|
||||
break;
|
||||
case Opt_ssd:
|
||||
printk(KERN_INFO "btrfs: use ssd allocation scheme\n");
|
||||
btrfs_set_opt(info->mount_opt, SSD);
|
||||
break;
|
||||
case Opt_nobarrier:
|
||||
printk(KERN_INFO "btrfs: turning off barriers\n");
|
||||
btrfs_set_opt(info->mount_opt, NOBARRIER);
|
||||
break;
|
||||
case Opt_thread_pool:
|
||||
intarg = 0;
|
||||
match_int(&args[0], &intarg);
|
||||
if (intarg) {
|
||||
info->thread_pool_size = intarg;
|
||||
printk(KERN_INFO "btrfs: thread pool %d\n",
|
||||
info->thread_pool_size);
|
||||
}
|
||||
break;
|
||||
case Opt_max_extent:
|
||||
num = match_strdup(&args[0]);
|
||||
if (num) {
|
||||
info->max_extent = btrfs_parse_size(num);
|
||||
kfree(num);
|
||||
|
||||
info->max_extent = max_t(u64,
|
||||
info->max_extent, root->sectorsize);
|
||||
printk(KERN_INFO "btrfs: max_extent at %llu\n",
|
||||
info->max_extent);
|
||||
}
|
||||
break;
|
||||
case Opt_max_inline:
|
||||
num = match_strdup(&args[0]);
|
||||
if (num) {
|
||||
info->max_inline = btrfs_parse_size(num);
|
||||
kfree(num);
|
||||
|
||||
if (info->max_inline) {
|
||||
info->max_inline = max_t(u64,
|
||||
info->max_inline,
|
||||
root->sectorsize);
|
||||
}
|
||||
printk(KERN_INFO "btrfs: max_inline at %llu\n",
|
||||
info->max_inline);
|
||||
}
|
||||
break;
|
||||
case Opt_alloc_start:
|
||||
num = match_strdup(&args[0]);
|
||||
if (num) {
|
||||
info->alloc_start = btrfs_parse_size(num);
|
||||
kfree(num);
|
||||
printk(KERN_INFO
|
||||
"btrfs: allocations start at %llu\n",
|
||||
info->alloc_start);
|
||||
}
|
||||
break;
|
||||
case Opt_noacl:
|
||||
root->fs_info->sb->s_flags &= ~MS_POSIXACL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
kfree(options);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse mount options that are required early in the mount process.
|
||||
*
|
||||
* All other options will be parsed on much later in the mount process and
|
||||
* only when we need to allocate a new super block.
|
||||
*/
|
||||
static int btrfs_parse_early_options(const char *options, fmode_t flags,
|
||||
void *holder, char **subvol_name,
|
||||
struct btrfs_fs_devices **fs_devices)
|
||||
{
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
char *opts, *p;
|
||||
int error = 0;
|
||||
|
||||
if (!options)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* strsep changes the string, duplicate it because parse_options
|
||||
* gets called twice
|
||||
*/
|
||||
opts = kstrdup(options, GFP_KERNEL);
|
||||
if (!opts)
|
||||
return -ENOMEM;
|
||||
|
||||
while ((p = strsep(&opts, ",")) != NULL) {
|
||||
int token;
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
token = match_token(p, tokens, args);
|
||||
switch (token) {
|
||||
case Opt_subvol:
|
||||
*subvol_name = match_strdup(&args[0]);
|
||||
break;
|
||||
case Opt_device:
|
||||
error = btrfs_scan_one_device(match_strdup(&args[0]),
|
||||
flags, holder, fs_devices);
|
||||
if (error)
|
||||
goto out_free_opts;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out_free_opts:
|
||||
kfree(opts);
|
||||
out:
|
||||
/*
|
||||
* If no subvolume name is specified we use the default one. Allocate
|
||||
* a copy of the string "." here so that code later in the
|
||||
* mount path doesn't care if it's the default volume or another one.
|
||||
*/
|
||||
if (!*subvol_name) {
|
||||
*subvol_name = kstrdup(".", GFP_KERNEL);
|
||||
if (!*subvol_name)
|
||||
return -ENOMEM;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static int btrfs_fill_super(struct super_block *sb,
|
||||
struct btrfs_fs_devices *fs_devices,
|
||||
void *data, int silent)
|
||||
{
|
||||
struct inode *inode;
|
||||
struct dentry *root_dentry;
|
||||
struct btrfs_super_block *disk_super;
|
||||
struct btrfs_root *tree_root;
|
||||
struct btrfs_inode *bi;
|
||||
int err;
|
||||
|
||||
sb->s_maxbytes = MAX_LFS_FILESIZE;
|
||||
sb->s_magic = BTRFS_SUPER_MAGIC;
|
||||
sb->s_op = &btrfs_super_ops;
|
||||
sb->s_export_op = &btrfs_export_ops;
|
||||
sb->s_xattr = btrfs_xattr_handlers;
|
||||
sb->s_time_gran = 1;
|
||||
sb->s_flags |= MS_POSIXACL;
|
||||
|
||||
tree_root = open_ctree(sb, fs_devices, (char *)data);
|
||||
|
||||
if (IS_ERR(tree_root)) {
|
||||
printk("btrfs: open_ctree failed\n");
|
||||
return PTR_ERR(tree_root);
|
||||
}
|
||||
sb->s_fs_info = tree_root;
|
||||
disk_super = &tree_root->fs_info->super_copy;
|
||||
inode = btrfs_iget_locked(sb, BTRFS_FIRST_FREE_OBJECTID,
|
||||
tree_root->fs_info->fs_root);
|
||||
bi = BTRFS_I(inode);
|
||||
bi->location.objectid = inode->i_ino;
|
||||
bi->location.offset = 0;
|
||||
bi->root = tree_root->fs_info->fs_root;
|
||||
|
||||
btrfs_set_key_type(&bi->location, BTRFS_INODE_ITEM_KEY);
|
||||
|
||||
if (!inode) {
|
||||
err = -ENOMEM;
|
||||
goto fail_close;
|
||||
}
|
||||
if (inode->i_state & I_NEW) {
|
||||
btrfs_read_locked_inode(inode);
|
||||
unlock_new_inode(inode);
|
||||
}
|
||||
|
||||
root_dentry = d_alloc_root(inode);
|
||||
if (!root_dentry) {
|
||||
iput(inode);
|
||||
err = -ENOMEM;
|
||||
goto fail_close;
|
||||
}
|
||||
#if 0
|
||||
/* this does the super kobj at the same time */
|
||||
err = btrfs_sysfs_add_super(tree_root->fs_info);
|
||||
if (err)
|
||||
goto fail_close;
|
||||
#endif
|
||||
|
||||
sb->s_root = root_dentry;
|
||||
|
||||
save_mount_options(sb, data);
|
||||
return 0;
|
||||
|
||||
fail_close:
|
||||
close_ctree(tree_root);
|
||||
return err;
|
||||
}
|
||||
|
||||
int btrfs_sync_fs(struct super_block *sb, int wait)
|
||||
{
|
||||
struct btrfs_trans_handle *trans;
|
||||
struct btrfs_root *root;
|
||||
int ret;
|
||||
root = btrfs_sb(sb);
|
||||
|
||||
if (sb->s_flags & MS_RDONLY)
|
||||
return 0;
|
||||
|
||||
sb->s_dirt = 0;
|
||||
if (!wait) {
|
||||
filemap_flush(root->fs_info->btree_inode->i_mapping);
|
||||
return 0;
|
||||
}
|
||||
|
||||
btrfs_start_delalloc_inodes(root);
|
||||
btrfs_wait_ordered_extents(root, 0);
|
||||
|
||||
btrfs_clean_old_snapshots(root);
|
||||
trans = btrfs_start_transaction(root, 1);
|
||||
ret = btrfs_commit_transaction(trans, root);
|
||||
sb->s_dirt = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void btrfs_write_super(struct super_block *sb)
|
||||
{
|
||||
sb->s_dirt = 0;
|
||||
}
|
||||
|
||||
static int btrfs_test_super(struct super_block *s, void *data)
|
||||
{
|
||||
struct btrfs_fs_devices *test_fs_devices = data;
|
||||
struct btrfs_root *root = btrfs_sb(s);
|
||||
|
||||
return root->fs_info->fs_devices == test_fs_devices;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find a superblock for the given device / mount point.
|
||||
*
|
||||
* Note: This is based on get_sb_bdev from fs/super.c with a few additions
|
||||
* for multiple device setup. Make sure to keep it in sync.
|
||||
*/
|
||||
static int btrfs_get_sb(struct file_system_type *fs_type, int flags,
|
||||
const char *dev_name, void *data, struct vfsmount *mnt)
|
||||
{
|
||||
char *subvol_name = NULL;
|
||||
struct block_device *bdev = NULL;
|
||||
struct super_block *s;
|
||||
struct dentry *root;
|
||||
struct btrfs_fs_devices *fs_devices = NULL;
|
||||
fmode_t mode = FMODE_READ;
|
||||
int error = 0;
|
||||
|
||||
if (!(flags & MS_RDONLY))
|
||||
mode |= FMODE_WRITE;
|
||||
|
||||
error = btrfs_parse_early_options(data, mode, fs_type,
|
||||
&subvol_name, &fs_devices);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = btrfs_scan_one_device(dev_name, mode, fs_type, &fs_devices);
|
||||
if (error)
|
||||
goto error_free_subvol_name;
|
||||
|
||||
error = btrfs_open_devices(fs_devices, mode, fs_type);
|
||||
if (error)
|
||||
goto error_free_subvol_name;
|
||||
|
||||
if (!(flags & MS_RDONLY) && fs_devices->rw_devices == 0) {
|
||||
error = -EACCES;
|
||||
goto error_close_devices;
|
||||
}
|
||||
|
||||
bdev = fs_devices->latest_bdev;
|
||||
s = sget(fs_type, btrfs_test_super, set_anon_super, fs_devices);
|
||||
if (IS_ERR(s))
|
||||
goto error_s;
|
||||
|
||||
if (s->s_root) {
|
||||
if ((flags ^ s->s_flags) & MS_RDONLY) {
|
||||
up_write(&s->s_umount);
|
||||
deactivate_super(s);
|
||||
error = -EBUSY;
|
||||
goto error_close_devices;
|
||||
}
|
||||
|
||||
btrfs_close_devices(fs_devices);
|
||||
} else {
|
||||
char b[BDEVNAME_SIZE];
|
||||
|
||||
s->s_flags = flags;
|
||||
strlcpy(s->s_id, bdevname(bdev, b), sizeof(s->s_id));
|
||||
error = btrfs_fill_super(s, fs_devices, data,
|
||||
flags & MS_SILENT ? 1 : 0);
|
||||
if (error) {
|
||||
up_write(&s->s_umount);
|
||||
deactivate_super(s);
|
||||
goto error_free_subvol_name;
|
||||
}
|
||||
|
||||
btrfs_sb(s)->fs_info->bdev_holder = fs_type;
|
||||
s->s_flags |= MS_ACTIVE;
|
||||
}
|
||||
|
||||
if (!strcmp(subvol_name, "."))
|
||||
root = dget(s->s_root);
|
||||
else {
|
||||
mutex_lock(&s->s_root->d_inode->i_mutex);
|
||||
root = lookup_one_len(subvol_name, s->s_root,
|
||||
strlen(subvol_name));
|
||||
mutex_unlock(&s->s_root->d_inode->i_mutex);
|
||||
|
||||
if (IS_ERR(root)) {
|
||||
up_write(&s->s_umount);
|
||||
deactivate_super(s);
|
||||
error = PTR_ERR(root);
|
||||
goto error_free_subvol_name;
|
||||
}
|
||||
if (!root->d_inode) {
|
||||
dput(root);
|
||||
up_write(&s->s_umount);
|
||||
deactivate_super(s);
|
||||
error = -ENXIO;
|
||||
goto error_free_subvol_name;
|
||||
}
|
||||
}
|
||||
|
||||
mnt->mnt_sb = s;
|
||||
mnt->mnt_root = root;
|
||||
|
||||
kfree(subvol_name);
|
||||
return 0;
|
||||
|
||||
error_s:
|
||||
error = PTR_ERR(s);
|
||||
error_close_devices:
|
||||
btrfs_close_devices(fs_devices);
|
||||
error_free_subvol_name:
|
||||
kfree(subvol_name);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int btrfs_remount(struct super_block *sb, int *flags, char *data)
|
||||
{
|
||||
struct btrfs_root *root = btrfs_sb(sb);
|
||||
int ret;
|
||||
|
||||
if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY))
|
||||
return 0;
|
||||
|
||||
if (*flags & MS_RDONLY) {
|
||||
sb->s_flags |= MS_RDONLY;
|
||||
|
||||
ret = btrfs_commit_super(root);
|
||||
WARN_ON(ret);
|
||||
} else {
|
||||
if (root->fs_info->fs_devices->rw_devices == 0)
|
||||
return -EACCES;
|
||||
|
||||
if (btrfs_super_log_root(&root->fs_info->super_copy) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = btrfs_cleanup_reloc_trees(root);
|
||||
WARN_ON(ret);
|
||||
|
||||
ret = btrfs_cleanup_fs_roots(root->fs_info);
|
||||
WARN_ON(ret);
|
||||
|
||||
sb->s_flags &= ~MS_RDONLY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
|
||||
{
|
||||
struct btrfs_root *root = btrfs_sb(dentry->d_sb);
|
||||
struct btrfs_super_block *disk_super = &root->fs_info->super_copy;
|
||||
int bits = dentry->d_sb->s_blocksize_bits;
|
||||
__be32 *fsid = (__be32 *)root->fs_info->fsid;
|
||||
|
||||
buf->f_namelen = BTRFS_NAME_LEN;
|
||||
buf->f_blocks = btrfs_super_total_bytes(disk_super) >> bits;
|
||||
buf->f_bfree = buf->f_blocks -
|
||||
(btrfs_super_bytes_used(disk_super) >> bits);
|
||||
buf->f_bavail = buf->f_bfree;
|
||||
buf->f_bsize = dentry->d_sb->s_blocksize;
|
||||
buf->f_type = BTRFS_SUPER_MAGIC;
|
||||
|
||||
/* We treat it as constant endianness (it doesn't matter _which_)
|
||||
because we want the fsid to come out the same whether mounted
|
||||
on a big-endian or little-endian host */
|
||||
buf->f_fsid.val[0] = be32_to_cpu(fsid[0]) ^ be32_to_cpu(fsid[2]);
|
||||
buf->f_fsid.val[1] = be32_to_cpu(fsid[1]) ^ be32_to_cpu(fsid[3]);
|
||||
/* Mask in the root object ID too, to disambiguate subvols */
|
||||
buf->f_fsid.val[0] ^= BTRFS_I(dentry->d_inode)->root->objectid >> 32;
|
||||
buf->f_fsid.val[1] ^= BTRFS_I(dentry->d_inode)->root->objectid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct file_system_type btrfs_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "btrfs",
|
||||
.get_sb = btrfs_get_sb,
|
||||
.kill_sb = kill_anon_super,
|
||||
.fs_flags = FS_REQUIRES_DEV,
|
||||
};
|
||||
|
||||
/*
|
||||
* used by btrfsctl to scan devices when no FS is mounted
|
||||
*/
|
||||
static long btrfs_control_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct btrfs_ioctl_vol_args *vol;
|
||||
struct btrfs_fs_devices *fs_devices;
|
||||
int ret = 0;
|
||||
int len;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
vol = kmalloc(sizeof(*vol), GFP_KERNEL);
|
||||
if (copy_from_user(vol, (void __user *)arg, sizeof(*vol))) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
len = strnlen(vol->name, BTRFS_PATH_NAME_MAX);
|
||||
switch (cmd) {
|
||||
case BTRFS_IOC_SCAN_DEV:
|
||||
ret = btrfs_scan_one_device(vol->name, FMODE_READ,
|
||||
&btrfs_fs_type, &fs_devices);
|
||||
break;
|
||||
}
|
||||
out:
|
||||
kfree(vol);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void btrfs_write_super_lockfs(struct super_block *sb)
|
||||
{
|
||||
struct btrfs_root *root = btrfs_sb(sb);
|
||||
mutex_lock(&root->fs_info->transaction_kthread_mutex);
|
||||
mutex_lock(&root->fs_info->cleaner_mutex);
|
||||
}
|
||||
|
||||
static void btrfs_unlockfs(struct super_block *sb)
|
||||
{
|
||||
struct btrfs_root *root = btrfs_sb(sb);
|
||||
mutex_unlock(&root->fs_info->cleaner_mutex);
|
||||
mutex_unlock(&root->fs_info->transaction_kthread_mutex);
|
||||
}
|
||||
|
||||
static struct super_operations btrfs_super_ops = {
|
||||
.delete_inode = btrfs_delete_inode,
|
||||
.put_super = btrfs_put_super,
|
||||
.write_super = btrfs_write_super,
|
||||
.sync_fs = btrfs_sync_fs,
|
||||
.show_options = generic_show_options,
|
||||
.write_inode = btrfs_write_inode,
|
||||
.dirty_inode = btrfs_dirty_inode,
|
||||
.alloc_inode = btrfs_alloc_inode,
|
||||
.destroy_inode = btrfs_destroy_inode,
|
||||
.statfs = btrfs_statfs,
|
||||
.remount_fs = btrfs_remount,
|
||||
.write_super_lockfs = btrfs_write_super_lockfs,
|
||||
.unlockfs = btrfs_unlockfs,
|
||||
};
|
||||
|
||||
static const struct file_operations btrfs_ctl_fops = {
|
||||
.unlocked_ioctl = btrfs_control_ioctl,
|
||||
.compat_ioctl = btrfs_control_ioctl,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct miscdevice btrfs_misc = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "btrfs-control",
|
||||
.fops = &btrfs_ctl_fops
|
||||
};
|
||||
|
||||
static int btrfs_interface_init(void)
|
||||
{
|
||||
return misc_register(&btrfs_misc);
|
||||
}
|
||||
|
||||
static void btrfs_interface_exit(void)
|
||||
{
|
||||
if (misc_deregister(&btrfs_misc) < 0)
|
||||
printk(KERN_INFO "misc_deregister failed for control device");
|
||||
}
|
||||
|
||||
static int __init init_btrfs_fs(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = btrfs_init_sysfs();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = btrfs_init_cachep();
|
||||
if (err)
|
||||
goto free_sysfs;
|
||||
|
||||
err = extent_io_init();
|
||||
if (err)
|
||||
goto free_cachep;
|
||||
|
||||
err = extent_map_init();
|
||||
if (err)
|
||||
goto free_extent_io;
|
||||
|
||||
err = btrfs_interface_init();
|
||||
if (err)
|
||||
goto free_extent_map;
|
||||
|
||||
err = register_filesystem(&btrfs_fs_type);
|
||||
if (err)
|
||||
goto unregister_ioctl;
|
||||
|
||||
printk(KERN_INFO "%s loaded\n", BTRFS_BUILD_VERSION);
|
||||
return 0;
|
||||
|
||||
unregister_ioctl:
|
||||
btrfs_interface_exit();
|
||||
free_extent_map:
|
||||
extent_map_exit();
|
||||
free_extent_io:
|
||||
extent_io_exit();
|
||||
free_cachep:
|
||||
btrfs_destroy_cachep();
|
||||
free_sysfs:
|
||||
btrfs_exit_sysfs();
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit exit_btrfs_fs(void)
|
||||
{
|
||||
btrfs_destroy_cachep();
|
||||
extent_map_exit();
|
||||
extent_io_exit();
|
||||
btrfs_interface_exit();
|
||||
unregister_filesystem(&btrfs_fs_type);
|
||||
btrfs_exit_sysfs();
|
||||
btrfs_cleanup_fs_uuids();
|
||||
btrfs_zlib_exit();
|
||||
}
|
||||
|
||||
module_init(init_btrfs_fs)
|
||||
module_exit(exit_btrfs_fs)
|
||||
|
||||
MODULE_LICENSE("GPL");
|
269
fs/btrfs/sysfs.c
Normal file
269
fs/btrfs/sysfs.c
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kobject.h>
|
||||
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "transaction.h"
|
||||
|
||||
static ssize_t root_blocks_used_show(struct btrfs_root *root, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%llu\n",
|
||||
(unsigned long long)btrfs_root_used(&root->root_item));
|
||||
}
|
||||
|
||||
static ssize_t root_block_limit_show(struct btrfs_root *root, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%llu\n",
|
||||
(unsigned long long)btrfs_root_limit(&root->root_item));
|
||||
}
|
||||
|
||||
static ssize_t super_blocks_used_show(struct btrfs_fs_info *fs, char *buf)
|
||||
{
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%llu\n",
|
||||
(unsigned long long)btrfs_super_bytes_used(&fs->super_copy));
|
||||
}
|
||||
|
||||
static ssize_t super_total_blocks_show(struct btrfs_fs_info *fs, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%llu\n",
|
||||
(unsigned long long)btrfs_super_total_bytes(&fs->super_copy));
|
||||
}
|
||||
|
||||
static ssize_t super_blocksize_show(struct btrfs_fs_info *fs, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%llu\n",
|
||||
(unsigned long long)btrfs_super_sectorsize(&fs->super_copy));
|
||||
}
|
||||
|
||||
/* this is for root attrs (subvols/snapshots) */
|
||||
struct btrfs_root_attr {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct btrfs_root *, char *);
|
||||
ssize_t (*store)(struct btrfs_root *, const char *, size_t);
|
||||
};
|
||||
|
||||
#define ROOT_ATTR(name, mode, show, store) \
|
||||
static struct btrfs_root_attr btrfs_root_attr_##name = __ATTR(name, mode, \
|
||||
show, store)
|
||||
|
||||
ROOT_ATTR(blocks_used, 0444, root_blocks_used_show, NULL);
|
||||
ROOT_ATTR(block_limit, 0644, root_block_limit_show, NULL);
|
||||
|
||||
static struct attribute *btrfs_root_attrs[] = {
|
||||
&btrfs_root_attr_blocks_used.attr,
|
||||
&btrfs_root_attr_block_limit.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* this is for super attrs (actual full fs) */
|
||||
struct btrfs_super_attr {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct btrfs_fs_info *, char *);
|
||||
ssize_t (*store)(struct btrfs_fs_info *, const char *, size_t);
|
||||
};
|
||||
|
||||
#define SUPER_ATTR(name, mode, show, store) \
|
||||
static struct btrfs_super_attr btrfs_super_attr_##name = __ATTR(name, mode, \
|
||||
show, store)
|
||||
|
||||
SUPER_ATTR(blocks_used, 0444, super_blocks_used_show, NULL);
|
||||
SUPER_ATTR(total_blocks, 0444, super_total_blocks_show, NULL);
|
||||
SUPER_ATTR(blocksize, 0444, super_blocksize_show, NULL);
|
||||
|
||||
static struct attribute *btrfs_super_attrs[] = {
|
||||
&btrfs_super_attr_blocks_used.attr,
|
||||
&btrfs_super_attr_total_blocks.attr,
|
||||
&btrfs_super_attr_blocksize.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static ssize_t btrfs_super_attr_show(struct kobject *kobj,
|
||||
struct attribute *attr, char *buf)
|
||||
{
|
||||
struct btrfs_fs_info *fs = container_of(kobj, struct btrfs_fs_info,
|
||||
super_kobj);
|
||||
struct btrfs_super_attr *a = container_of(attr,
|
||||
struct btrfs_super_attr,
|
||||
attr);
|
||||
|
||||
return a->show ? a->show(fs, buf) : 0;
|
||||
}
|
||||
|
||||
static ssize_t btrfs_super_attr_store(struct kobject *kobj,
|
||||
struct attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct btrfs_fs_info *fs = container_of(kobj, struct btrfs_fs_info,
|
||||
super_kobj);
|
||||
struct btrfs_super_attr *a = container_of(attr,
|
||||
struct btrfs_super_attr,
|
||||
attr);
|
||||
|
||||
return a->store ? a->store(fs, buf, len) : 0;
|
||||
}
|
||||
|
||||
static ssize_t btrfs_root_attr_show(struct kobject *kobj,
|
||||
struct attribute *attr, char *buf)
|
||||
{
|
||||
struct btrfs_root *root = container_of(kobj, struct btrfs_root,
|
||||
root_kobj);
|
||||
struct btrfs_root_attr *a = container_of(attr,
|
||||
struct btrfs_root_attr,
|
||||
attr);
|
||||
|
||||
return a->show ? a->show(root, buf) : 0;
|
||||
}
|
||||
|
||||
static ssize_t btrfs_root_attr_store(struct kobject *kobj,
|
||||
struct attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct btrfs_root *root = container_of(kobj, struct btrfs_root,
|
||||
root_kobj);
|
||||
struct btrfs_root_attr *a = container_of(attr,
|
||||
struct btrfs_root_attr,
|
||||
attr);
|
||||
return a->store ? a->store(root, buf, len) : 0;
|
||||
}
|
||||
|
||||
static void btrfs_super_release(struct kobject *kobj)
|
||||
{
|
||||
struct btrfs_fs_info *fs = container_of(kobj, struct btrfs_fs_info,
|
||||
super_kobj);
|
||||
complete(&fs->kobj_unregister);
|
||||
}
|
||||
|
||||
static void btrfs_root_release(struct kobject *kobj)
|
||||
{
|
||||
struct btrfs_root *root = container_of(kobj, struct btrfs_root,
|
||||
root_kobj);
|
||||
complete(&root->kobj_unregister);
|
||||
}
|
||||
|
||||
static struct sysfs_ops btrfs_super_attr_ops = {
|
||||
.show = btrfs_super_attr_show,
|
||||
.store = btrfs_super_attr_store,
|
||||
};
|
||||
|
||||
static struct sysfs_ops btrfs_root_attr_ops = {
|
||||
.show = btrfs_root_attr_show,
|
||||
.store = btrfs_root_attr_store,
|
||||
};
|
||||
|
||||
static struct kobj_type btrfs_root_ktype = {
|
||||
.default_attrs = btrfs_root_attrs,
|
||||
.sysfs_ops = &btrfs_root_attr_ops,
|
||||
.release = btrfs_root_release,
|
||||
};
|
||||
|
||||
static struct kobj_type btrfs_super_ktype = {
|
||||
.default_attrs = btrfs_super_attrs,
|
||||
.sysfs_ops = &btrfs_super_attr_ops,
|
||||
.release = btrfs_super_release,
|
||||
};
|
||||
|
||||
/* /sys/fs/btrfs/ entry */
|
||||
static struct kset *btrfs_kset;
|
||||
|
||||
int btrfs_sysfs_add_super(struct btrfs_fs_info *fs)
|
||||
{
|
||||
int error;
|
||||
char *name;
|
||||
char c;
|
||||
int len = strlen(fs->sb->s_id) + 1;
|
||||
int i;
|
||||
|
||||
name = kmalloc(len, GFP_NOFS);
|
||||
if (!name) {
|
||||
error = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
c = fs->sb->s_id[i];
|
||||
if (c == '/' || c == '\\')
|
||||
c = '!';
|
||||
name[i] = c;
|
||||
}
|
||||
name[len] = '\0';
|
||||
|
||||
fs->super_kobj.kset = btrfs_kset;
|
||||
error = kobject_init_and_add(&fs->super_kobj, &btrfs_super_ktype,
|
||||
NULL, "%s", name);
|
||||
kfree(name);
|
||||
if (error)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
printk(KERN_ERR "btrfs: sysfs creation for super failed\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
int btrfs_sysfs_add_root(struct btrfs_root *root)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = kobject_init_and_add(&root->root_kobj, &btrfs_root_ktype,
|
||||
&root->fs_info->super_kobj,
|
||||
"%s", root->name);
|
||||
if (error)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
printk(KERN_ERR "btrfs: sysfs creation for root failed\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
void btrfs_sysfs_del_root(struct btrfs_root *root)
|
||||
{
|
||||
kobject_put(&root->root_kobj);
|
||||
wait_for_completion(&root->kobj_unregister);
|
||||
}
|
||||
|
||||
void btrfs_sysfs_del_super(struct btrfs_fs_info *fs)
|
||||
{
|
||||
kobject_put(&fs->super_kobj);
|
||||
wait_for_completion(&fs->kobj_unregister);
|
||||
}
|
||||
|
||||
int btrfs_init_sysfs(void)
|
||||
{
|
||||
btrfs_kset = kset_create_and_add("btrfs", NULL, fs_kobj);
|
||||
if (!btrfs_kset)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void btrfs_exit_sysfs(void)
|
||||
{
|
||||
kset_unregister(btrfs_kset);
|
||||
}
|
||||
|
1097
fs/btrfs/transaction.c
Normal file
1097
fs/btrfs/transaction.c
Normal file
File diff suppressed because it is too large
Load Diff
106
fs/btrfs/transaction.h
Normal file
106
fs/btrfs/transaction.h
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BTRFS_TRANSACTION__
|
||||
#define __BTRFS_TRANSACTION__
|
||||
#include "btrfs_inode.h"
|
||||
|
||||
struct btrfs_transaction {
|
||||
u64 transid;
|
||||
unsigned long num_writers;
|
||||
unsigned long num_joined;
|
||||
int in_commit;
|
||||
int use_count;
|
||||
int commit_done;
|
||||
int blocked;
|
||||
struct list_head list;
|
||||
struct extent_io_tree dirty_pages;
|
||||
unsigned long start_time;
|
||||
wait_queue_head_t writer_wait;
|
||||
wait_queue_head_t commit_wait;
|
||||
struct list_head pending_snapshots;
|
||||
};
|
||||
|
||||
struct btrfs_trans_handle {
|
||||
u64 transid;
|
||||
unsigned long blocks_reserved;
|
||||
unsigned long blocks_used;
|
||||
struct btrfs_transaction *transaction;
|
||||
u64 block_group;
|
||||
u64 alloc_exclude_start;
|
||||
u64 alloc_exclude_nr;
|
||||
};
|
||||
|
||||
struct btrfs_pending_snapshot {
|
||||
struct dentry *dentry;
|
||||
struct btrfs_root *root;
|
||||
char *name;
|
||||
struct btrfs_key root_key;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct btrfs_dirty_root {
|
||||
struct list_head list;
|
||||
struct btrfs_root *root;
|
||||
struct btrfs_root *latest_root;
|
||||
};
|
||||
|
||||
static inline void btrfs_set_trans_block_group(struct btrfs_trans_handle *trans,
|
||||
struct inode *inode)
|
||||
{
|
||||
trans->block_group = BTRFS_I(inode)->block_group;
|
||||
}
|
||||
|
||||
static inline void btrfs_update_inode_block_group(
|
||||
struct btrfs_trans_handle *trans,
|
||||
struct inode *inode)
|
||||
{
|
||||
BTRFS_I(inode)->block_group = trans->block_group;
|
||||
}
|
||||
|
||||
static inline void btrfs_set_inode_last_trans(struct btrfs_trans_handle *trans,
|
||||
struct inode *inode)
|
||||
{
|
||||
BTRFS_I(inode)->last_trans = trans->transaction->transid;
|
||||
}
|
||||
|
||||
int btrfs_end_transaction(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root);
|
||||
struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
|
||||
int num_blocks);
|
||||
struct btrfs_trans_handle *btrfs_join_transaction(struct btrfs_root *root,
|
||||
int num_blocks);
|
||||
struct btrfs_trans_handle *btrfs_start_ioctl_transaction(struct btrfs_root *r,
|
||||
int num_blocks);
|
||||
int btrfs_write_and_wait_transaction(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root);
|
||||
int btrfs_commit_tree_roots(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root);
|
||||
|
||||
int btrfs_add_dead_root(struct btrfs_root *root, struct btrfs_root *latest);
|
||||
int btrfs_defrag_root(struct btrfs_root *root, int cacheonly);
|
||||
int btrfs_clean_old_snapshots(struct btrfs_root *root);
|
||||
int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root);
|
||||
int btrfs_end_transaction_throttle(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root);
|
||||
void btrfs_throttle(struct btrfs_root *root);
|
||||
int btrfs_record_root_in_trans(struct btrfs_root *root);
|
||||
int btrfs_write_and_wait_marked_extents(struct btrfs_root *root,
|
||||
struct extent_io_tree *dirty_pages);
|
||||
#endif
|
147
fs/btrfs/tree-defrag.c
Normal file
147
fs/btrfs/tree-defrag.c
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include "ctree.h"
|
||||
#include "disk-io.h"
|
||||
#include "print-tree.h"
|
||||
#include "transaction.h"
|
||||
#include "locking.h"
|
||||
|
||||
/* defrag all the leaves in a given btree. If cache_only == 1, don't read
|
||||
* things from disk, otherwise read all the leaves and try to get key order to
|
||||
* better reflect disk order
|
||||
*/
|
||||
|
||||
int btrfs_defrag_leaves(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, int cache_only)
|
||||
{
|
||||
struct btrfs_path *path = NULL;
|
||||
struct btrfs_key key;
|
||||
int ret = 0;
|
||||
int wret;
|
||||
int level;
|
||||
int orig_level;
|
||||
int is_extent = 0;
|
||||
int next_key_ret = 0;
|
||||
u64 last_ret = 0;
|
||||
u64 min_trans = 0;
|
||||
|
||||
if (cache_only)
|
||||
goto out;
|
||||
|
||||
if (root->fs_info->extent_root == root) {
|
||||
/*
|
||||
* there's recursion here right now in the tree locking,
|
||||
* we can't defrag the extent root without deadlock
|
||||
*/
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (root->ref_cows == 0 && !is_extent)
|
||||
goto out;
|
||||
|
||||
if (btrfs_test_opt(root, SSD))
|
||||
goto out;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
level = btrfs_header_level(root->node);
|
||||
orig_level = level;
|
||||
|
||||
if (level == 0)
|
||||
goto out;
|
||||
|
||||
if (root->defrag_progress.objectid == 0) {
|
||||
struct extent_buffer *root_node;
|
||||
u32 nritems;
|
||||
|
||||
root_node = btrfs_lock_root_node(root);
|
||||
nritems = btrfs_header_nritems(root_node);
|
||||
root->defrag_max.objectid = 0;
|
||||
/* from above we know this is not a leaf */
|
||||
btrfs_node_key_to_cpu(root_node, &root->defrag_max,
|
||||
nritems - 1);
|
||||
btrfs_tree_unlock(root_node);
|
||||
free_extent_buffer(root_node);
|
||||
memset(&key, 0, sizeof(key));
|
||||
} else {
|
||||
memcpy(&key, &root->defrag_progress, sizeof(key));
|
||||
}
|
||||
|
||||
path->keep_locks = 1;
|
||||
if (cache_only)
|
||||
min_trans = root->defrag_trans_start;
|
||||
|
||||
ret = btrfs_search_forward(root, &key, NULL, path,
|
||||
cache_only, min_trans);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
if (ret > 0) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
btrfs_release_path(root, path);
|
||||
wret = btrfs_search_slot(trans, root, &key, path, 0, 1);
|
||||
|
||||
if (wret < 0) {
|
||||
ret = wret;
|
||||
goto out;
|
||||
}
|
||||
if (!path->nodes[1]) {
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
path->slots[1] = btrfs_header_nritems(path->nodes[1]);
|
||||
next_key_ret = btrfs_find_next_key(root, path, &key, 1, cache_only,
|
||||
min_trans);
|
||||
ret = btrfs_realloc_node(trans, root,
|
||||
path->nodes[1], 0,
|
||||
cache_only, &last_ret,
|
||||
&root->defrag_progress);
|
||||
WARN_ON(ret && ret != -EAGAIN);
|
||||
if (next_key_ret == 0) {
|
||||
memcpy(&root->defrag_progress, &key, sizeof(key));
|
||||
ret = -EAGAIN;
|
||||
}
|
||||
|
||||
btrfs_release_path(root, path);
|
||||
if (is_extent)
|
||||
btrfs_extent_post_op(trans, root);
|
||||
out:
|
||||
if (path)
|
||||
btrfs_free_path(path);
|
||||
if (ret == -EAGAIN) {
|
||||
if (root->defrag_max.objectid > root->defrag_progress.objectid)
|
||||
goto done;
|
||||
if (root->defrag_max.type > root->defrag_progress.type)
|
||||
goto done;
|
||||
if (root->defrag_max.offset > root->defrag_progress.offset)
|
||||
goto done;
|
||||
ret = 0;
|
||||
}
|
||||
done:
|
||||
if (ret != -EAGAIN) {
|
||||
memset(&root->defrag_progress, 0,
|
||||
sizeof(root->defrag_progress));
|
||||
root->defrag_trans_start = trans->transid;
|
||||
}
|
||||
return ret;
|
||||
}
|
2898
fs/btrfs/tree-log.c
Normal file
2898
fs/btrfs/tree-log.c
Normal file
File diff suppressed because it is too large
Load Diff
41
fs/btrfs/tree-log.h
Normal file
41
fs/btrfs/tree-log.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __TREE_LOG_
|
||||
#define __TREE_LOG_
|
||||
|
||||
int btrfs_sync_log(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root);
|
||||
int btrfs_free_log(struct btrfs_trans_handle *trans, struct btrfs_root *root);
|
||||
int btrfs_log_dentry(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, struct dentry *dentry);
|
||||
int btrfs_recover_log_trees(struct btrfs_root *tree_root);
|
||||
int btrfs_log_dentry_safe(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, struct dentry *dentry);
|
||||
int btrfs_log_inode(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root, struct inode *inode,
|
||||
int inode_only);
|
||||
int btrfs_del_dir_entries_in_log(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
const char *name, int name_len,
|
||||
struct inode *dir, u64 index);
|
||||
int btrfs_del_inode_ref_in_log(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
const char *name, int name_len,
|
||||
struct inode *inode, u64 dirid);
|
||||
#endif
|
4
fs/btrfs/version.h
Normal file
4
fs/btrfs/version.h
Normal file
@ -0,0 +1,4 @@
|
||||
#ifndef __BTRFS_VERSION_H
|
||||
#define __BTRFS_VERSION_H
|
||||
#define BTRFS_BUILD_VERSION "Btrfs"
|
||||
#endif
|
43
fs/btrfs/version.sh
Normal file
43
fs/btrfs/version.sh
Normal file
@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# determine-version -- report a useful version for releases
|
||||
#
|
||||
# Copyright 2008, Aron Griffis <agriffis@n01se.net>
|
||||
# Copyright 2008, Oracle
|
||||
# Released under the GNU GPLv2
|
||||
|
||||
v="v0.16"
|
||||
|
||||
which git &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
git branch >& /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
if head=`git rev-parse --verify HEAD 2>/dev/null`; then
|
||||
if tag=`git describe --tags 2>/dev/null`; then
|
||||
v="$tag"
|
||||
fi
|
||||
|
||||
# Are there uncommitted changes?
|
||||
git update-index --refresh --unmerged > /dev/null
|
||||
if git diff-index --name-only HEAD | \
|
||||
grep -v "^scripts/package" \
|
||||
| read dummy; then
|
||||
v="$v"-dirty
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "#ifndef __BUILD_VERSION" > .build-version.h
|
||||
echo "#define __BUILD_VERSION" >> .build-version.h
|
||||
echo "#define BTRFS_BUILD_VERSION \"Btrfs $v\"" >> .build-version.h
|
||||
echo "#endif" >> .build-version.h
|
||||
|
||||
diff -q version.h .build-version.h >& /dev/null
|
||||
|
||||
if [ $? == 0 ]; then
|
||||
rm .build-version.h
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mv .build-version.h version.h
|
3218
fs/btrfs/volumes.c
Normal file
3218
fs/btrfs/volumes.c
Normal file
File diff suppressed because it is too large
Load Diff
162
fs/btrfs/volumes.h
Normal file
162
fs/btrfs/volumes.h
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BTRFS_VOLUMES_
|
||||
#define __BTRFS_VOLUMES_
|
||||
|
||||
#include <linux/bio.h>
|
||||
#include "async-thread.h"
|
||||
|
||||
struct buffer_head;
|
||||
struct btrfs_device {
|
||||
struct list_head dev_list;
|
||||
struct list_head dev_alloc_list;
|
||||
struct btrfs_fs_devices *fs_devices;
|
||||
struct btrfs_root *dev_root;
|
||||
struct bio *pending_bios;
|
||||
struct bio *pending_bio_tail;
|
||||
int running_pending;
|
||||
u64 generation;
|
||||
|
||||
int barriers;
|
||||
int writeable;
|
||||
int in_fs_metadata;
|
||||
|
||||
spinlock_t io_lock;
|
||||
|
||||
struct block_device *bdev;
|
||||
|
||||
/* the mode sent to open_bdev_exclusive */
|
||||
fmode_t mode;
|
||||
|
||||
char *name;
|
||||
|
||||
/* the internal btrfs device id */
|
||||
u64 devid;
|
||||
|
||||
/* size of the device */
|
||||
u64 total_bytes;
|
||||
|
||||
/* bytes used */
|
||||
u64 bytes_used;
|
||||
|
||||
/* optimal io alignment for this device */
|
||||
u32 io_align;
|
||||
|
||||
/* optimal io width for this device */
|
||||
u32 io_width;
|
||||
|
||||
/* minimal io size for this device */
|
||||
u32 sector_size;
|
||||
|
||||
/* type and info about this device */
|
||||
u64 type;
|
||||
|
||||
/* physical drive uuid (or lvm uuid) */
|
||||
u8 uuid[BTRFS_UUID_SIZE];
|
||||
|
||||
struct btrfs_work work;
|
||||
};
|
||||
|
||||
struct btrfs_fs_devices {
|
||||
u8 fsid[BTRFS_FSID_SIZE]; /* FS specific uuid */
|
||||
|
||||
/* the device with this id has the most recent coyp of the super */
|
||||
u64 latest_devid;
|
||||
u64 latest_trans;
|
||||
u64 num_devices;
|
||||
u64 open_devices;
|
||||
u64 rw_devices;
|
||||
u64 total_rw_bytes;
|
||||
struct block_device *latest_bdev;
|
||||
/* all of the devices in the FS */
|
||||
struct list_head devices;
|
||||
|
||||
/* devices not currently being allocated */
|
||||
struct list_head alloc_list;
|
||||
struct list_head list;
|
||||
|
||||
struct btrfs_fs_devices *seed;
|
||||
int seeding;
|
||||
|
||||
int opened;
|
||||
};
|
||||
|
||||
struct btrfs_bio_stripe {
|
||||
struct btrfs_device *dev;
|
||||
u64 physical;
|
||||
};
|
||||
|
||||
struct btrfs_multi_bio {
|
||||
atomic_t stripes_pending;
|
||||
bio_end_io_t *end_io;
|
||||
struct bio *orig_bio;
|
||||
void *private;
|
||||
atomic_t error;
|
||||
int max_errors;
|
||||
int num_stripes;
|
||||
struct btrfs_bio_stripe stripes[];
|
||||
};
|
||||
|
||||
#define btrfs_multi_bio_size(n) (sizeof(struct btrfs_multi_bio) + \
|
||||
(sizeof(struct btrfs_bio_stripe) * (n)))
|
||||
|
||||
int btrfs_alloc_dev_extent(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_device *device,
|
||||
u64 chunk_tree, u64 chunk_objectid,
|
||||
u64 chunk_offset, u64 start, u64 num_bytes);
|
||||
int btrfs_map_block(struct btrfs_mapping_tree *map_tree, int rw,
|
||||
u64 logical, u64 *length,
|
||||
struct btrfs_multi_bio **multi_ret, int mirror_num);
|
||||
int btrfs_rmap_block(struct btrfs_mapping_tree *map_tree,
|
||||
u64 chunk_start, u64 physical, u64 devid,
|
||||
u64 **logical, int *naddrs, int *stripe_len);
|
||||
int btrfs_read_sys_array(struct btrfs_root *root);
|
||||
int btrfs_read_chunk_tree(struct btrfs_root *root);
|
||||
int btrfs_alloc_chunk(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *extent_root, u64 type);
|
||||
void btrfs_mapping_init(struct btrfs_mapping_tree *tree);
|
||||
void btrfs_mapping_tree_free(struct btrfs_mapping_tree *tree);
|
||||
int btrfs_map_bio(struct btrfs_root *root, int rw, struct bio *bio,
|
||||
int mirror_num, int async_submit);
|
||||
int btrfs_read_super_device(struct btrfs_root *root, struct extent_buffer *buf);
|
||||
int btrfs_open_devices(struct btrfs_fs_devices *fs_devices,
|
||||
fmode_t flags, void *holder);
|
||||
int btrfs_scan_one_device(const char *path, fmode_t flags, void *holder,
|
||||
struct btrfs_fs_devices **fs_devices_ret);
|
||||
int btrfs_close_devices(struct btrfs_fs_devices *fs_devices);
|
||||
int btrfs_close_extra_devices(struct btrfs_fs_devices *fs_devices);
|
||||
int btrfs_add_device(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_root *root,
|
||||
struct btrfs_device *device);
|
||||
int btrfs_rm_device(struct btrfs_root *root, char *device_path);
|
||||
int btrfs_cleanup_fs_uuids(void);
|
||||
int btrfs_num_copies(struct btrfs_mapping_tree *map_tree, u64 logical, u64 len);
|
||||
int btrfs_unplug_page(struct btrfs_mapping_tree *map_tree,
|
||||
u64 logical, struct page *page);
|
||||
int btrfs_grow_device(struct btrfs_trans_handle *trans,
|
||||
struct btrfs_device *device, u64 new_size);
|
||||
struct btrfs_device *btrfs_find_device(struct btrfs_root *root, u64 devid,
|
||||
u8 *uuid, u8 *fsid);
|
||||
int btrfs_shrink_device(struct btrfs_device *device, u64 new_size);
|
||||
int btrfs_init_new_device(struct btrfs_root *root, char *path);
|
||||
int btrfs_balance(struct btrfs_root *dev_root);
|
||||
void btrfs_unlock_volumes(void);
|
||||
void btrfs_lock_volumes(void);
|
||||
int btrfs_chunk_readonly(struct btrfs_root *root, u64 chunk_offset);
|
||||
#endif
|
322
fs/btrfs/xattr.c
Normal file
322
fs/btrfs/xattr.c
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Red Hat. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/xattr.h>
|
||||
#include "ctree.h"
|
||||
#include "btrfs_inode.h"
|
||||
#include "transaction.h"
|
||||
#include "xattr.h"
|
||||
#include "disk-io.h"
|
||||
|
||||
|
||||
ssize_t __btrfs_getxattr(struct inode *inode, const char *name,
|
||||
void *buffer, size_t size)
|
||||
{
|
||||
struct btrfs_dir_item *di;
|
||||
struct btrfs_root *root = BTRFS_I(inode)->root;
|
||||
struct btrfs_path *path;
|
||||
struct extent_buffer *leaf;
|
||||
int ret = 0;
|
||||
unsigned long data_ptr;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
/* lookup the xattr by name */
|
||||
di = btrfs_lookup_xattr(NULL, root, path, inode->i_ino, name,
|
||||
strlen(name), 0);
|
||||
if (!di || IS_ERR(di)) {
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
|
||||
leaf = path->nodes[0];
|
||||
/* if size is 0, that means we want the size of the attr */
|
||||
if (!size) {
|
||||
ret = btrfs_dir_data_len(leaf, di);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* now get the data out of our dir_item */
|
||||
if (btrfs_dir_data_len(leaf, di) > size) {
|
||||
ret = -ERANGE;
|
||||
goto out;
|
||||
}
|
||||
data_ptr = (unsigned long)((char *)(di + 1) +
|
||||
btrfs_dir_name_len(leaf, di));
|
||||
read_extent_buffer(leaf, buffer, data_ptr,
|
||||
btrfs_dir_data_len(leaf, di));
|
||||
ret = btrfs_dir_data_len(leaf, di);
|
||||
|
||||
out:
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __btrfs_setxattr(struct inode *inode, const char *name,
|
||||
const void *value, size_t size, int flags)
|
||||
{
|
||||
struct btrfs_dir_item *di;
|
||||
struct btrfs_root *root = BTRFS_I(inode)->root;
|
||||
struct btrfs_trans_handle *trans;
|
||||
struct btrfs_path *path;
|
||||
int ret = 0, mod = 0;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
trans = btrfs_start_transaction(root, 1);
|
||||
btrfs_set_trans_block_group(trans, inode);
|
||||
|
||||
/* first lets see if we already have this xattr */
|
||||
di = btrfs_lookup_xattr(trans, root, path, inode->i_ino, name,
|
||||
strlen(name), -1);
|
||||
if (IS_ERR(di)) {
|
||||
ret = PTR_ERR(di);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* ok we already have this xattr, lets remove it */
|
||||
if (di) {
|
||||
/* if we want create only exit */
|
||||
if (flags & XATTR_CREATE) {
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = btrfs_delete_one_dir_name(trans, root, path, di);
|
||||
if (ret)
|
||||
goto out;
|
||||
btrfs_release_path(root, path);
|
||||
|
||||
/* if we don't have a value then we are removing the xattr */
|
||||
if (!value) {
|
||||
mod = 1;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
btrfs_release_path(root, path);
|
||||
|
||||
if (flags & XATTR_REPLACE) {
|
||||
/* we couldn't find the attr to replace */
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* ok we have to create a completely new xattr */
|
||||
ret = btrfs_insert_xattr_item(trans, root, name, strlen(name),
|
||||
value, size, inode->i_ino);
|
||||
if (ret)
|
||||
goto out;
|
||||
mod = 1;
|
||||
|
||||
out:
|
||||
if (mod) {
|
||||
inode->i_ctime = CURRENT_TIME;
|
||||
ret = btrfs_update_inode(trans, root, inode);
|
||||
}
|
||||
|
||||
btrfs_end_transaction(trans, root);
|
||||
btrfs_free_path(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
|
||||
{
|
||||
struct btrfs_key key, found_key;
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct btrfs_root *root = BTRFS_I(inode)->root;
|
||||
struct btrfs_path *path;
|
||||
struct btrfs_item *item;
|
||||
struct extent_buffer *leaf;
|
||||
struct btrfs_dir_item *di;
|
||||
int ret = 0, slot, advance;
|
||||
size_t total_size = 0, size_left = size;
|
||||
unsigned long name_ptr;
|
||||
size_t name_len;
|
||||
u32 nritems;
|
||||
|
||||
/*
|
||||
* ok we want all objects associated with this id.
|
||||
* NOTE: we set key.offset = 0; because we want to start with the
|
||||
* first xattr that we find and walk forward
|
||||
*/
|
||||
key.objectid = inode->i_ino;
|
||||
btrfs_set_key_type(&key, BTRFS_XATTR_ITEM_KEY);
|
||||
key.offset = 0;
|
||||
|
||||
path = btrfs_alloc_path();
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
path->reada = 2;
|
||||
|
||||
/* search for our xattrs */
|
||||
ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
ret = 0;
|
||||
advance = 0;
|
||||
while (1) {
|
||||
leaf = path->nodes[0];
|
||||
nritems = btrfs_header_nritems(leaf);
|
||||
slot = path->slots[0];
|
||||
|
||||
/* this is where we start walking through the path */
|
||||
if (advance || slot >= nritems) {
|
||||
/*
|
||||
* if we've reached the last slot in this leaf we need
|
||||
* to go to the next leaf and reset everything
|
||||
*/
|
||||
if (slot >= nritems-1) {
|
||||
ret = btrfs_next_leaf(root, path);
|
||||
if (ret)
|
||||
break;
|
||||
leaf = path->nodes[0];
|
||||
nritems = btrfs_header_nritems(leaf);
|
||||
slot = path->slots[0];
|
||||
} else {
|
||||
/*
|
||||
* just walking through the slots on this leaf
|
||||
*/
|
||||
slot++;
|
||||
path->slots[0]++;
|
||||
}
|
||||
}
|
||||
advance = 1;
|
||||
|
||||
item = btrfs_item_nr(leaf, slot);
|
||||
btrfs_item_key_to_cpu(leaf, &found_key, slot);
|
||||
|
||||
/* check to make sure this item is what we want */
|
||||
if (found_key.objectid != key.objectid)
|
||||
break;
|
||||
if (btrfs_key_type(&found_key) != BTRFS_XATTR_ITEM_KEY)
|
||||
break;
|
||||
|
||||
di = btrfs_item_ptr(leaf, slot, struct btrfs_dir_item);
|
||||
|
||||
name_len = btrfs_dir_name_len(leaf, di);
|
||||
total_size += name_len + 1;
|
||||
|
||||
/* we are just looking for how big our buffer needs to be */
|
||||
if (!size)
|
||||
continue;
|
||||
|
||||
if (!buffer || (name_len + 1) > size_left) {
|
||||
ret = -ERANGE;
|
||||
goto err;
|
||||
}
|
||||
|
||||
name_ptr = (unsigned long)(di + 1);
|
||||
read_extent_buffer(leaf, buffer, name_ptr, name_len);
|
||||
buffer[name_len] = '\0';
|
||||
|
||||
size_left -= name_len + 1;
|
||||
buffer += name_len + 1;
|
||||
}
|
||||
ret = total_size;
|
||||
|
||||
err:
|
||||
btrfs_free_path(path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* List of handlers for synthetic system.* attributes. All real ondisk
|
||||
* attributes are handled directly.
|
||||
*/
|
||||
struct xattr_handler *btrfs_xattr_handlers[] = {
|
||||
#ifdef CONFIG_FS_POSIX_ACL
|
||||
&btrfs_xattr_acl_access_handler,
|
||||
&btrfs_xattr_acl_default_handler,
|
||||
#endif
|
||||
NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Check if the attribute is in a supported namespace.
|
||||
*
|
||||
* This applied after the check for the synthetic attributes in the system
|
||||
* namespace.
|
||||
*/
|
||||
static bool btrfs_is_valid_xattr(const char *name)
|
||||
{
|
||||
return !strncmp(name, XATTR_SECURITY_PREFIX,
|
||||
XATTR_SECURITY_PREFIX_LEN) ||
|
||||
!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) ||
|
||||
!strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) ||
|
||||
!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
|
||||
}
|
||||
|
||||
ssize_t btrfs_getxattr(struct dentry *dentry, const char *name,
|
||||
void *buffer, size_t size)
|
||||
{
|
||||
/*
|
||||
* If this is a request for a synthetic attribute in the system.*
|
||||
* namespace use the generic infrastructure to resolve a handler
|
||||
* for it via sb->s_xattr.
|
||||
*/
|
||||
if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
|
||||
return generic_getxattr(dentry, name, buffer, size);
|
||||
|
||||
if (!btrfs_is_valid_xattr(name))
|
||||
return -EOPNOTSUPP;
|
||||
return __btrfs_getxattr(dentry->d_inode, name, buffer, size);
|
||||
}
|
||||
|
||||
int btrfs_setxattr(struct dentry *dentry, const char *name, const void *value,
|
||||
size_t size, int flags)
|
||||
{
|
||||
/*
|
||||
* If this is a request for a synthetic attribute in the system.*
|
||||
* namespace use the generic infrastructure to resolve a handler
|
||||
* for it via sb->s_xattr.
|
||||
*/
|
||||
if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
|
||||
return generic_setxattr(dentry, name, value, size, flags);
|
||||
|
||||
if (!btrfs_is_valid_xattr(name))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (size == 0)
|
||||
value = ""; /* empty EA, do not remove */
|
||||
return __btrfs_setxattr(dentry->d_inode, name, value, size, flags);
|
||||
}
|
||||
|
||||
int btrfs_removexattr(struct dentry *dentry, const char *name)
|
||||
{
|
||||
/*
|
||||
* If this is a request for a synthetic attribute in the system.*
|
||||
* namespace use the generic infrastructure to resolve a handler
|
||||
* for it via sb->s_xattr.
|
||||
*/
|
||||
if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
|
||||
return generic_removexattr(dentry, name);
|
||||
|
||||
if (!btrfs_is_valid_xattr(name))
|
||||
return -EOPNOTSUPP;
|
||||
return __btrfs_setxattr(dentry->d_inode, name, NULL, 0, XATTR_REPLACE);
|
||||
}
|
39
fs/btrfs/xattr.h
Normal file
39
fs/btrfs/xattr.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2007 Red Hat. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __XATTR__
|
||||
#define __XATTR__
|
||||
|
||||
#include <linux/xattr.h>
|
||||
|
||||
extern struct xattr_handler btrfs_xattr_acl_access_handler;
|
||||
extern struct xattr_handler btrfs_xattr_acl_default_handler;
|
||||
extern struct xattr_handler *btrfs_xattr_handlers[];
|
||||
|
||||
extern ssize_t __btrfs_getxattr(struct inode *inode, const char *name,
|
||||
void *buffer, size_t size);
|
||||
extern int __btrfs_setxattr(struct inode *inode, const char *name,
|
||||
const void *value, size_t size, int flags);
|
||||
|
||||
extern ssize_t btrfs_getxattr(struct dentry *dentry, const char *name,
|
||||
void *buffer, size_t size);
|
||||
extern int btrfs_setxattr(struct dentry *dentry, const char *name,
|
||||
const void *value, size_t size, int flags);
|
||||
extern int btrfs_removexattr(struct dentry *dentry, const char *name);
|
||||
|
||||
#endif /* __XATTR__ */
|
632
fs/btrfs/zlib.c
Normal file
632
fs/btrfs/zlib.c
Normal file
@ -0,0 +1,632 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Oracle. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
* License v2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public
|
||||
* License along with this program; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 021110-1307, USA.
|
||||
*
|
||||
* Based on jffs2 zlib code:
|
||||
* Copyright © 2001-2007 Red Hat, Inc.
|
||||
* Created by David Woodhouse <dwmw2@infradead.org>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/zlib.h>
|
||||
#include <linux/zutil.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/bio.h>
|
||||
#include "compression.h"
|
||||
|
||||
/* Plan: call deflate() with avail_in == *sourcelen,
|
||||
avail_out = *dstlen - 12 and flush == Z_FINISH.
|
||||
If it doesn't manage to finish, call it again with
|
||||
avail_in == 0 and avail_out set to the remaining 12
|
||||
bytes for it to clean up.
|
||||
Q: Is 12 bytes sufficient?
|
||||
*/
|
||||
#define STREAM_END_SPACE 12
|
||||
|
||||
struct workspace {
|
||||
z_stream inf_strm;
|
||||
z_stream def_strm;
|
||||
char *buf;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
static LIST_HEAD(idle_workspace);
|
||||
static DEFINE_SPINLOCK(workspace_lock);
|
||||
static unsigned long num_workspace;
|
||||
static atomic_t alloc_workspace = ATOMIC_INIT(0);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(workspace_wait);
|
||||
|
||||
/*
|
||||
* this finds an available zlib workspace or allocates a new one
|
||||
* NULL or an ERR_PTR is returned if things go bad.
|
||||
*/
|
||||
static struct workspace *find_zlib_workspace(void)
|
||||
{
|
||||
struct workspace *workspace;
|
||||
int ret;
|
||||
int cpus = num_online_cpus();
|
||||
|
||||
again:
|
||||
spin_lock(&workspace_lock);
|
||||
if (!list_empty(&idle_workspace)) {
|
||||
workspace = list_entry(idle_workspace.next, struct workspace,
|
||||
list);
|
||||
list_del(&workspace->list);
|
||||
num_workspace--;
|
||||
spin_unlock(&workspace_lock);
|
||||
return workspace;
|
||||
|
||||
}
|
||||
spin_unlock(&workspace_lock);
|
||||
if (atomic_read(&alloc_workspace) > cpus) {
|
||||
DEFINE_WAIT(wait);
|
||||
prepare_to_wait(&workspace_wait, &wait, TASK_UNINTERRUPTIBLE);
|
||||
if (atomic_read(&alloc_workspace) > cpus)
|
||||
schedule();
|
||||
finish_wait(&workspace_wait, &wait);
|
||||
goto again;
|
||||
}
|
||||
atomic_inc(&alloc_workspace);
|
||||
workspace = kzalloc(sizeof(*workspace), GFP_NOFS);
|
||||
if (!workspace) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
workspace->def_strm.workspace = vmalloc(zlib_deflate_workspacesize());
|
||||
if (!workspace->def_strm.workspace) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
workspace->inf_strm.workspace = vmalloc(zlib_inflate_workspacesize());
|
||||
if (!workspace->inf_strm.workspace) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_inflate;
|
||||
}
|
||||
workspace->buf = kmalloc(PAGE_CACHE_SIZE, GFP_NOFS);
|
||||
if (!workspace->buf) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_kmalloc;
|
||||
}
|
||||
return workspace;
|
||||
|
||||
fail_kmalloc:
|
||||
vfree(workspace->inf_strm.workspace);
|
||||
fail_inflate:
|
||||
vfree(workspace->def_strm.workspace);
|
||||
fail:
|
||||
kfree(workspace);
|
||||
atomic_dec(&alloc_workspace);
|
||||
wake_up(&workspace_wait);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* put a workspace struct back on the list or free it if we have enough
|
||||
* idle ones sitting around
|
||||
*/
|
||||
static int free_workspace(struct workspace *workspace)
|
||||
{
|
||||
spin_lock(&workspace_lock);
|
||||
if (num_workspace < num_online_cpus()) {
|
||||
list_add_tail(&workspace->list, &idle_workspace);
|
||||
num_workspace++;
|
||||
spin_unlock(&workspace_lock);
|
||||
if (waitqueue_active(&workspace_wait))
|
||||
wake_up(&workspace_wait);
|
||||
return 0;
|
||||
}
|
||||
spin_unlock(&workspace_lock);
|
||||
vfree(workspace->def_strm.workspace);
|
||||
vfree(workspace->inf_strm.workspace);
|
||||
kfree(workspace->buf);
|
||||
kfree(workspace);
|
||||
|
||||
atomic_dec(&alloc_workspace);
|
||||
if (waitqueue_active(&workspace_wait))
|
||||
wake_up(&workspace_wait);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* cleanup function for module exit
|
||||
*/
|
||||
static void free_workspaces(void)
|
||||
{
|
||||
struct workspace *workspace;
|
||||
while (!list_empty(&idle_workspace)) {
|
||||
workspace = list_entry(idle_workspace.next, struct workspace,
|
||||
list);
|
||||
list_del(&workspace->list);
|
||||
vfree(workspace->def_strm.workspace);
|
||||
vfree(workspace->inf_strm.workspace);
|
||||
kfree(workspace->buf);
|
||||
kfree(workspace);
|
||||
atomic_dec(&alloc_workspace);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* given an address space and start/len, compress the bytes.
|
||||
*
|
||||
* pages are allocated to hold the compressed result and stored
|
||||
* in 'pages'
|
||||
*
|
||||
* out_pages is used to return the number of pages allocated. There
|
||||
* may be pages allocated even if we return an error
|
||||
*
|
||||
* total_in is used to return the number of bytes actually read. It
|
||||
* may be smaller then len if we had to exit early because we
|
||||
* ran out of room in the pages array or because we cross the
|
||||
* max_out threshold.
|
||||
*
|
||||
* total_out is used to return the total number of compressed bytes
|
||||
*
|
||||
* max_out tells us the max number of bytes that we're allowed to
|
||||
* stuff into pages
|
||||
*/
|
||||
int btrfs_zlib_compress_pages(struct address_space *mapping,
|
||||
u64 start, unsigned long len,
|
||||
struct page **pages,
|
||||
unsigned long nr_dest_pages,
|
||||
unsigned long *out_pages,
|
||||
unsigned long *total_in,
|
||||
unsigned long *total_out,
|
||||
unsigned long max_out)
|
||||
{
|
||||
int ret;
|
||||
struct workspace *workspace;
|
||||
char *data_in;
|
||||
char *cpage_out;
|
||||
int nr_pages = 0;
|
||||
struct page *in_page = NULL;
|
||||
struct page *out_page = NULL;
|
||||
int out_written = 0;
|
||||
int in_read = 0;
|
||||
unsigned long bytes_left;
|
||||
|
||||
*out_pages = 0;
|
||||
*total_out = 0;
|
||||
*total_in = 0;
|
||||
|
||||
workspace = find_zlib_workspace();
|
||||
if (!workspace)
|
||||
return -1;
|
||||
|
||||
if (Z_OK != zlib_deflateInit(&workspace->def_strm, 3)) {
|
||||
printk(KERN_WARNING "deflateInit failed\n");
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
workspace->def_strm.total_in = 0;
|
||||
workspace->def_strm.total_out = 0;
|
||||
|
||||
in_page = find_get_page(mapping, start >> PAGE_CACHE_SHIFT);
|
||||
data_in = kmap(in_page);
|
||||
|
||||
out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM);
|
||||
cpage_out = kmap(out_page);
|
||||
pages[0] = out_page;
|
||||
nr_pages = 1;
|
||||
|
||||
workspace->def_strm.next_in = data_in;
|
||||
workspace->def_strm.next_out = cpage_out;
|
||||
workspace->def_strm.avail_out = PAGE_CACHE_SIZE;
|
||||
workspace->def_strm.avail_in = min(len, PAGE_CACHE_SIZE);
|
||||
|
||||
out_written = 0;
|
||||
in_read = 0;
|
||||
|
||||
while (workspace->def_strm.total_in < len) {
|
||||
ret = zlib_deflate(&workspace->def_strm, Z_SYNC_FLUSH);
|
||||
if (ret != Z_OK) {
|
||||
printk(KERN_DEBUG "btrfs deflate in loop returned %d\n",
|
||||
ret);
|
||||
zlib_deflateEnd(&workspace->def_strm);
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* we're making it bigger, give up */
|
||||
if (workspace->def_strm.total_in > 8192 &&
|
||||
workspace->def_strm.total_in <
|
||||
workspace->def_strm.total_out) {
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
/* we need another page for writing out. Test this
|
||||
* before the total_in so we will pull in a new page for
|
||||
* the stream end if required
|
||||
*/
|
||||
if (workspace->def_strm.avail_out == 0) {
|
||||
kunmap(out_page);
|
||||
if (nr_pages == nr_dest_pages) {
|
||||
out_page = NULL;
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
out_page = alloc_page(GFP_NOFS | __GFP_HIGHMEM);
|
||||
cpage_out = kmap(out_page);
|
||||
pages[nr_pages] = out_page;
|
||||
nr_pages++;
|
||||
workspace->def_strm.avail_out = PAGE_CACHE_SIZE;
|
||||
workspace->def_strm.next_out = cpage_out;
|
||||
}
|
||||
/* we're all done */
|
||||
if (workspace->def_strm.total_in >= len)
|
||||
break;
|
||||
|
||||
/* we've read in a full page, get a new one */
|
||||
if (workspace->def_strm.avail_in == 0) {
|
||||
if (workspace->def_strm.total_out > max_out)
|
||||
break;
|
||||
|
||||
bytes_left = len - workspace->def_strm.total_in;
|
||||
kunmap(in_page);
|
||||
page_cache_release(in_page);
|
||||
|
||||
start += PAGE_CACHE_SIZE;
|
||||
in_page = find_get_page(mapping,
|
||||
start >> PAGE_CACHE_SHIFT);
|
||||
data_in = kmap(in_page);
|
||||
workspace->def_strm.avail_in = min(bytes_left,
|
||||
PAGE_CACHE_SIZE);
|
||||
workspace->def_strm.next_in = data_in;
|
||||
}
|
||||
}
|
||||
workspace->def_strm.avail_in = 0;
|
||||
ret = zlib_deflate(&workspace->def_strm, Z_FINISH);
|
||||
zlib_deflateEnd(&workspace->def_strm);
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (workspace->def_strm.total_out >= workspace->def_strm.total_in) {
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
*total_out = workspace->def_strm.total_out;
|
||||
*total_in = workspace->def_strm.total_in;
|
||||
out:
|
||||
*out_pages = nr_pages;
|
||||
if (out_page)
|
||||
kunmap(out_page);
|
||||
|
||||
if (in_page) {
|
||||
kunmap(in_page);
|
||||
page_cache_release(in_page);
|
||||
}
|
||||
free_workspace(workspace);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* pages_in is an array of pages with compressed data.
|
||||
*
|
||||
* disk_start is the starting logical offset of this array in the file
|
||||
*
|
||||
* bvec is a bio_vec of pages from the file that we want to decompress into
|
||||
*
|
||||
* vcnt is the count of pages in the biovec
|
||||
*
|
||||
* srclen is the number of bytes in pages_in
|
||||
*
|
||||
* The basic idea is that we have a bio that was created by readpages.
|
||||
* The pages in the bio are for the uncompressed data, and they may not
|
||||
* be contiguous. They all correspond to the range of bytes covered by
|
||||
* the compressed extent.
|
||||
*/
|
||||
int btrfs_zlib_decompress_biovec(struct page **pages_in,
|
||||
u64 disk_start,
|
||||
struct bio_vec *bvec,
|
||||
int vcnt,
|
||||
size_t srclen)
|
||||
{
|
||||
int ret = 0;
|
||||
int wbits = MAX_WBITS;
|
||||
struct workspace *workspace;
|
||||
char *data_in;
|
||||
size_t total_out = 0;
|
||||
unsigned long page_bytes_left;
|
||||
unsigned long page_in_index = 0;
|
||||
unsigned long page_out_index = 0;
|
||||
struct page *page_out;
|
||||
unsigned long total_pages_in = (srclen + PAGE_CACHE_SIZE - 1) /
|
||||
PAGE_CACHE_SIZE;
|
||||
unsigned long buf_start;
|
||||
unsigned long buf_offset;
|
||||
unsigned long bytes;
|
||||
unsigned long working_bytes;
|
||||
unsigned long pg_offset;
|
||||
unsigned long start_byte;
|
||||
unsigned long current_buf_start;
|
||||
char *kaddr;
|
||||
|
||||
workspace = find_zlib_workspace();
|
||||
if (!workspace)
|
||||
return -ENOMEM;
|
||||
|
||||
data_in = kmap(pages_in[page_in_index]);
|
||||
workspace->inf_strm.next_in = data_in;
|
||||
workspace->inf_strm.avail_in = min_t(size_t, srclen, PAGE_CACHE_SIZE);
|
||||
workspace->inf_strm.total_in = 0;
|
||||
|
||||
workspace->inf_strm.total_out = 0;
|
||||
workspace->inf_strm.next_out = workspace->buf;
|
||||
workspace->inf_strm.avail_out = PAGE_CACHE_SIZE;
|
||||
page_out = bvec[page_out_index].bv_page;
|
||||
page_bytes_left = PAGE_CACHE_SIZE;
|
||||
pg_offset = 0;
|
||||
|
||||
/* If it's deflate, and it's got no preset dictionary, then
|
||||
we can tell zlib to skip the adler32 check. */
|
||||
if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
|
||||
((data_in[0] & 0x0f) == Z_DEFLATED) &&
|
||||
!(((data_in[0]<<8) + data_in[1]) % 31)) {
|
||||
|
||||
wbits = -((data_in[0] >> 4) + 8);
|
||||
workspace->inf_strm.next_in += 2;
|
||||
workspace->inf_strm.avail_in -= 2;
|
||||
}
|
||||
|
||||
if (Z_OK != zlib_inflateInit2(&workspace->inf_strm, wbits)) {
|
||||
printk(KERN_WARNING "inflateInit failed\n");
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
while (workspace->inf_strm.total_in < srclen) {
|
||||
ret = zlib_inflate(&workspace->inf_strm, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_STREAM_END)
|
||||
break;
|
||||
/*
|
||||
* buf start is the byte offset we're of the start of
|
||||
* our workspace buffer
|
||||
*/
|
||||
buf_start = total_out;
|
||||
|
||||
/* total_out is the last byte of the workspace buffer */
|
||||
total_out = workspace->inf_strm.total_out;
|
||||
|
||||
working_bytes = total_out - buf_start;
|
||||
|
||||
/*
|
||||
* start byte is the first byte of the page we're currently
|
||||
* copying into relative to the start of the compressed data.
|
||||
*/
|
||||
start_byte = page_offset(page_out) - disk_start;
|
||||
|
||||
if (working_bytes == 0) {
|
||||
/* we didn't make progress in this inflate
|
||||
* call, we're done
|
||||
*/
|
||||
if (ret != Z_STREAM_END)
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* we haven't yet hit data corresponding to this page */
|
||||
if (total_out <= start_byte)
|
||||
goto next;
|
||||
|
||||
/*
|
||||
* the start of the data we care about is offset into
|
||||
* the middle of our working buffer
|
||||
*/
|
||||
if (total_out > start_byte && buf_start < start_byte) {
|
||||
buf_offset = start_byte - buf_start;
|
||||
working_bytes -= buf_offset;
|
||||
} else {
|
||||
buf_offset = 0;
|
||||
}
|
||||
current_buf_start = buf_start;
|
||||
|
||||
/* copy bytes from the working buffer into the pages */
|
||||
while (working_bytes > 0) {
|
||||
bytes = min(PAGE_CACHE_SIZE - pg_offset,
|
||||
PAGE_CACHE_SIZE - buf_offset);
|
||||
bytes = min(bytes, working_bytes);
|
||||
kaddr = kmap_atomic(page_out, KM_USER0);
|
||||
memcpy(kaddr + pg_offset, workspace->buf + buf_offset,
|
||||
bytes);
|
||||
kunmap_atomic(kaddr, KM_USER0);
|
||||
flush_dcache_page(page_out);
|
||||
|
||||
pg_offset += bytes;
|
||||
page_bytes_left -= bytes;
|
||||
buf_offset += bytes;
|
||||
working_bytes -= bytes;
|
||||
current_buf_start += bytes;
|
||||
|
||||
/* check if we need to pick another page */
|
||||
if (page_bytes_left == 0) {
|
||||
page_out_index++;
|
||||
if (page_out_index >= vcnt) {
|
||||
ret = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
page_out = bvec[page_out_index].bv_page;
|
||||
pg_offset = 0;
|
||||
page_bytes_left = PAGE_CACHE_SIZE;
|
||||
start_byte = page_offset(page_out) - disk_start;
|
||||
|
||||
/*
|
||||
* make sure our new page is covered by this
|
||||
* working buffer
|
||||
*/
|
||||
if (total_out <= start_byte)
|
||||
goto next;
|
||||
|
||||
/* the next page in the biovec might not
|
||||
* be adjacent to the last page, but it
|
||||
* might still be found inside this working
|
||||
* buffer. bump our offset pointer
|
||||
*/
|
||||
if (total_out > start_byte &&
|
||||
current_buf_start < start_byte) {
|
||||
buf_offset = start_byte - buf_start;
|
||||
working_bytes = total_out - start_byte;
|
||||
current_buf_start = buf_start +
|
||||
buf_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
next:
|
||||
workspace->inf_strm.next_out = workspace->buf;
|
||||
workspace->inf_strm.avail_out = PAGE_CACHE_SIZE;
|
||||
|
||||
if (workspace->inf_strm.avail_in == 0) {
|
||||
unsigned long tmp;
|
||||
kunmap(pages_in[page_in_index]);
|
||||
page_in_index++;
|
||||
if (page_in_index >= total_pages_in) {
|
||||
data_in = NULL;
|
||||
break;
|
||||
}
|
||||
data_in = kmap(pages_in[page_in_index]);
|
||||
workspace->inf_strm.next_in = data_in;
|
||||
tmp = srclen - workspace->inf_strm.total_in;
|
||||
workspace->inf_strm.avail_in = min(tmp,
|
||||
PAGE_CACHE_SIZE);
|
||||
}
|
||||
}
|
||||
if (ret != Z_STREAM_END)
|
||||
ret = -1;
|
||||
else
|
||||
ret = 0;
|
||||
done:
|
||||
zlib_inflateEnd(&workspace->inf_strm);
|
||||
if (data_in)
|
||||
kunmap(pages_in[page_in_index]);
|
||||
out:
|
||||
free_workspace(workspace);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* a less complex decompression routine. Our compressed data fits in a
|
||||
* single page, and we want to read a single page out of it.
|
||||
* start_byte tells us the offset into the compressed data we're interested in
|
||||
*/
|
||||
int btrfs_zlib_decompress(unsigned char *data_in,
|
||||
struct page *dest_page,
|
||||
unsigned long start_byte,
|
||||
size_t srclen, size_t destlen)
|
||||
{
|
||||
int ret = 0;
|
||||
int wbits = MAX_WBITS;
|
||||
struct workspace *workspace;
|
||||
unsigned long bytes_left = destlen;
|
||||
unsigned long total_out = 0;
|
||||
char *kaddr;
|
||||
|
||||
if (destlen > PAGE_CACHE_SIZE)
|
||||
return -ENOMEM;
|
||||
|
||||
workspace = find_zlib_workspace();
|
||||
if (!workspace)
|
||||
return -ENOMEM;
|
||||
|
||||
workspace->inf_strm.next_in = data_in;
|
||||
workspace->inf_strm.avail_in = srclen;
|
||||
workspace->inf_strm.total_in = 0;
|
||||
|
||||
workspace->inf_strm.next_out = workspace->buf;
|
||||
workspace->inf_strm.avail_out = PAGE_CACHE_SIZE;
|
||||
workspace->inf_strm.total_out = 0;
|
||||
/* If it's deflate, and it's got no preset dictionary, then
|
||||
we can tell zlib to skip the adler32 check. */
|
||||
if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
|
||||
((data_in[0] & 0x0f) == Z_DEFLATED) &&
|
||||
!(((data_in[0]<<8) + data_in[1]) % 31)) {
|
||||
|
||||
wbits = -((data_in[0] >> 4) + 8);
|
||||
workspace->inf_strm.next_in += 2;
|
||||
workspace->inf_strm.avail_in -= 2;
|
||||
}
|
||||
|
||||
if (Z_OK != zlib_inflateInit2(&workspace->inf_strm, wbits)) {
|
||||
printk(KERN_WARNING "inflateInit failed\n");
|
||||
ret = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (bytes_left > 0) {
|
||||
unsigned long buf_start;
|
||||
unsigned long buf_offset;
|
||||
unsigned long bytes;
|
||||
unsigned long pg_offset = 0;
|
||||
|
||||
ret = zlib_inflate(&workspace->inf_strm, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_STREAM_END)
|
||||
break;
|
||||
|
||||
buf_start = total_out;
|
||||
total_out = workspace->inf_strm.total_out;
|
||||
|
||||
if (total_out == buf_start) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (total_out <= start_byte)
|
||||
goto next;
|
||||
|
||||
if (total_out > start_byte && buf_start < start_byte)
|
||||
buf_offset = start_byte - buf_start;
|
||||
else
|
||||
buf_offset = 0;
|
||||
|
||||
bytes = min(PAGE_CACHE_SIZE - pg_offset,
|
||||
PAGE_CACHE_SIZE - buf_offset);
|
||||
bytes = min(bytes, bytes_left);
|
||||
|
||||
kaddr = kmap_atomic(dest_page, KM_USER0);
|
||||
memcpy(kaddr + pg_offset, workspace->buf + buf_offset, bytes);
|
||||
kunmap_atomic(kaddr, KM_USER0);
|
||||
|
||||
pg_offset += bytes;
|
||||
bytes_left -= bytes;
|
||||
next:
|
||||
workspace->inf_strm.next_out = workspace->buf;
|
||||
workspace->inf_strm.avail_out = PAGE_CACHE_SIZE;
|
||||
}
|
||||
|
||||
if (ret != Z_STREAM_END && bytes_left != 0)
|
||||
ret = -1;
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
zlib_inflateEnd(&workspace->inf_strm);
|
||||
out:
|
||||
free_workspace(workspace);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void btrfs_zlib_exit(void)
|
||||
{
|
||||
free_workspaces();
|
||||
}
|
Loading…
Reference in New Issue
Block a user