2007-06-12 17:07:21 +04:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
#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/backing-dev.h>
|
|
|
|
#include <linux/mpage.h>
|
|
|
|
#include <linux/swap.h>
|
|
|
|
#include <linux/writeback.h>
|
|
|
|
#include <linux/statfs.h>
|
|
|
|
#include <linux/compat.h>
|
|
|
|
#include "ctree.h"
|
|
|
|
#include "disk-io.h"
|
|
|
|
#include "transaction.h"
|
|
|
|
#include "btrfs_inode.h"
|
|
|
|
#include "ioctl.h"
|
|
|
|
#include "print-tree.h"
|
2008-09-06 00:13:11 +04:00
|
|
|
#include "tree-log.h"
|
|
|
|
#include "locking.h"
|
Btrfs: Add workaround for AppArmor changing remove_suid()
In openSUSE 10.3, AppArmor modifies remove_suid to take a struct path
rather than just a dentry. This patch tests that the kernel is openSUSE
10.3 or newer and adjusts the call accordingly.
Debian/Ubuntu with AppArmor applied will also need a similar patch.
Maintainers of btrfs under those distributions should build on this
patch or, alternatively, alter their package descriptions to add
-DREMOVE_SUID_PATH to the compiler command line.
Signed-off-by: Jeff Mahoney <jeffm@suse.com>
- --- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ b/compat.h 2008-02-06 16:46:13.000000000 -0500
@@ -0,0 +1,15 @@
+#ifndef _COMPAT_H_
+#define _COMPAT_H_
+
+
+/*
+ * Even if AppArmor isn't enabled, it still has different prototypes.
+ * Add more distro/version pairs here to declare which has AppArmor applied.
+ */
+#if defined(CONFIG_SUSE_KERNEL)
+# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
+# define REMOVE_SUID_PATH 1
+# endif
+#endif
+
+#endif /* _COMPAT_H_ */
- --- a/file.c 2008-02-06 11:37:39.000000000 -0500
+++ b/file.c 2008-02-06 16:46:23.000000000 -0500
@@ -37,6 +37,7 @@
#include "ordered-data.h"
#include "ioctl.h"
#include "print-tree.h"
+#include "compat.h"
static int btrfs_copy_from_user(loff_t pos, int num_pages, int write_bytes,
@@ -790,7 +791,11 @@ static ssize_t btrfs_file_write(struct f
goto out_nolock;
if (count == 0)
goto out_nolock;
+#ifdef REMOVE_SUID_PATH
+ err = remove_suid(&file->f_path);
+#else
err = remove_suid(fdentry(file));
+#endif
if (err)
goto out_nolock;
file_update_time(file);
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-05-02 23:03:58 +04:00
|
|
|
#include "compat.h"
|
2007-06-12 14:35:45 +04:00
|
|
|
|
|
|
|
|
2008-09-29 23:18:18 +04:00
|
|
|
/* simple helper to fault in pages and copy. This should go away
|
|
|
|
* and be replaced with calls into generic code.
|
|
|
|
*/
|
2009-01-06 05:25:51 +03:00
|
|
|
static noinline int btrfs_copy_from_user(loff_t pos, int num_pages,
|
2008-09-06 00:09:51 +04:00
|
|
|
int write_bytes,
|
|
|
|
struct page **prepared_pages,
|
2009-01-06 05:25:51 +03:00
|
|
|
const char __user *buf)
|
2007-06-12 14:35:45 +04:00
|
|
|
{
|
|
|
|
long page_fault = 0;
|
|
|
|
int i;
|
|
|
|
int offset = pos & (PAGE_CACHE_SIZE - 1);
|
|
|
|
|
|
|
|
for (i = 0; i < num_pages && write_bytes > 0; i++, offset = 0) {
|
|
|
|
size_t count = min_t(size_t,
|
|
|
|
PAGE_CACHE_SIZE - offset, write_bytes);
|
|
|
|
struct page *page = prepared_pages[i];
|
|
|
|
fault_in_pages_readable(buf, count);
|
|
|
|
|
|
|
|
/* Copy data from userspace to the current page */
|
|
|
|
kmap(page);
|
|
|
|
page_fault = __copy_from_user(page_address(page) + offset,
|
|
|
|
buf, count);
|
|
|
|
/* Flush processor's dcache for this page */
|
|
|
|
flush_dcache_page(page);
|
|
|
|
kunmap(page);
|
|
|
|
buf += count;
|
|
|
|
write_bytes -= count;
|
|
|
|
|
|
|
|
if (page_fault)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return page_fault ? -EFAULT : 0;
|
|
|
|
}
|
|
|
|
|
2008-09-29 23:18:18 +04:00
|
|
|
/*
|
|
|
|
* unlocks pages after btrfs_file_write is done with them
|
|
|
|
*/
|
2009-01-06 05:25:51 +03:00
|
|
|
static noinline void btrfs_drop_pages(struct page **pages, size_t num_pages)
|
2007-06-12 14:35:45 +04:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < num_pages; i++) {
|
|
|
|
if (!pages[i])
|
|
|
|
break;
|
2008-09-29 23:18:18 +04:00
|
|
|
/* page checked is some magic around finding pages that
|
|
|
|
* have been modified without going through btrfs_set_page_dirty
|
|
|
|
* clear it here
|
|
|
|
*/
|
2008-07-21 18:29:44 +04:00
|
|
|
ClearPageChecked(pages[i]);
|
2007-06-12 14:35:45 +04:00
|
|
|
unlock_page(pages[i]);
|
|
|
|
mark_page_accessed(pages[i]);
|
|
|
|
page_cache_release(pages[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-09-29 23:18:18 +04:00
|
|
|
/*
|
|
|
|
* after copy_from_user, pages need to be dirtied and we need to make
|
|
|
|
* sure holes are created between the current EOF and the start of
|
|
|
|
* any next extents (if required).
|
|
|
|
*
|
|
|
|
* this also makes the decision about creating an inline extent vs
|
|
|
|
* doing real data extents, marking pages dirty and delalloc as required.
|
|
|
|
*/
|
2009-01-06 05:25:51 +03:00
|
|
|
static noinline int dirty_and_release_pages(struct btrfs_trans_handle *trans,
|
2007-06-12 14:35:45 +04:00
|
|
|
struct btrfs_root *root,
|
|
|
|
struct file *file,
|
|
|
|
struct page **pages,
|
|
|
|
size_t num_pages,
|
|
|
|
loff_t pos,
|
|
|
|
size_t write_bytes)
|
|
|
|
{
|
|
|
|
int err = 0;
|
2007-08-28 00:49:44 +04:00
|
|
|
int i;
|
2007-12-19 00:15:09 +03:00
|
|
|
struct inode *inode = fdentry(file)->d_inode;
|
2007-10-16 00:15:53 +04:00
|
|
|
u64 num_bytes;
|
2007-08-28 00:49:44 +04:00
|
|
|
u64 start_pos;
|
|
|
|
u64 end_of_last_block;
|
|
|
|
u64 end_pos = pos + write_bytes;
|
|
|
|
loff_t isize = i_size_read(inode);
|
2007-06-12 14:35:45 +04:00
|
|
|
|
2007-10-16 00:14:19 +04:00
|
|
|
start_pos = pos & ~((u64)root->sectorsize - 1);
|
2007-10-16 00:15:53 +04:00
|
|
|
num_bytes = (write_bytes + pos - start_pos +
|
|
|
|
root->sectorsize - 1) & ~((u64)root->sectorsize - 1);
|
2007-06-12 14:35:45 +04:00
|
|
|
|
2007-10-16 00:15:53 +04:00
|
|
|
end_of_last_block = start_pos + num_bytes - 1;
|
2009-09-12 00:12:44 +04:00
|
|
|
err = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
Btrfs: Add zlib compression support
This is a large change for adding compression on reading and writing,
both for inline and regular extents. It does some fairly large
surgery to the writeback paths.
Compression is off by default and enabled by mount -o compress. Even
when the -o compress mount option is not used, it is possible to read
compressed extents off the disk.
If compression for a given set of pages fails to make them smaller, the
file is flagged to avoid future compression attempts later.
* While finding delalloc extents, the pages are locked before being sent down
to the delalloc handler. This allows the delalloc handler to do complex things
such as cleaning the pages, marking them writeback and starting IO on their
behalf.
* Inline extents are inserted at delalloc time now. This allows us to compress
the data before inserting the inline extent, and it allows us to insert
an inline extent that spans multiple pages.
* All of the in-memory extent representations (extent_map.c, ordered-data.c etc)
are changed to record both an in-memory size and an on disk size, as well
as a flag for compression.
From a disk format point of view, the extent pointers in the file are changed
to record the on disk size of a given extent and some encoding flags.
Space in the disk format is allocated for compression encoding, as well
as encryption and a generic 'other' field. Neither the encryption or the
'other' field are currently used.
In order to limit the amount of data read for a single random read in the
file, the size of a compressed extent is limited to 128k. This is a
software only limit, the disk format supports u64 sized compressed extents.
In order to limit the ram consumed while processing extents, the uncompressed
size of a compressed extent is limited to 256k. This is a software only limit
and will be subject to tuning later.
Checksumming is still done on compressed extents, and it is done on the
uncompressed version of the data. This way additional encodings can be
layered on without having to figure out which encoding to checksum.
Compression happens at delalloc time, which is basically singled threaded because
it is usually done by a single pdflush thread. This makes it tricky to
spread the compression load across all the cpus on the box. We'll have to
look at parallel pdflush walks of dirty inodes at a later time.
Decompression is hooked into readpages and it does spread across CPUs nicely.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-10-29 21:49:59 +03:00
|
|
|
for (i = 0; i < num_pages; i++) {
|
|
|
|
struct page *p = pages[i];
|
|
|
|
SetPageUptodate(p);
|
|
|
|
ClearPageChecked(p);
|
|
|
|
set_page_dirty(p);
|
2007-08-28 00:49:44 +04:00
|
|
|
}
|
|
|
|
if (end_pos > isize) {
|
|
|
|
i_size_write(inode, end_pos);
|
2009-06-28 05:06:22 +04:00
|
|
|
/* we've only changed i_size in ram, and we haven't updated
|
|
|
|
* the disk i_size. There is no need to log the inode
|
|
|
|
* at this time.
|
|
|
|
*/
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2008-09-29 23:18:18 +04:00
|
|
|
/*
|
|
|
|
* this drops all the extents in the cache that intersect the range
|
|
|
|
* [start, end]. Existing extents are split as required.
|
|
|
|
*/
|
2008-09-26 18:05:38 +04:00
|
|
|
int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end,
|
|
|
|
int skip_pinned)
|
2007-08-28 00:49:44 +04:00
|
|
|
{
|
|
|
|
struct extent_map *em;
|
2008-04-17 19:29:12 +04:00
|
|
|
struct extent_map *split = NULL;
|
|
|
|
struct extent_map *split2 = NULL;
|
2007-08-28 00:49:44 +04:00
|
|
|
struct extent_map_tree *em_tree = &BTRFS_I(inode)->extent_tree;
|
2008-02-15 18:40:50 +03:00
|
|
|
u64 len = end - start + 1;
|
2008-04-17 19:29:12 +04:00
|
|
|
int ret;
|
|
|
|
int testend = 1;
|
2008-09-26 18:05:38 +04:00
|
|
|
unsigned long flags;
|
Btrfs: Add zlib compression support
This is a large change for adding compression on reading and writing,
both for inline and regular extents. It does some fairly large
surgery to the writeback paths.
Compression is off by default and enabled by mount -o compress. Even
when the -o compress mount option is not used, it is possible to read
compressed extents off the disk.
If compression for a given set of pages fails to make them smaller, the
file is flagged to avoid future compression attempts later.
* While finding delalloc extents, the pages are locked before being sent down
to the delalloc handler. This allows the delalloc handler to do complex things
such as cleaning the pages, marking them writeback and starting IO on their
behalf.
* Inline extents are inserted at delalloc time now. This allows us to compress
the data before inserting the inline extent, and it allows us to insert
an inline extent that spans multiple pages.
* All of the in-memory extent representations (extent_map.c, ordered-data.c etc)
are changed to record both an in-memory size and an on disk size, as well
as a flag for compression.
From a disk format point of view, the extent pointers in the file are changed
to record the on disk size of a given extent and some encoding flags.
Space in the disk format is allocated for compression encoding, as well
as encryption and a generic 'other' field. Neither the encryption or the
'other' field are currently used.
In order to limit the amount of data read for a single random read in the
file, the size of a compressed extent is limited to 128k. This is a
software only limit, the disk format supports u64 sized compressed extents.
In order to limit the ram consumed while processing extents, the uncompressed
size of a compressed extent is limited to 256k. This is a software only limit
and will be subject to tuning later.
Checksumming is still done on compressed extents, and it is done on the
uncompressed version of the data. This way additional encodings can be
layered on without having to figure out which encoding to checksum.
Compression happens at delalloc time, which is basically singled threaded because
it is usually done by a single pdflush thread. This makes it tricky to
spread the compression load across all the cpus on the box. We'll have to
look at parallel pdflush walks of dirty inodes at a later time.
Decompression is hooked into readpages and it does spread across CPUs nicely.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-10-29 21:49:59 +03:00
|
|
|
int compressed = 0;
|
2007-08-28 00:49:44 +04:00
|
|
|
|
2008-07-17 20:53:50 +04:00
|
|
|
WARN_ON(end < start);
|
2008-04-17 19:29:12 +04:00
|
|
|
if (end == (u64)-1) {
|
2008-02-15 18:40:50 +03:00
|
|
|
len = (u64)-1;
|
2008-04-17 19:29:12 +04:00
|
|
|
testend = 0;
|
|
|
|
}
|
2009-01-06 05:25:51 +03:00
|
|
|
while (1) {
|
2008-04-17 19:29:12 +04:00
|
|
|
if (!split)
|
|
|
|
split = alloc_extent_map(GFP_NOFS);
|
|
|
|
if (!split2)
|
|
|
|
split2 = alloc_extent_map(GFP_NOFS);
|
|
|
|
|
2009-09-03 00:24:52 +04:00
|
|
|
write_lock(&em_tree->lock);
|
2008-02-15 18:40:50 +03:00
|
|
|
em = lookup_extent_mapping(em_tree, start, len);
|
2008-01-25 00:13:08 +03:00
|
|
|
if (!em) {
|
2009-09-03 00:24:52 +04:00
|
|
|
write_unlock(&em_tree->lock);
|
2007-08-28 00:49:44 +04:00
|
|
|
break;
|
2008-01-25 00:13:08 +03:00
|
|
|
}
|
2008-09-26 18:05:38 +04:00
|
|
|
flags = em->flags;
|
|
|
|
if (skip_pinned && test_bit(EXTENT_FLAG_PINNED, &em->flags)) {
|
2009-11-12 12:36:44 +03:00
|
|
|
if (testend && em->start + em->len >= start + len) {
|
2008-09-26 18:05:38 +04:00
|
|
|
free_extent_map(em);
|
2009-09-11 20:27:37 +04:00
|
|
|
write_unlock(&em_tree->lock);
|
2008-09-26 18:05:38 +04:00
|
|
|
break;
|
|
|
|
}
|
2009-11-12 12:36:44 +03:00
|
|
|
start = em->start + em->len;
|
|
|
|
if (testend)
|
2008-09-26 18:05:38 +04:00
|
|
|
len = start + len - (em->start + em->len);
|
|
|
|
free_extent_map(em);
|
2009-09-11 20:27:37 +04:00
|
|
|
write_unlock(&em_tree->lock);
|
2008-09-26 18:05:38 +04:00
|
|
|
continue;
|
|
|
|
}
|
Btrfs: Add zlib compression support
This is a large change for adding compression on reading and writing,
both for inline and regular extents. It does some fairly large
surgery to the writeback paths.
Compression is off by default and enabled by mount -o compress. Even
when the -o compress mount option is not used, it is possible to read
compressed extents off the disk.
If compression for a given set of pages fails to make them smaller, the
file is flagged to avoid future compression attempts later.
* While finding delalloc extents, the pages are locked before being sent down
to the delalloc handler. This allows the delalloc handler to do complex things
such as cleaning the pages, marking them writeback and starting IO on their
behalf.
* Inline extents are inserted at delalloc time now. This allows us to compress
the data before inserting the inline extent, and it allows us to insert
an inline extent that spans multiple pages.
* All of the in-memory extent representations (extent_map.c, ordered-data.c etc)
are changed to record both an in-memory size and an on disk size, as well
as a flag for compression.
From a disk format point of view, the extent pointers in the file are changed
to record the on disk size of a given extent and some encoding flags.
Space in the disk format is allocated for compression encoding, as well
as encryption and a generic 'other' field. Neither the encryption or the
'other' field are currently used.
In order to limit the amount of data read for a single random read in the
file, the size of a compressed extent is limited to 128k. This is a
software only limit, the disk format supports u64 sized compressed extents.
In order to limit the ram consumed while processing extents, the uncompressed
size of a compressed extent is limited to 256k. This is a software only limit
and will be subject to tuning later.
Checksumming is still done on compressed extents, and it is done on the
uncompressed version of the data. This way additional encodings can be
layered on without having to figure out which encoding to checksum.
Compression happens at delalloc time, which is basically singled threaded because
it is usually done by a single pdflush thread. This makes it tricky to
spread the compression load across all the cpus on the box. We'll have to
look at parallel pdflush walks of dirty inodes at a later time.
Decompression is hooked into readpages and it does spread across CPUs nicely.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-10-29 21:49:59 +03:00
|
|
|
compressed = test_bit(EXTENT_FLAG_COMPRESSED, &em->flags);
|
2008-07-31 23:42:54 +04:00
|
|
|
clear_bit(EXTENT_FLAG_PINNED, &em->flags);
|
2007-08-28 00:49:44 +04:00
|
|
|
remove_extent_mapping(em_tree, em);
|
2008-04-17 19:29:12 +04:00
|
|
|
|
|
|
|
if (em->block_start < EXTENT_MAP_LAST_BYTE &&
|
|
|
|
em->start < start) {
|
|
|
|
split->start = em->start;
|
|
|
|
split->len = start - em->start;
|
2008-11-10 15:34:43 +03:00
|
|
|
split->orig_start = em->orig_start;
|
2008-04-17 19:29:12 +04:00
|
|
|
split->block_start = em->block_start;
|
Btrfs: Add zlib compression support
This is a large change for adding compression on reading and writing,
both for inline and regular extents. It does some fairly large
surgery to the writeback paths.
Compression is off by default and enabled by mount -o compress. Even
when the -o compress mount option is not used, it is possible to read
compressed extents off the disk.
If compression for a given set of pages fails to make them smaller, the
file is flagged to avoid future compression attempts later.
* While finding delalloc extents, the pages are locked before being sent down
to the delalloc handler. This allows the delalloc handler to do complex things
such as cleaning the pages, marking them writeback and starting IO on their
behalf.
* Inline extents are inserted at delalloc time now. This allows us to compress
the data before inserting the inline extent, and it allows us to insert
an inline extent that spans multiple pages.
* All of the in-memory extent representations (extent_map.c, ordered-data.c etc)
are changed to record both an in-memory size and an on disk size, as well
as a flag for compression.
From a disk format point of view, the extent pointers in the file are changed
to record the on disk size of a given extent and some encoding flags.
Space in the disk format is allocated for compression encoding, as well
as encryption and a generic 'other' field. Neither the encryption or the
'other' field are currently used.
In order to limit the amount of data read for a single random read in the
file, the size of a compressed extent is limited to 128k. This is a
software only limit, the disk format supports u64 sized compressed extents.
In order to limit the ram consumed while processing extents, the uncompressed
size of a compressed extent is limited to 256k. This is a software only limit
and will be subject to tuning later.
Checksumming is still done on compressed extents, and it is done on the
uncompressed version of the data. This way additional encodings can be
layered on without having to figure out which encoding to checksum.
Compression happens at delalloc time, which is basically singled threaded because
it is usually done by a single pdflush thread. This makes it tricky to
spread the compression load across all the cpus on the box. We'll have to
look at parallel pdflush walks of dirty inodes at a later time.
Decompression is hooked into readpages and it does spread across CPUs nicely.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-10-29 21:49:59 +03:00
|
|
|
|
|
|
|
if (compressed)
|
|
|
|
split->block_len = em->block_len;
|
|
|
|
else
|
|
|
|
split->block_len = split->len;
|
|
|
|
|
2008-04-17 19:29:12 +04:00
|
|
|
split->bdev = em->bdev;
|
2008-09-26 18:05:38 +04:00
|
|
|
split->flags = flags;
|
2008-04-17 19:29:12 +04:00
|
|
|
ret = add_extent_mapping(em_tree, split);
|
|
|
|
BUG_ON(ret);
|
|
|
|
free_extent_map(split);
|
|
|
|
split = split2;
|
|
|
|
split2 = NULL;
|
|
|
|
}
|
|
|
|
if (em->block_start < EXTENT_MAP_LAST_BYTE &&
|
|
|
|
testend && em->start + em->len > start + len) {
|
|
|
|
u64 diff = start + len - em->start;
|
|
|
|
|
|
|
|
split->start = start + len;
|
|
|
|
split->len = em->start + em->len - (start + len);
|
|
|
|
split->bdev = em->bdev;
|
2008-09-26 18:05:38 +04:00
|
|
|
split->flags = flags;
|
2008-04-17 19:29:12 +04:00
|
|
|
|
Btrfs: Add zlib compression support
This is a large change for adding compression on reading and writing,
both for inline and regular extents. It does some fairly large
surgery to the writeback paths.
Compression is off by default and enabled by mount -o compress. Even
when the -o compress mount option is not used, it is possible to read
compressed extents off the disk.
If compression for a given set of pages fails to make them smaller, the
file is flagged to avoid future compression attempts later.
* While finding delalloc extents, the pages are locked before being sent down
to the delalloc handler. This allows the delalloc handler to do complex things
such as cleaning the pages, marking them writeback and starting IO on their
behalf.
* Inline extents are inserted at delalloc time now. This allows us to compress
the data before inserting the inline extent, and it allows us to insert
an inline extent that spans multiple pages.
* All of the in-memory extent representations (extent_map.c, ordered-data.c etc)
are changed to record both an in-memory size and an on disk size, as well
as a flag for compression.
From a disk format point of view, the extent pointers in the file are changed
to record the on disk size of a given extent and some encoding flags.
Space in the disk format is allocated for compression encoding, as well
as encryption and a generic 'other' field. Neither the encryption or the
'other' field are currently used.
In order to limit the amount of data read for a single random read in the
file, the size of a compressed extent is limited to 128k. This is a
software only limit, the disk format supports u64 sized compressed extents.
In order to limit the ram consumed while processing extents, the uncompressed
size of a compressed extent is limited to 256k. This is a software only limit
and will be subject to tuning later.
Checksumming is still done on compressed extents, and it is done on the
uncompressed version of the data. This way additional encodings can be
layered on without having to figure out which encoding to checksum.
Compression happens at delalloc time, which is basically singled threaded because
it is usually done by a single pdflush thread. This makes it tricky to
spread the compression load across all the cpus on the box. We'll have to
look at parallel pdflush walks of dirty inodes at a later time.
Decompression is hooked into readpages and it does spread across CPUs nicely.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-10-29 21:49:59 +03:00
|
|
|
if (compressed) {
|
|
|
|
split->block_len = em->block_len;
|
|
|
|
split->block_start = em->block_start;
|
2008-11-10 19:53:33 +03:00
|
|
|
split->orig_start = em->orig_start;
|
Btrfs: Add zlib compression support
This is a large change for adding compression on reading and writing,
both for inline and regular extents. It does some fairly large
surgery to the writeback paths.
Compression is off by default and enabled by mount -o compress. Even
when the -o compress mount option is not used, it is possible to read
compressed extents off the disk.
If compression for a given set of pages fails to make them smaller, the
file is flagged to avoid future compression attempts later.
* While finding delalloc extents, the pages are locked before being sent down
to the delalloc handler. This allows the delalloc handler to do complex things
such as cleaning the pages, marking them writeback and starting IO on their
behalf.
* Inline extents are inserted at delalloc time now. This allows us to compress
the data before inserting the inline extent, and it allows us to insert
an inline extent that spans multiple pages.
* All of the in-memory extent representations (extent_map.c, ordered-data.c etc)
are changed to record both an in-memory size and an on disk size, as well
as a flag for compression.
From a disk format point of view, the extent pointers in the file are changed
to record the on disk size of a given extent and some encoding flags.
Space in the disk format is allocated for compression encoding, as well
as encryption and a generic 'other' field. Neither the encryption or the
'other' field are currently used.
In order to limit the amount of data read for a single random read in the
file, the size of a compressed extent is limited to 128k. This is a
software only limit, the disk format supports u64 sized compressed extents.
In order to limit the ram consumed while processing extents, the uncompressed
size of a compressed extent is limited to 256k. This is a software only limit
and will be subject to tuning later.
Checksumming is still done on compressed extents, and it is done on the
uncompressed version of the data. This way additional encodings can be
layered on without having to figure out which encoding to checksum.
Compression happens at delalloc time, which is basically singled threaded because
it is usually done by a single pdflush thread. This makes it tricky to
spread the compression load across all the cpus on the box. We'll have to
look at parallel pdflush walks of dirty inodes at a later time.
Decompression is hooked into readpages and it does spread across CPUs nicely.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-10-29 21:49:59 +03:00
|
|
|
} else {
|
|
|
|
split->block_len = split->len;
|
|
|
|
split->block_start = em->block_start + diff;
|
2008-11-10 19:53:33 +03:00
|
|
|
split->orig_start = split->start;
|
Btrfs: Add zlib compression support
This is a large change for adding compression on reading and writing,
both for inline and regular extents. It does some fairly large
surgery to the writeback paths.
Compression is off by default and enabled by mount -o compress. Even
when the -o compress mount option is not used, it is possible to read
compressed extents off the disk.
If compression for a given set of pages fails to make them smaller, the
file is flagged to avoid future compression attempts later.
* While finding delalloc extents, the pages are locked before being sent down
to the delalloc handler. This allows the delalloc handler to do complex things
such as cleaning the pages, marking them writeback and starting IO on their
behalf.
* Inline extents are inserted at delalloc time now. This allows us to compress
the data before inserting the inline extent, and it allows us to insert
an inline extent that spans multiple pages.
* All of the in-memory extent representations (extent_map.c, ordered-data.c etc)
are changed to record both an in-memory size and an on disk size, as well
as a flag for compression.
From a disk format point of view, the extent pointers in the file are changed
to record the on disk size of a given extent and some encoding flags.
Space in the disk format is allocated for compression encoding, as well
as encryption and a generic 'other' field. Neither the encryption or the
'other' field are currently used.
In order to limit the amount of data read for a single random read in the
file, the size of a compressed extent is limited to 128k. This is a
software only limit, the disk format supports u64 sized compressed extents.
In order to limit the ram consumed while processing extents, the uncompressed
size of a compressed extent is limited to 256k. This is a software only limit
and will be subject to tuning later.
Checksumming is still done on compressed extents, and it is done on the
uncompressed version of the data. This way additional encodings can be
layered on without having to figure out which encoding to checksum.
Compression happens at delalloc time, which is basically singled threaded because
it is usually done by a single pdflush thread. This makes it tricky to
spread the compression load across all the cpus on the box. We'll have to
look at parallel pdflush walks of dirty inodes at a later time.
Decompression is hooked into readpages and it does spread across CPUs nicely.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-10-29 21:49:59 +03:00
|
|
|
}
|
2008-04-17 19:29:12 +04:00
|
|
|
|
|
|
|
ret = add_extent_mapping(em_tree, split);
|
|
|
|
BUG_ON(ret);
|
|
|
|
free_extent_map(split);
|
|
|
|
split = NULL;
|
|
|
|
}
|
2009-09-03 00:24:52 +04:00
|
|
|
write_unlock(&em_tree->lock);
|
2008-01-25 00:13:08 +03:00
|
|
|
|
2007-08-28 00:49:44 +04:00
|
|
|
/* once for us */
|
|
|
|
free_extent_map(em);
|
|
|
|
/* once for the tree*/
|
|
|
|
free_extent_map(em);
|
|
|
|
}
|
2008-04-17 19:29:12 +04:00
|
|
|
if (split)
|
|
|
|
free_extent_map(split);
|
|
|
|
if (split2)
|
|
|
|
free_extent_map(split2);
|
2007-08-28 00:49:44 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
/*
|
|
|
|
* this is very complex, but the basic idea is to drop all extents
|
|
|
|
* in the range start - end. hint_block is filled in with a block number
|
|
|
|
* that would be a good hint to the block allocator for this file.
|
|
|
|
*
|
|
|
|
* If an extent intersects the range but is not entirely inside the range
|
|
|
|
* it is either truncated or split. Anything entirely inside the range
|
|
|
|
* is deleted from the tree.
|
|
|
|
*/
|
2009-11-12 12:34:08 +03:00
|
|
|
int btrfs_drop_extents(struct btrfs_trans_handle *trans, struct inode *inode,
|
|
|
|
u64 start, u64 end, u64 *hint_byte, int drop_cache)
|
2007-06-12 14:35:45 +04:00
|
|
|
{
|
2009-11-12 12:34:08 +03:00
|
|
|
struct btrfs_root *root = BTRFS_I(inode)->root;
|
2007-10-16 00:14:19 +04:00
|
|
|
struct extent_buffer *leaf;
|
2009-11-12 12:34:08 +03:00
|
|
|
struct btrfs_file_extent_item *fi;
|
2007-06-12 14:35:45 +04:00
|
|
|
struct btrfs_path *path;
|
2007-11-30 18:09:33 +03:00
|
|
|
struct btrfs_key key;
|
2009-11-12 12:34:08 +03:00
|
|
|
struct btrfs_key new_key;
|
|
|
|
u64 search_start = start;
|
|
|
|
u64 disk_bytenr = 0;
|
|
|
|
u64 num_bytes = 0;
|
|
|
|
u64 extent_offset = 0;
|
|
|
|
u64 extent_end = 0;
|
|
|
|
int del_nr = 0;
|
|
|
|
int del_slot = 0;
|
|
|
|
int extent_type;
|
2007-06-28 23:57:36 +04:00
|
|
|
int recow;
|
2007-11-30 18:09:33 +03:00
|
|
|
int ret;
|
2007-06-12 14:35:45 +04:00
|
|
|
|
2009-09-11 20:27:37 +04:00
|
|
|
if (drop_cache)
|
|
|
|
btrfs_drop_extent_cache(inode, start, end - 1, 0);
|
2007-08-28 00:49:44 +04:00
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
path = btrfs_alloc_path();
|
|
|
|
if (!path)
|
|
|
|
return -ENOMEM;
|
2009-11-12 12:34:08 +03:00
|
|
|
|
2009-01-06 05:25:51 +03:00
|
|
|
while (1) {
|
2007-06-28 23:57:36 +04:00
|
|
|
recow = 0;
|
2007-06-12 14:35:45 +04:00
|
|
|
ret = btrfs_lookup_file_extent(trans, root, path, inode->i_ino,
|
|
|
|
search_start, -1);
|
|
|
|
if (ret < 0)
|
2009-11-12 12:34:08 +03:00
|
|
|
break;
|
|
|
|
if (ret > 0 && path->slots[0] > 0 && search_start == start) {
|
|
|
|
leaf = path->nodes[0];
|
|
|
|
btrfs_item_key_to_cpu(leaf, &key, path->slots[0] - 1);
|
|
|
|
if (key.objectid == inode->i_ino &&
|
|
|
|
key.type == BTRFS_EXTENT_DATA_KEY)
|
|
|
|
path->slots[0]--;
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
ret = 0;
|
2007-06-18 17:57:58 +04:00
|
|
|
next_slot:
|
2007-10-16 00:14:19 +04:00
|
|
|
leaf = path->nodes[0];
|
2009-11-12 12:34:08 +03:00
|
|
|
if (path->slots[0] >= btrfs_header_nritems(leaf)) {
|
|
|
|
BUG_ON(del_nr > 0);
|
|
|
|
ret = btrfs_next_leaf(root, path);
|
|
|
|
if (ret < 0)
|
|
|
|
break;
|
|
|
|
if (ret > 0) {
|
|
|
|
ret = 0;
|
|
|
|
break;
|
2007-06-18 17:57:58 +04:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
leaf = path->nodes[0];
|
|
|
|
recow = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
|
|
|
|
if (key.objectid > inode->i_ino ||
|
|
|
|
key.type > BTRFS_EXTENT_DATA_KEY || key.offset >= end)
|
|
|
|
break;
|
|
|
|
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0],
|
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
extent_type = btrfs_file_extent_type(leaf, fi);
|
|
|
|
|
|
|
|
if (extent_type == BTRFS_FILE_EXTENT_REG ||
|
|
|
|
extent_type == BTRFS_FILE_EXTENT_PREALLOC) {
|
|
|
|
disk_bytenr = btrfs_file_extent_disk_bytenr(leaf, fi);
|
|
|
|
num_bytes = btrfs_file_extent_disk_num_bytes(leaf, fi);
|
|
|
|
extent_offset = btrfs_file_extent_offset(leaf, fi);
|
|
|
|
extent_end = key.offset +
|
|
|
|
btrfs_file_extent_num_bytes(leaf, fi);
|
|
|
|
} else if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
|
|
|
|
extent_end = key.offset +
|
|
|
|
btrfs_file_extent_inline_len(leaf, fi);
|
2007-06-18 17:57:58 +04:00
|
|
|
} else {
|
2009-11-12 12:34:08 +03:00
|
|
|
WARN_ON(1);
|
2007-06-18 17:57:58 +04:00
|
|
|
extent_end = search_start;
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
if (extent_end <= search_start) {
|
|
|
|
path->slots[0]++;
|
2007-06-18 17:57:58 +04:00
|
|
|
goto next_slot;
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
search_start = max(key.offset, start);
|
|
|
|
if (recow) {
|
|
|
|
btrfs_release_path(root, path);
|
|
|
|
continue;
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
2008-10-30 21:19:50 +03:00
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
/*
|
|
|
|
* | - range to drop - |
|
|
|
|
* | -------- extent -------- |
|
|
|
|
*/
|
|
|
|
if (start > key.offset && end < extent_end) {
|
|
|
|
BUG_ON(del_nr > 0);
|
|
|
|
BUG_ON(extent_type == BTRFS_FILE_EXTENT_INLINE);
|
|
|
|
|
|
|
|
memcpy(&new_key, &key, sizeof(new_key));
|
|
|
|
new_key.offset = start;
|
|
|
|
ret = btrfs_duplicate_item(trans, root, path,
|
|
|
|
&new_key);
|
|
|
|
if (ret == -EAGAIN) {
|
|
|
|
btrfs_release_path(root, path);
|
|
|
|
continue;
|
2008-10-30 21:19:50 +03:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
if (ret < 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
leaf = path->nodes[0];
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0] - 1,
|
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
start - key.offset);
|
|
|
|
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0],
|
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
|
|
|
|
extent_offset += start - key.offset;
|
|
|
|
btrfs_set_file_extent_offset(leaf, fi, extent_offset);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
extent_end - start);
|
|
|
|
btrfs_mark_buffer_dirty(leaf);
|
|
|
|
|
|
|
|
if (disk_bytenr > 0) {
|
2008-11-07 06:02:51 +03:00
|
|
|
ret = btrfs_inc_extent_ref(trans, root,
|
2009-11-12 12:34:08 +03:00
|
|
|
disk_bytenr, num_bytes, 0,
|
|
|
|
root->root_key.objectid,
|
|
|
|
new_key.objectid,
|
|
|
|
start - extent_offset);
|
2008-11-07 06:02:51 +03:00
|
|
|
BUG_ON(ret);
|
2009-11-12 12:34:08 +03:00
|
|
|
*hint_byte = disk_bytenr;
|
2008-11-07 06:02:51 +03:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
key.offset = start;
|
2008-10-30 21:19:50 +03:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
/*
|
|
|
|
* | ---- range to drop ----- |
|
|
|
|
* | -------- extent -------- |
|
|
|
|
*/
|
|
|
|
if (start <= key.offset && end < extent_end) {
|
|
|
|
BUG_ON(extent_type == BTRFS_FILE_EXTENT_INLINE);
|
2008-10-30 21:19:50 +03:00
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
memcpy(&new_key, &key, sizeof(new_key));
|
|
|
|
new_key.offset = end;
|
|
|
|
btrfs_set_item_key_safe(trans, root, path, &new_key);
|
|
|
|
|
|
|
|
extent_offset += end - key.offset;
|
|
|
|
btrfs_set_file_extent_offset(leaf, fi, extent_offset);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
extent_end - end);
|
|
|
|
btrfs_mark_buffer_dirty(leaf);
|
|
|
|
if (disk_bytenr > 0) {
|
|
|
|
inode_sub_bytes(inode, end - key.offset);
|
|
|
|
*hint_byte = disk_bytenr;
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
break;
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
2008-11-07 06:02:51 +03:00
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
search_start = extent_end;
|
|
|
|
/*
|
|
|
|
* | ---- range to drop ----- |
|
|
|
|
* | -------- extent -------- |
|
|
|
|
*/
|
|
|
|
if (start > key.offset && end >= extent_end) {
|
|
|
|
BUG_ON(del_nr > 0);
|
|
|
|
BUG_ON(extent_type == BTRFS_FILE_EXTENT_INLINE);
|
2007-06-18 17:57:58 +04:00
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
start - key.offset);
|
|
|
|
btrfs_mark_buffer_dirty(leaf);
|
|
|
|
if (disk_bytenr > 0) {
|
|
|
|
inode_sub_bytes(inode, extent_end - start);
|
|
|
|
*hint_byte = disk_bytenr;
|
|
|
|
}
|
|
|
|
if (end == extent_end)
|
|
|
|
break;
|
Btrfs: Add zlib compression support
This is a large change for adding compression on reading and writing,
both for inline and regular extents. It does some fairly large
surgery to the writeback paths.
Compression is off by default and enabled by mount -o compress. Even
when the -o compress mount option is not used, it is possible to read
compressed extents off the disk.
If compression for a given set of pages fails to make them smaller, the
file is flagged to avoid future compression attempts later.
* While finding delalloc extents, the pages are locked before being sent down
to the delalloc handler. This allows the delalloc handler to do complex things
such as cleaning the pages, marking them writeback and starting IO on their
behalf.
* Inline extents are inserted at delalloc time now. This allows us to compress
the data before inserting the inline extent, and it allows us to insert
an inline extent that spans multiple pages.
* All of the in-memory extent representations (extent_map.c, ordered-data.c etc)
are changed to record both an in-memory size and an on disk size, as well
as a flag for compression.
From a disk format point of view, the extent pointers in the file are changed
to record the on disk size of a given extent and some encoding flags.
Space in the disk format is allocated for compression encoding, as well
as encryption and a generic 'other' field. Neither the encryption or the
'other' field are currently used.
In order to limit the amount of data read for a single random read in the
file, the size of a compressed extent is limited to 128k. This is a
software only limit, the disk format supports u64 sized compressed extents.
In order to limit the ram consumed while processing extents, the uncompressed
size of a compressed extent is limited to 256k. This is a software only limit
and will be subject to tuning later.
Checksumming is still done on compressed extents, and it is done on the
uncompressed version of the data. This way additional encodings can be
layered on without having to figure out which encoding to checksum.
Compression happens at delalloc time, which is basically singled threaded because
it is usually done by a single pdflush thread. This makes it tricky to
spread the compression load across all the cpus on the box. We'll have to
look at parallel pdflush walks of dirty inodes at a later time.
Decompression is hooked into readpages and it does spread across CPUs nicely.
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2008-10-29 21:49:59 +03:00
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
path->slots[0]++;
|
|
|
|
goto next_slot;
|
2008-09-23 21:14:14 +04:00
|
|
|
}
|
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
/*
|
|
|
|
* | ---- range to drop ----- |
|
|
|
|
* | ------ extent ------ |
|
|
|
|
*/
|
|
|
|
if (start <= key.offset && end >= extent_end) {
|
|
|
|
if (del_nr == 0) {
|
|
|
|
del_slot = path->slots[0];
|
|
|
|
del_nr = 1;
|
|
|
|
} else {
|
|
|
|
BUG_ON(del_slot + del_nr != path->slots[0]);
|
|
|
|
del_nr++;
|
|
|
|
}
|
2008-09-23 21:14:14 +04:00
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
|
2008-10-09 19:46:29 +04:00
|
|
|
inode_sub_bytes(inode,
|
2009-11-12 12:34:08 +03:00
|
|
|
extent_end - key.offset);
|
|
|
|
extent_end = ALIGN(extent_end,
|
|
|
|
root->sectorsize);
|
|
|
|
} else if (disk_bytenr > 0) {
|
2008-09-23 21:14:14 +04:00
|
|
|
ret = btrfs_free_extent(trans, root,
|
2009-11-12 12:34:08 +03:00
|
|
|
disk_bytenr, num_bytes, 0,
|
|
|
|
root->root_key.objectid,
|
Btrfs: Mixed back reference (FORWARD ROLLING FORMAT CHANGE)
This commit introduces a new kind of back reference for btrfs metadata.
Once a filesystem has been mounted with this commit, IT WILL NO LONGER
BE MOUNTABLE BY OLDER KERNELS.
When a tree block in subvolume tree is cow'd, the reference counts of all
extents it points to are increased by one. At transaction commit time,
the old root of the subvolume is recorded in a "dead root" data structure,
and the btree it points to is later walked, dropping reference counts
and freeing any blocks where the reference count goes to 0.
The increments done during cow and decrements done after commit cancel out,
and the walk is a very expensive way to go about freeing the blocks that
are no longer referenced by the new btree root. This commit reduces the
transaction overhead by avoiding the need for dead root records.
When a non-shared tree block is cow'd, we free the old block at once, and the
new block inherits old block's references. When a tree block with reference
count > 1 is cow'd, we increase the reference counts of all extents
the new block points to by one, and decrease the old block's reference count by
one.
This dead tree avoidance code removes the need to modify the reference
counts of lower level extents when a non-shared tree block is cow'd.
But we still need to update back ref for all pointers in the block.
This is because the location of the block is recorded in the back ref
item.
We can solve this by introducing a new type of back ref. The new
back ref provides information about pointer's key, level and in which
tree the pointer lives. This information allow us to find the pointer
by searching the tree. The shortcoming of the new back ref is that it
only works for pointers in tree blocks referenced by their owner trees.
This is mostly a problem for snapshots, where resolving one of these
fuzzy back references would be O(number_of_snapshots) and quite slow.
The solution used here is to use the fuzzy back references in the common
case where a given tree block is only referenced by one root,
and use the full back references when multiple roots have a reference
on a given block.
This commit adds per subvolume red-black tree to keep trace of cached
inodes. The red-black tree helps the balancing code to find cached
inodes whose inode numbers within a given range.
This commit improves the balancing code by introducing several data
structures to keep the state of balancing. The most important one
is the back ref cache. It caches how the upper level tree blocks are
referenced. This greatly reduce the overhead of checking back ref.
The improved balancing code scales significantly better with a large
number of snapshots.
This is a very large commit and was written in a number of
pieces. But, they depend heavily on the disk format change and were
squashed together to make sure git bisect didn't end up in a
bad state wrt space balancing or the format change.
Signed-off-by: Yan Zheng <zheng.yan@oracle.com>
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2009-06-10 18:45:14 +04:00
|
|
|
key.objectid, key.offset -
|
2009-11-12 12:34:08 +03:00
|
|
|
extent_offset);
|
2008-09-23 21:14:14 +04:00
|
|
|
BUG_ON(ret);
|
2009-11-12 12:34:08 +03:00
|
|
|
inode_sub_bytes(inode,
|
|
|
|
extent_end - key.offset);
|
|
|
|
*hint_byte = disk_bytenr;
|
2008-09-23 21:14:14 +04:00
|
|
|
}
|
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
if (end == extent_end)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (path->slots[0] + 1 < btrfs_header_nritems(leaf)) {
|
|
|
|
path->slots[0]++;
|
|
|
|
goto next_slot;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = btrfs_del_items(trans, root, path, del_slot,
|
|
|
|
del_nr);
|
|
|
|
BUG_ON(ret);
|
|
|
|
|
|
|
|
del_nr = 0;
|
|
|
|
del_slot = 0;
|
|
|
|
|
|
|
|
btrfs_release_path(root, path);
|
|
|
|
continue;
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
|
|
|
|
BUG_ON(1);
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
|
|
|
|
if (del_nr > 0) {
|
|
|
|
ret = btrfs_del_items(trans, root, path, del_slot, del_nr);
|
|
|
|
BUG_ON(ret);
|
2008-10-30 21:19:50 +03:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
|
|
|
|
btrfs_free_path(path);
|
2007-06-12 14:35:45 +04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-10-30 21:25:28 +03:00
|
|
|
static int extent_mergeable(struct extent_buffer *leaf, int slot,
|
2010-01-15 11:43:09 +03:00
|
|
|
u64 objectid, u64 bytenr, u64 orig_offset,
|
|
|
|
u64 *start, u64 *end)
|
2008-10-30 21:25:28 +03:00
|
|
|
{
|
|
|
|
struct btrfs_file_extent_item *fi;
|
|
|
|
struct btrfs_key key;
|
|
|
|
u64 extent_end;
|
|
|
|
|
|
|
|
if (slot < 0 || slot >= btrfs_header_nritems(leaf))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
btrfs_item_key_to_cpu(leaf, &key, slot);
|
|
|
|
if (key.objectid != objectid || key.type != BTRFS_EXTENT_DATA_KEY)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fi = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item);
|
|
|
|
if (btrfs_file_extent_type(leaf, fi) != BTRFS_FILE_EXTENT_REG ||
|
|
|
|
btrfs_file_extent_disk_bytenr(leaf, fi) != bytenr ||
|
2010-01-15 11:43:09 +03:00
|
|
|
btrfs_file_extent_offset(leaf, fi) != key.offset - orig_offset ||
|
2008-10-30 21:25:28 +03:00
|
|
|
btrfs_file_extent_compression(leaf, fi) ||
|
|
|
|
btrfs_file_extent_encryption(leaf, fi) ||
|
|
|
|
btrfs_file_extent_other_encoding(leaf, fi))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
extent_end = key.offset + btrfs_file_extent_num_bytes(leaf, fi);
|
|
|
|
if ((*start && *start != key.offset) || (*end && *end != extent_end))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
*start = key.offset;
|
|
|
|
*end = extent_end;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Mark extent in the range start - end as written.
|
|
|
|
*
|
|
|
|
* This changes extent type from 'pre-allocated' to 'regular'. If only
|
|
|
|
* part of extent is marked as written, the extent will be split into
|
|
|
|
* two or three.
|
|
|
|
*/
|
|
|
|
int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
|
|
|
|
struct inode *inode, u64 start, u64 end)
|
|
|
|
{
|
2009-11-12 12:34:08 +03:00
|
|
|
struct btrfs_root *root = BTRFS_I(inode)->root;
|
2008-10-30 21:25:28 +03:00
|
|
|
struct extent_buffer *leaf;
|
|
|
|
struct btrfs_path *path;
|
|
|
|
struct btrfs_file_extent_item *fi;
|
|
|
|
struct btrfs_key key;
|
2009-11-12 12:34:08 +03:00
|
|
|
struct btrfs_key new_key;
|
2008-10-30 21:25:28 +03:00
|
|
|
u64 bytenr;
|
|
|
|
u64 num_bytes;
|
|
|
|
u64 extent_end;
|
Btrfs: Mixed back reference (FORWARD ROLLING FORMAT CHANGE)
This commit introduces a new kind of back reference for btrfs metadata.
Once a filesystem has been mounted with this commit, IT WILL NO LONGER
BE MOUNTABLE BY OLDER KERNELS.
When a tree block in subvolume tree is cow'd, the reference counts of all
extents it points to are increased by one. At transaction commit time,
the old root of the subvolume is recorded in a "dead root" data structure,
and the btree it points to is later walked, dropping reference counts
and freeing any blocks where the reference count goes to 0.
The increments done during cow and decrements done after commit cancel out,
and the walk is a very expensive way to go about freeing the blocks that
are no longer referenced by the new btree root. This commit reduces the
transaction overhead by avoiding the need for dead root records.
When a non-shared tree block is cow'd, we free the old block at once, and the
new block inherits old block's references. When a tree block with reference
count > 1 is cow'd, we increase the reference counts of all extents
the new block points to by one, and decrease the old block's reference count by
one.
This dead tree avoidance code removes the need to modify the reference
counts of lower level extents when a non-shared tree block is cow'd.
But we still need to update back ref for all pointers in the block.
This is because the location of the block is recorded in the back ref
item.
We can solve this by introducing a new type of back ref. The new
back ref provides information about pointer's key, level and in which
tree the pointer lives. This information allow us to find the pointer
by searching the tree. The shortcoming of the new back ref is that it
only works for pointers in tree blocks referenced by their owner trees.
This is mostly a problem for snapshots, where resolving one of these
fuzzy back references would be O(number_of_snapshots) and quite slow.
The solution used here is to use the fuzzy back references in the common
case where a given tree block is only referenced by one root,
and use the full back references when multiple roots have a reference
on a given block.
This commit adds per subvolume red-black tree to keep trace of cached
inodes. The red-black tree helps the balancing code to find cached
inodes whose inode numbers within a given range.
This commit improves the balancing code by introducing several data
structures to keep the state of balancing. The most important one
is the back ref cache. It caches how the upper level tree blocks are
referenced. This greatly reduce the overhead of checking back ref.
The improved balancing code scales significantly better with a large
number of snapshots.
This is a very large commit and was written in a number of
pieces. But, they depend heavily on the disk format change and were
squashed together to make sure git bisect didn't end up in a
bad state wrt space balancing or the format change.
Signed-off-by: Yan Zheng <zheng.yan@oracle.com>
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2009-06-10 18:45:14 +04:00
|
|
|
u64 orig_offset;
|
2008-10-30 21:25:28 +03:00
|
|
|
u64 other_start;
|
|
|
|
u64 other_end;
|
2009-11-12 12:34:08 +03:00
|
|
|
u64 split;
|
|
|
|
int del_nr = 0;
|
|
|
|
int del_slot = 0;
|
2010-01-15 11:43:09 +03:00
|
|
|
int recow;
|
2008-10-30 21:25:28 +03:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
btrfs_drop_extent_cache(inode, start, end - 1, 0);
|
|
|
|
|
|
|
|
path = btrfs_alloc_path();
|
|
|
|
BUG_ON(!path);
|
|
|
|
again:
|
2010-01-15 11:43:09 +03:00
|
|
|
recow = 0;
|
2009-11-12 12:34:08 +03:00
|
|
|
split = start;
|
2008-10-30 21:25:28 +03:00
|
|
|
key.objectid = inode->i_ino;
|
|
|
|
key.type = BTRFS_EXTENT_DATA_KEY;
|
2009-11-12 12:34:08 +03:00
|
|
|
key.offset = split;
|
2008-10-30 21:25:28 +03:00
|
|
|
|
|
|
|
ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
|
|
|
|
if (ret > 0 && path->slots[0] > 0)
|
|
|
|
path->slots[0]--;
|
|
|
|
|
|
|
|
leaf = path->nodes[0];
|
|
|
|
btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
|
|
|
|
BUG_ON(key.objectid != inode->i_ino ||
|
|
|
|
key.type != BTRFS_EXTENT_DATA_KEY);
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0],
|
|
|
|
struct btrfs_file_extent_item);
|
2009-11-12 12:34:08 +03:00
|
|
|
BUG_ON(btrfs_file_extent_type(leaf, fi) !=
|
|
|
|
BTRFS_FILE_EXTENT_PREALLOC);
|
2008-10-30 21:25:28 +03:00
|
|
|
extent_end = key.offset + btrfs_file_extent_num_bytes(leaf, fi);
|
|
|
|
BUG_ON(key.offset > start || extent_end < end);
|
|
|
|
|
|
|
|
bytenr = btrfs_file_extent_disk_bytenr(leaf, fi);
|
|
|
|
num_bytes = btrfs_file_extent_disk_num_bytes(leaf, fi);
|
Btrfs: Mixed back reference (FORWARD ROLLING FORMAT CHANGE)
This commit introduces a new kind of back reference for btrfs metadata.
Once a filesystem has been mounted with this commit, IT WILL NO LONGER
BE MOUNTABLE BY OLDER KERNELS.
When a tree block in subvolume tree is cow'd, the reference counts of all
extents it points to are increased by one. At transaction commit time,
the old root of the subvolume is recorded in a "dead root" data structure,
and the btree it points to is later walked, dropping reference counts
and freeing any blocks where the reference count goes to 0.
The increments done during cow and decrements done after commit cancel out,
and the walk is a very expensive way to go about freeing the blocks that
are no longer referenced by the new btree root. This commit reduces the
transaction overhead by avoiding the need for dead root records.
When a non-shared tree block is cow'd, we free the old block at once, and the
new block inherits old block's references. When a tree block with reference
count > 1 is cow'd, we increase the reference counts of all extents
the new block points to by one, and decrease the old block's reference count by
one.
This dead tree avoidance code removes the need to modify the reference
counts of lower level extents when a non-shared tree block is cow'd.
But we still need to update back ref for all pointers in the block.
This is because the location of the block is recorded in the back ref
item.
We can solve this by introducing a new type of back ref. The new
back ref provides information about pointer's key, level and in which
tree the pointer lives. This information allow us to find the pointer
by searching the tree. The shortcoming of the new back ref is that it
only works for pointers in tree blocks referenced by their owner trees.
This is mostly a problem for snapshots, where resolving one of these
fuzzy back references would be O(number_of_snapshots) and quite slow.
The solution used here is to use the fuzzy back references in the common
case where a given tree block is only referenced by one root,
and use the full back references when multiple roots have a reference
on a given block.
This commit adds per subvolume red-black tree to keep trace of cached
inodes. The red-black tree helps the balancing code to find cached
inodes whose inode numbers within a given range.
This commit improves the balancing code by introducing several data
structures to keep the state of balancing. The most important one
is the back ref cache. It caches how the upper level tree blocks are
referenced. This greatly reduce the overhead of checking back ref.
The improved balancing code scales significantly better with a large
number of snapshots.
This is a very large commit and was written in a number of
pieces. But, they depend heavily on the disk format change and were
squashed together to make sure git bisect didn't end up in a
bad state wrt space balancing or the format change.
Signed-off-by: Yan Zheng <zheng.yan@oracle.com>
Signed-off-by: Chris Mason <chris.mason@oracle.com>
2009-06-10 18:45:14 +04:00
|
|
|
orig_offset = key.offset - btrfs_file_extent_offset(leaf, fi);
|
2010-01-15 11:43:09 +03:00
|
|
|
memcpy(&new_key, &key, sizeof(new_key));
|
|
|
|
|
|
|
|
if (start == key.offset && end < extent_end) {
|
|
|
|
other_start = 0;
|
|
|
|
other_end = start;
|
|
|
|
if (extent_mergeable(leaf, path->slots[0] - 1,
|
|
|
|
inode->i_ino, bytenr, orig_offset,
|
|
|
|
&other_start, &other_end)) {
|
|
|
|
new_key.offset = end;
|
|
|
|
btrfs_set_item_key_safe(trans, root, path, &new_key);
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0],
|
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
extent_end - end);
|
|
|
|
btrfs_set_file_extent_offset(leaf, fi,
|
|
|
|
end - orig_offset);
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0] - 1,
|
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
end - other_start);
|
|
|
|
btrfs_mark_buffer_dirty(leaf);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start > key.offset && end == extent_end) {
|
|
|
|
other_start = end;
|
|
|
|
other_end = 0;
|
|
|
|
if (extent_mergeable(leaf, path->slots[0] + 1,
|
|
|
|
inode->i_ino, bytenr, orig_offset,
|
|
|
|
&other_start, &other_end)) {
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0],
|
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
start - key.offset);
|
|
|
|
path->slots[0]++;
|
|
|
|
new_key.offset = start;
|
|
|
|
btrfs_set_item_key_safe(trans, root, path, &new_key);
|
|
|
|
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0],
|
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
other_end - start);
|
|
|
|
btrfs_set_file_extent_offset(leaf, fi,
|
|
|
|
start - orig_offset);
|
|
|
|
btrfs_mark_buffer_dirty(leaf);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
2008-10-30 21:25:28 +03:00
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
while (start > key.offset || end < extent_end) {
|
|
|
|
if (key.offset == start)
|
|
|
|
split = end;
|
|
|
|
|
|
|
|
new_key.offset = split;
|
|
|
|
ret = btrfs_duplicate_item(trans, root, path, &new_key);
|
|
|
|
if (ret == -EAGAIN) {
|
|
|
|
btrfs_release_path(root, path);
|
|
|
|
goto again;
|
2008-10-30 21:25:28 +03:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
BUG_ON(ret < 0);
|
2008-10-30 21:25:28 +03:00
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
leaf = path->nodes[0];
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0] - 1,
|
2008-10-30 21:25:28 +03:00
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
2009-11-12 12:34:08 +03:00
|
|
|
split - key.offset);
|
|
|
|
|
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0],
|
|
|
|
struct btrfs_file_extent_item);
|
|
|
|
|
|
|
|
btrfs_set_file_extent_offset(leaf, fi, split - orig_offset);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
extent_end - split);
|
2008-10-30 21:25:28 +03:00
|
|
|
btrfs_mark_buffer_dirty(leaf);
|
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
ret = btrfs_inc_extent_ref(trans, root, bytenr, num_bytes, 0,
|
|
|
|
root->root_key.objectid,
|
|
|
|
inode->i_ino, orig_offset);
|
2008-10-30 21:25:28 +03:00
|
|
|
BUG_ON(ret);
|
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
if (split == start) {
|
|
|
|
key.offset = start;
|
|
|
|
} else {
|
|
|
|
BUG_ON(start != key.offset);
|
2008-10-30 21:25:28 +03:00
|
|
|
path->slots[0]--;
|
2009-11-12 12:34:08 +03:00
|
|
|
extent_end = end;
|
2008-10-30 21:25:28 +03:00
|
|
|
}
|
2010-01-15 11:43:09 +03:00
|
|
|
recow = 1;
|
2008-10-30 21:25:28 +03:00
|
|
|
}
|
|
|
|
|
2009-11-12 12:34:08 +03:00
|
|
|
other_start = end;
|
|
|
|
other_end = 0;
|
2010-01-15 11:43:09 +03:00
|
|
|
if (extent_mergeable(leaf, path->slots[0] + 1,
|
|
|
|
inode->i_ino, bytenr, orig_offset,
|
|
|
|
&other_start, &other_end)) {
|
|
|
|
if (recow) {
|
|
|
|
btrfs_release_path(root, path);
|
|
|
|
goto again;
|
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
extent_end = other_end;
|
|
|
|
del_slot = path->slots[0] + 1;
|
|
|
|
del_nr++;
|
|
|
|
ret = btrfs_free_extent(trans, root, bytenr, num_bytes,
|
|
|
|
0, root->root_key.objectid,
|
|
|
|
inode->i_ino, orig_offset);
|
|
|
|
BUG_ON(ret);
|
2008-10-30 21:25:28 +03:00
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
other_start = 0;
|
|
|
|
other_end = start;
|
2010-01-15 11:43:09 +03:00
|
|
|
if (extent_mergeable(leaf, path->slots[0] - 1,
|
|
|
|
inode->i_ino, bytenr, orig_offset,
|
|
|
|
&other_start, &other_end)) {
|
|
|
|
if (recow) {
|
|
|
|
btrfs_release_path(root, path);
|
|
|
|
goto again;
|
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
key.offset = other_start;
|
|
|
|
del_slot = path->slots[0];
|
|
|
|
del_nr++;
|
|
|
|
ret = btrfs_free_extent(trans, root, bytenr, num_bytes,
|
|
|
|
0, root->root_key.objectid,
|
|
|
|
inode->i_ino, orig_offset);
|
|
|
|
BUG_ON(ret);
|
|
|
|
}
|
2010-01-15 11:43:09 +03:00
|
|
|
fi = btrfs_item_ptr(leaf, path->slots[0],
|
|
|
|
struct btrfs_file_extent_item);
|
2009-11-12 12:34:08 +03:00
|
|
|
if (del_nr == 0) {
|
|
|
|
btrfs_set_file_extent_type(leaf, fi,
|
|
|
|
BTRFS_FILE_EXTENT_REG);
|
|
|
|
btrfs_mark_buffer_dirty(leaf);
|
2010-01-15 11:43:09 +03:00
|
|
|
} else {
|
|
|
|
btrfs_set_file_extent_type(leaf, fi,
|
|
|
|
BTRFS_FILE_EXTENT_REG);
|
|
|
|
btrfs_set_file_extent_num_bytes(leaf, fi,
|
|
|
|
extent_end - key.offset);
|
|
|
|
btrfs_mark_buffer_dirty(leaf);
|
2009-11-12 12:34:08 +03:00
|
|
|
|
2010-01-15 11:43:09 +03:00
|
|
|
ret = btrfs_del_items(trans, root, path, del_slot, del_nr);
|
|
|
|
BUG_ON(ret);
|
|
|
|
}
|
2009-11-12 12:34:08 +03:00
|
|
|
out:
|
2008-10-30 21:25:28 +03:00
|
|
|
btrfs_free_path(path);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
/*
|
2008-09-29 23:18:18 +04:00
|
|
|
* this gets pages into the page cache and locks them down, it also properly
|
|
|
|
* waits for data=ordered extents to finish before allowing the pages to be
|
|
|
|
* modified.
|
2007-06-12 14:35:45 +04:00
|
|
|
*/
|
2009-01-06 05:25:51 +03:00
|
|
|
static noinline int prepare_pages(struct btrfs_root *root, struct file *file,
|
2008-01-03 18:01:48 +03:00
|
|
|
struct page **pages, size_t num_pages,
|
|
|
|
loff_t pos, unsigned long first_index,
|
|
|
|
unsigned long last_index, size_t write_bytes)
|
2007-06-12 14:35:45 +04:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
unsigned long index = pos >> PAGE_CACHE_SHIFT;
|
2007-12-19 00:15:09 +03:00
|
|
|
struct inode *inode = fdentry(file)->d_inode;
|
2007-06-12 14:35:45 +04:00
|
|
|
int err = 0;
|
2007-06-18 17:57:58 +04:00
|
|
|
u64 start_pos;
|
2008-07-17 20:53:50 +04:00
|
|
|
u64 last_pos;
|
2007-06-18 17:57:58 +04:00
|
|
|
|
2007-10-16 00:14:19 +04:00
|
|
|
start_pos = pos & ~((u64)root->sectorsize - 1);
|
2008-07-17 20:53:50 +04:00
|
|
|
last_pos = ((u64)index + num_pages) << PAGE_CACHE_SHIFT;
|
2007-06-12 14:35:45 +04:00
|
|
|
|
2008-10-30 21:19:41 +03:00
|
|
|
if (start_pos > inode->i_size) {
|
|
|
|
err = btrfs_cont_expand(inode, start_pos);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
memset(pages, 0, num_pages * sizeof(struct page *));
|
2008-07-17 20:53:50 +04:00
|
|
|
again:
|
2007-06-12 14:35:45 +04:00
|
|
|
for (i = 0; i < num_pages; i++) {
|
|
|
|
pages[i] = grab_cache_page(inode->i_mapping, index + i);
|
|
|
|
if (!pages[i]) {
|
|
|
|
err = -ENOMEM;
|
2007-08-28 00:49:44 +04:00
|
|
|
BUG_ON(1);
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
2007-06-28 23:57:36 +04:00
|
|
|
wait_on_page_writeback(pages[i]);
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
2008-02-19 19:29:24 +03:00
|
|
|
if (start_pos < inode->i_size) {
|
2008-07-17 20:53:50 +04:00
|
|
|
struct btrfs_ordered_extent *ordered;
|
2008-02-19 20:55:05 +03:00
|
|
|
lock_extent(&BTRFS_I(inode)->io_tree,
|
|
|
|
start_pos, last_pos - 1, GFP_NOFS);
|
2009-01-06 05:25:51 +03:00
|
|
|
ordered = btrfs_lookup_first_ordered_extent(inode,
|
|
|
|
last_pos - 1);
|
2008-07-17 20:53:50 +04:00
|
|
|
if (ordered &&
|
|
|
|
ordered->file_offset + ordered->len > start_pos &&
|
|
|
|
ordered->file_offset < last_pos) {
|
|
|
|
btrfs_put_ordered_extent(ordered);
|
|
|
|
unlock_extent(&BTRFS_I(inode)->io_tree,
|
|
|
|
start_pos, last_pos - 1, GFP_NOFS);
|
|
|
|
for (i = 0; i < num_pages; i++) {
|
|
|
|
unlock_page(pages[i]);
|
|
|
|
page_cache_release(pages[i]);
|
|
|
|
}
|
|
|
|
btrfs_wait_ordered_range(inode, start_pos,
|
|
|
|
last_pos - start_pos);
|
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
if (ordered)
|
|
|
|
btrfs_put_ordered_extent(ordered);
|
|
|
|
|
2008-02-19 19:29:24 +03:00
|
|
|
clear_extent_bits(&BTRFS_I(inode)->io_tree, start_pos,
|
2009-10-08 21:34:05 +04:00
|
|
|
last_pos - 1, EXTENT_DIRTY | EXTENT_DELALLOC |
|
|
|
|
EXTENT_DO_ACCOUNTING,
|
2008-02-19 19:29:24 +03:00
|
|
|
GFP_NOFS);
|
2008-02-19 20:55:05 +03:00
|
|
|
unlock_extent(&BTRFS_I(inode)->io_tree,
|
|
|
|
start_pos, last_pos - 1, GFP_NOFS);
|
2008-02-19 19:29:24 +03:00
|
|
|
}
|
2008-07-17 20:53:50 +04:00
|
|
|
for (i = 0; i < num_pages; i++) {
|
2008-08-01 19:27:23 +04:00
|
|
|
clear_page_dirty_for_io(pages[i]);
|
2008-07-17 20:53:50 +04:00
|
|
|
set_page_extent_mapped(pages[i]);
|
|
|
|
WARN_ON(!PageLocked(pages[i]));
|
|
|
|
}
|
2007-06-12 14:35:45 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t btrfs_file_write(struct file *file, const char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
|
|
|
loff_t pos;
|
2007-10-29 21:36:41 +03:00
|
|
|
loff_t start_pos;
|
|
|
|
ssize_t num_written = 0;
|
|
|
|
ssize_t err = 0;
|
2007-06-12 14:35:45 +04:00
|
|
|
int ret = 0;
|
2007-12-19 00:15:09 +03:00
|
|
|
struct inode *inode = fdentry(file)->d_inode;
|
2007-06-12 14:35:45 +04:00
|
|
|
struct btrfs_root *root = BTRFS_I(inode)->root;
|
2007-06-18 17:57:58 +04:00
|
|
|
struct page **pages = NULL;
|
|
|
|
int nrptrs;
|
2007-06-12 14:35:45 +04:00
|
|
|
struct page *pinned[2];
|
|
|
|
unsigned long first_index;
|
|
|
|
unsigned long last_index;
|
2008-10-03 20:30:02 +04:00
|
|
|
int will_write;
|
|
|
|
|
|
|
|
will_write = ((file->f_flags & O_SYNC) || IS_SYNC(inode) ||
|
|
|
|
(file->f_flags & O_DIRECT));
|
2007-06-18 17:57:58 +04:00
|
|
|
|
|
|
|
nrptrs = min((count + PAGE_CACHE_SIZE - 1) / PAGE_CACHE_SIZE,
|
|
|
|
PAGE_CACHE_SIZE / (sizeof(struct page *)));
|
2007-06-12 14:35:45 +04:00
|
|
|
pinned[0] = NULL;
|
|
|
|
pinned[1] = NULL;
|
2007-10-29 21:36:41 +03:00
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
pos = *ppos;
|
2007-10-29 21:36:41 +03:00
|
|
|
start_pos = pos;
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
vfs_check_frozen(inode->i_sb, SB_FREEZE_WRITE);
|
2009-10-01 20:29:10 +04:00
|
|
|
|
|
|
|
/* do the reserve before the mutex lock in case we have to do some
|
|
|
|
* flushing. We wouldn't deadlock, but this is more polite.
|
|
|
|
*/
|
|
|
|
err = btrfs_reserve_metadata_for_delalloc(root, inode, 1);
|
|
|
|
if (err)
|
|
|
|
goto out_nolock;
|
|
|
|
|
|
|
|
mutex_lock(&inode->i_mutex);
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
current->backing_dev_info = inode->i_mapping->backing_dev_info;
|
|
|
|
err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
|
|
|
|
if (err)
|
2009-10-01 20:29:10 +04:00
|
|
|
goto out;
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
if (count == 0)
|
2009-10-01 20:29:10 +04:00
|
|
|
goto out;
|
2008-09-24 19:48:04 +04:00
|
|
|
|
2008-07-31 00:54:26 +04:00
|
|
|
err = file_remove_suid(file);
|
2007-06-12 14:35:45 +04:00
|
|
|
if (err)
|
2009-10-01 20:29:10 +04:00
|
|
|
goto out;
|
2009-09-12 00:12:44 +04:00
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
file_update_time(file);
|
|
|
|
|
2007-06-18 17:57:58 +04:00
|
|
|
pages = kmalloc(nrptrs * sizeof(struct page *), GFP_KERNEL);
|
2007-06-12 14:35:45 +04:00
|
|
|
|
2009-10-01 20:29:10 +04:00
|
|
|
/* generic_write_checks can change our pos */
|
|
|
|
start_pos = pos;
|
|
|
|
|
2008-12-09 00:40:21 +03:00
|
|
|
BTRFS_I(inode)->sequence++;
|
2007-06-12 14:35:45 +04:00
|
|
|
first_index = pos >> PAGE_CACHE_SHIFT;
|
|
|
|
last_index = (pos + count) >> PAGE_CACHE_SHIFT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* there are lots of better ways to do this, but this code
|
|
|
|
* makes sure the first and last page in the file range are
|
|
|
|
* up to date and ready for cow
|
|
|
|
*/
|
|
|
|
if ((pos & (PAGE_CACHE_SIZE - 1))) {
|
|
|
|
pinned[0] = grab_cache_page(inode->i_mapping, first_index);
|
|
|
|
if (!PageUptodate(pinned[0])) {
|
2007-06-15 21:50:00 +04:00
|
|
|
ret = btrfs_readpage(NULL, pinned[0]);
|
2007-06-12 14:35:45 +04:00
|
|
|
BUG_ON(ret);
|
|
|
|
wait_on_page_locked(pinned[0]);
|
|
|
|
} else {
|
|
|
|
unlock_page(pinned[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((pos + count) & (PAGE_CACHE_SIZE - 1)) {
|
|
|
|
pinned[1] = grab_cache_page(inode->i_mapping, last_index);
|
|
|
|
if (!PageUptodate(pinned[1])) {
|
2007-06-15 21:50:00 +04:00
|
|
|
ret = btrfs_readpage(NULL, pinned[1]);
|
2007-06-12 14:35:45 +04:00
|
|
|
BUG_ON(ret);
|
|
|
|
wait_on_page_locked(pinned[1]);
|
|
|
|
} else {
|
|
|
|
unlock_page(pinned[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-01-06 05:25:51 +03:00
|
|
|
while (count > 0) {
|
2007-06-12 14:35:45 +04:00
|
|
|
size_t offset = pos & (PAGE_CACHE_SIZE - 1);
|
2007-06-22 22:16:24 +04:00
|
|
|
size_t write_bytes = min(count, nrptrs *
|
|
|
|
(size_t)PAGE_CACHE_SIZE -
|
2007-06-18 17:57:58 +04:00
|
|
|
offset);
|
2007-06-12 14:35:45 +04:00
|
|
|
size_t num_pages = (write_bytes + PAGE_CACHE_SIZE - 1) >>
|
|
|
|
PAGE_CACHE_SHIFT;
|
|
|
|
|
2007-06-18 17:57:58 +04:00
|
|
|
WARN_ON(num_pages > nrptrs);
|
2009-01-05 23:49:11 +03:00
|
|
|
memset(pages, 0, sizeof(struct page *) * nrptrs);
|
2007-12-22 00:27:21 +03:00
|
|
|
|
2009-02-20 19:00:09 +03:00
|
|
|
ret = btrfs_check_data_free_space(root, inode, write_bytes);
|
2007-12-22 00:27:21 +03:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
ret = prepare_pages(root, file, pages, num_pages,
|
|
|
|
pos, first_index, last_index,
|
2007-06-18 17:57:58 +04:00
|
|
|
write_bytes);
|
2009-02-20 19:00:09 +03:00
|
|
|
if (ret) {
|
|
|
|
btrfs_free_reserved_data_space(root, inode,
|
|
|
|
write_bytes);
|
2007-06-22 22:16:25 +04:00
|
|
|
goto out;
|
2009-02-20 19:00:09 +03:00
|
|
|
}
|
2007-06-12 14:35:45 +04:00
|
|
|
|
|
|
|
ret = btrfs_copy_from_user(pos, num_pages,
|
|
|
|
write_bytes, pages, buf);
|
2007-06-22 22:16:25 +04:00
|
|
|
if (ret) {
|
2009-02-20 19:00:09 +03:00
|
|
|
btrfs_free_reserved_data_space(root, inode,
|
|
|
|
write_bytes);
|
2007-06-22 22:16:25 +04:00
|
|
|
btrfs_drop_pages(pages, num_pages);
|
|
|
|
goto out;
|
|
|
|
}
|
2007-06-12 14:35:45 +04:00
|
|
|
|
|
|
|
ret = dirty_and_release_pages(NULL, root, file, pages,
|
|
|
|
num_pages, pos, write_bytes);
|
|
|
|
btrfs_drop_pages(pages, num_pages);
|
2009-02-20 19:00:09 +03:00
|
|
|
if (ret) {
|
|
|
|
btrfs_free_reserved_data_space(root, inode,
|
|
|
|
write_bytes);
|
2007-06-22 22:16:25 +04:00
|
|
|
goto out;
|
2009-02-20 19:00:09 +03:00
|
|
|
}
|
2007-06-12 14:35:45 +04:00
|
|
|
|
2008-10-03 20:30:02 +04:00
|
|
|
if (will_write) {
|
2009-10-01 20:58:30 +04:00
|
|
|
filemap_fdatawrite_range(inode->i_mapping, pos,
|
|
|
|
pos + write_bytes - 1);
|
2008-10-03 20:30:02 +04:00
|
|
|
} else {
|
|
|
|
balance_dirty_pages_ratelimited_nr(inode->i_mapping,
|
|
|
|
num_pages);
|
|
|
|
if (num_pages <
|
|
|
|
(root->leafsize >> PAGE_CACHE_SHIFT) + 1)
|
|
|
|
btrfs_btree_balance_dirty(root, 1);
|
|
|
|
btrfs_throttle(root);
|
|
|
|
}
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
buf += write_bytes;
|
|
|
|
count -= write_bytes;
|
|
|
|
pos += write_bytes;
|
|
|
|
num_written += write_bytes;
|
|
|
|
|
|
|
|
cond_resched();
|
|
|
|
}
|
|
|
|
out:
|
2007-12-22 00:27:21 +03:00
|
|
|
mutex_unlock(&inode->i_mutex);
|
2009-02-20 19:00:09 +03:00
|
|
|
if (ret)
|
|
|
|
err = ret;
|
2009-09-12 00:12:44 +04:00
|
|
|
btrfs_unreserve_metadata_for_delalloc(root, inode, 1);
|
2008-01-03 21:46:11 +03:00
|
|
|
|
2007-12-22 00:27:21 +03:00
|
|
|
out_nolock:
|
2007-06-18 17:57:58 +04:00
|
|
|
kfree(pages);
|
2007-06-12 14:35:45 +04:00
|
|
|
if (pinned[0])
|
|
|
|
page_cache_release(pinned[0]);
|
|
|
|
if (pinned[1])
|
|
|
|
page_cache_release(pinned[1]);
|
|
|
|
*ppos = pos;
|
2007-10-29 21:36:41 +03:00
|
|
|
|
2009-03-31 21:27:11 +04:00
|
|
|
/*
|
|
|
|
* we want to make sure fsync finds this change
|
|
|
|
* but we haven't joined a transaction running right now.
|
|
|
|
*
|
|
|
|
* Later on, someone is sure to update the inode and get the
|
|
|
|
* real transid recorded.
|
|
|
|
*
|
|
|
|
* We set last_trans now to the fs_info generation + 1,
|
|
|
|
* this will either be one more than the running transaction
|
|
|
|
* or the generation used for the next transaction if there isn't
|
|
|
|
* one running right now.
|
|
|
|
*/
|
|
|
|
BTRFS_I(inode)->last_trans = root->fs_info->generation + 1;
|
|
|
|
|
2008-10-03 20:30:02 +04:00
|
|
|
if (num_written > 0 && will_write) {
|
2008-09-06 00:13:11 +04:00
|
|
|
struct btrfs_trans_handle *trans;
|
|
|
|
|
2008-10-03 20:30:02 +04:00
|
|
|
err = btrfs_wait_ordered_range(inode, start_pos, num_written);
|
|
|
|
if (err)
|
2007-10-29 21:36:41 +03:00
|
|
|
num_written = err;
|
2008-09-06 00:13:11 +04:00
|
|
|
|
2008-10-03 20:30:02 +04:00
|
|
|
if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) {
|
|
|
|
trans = btrfs_start_transaction(root, 1);
|
|
|
|
ret = btrfs_log_dentry_safe(trans, root,
|
|
|
|
file->f_dentry);
|
|
|
|
if (ret == 0) {
|
2009-03-24 17:24:20 +03:00
|
|
|
ret = btrfs_sync_log(trans, root);
|
|
|
|
if (ret == 0)
|
|
|
|
btrfs_end_transaction(trans, root);
|
|
|
|
else
|
|
|
|
btrfs_commit_transaction(trans, root);
|
2009-10-13 21:21:08 +04:00
|
|
|
} else if (ret != BTRFS_NO_LOG_SYNC) {
|
2008-10-03 20:30:02 +04:00
|
|
|
btrfs_commit_transaction(trans, root);
|
2009-10-13 21:21:08 +04:00
|
|
|
} else {
|
|
|
|
btrfs_end_transaction(trans, root);
|
2008-10-03 20:30:02 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (file->f_flags & O_DIRECT) {
|
|
|
|
invalidate_mapping_pages(inode->i_mapping,
|
|
|
|
start_pos >> PAGE_CACHE_SHIFT,
|
|
|
|
(start_pos + num_written - 1) >> PAGE_CACHE_SHIFT);
|
2008-09-06 00:13:11 +04:00
|
|
|
}
|
2007-10-29 21:36:41 +03:00
|
|
|
}
|
2007-06-12 14:35:45 +04:00
|
|
|
current->backing_dev_info = NULL;
|
|
|
|
return num_written ? num_written : err;
|
|
|
|
}
|
|
|
|
|
2009-01-06 05:25:51 +03:00
|
|
|
int btrfs_release_file(struct inode *inode, struct file *filp)
|
2008-05-27 18:55:43 +04:00
|
|
|
{
|
2009-03-31 21:27:11 +04:00
|
|
|
/*
|
|
|
|
* ordered_data_close is set by settattr when we are about to truncate
|
|
|
|
* a file from a non-zero size to a zero size. This tries to
|
|
|
|
* flush down new bytes that may have been written if the
|
|
|
|
* application were using truncate to replace a file in place.
|
|
|
|
*/
|
|
|
|
if (BTRFS_I(inode)->ordered_data_close) {
|
|
|
|
BTRFS_I(inode)->ordered_data_close = 0;
|
|
|
|
btrfs_add_ordered_operation(NULL, BTRFS_I(inode)->root, inode);
|
|
|
|
if (inode->i_size > BTRFS_ORDERED_OPERATIONS_FLUSH_LIMIT)
|
|
|
|
filemap_flush(inode->i_mapping);
|
|
|
|
}
|
2008-06-10 18:07:39 +04:00
|
|
|
if (filp->private_data)
|
|
|
|
btrfs_ioctl_trans_end(filp);
|
2008-05-27 18:55:43 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-09-29 23:18:18 +04:00
|
|
|
/*
|
|
|
|
* fsync call for both files and directories. This logs the inode into
|
|
|
|
* the tree log instead of forcing full commits whenever possible.
|
|
|
|
*
|
|
|
|
* It needs to call filemap_fdatawait so that all ordered extent updates are
|
|
|
|
* in the metadata btree are up to date for copying to the log.
|
|
|
|
*
|
|
|
|
* It drops the inode mutex before doing the tree log commit. This is an
|
|
|
|
* important optimization for directories because holding the mutex prevents
|
|
|
|
* new operations on the dir while we write to disk.
|
|
|
|
*/
|
2008-09-06 00:13:11 +04:00
|
|
|
int btrfs_sync_file(struct file *file, struct dentry *dentry, int datasync)
|
2007-06-12 14:35:45 +04:00
|
|
|
{
|
|
|
|
struct inode *inode = dentry->d_inode;
|
|
|
|
struct btrfs_root *root = BTRFS_I(inode)->root;
|
2007-08-11 00:22:09 +04:00
|
|
|
int ret = 0;
|
2007-06-12 14:35:45 +04:00
|
|
|
struct btrfs_trans_handle *trans;
|
|
|
|
|
2009-10-13 21:21:08 +04:00
|
|
|
|
|
|
|
/* we wait first, since the writeback may change the inode */
|
|
|
|
root->log_batch++;
|
|
|
|
/* the VFS called filemap_fdatawrite for us */
|
|
|
|
btrfs_wait_ordered_range(inode, 0, (u64)-1);
|
|
|
|
root->log_batch++;
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
/*
|
2007-08-11 00:22:09 +04:00
|
|
|
* check the transaction that last modified this inode
|
|
|
|
* and see if its already been committed
|
2007-06-12 14:35:45 +04:00
|
|
|
*/
|
2007-08-11 00:22:09 +04:00
|
|
|
if (!BTRFS_I(inode)->last_trans)
|
|
|
|
goto out;
|
2008-06-26 00:01:30 +04:00
|
|
|
|
2009-10-13 21:21:08 +04:00
|
|
|
/*
|
|
|
|
* if the last transaction that changed this file was before
|
|
|
|
* the current transaction, we can bail out now without any
|
|
|
|
* syncing
|
|
|
|
*/
|
2007-08-11 00:22:09 +04:00
|
|
|
mutex_lock(&root->fs_info->trans_mutex);
|
|
|
|
if (BTRFS_I(inode)->last_trans <=
|
|
|
|
root->fs_info->last_trans_committed) {
|
|
|
|
BTRFS_I(inode)->last_trans = 0;
|
|
|
|
mutex_unlock(&root->fs_info->trans_mutex);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
mutex_unlock(&root->fs_info->trans_mutex);
|
|
|
|
|
|
|
|
/*
|
2007-08-28 00:49:44 +04:00
|
|
|
* ok we haven't committed the transaction yet, lets do a commit
|
|
|
|
*/
|
2009-02-20 18:55:10 +03:00
|
|
|
if (file && file->private_data)
|
2008-06-10 18:07:39 +04:00
|
|
|
btrfs_ioctl_trans_end(file);
|
|
|
|
|
2007-06-12 14:35:45 +04:00
|
|
|
trans = btrfs_start_transaction(root, 1);
|
|
|
|
if (!trans) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
2008-09-06 00:13:11 +04:00
|
|
|
|
2009-02-20 18:55:10 +03:00
|
|
|
ret = btrfs_log_dentry_safe(trans, root, dentry);
|
2009-01-06 05:25:51 +03:00
|
|
|
if (ret < 0)
|
2008-09-06 00:13:11 +04:00
|
|
|
goto out;
|
2008-09-11 23:53:12 +04:00
|
|
|
|
|
|
|
/* we've logged all the items and now have a consistent
|
|
|
|
* version of the file in the log. It is possible that
|
|
|
|
* someone will come in and modify the file, but that's
|
|
|
|
* fine because the log is consistent on disk, and we
|
|
|
|
* have references to all of the file's extents
|
|
|
|
*
|
|
|
|
* It is possible that someone will come in and log the
|
|
|
|
* file again, but that will end up using the synchronization
|
|
|
|
* inside btrfs_sync_log to keep things safe.
|
|
|
|
*/
|
2009-02-20 18:55:10 +03:00
|
|
|
mutex_unlock(&dentry->d_inode->i_mutex);
|
2008-09-11 23:53:12 +04:00
|
|
|
|
2009-10-13 21:21:08 +04:00
|
|
|
if (ret != BTRFS_NO_LOG_SYNC) {
|
|
|
|
if (ret > 0) {
|
2009-03-24 17:24:20 +03:00
|
|
|
ret = btrfs_commit_transaction(trans, root);
|
2009-10-13 21:21:08 +04:00
|
|
|
} else {
|
|
|
|
ret = btrfs_sync_log(trans, root);
|
|
|
|
if (ret == 0)
|
|
|
|
ret = btrfs_end_transaction(trans, root);
|
|
|
|
else
|
|
|
|
ret = btrfs_commit_transaction(trans, root);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ret = btrfs_end_transaction(trans, root);
|
2008-09-06 00:13:11 +04:00
|
|
|
}
|
2009-02-20 18:55:10 +03:00
|
|
|
mutex_lock(&dentry->d_inode->i_mutex);
|
2007-06-12 14:35:45 +04:00
|
|
|
out:
|
2010-01-29 13:42:11 +03:00
|
|
|
return ret > 0 ? -EIO : ret;
|
2007-06-12 14:35:45 +04:00
|
|
|
}
|
|
|
|
|
2009-09-27 22:29:37 +04:00
|
|
|
static const struct vm_operations_struct btrfs_file_vm_ops = {
|
2007-07-25 20:31:35 +04:00
|
|
|
.fault = filemap_fault,
|
2007-06-15 21:50:00 +04:00
|
|
|
.page_mkwrite = btrfs_page_mkwrite,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int btrfs_file_mmap(struct file *filp, struct vm_area_struct *vma)
|
|
|
|
{
|
|
|
|
vma->vm_ops = &btrfs_file_vm_ops;
|
|
|
|
file_accessed(filp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-10-02 02:43:56 +04:00
|
|
|
const struct file_operations btrfs_file_operations = {
|
2007-06-12 14:35:45 +04:00
|
|
|
.llseek = generic_file_llseek,
|
|
|
|
.read = do_sync_read,
|
2007-06-15 21:50:00 +04:00
|
|
|
.aio_read = generic_file_aio_read,
|
2007-12-14 20:56:58 +03:00
|
|
|
.splice_read = generic_file_splice_read,
|
2007-06-12 14:35:45 +04:00
|
|
|
.write = btrfs_file_write,
|
2007-06-15 21:50:00 +04:00
|
|
|
.mmap = btrfs_file_mmap,
|
2007-06-12 14:35:45 +04:00
|
|
|
.open = generic_file_open,
|
2008-05-27 18:55:43 +04:00
|
|
|
.release = btrfs_release_file,
|
2007-06-12 14:35:45 +04:00
|
|
|
.fsync = btrfs_sync_file,
|
2007-09-14 18:22:47 +04:00
|
|
|
.unlocked_ioctl = btrfs_ioctl,
|
2007-06-12 14:35:45 +04:00
|
|
|
#ifdef CONFIG_COMPAT
|
2007-09-14 18:22:47 +04:00
|
|
|
.compat_ioctl = btrfs_ioctl,
|
2007-06-12 14:35:45 +04:00
|
|
|
#endif
|
|
|
|
};
|