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/smp_lock.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>
2007-07-25 20:31:35 +04:00
# include <linux/version.h>
2007-06-12 14:35:45 +04:00
# include "ctree.h"
# include "disk-io.h"
# include "transaction.h"
# include "btrfs_inode.h"
2008-01-08 23:46:30 +03:00
# include "ordered-data.h"
2007-06-12 14:35:45 +04:00
# include "ioctl.h"
# include "print-tree.h"
static int btrfs_copy_from_user ( loff_t pos , int num_pages , int write_bytes ,
struct page * * prepared_pages ,
const char __user * buf )
{
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 ;
}
static void btrfs_drop_pages ( struct page * * pages , size_t num_pages )
{
size_t i ;
for ( i = 0 ; i < num_pages ; i + + ) {
if ( ! pages [ i ] )
break ;
unlock_page ( pages [ i ] ) ;
mark_page_accessed ( pages [ i ] ) ;
page_cache_release ( pages [ i ] ) ;
}
}
2008-01-03 18:01:48 +03:00
static int noinline insert_inline_extent ( struct btrfs_trans_handle * trans ,
2007-08-28 00:49:44 +04:00
struct btrfs_root * root , struct inode * inode ,
2007-10-16 00:18:25 +04:00
u64 offset , size_t size ,
struct page * * pages , size_t page_offset ,
int num_pages )
2007-06-22 22:16:25 +04:00
{
struct btrfs_key key ;
struct btrfs_path * path ;
2007-10-16 00:14:19 +04:00
struct extent_buffer * leaf ;
char * kaddr ;
unsigned long ptr ;
2007-06-22 22:16:25 +04:00
struct btrfs_file_extent_item * ei ;
2007-10-16 00:18:25 +04:00
struct page * page ;
2007-06-22 22:16:25 +04:00
u32 datasize ;
int err = 0 ;
int ret ;
2007-10-16 00:18:25 +04:00
int i ;
ssize_t cur_size ;
2007-06-22 22:16:25 +04:00
path = btrfs_alloc_path ( ) ;
if ( ! path )
return - ENOMEM ;
btrfs_set_trans_block_group ( trans , inode ) ;
key . objectid = inode - > i_ino ;
key . offset = offset ;
btrfs_set_key_type ( & key , BTRFS_EXTENT_DATA_KEY ) ;
2007-10-16 00:18:25 +04:00
ret = btrfs_search_slot ( trans , root , & key , path , 0 , 1 ) ;
if ( ret < 0 ) {
2007-06-22 22:16:25 +04:00
err = ret ;
goto fail ;
}
2007-10-16 00:18:25 +04:00
if ( ret = = 1 ) {
2007-11-01 18:28:41 +03:00
struct btrfs_key found_key ;
if ( path - > slots [ 0 ] = = 0 )
goto insert ;
2007-10-16 00:18:25 +04:00
path - > slots [ 0 ] - - ;
leaf = path - > nodes [ 0 ] ;
2007-11-01 18:28:41 +03:00
btrfs_item_key_to_cpu ( leaf , & found_key , path - > slots [ 0 ] ) ;
if ( found_key . objectid ! = inode - > i_ino )
goto insert ;
if ( found_key . type ! = BTRFS_EXTENT_DATA_KEY )
goto insert ;
2007-10-16 00:18:25 +04:00
ei = btrfs_item_ptr ( leaf , path - > slots [ 0 ] ,
struct btrfs_file_extent_item ) ;
if ( btrfs_file_extent_type ( leaf , ei ) ! =
BTRFS_FILE_EXTENT_INLINE ) {
goto insert ;
}
btrfs_item_key_to_cpu ( leaf , & key , path - > slots [ 0 ] ) ;
ret = 0 ;
}
if ( ret = = 0 ) {
u32 found_size ;
2007-10-25 23:42:57 +04:00
u64 found_end ;
2007-10-16 00:18:25 +04:00
leaf = path - > nodes [ 0 ] ;
ei = btrfs_item_ptr ( leaf , path - > slots [ 0 ] ,
struct btrfs_file_extent_item ) ;
if ( btrfs_file_extent_type ( leaf , ei ) ! =
BTRFS_FILE_EXTENT_INLINE ) {
err = ret ;
btrfs_print_leaf ( root , leaf ) ;
printk ( " found wasn't inline offset %Lu inode %lu \n " ,
offset , inode - > i_ino ) ;
goto fail ;
}
found_size = btrfs_file_extent_inline_len ( leaf ,
btrfs_item_nr ( leaf , path - > slots [ 0 ] ) ) ;
2007-10-25 23:42:57 +04:00
found_end = key . offset + found_size ;
2007-10-16 00:18:25 +04:00
2007-10-25 23:42:57 +04:00
if ( found_end < offset + size ) {
2007-10-16 00:18:25 +04:00
btrfs_release_path ( root , path ) ;
ret = btrfs_search_slot ( trans , root , & key , path ,
2007-10-25 23:42:57 +04:00
offset + size - found_end , 1 ) ;
2007-10-16 00:18:25 +04:00
BUG_ON ( ret ! = 0 ) ;
2007-11-01 18:28:41 +03:00
2007-10-16 00:18:25 +04:00
ret = btrfs_extend_item ( trans , root , path ,
2007-10-25 23:42:57 +04:00
offset + size - found_end ) ;
2007-10-16 00:18:25 +04:00
if ( ret ) {
err = ret ;
goto fail ;
}
leaf = path - > nodes [ 0 ] ;
ei = btrfs_item_ptr ( leaf , path - > slots [ 0 ] ,
struct btrfs_file_extent_item ) ;
}
2007-10-25 23:42:57 +04:00
if ( found_end < offset ) {
ptr = btrfs_file_extent_inline_start ( ei ) + found_size ;
memset_extent_buffer ( leaf , 0 , ptr , offset - found_end ) ;
}
2007-10-16 00:18:25 +04:00
} else {
insert :
btrfs_release_path ( root , path ) ;
2007-10-25 23:42:57 +04:00
datasize = offset + size - key . offset ;
datasize = btrfs_file_extent_calc_inline_size ( datasize ) ;
2007-10-16 00:18:25 +04:00
ret = btrfs_insert_empty_item ( trans , root , path , & key ,
datasize ) ;
if ( ret ) {
err = ret ;
printk ( " got bad ret %d \n " , ret ) ;
goto fail ;
}
leaf = path - > nodes [ 0 ] ;
ei = btrfs_item_ptr ( leaf , path - > slots [ 0 ] ,
struct btrfs_file_extent_item ) ;
btrfs_set_file_extent_generation ( leaf , ei , trans - > transid ) ;
btrfs_set_file_extent_type ( leaf , ei , BTRFS_FILE_EXTENT_INLINE ) ;
}
2007-10-25 23:42:57 +04:00
ptr = btrfs_file_extent_inline_start ( ei ) + offset - key . offset ;
2007-10-16 00:18:25 +04:00
cur_size = size ;
i = 0 ;
while ( size > 0 ) {
page = pages [ i ] ;
kaddr = kmap_atomic ( page , KM_USER0 ) ;
2007-10-19 17:22:59 +04:00
cur_size = min_t ( size_t , PAGE_CACHE_SIZE - page_offset , size ) ;
2007-10-16 00:18:25 +04:00
write_extent_buffer ( leaf , kaddr + page_offset , ptr , cur_size ) ;
kunmap_atomic ( kaddr , KM_USER0 ) ;
page_offset = 0 ;
ptr + = cur_size ;
size - = cur_size ;
if ( i > = num_pages ) {
printk ( " i %d num_pages %d \n " , i , num_pages ) ;
}
i + + ;
}
2007-10-16 00:14:19 +04:00
btrfs_mark_buffer_dirty ( leaf ) ;
2007-06-22 22:16:25 +04:00
fail :
btrfs_free_path ( path ) ;
return err ;
}
2008-01-03 18:01:48 +03:00
static int noinline 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 ;
2008-01-25 00:13:08 +03:00
struct extent_io_tree * io_tree = & BTRFS_I ( inode ) - > io_tree ;
2007-10-16 00:15:53 +04:00
u64 hint_byte ;
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 ;
2007-11-06 18:26:26 +03:00
u64 inline_size ;
2007-08-28 00:49:44 +04:00
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 ;
2008-01-25 00:13:08 +03:00
lock_extent ( io_tree , start_pos , end_of_last_block , GFP_NOFS ) ;
2007-08-28 00:49:44 +04:00
mutex_lock ( & root - > fs_info - > fs_mutex ) ;
trans = btrfs_start_transaction ( root , 1 ) ;
if ( ! trans ) {
err = - ENOMEM ;
goto out_unlock ;
}
btrfs_set_trans_block_group ( trans , inode ) ;
2007-10-16 00:15:53 +04:00
inode - > i_blocks + = num_bytes > > 9 ;
hint_byte = 0 ;
2007-08-28 00:49:44 +04:00
if ( ( end_of_last_block & 4095 ) = = 0 ) {
2007-09-11 04:02:22 +04:00
printk ( " strange end of last %Lu %zu %Lu \n " , start_pos , write_bytes , end_of_last_block ) ;
2007-08-28 00:49:44 +04:00
}
2008-01-25 00:13:08 +03:00
set_extent_uptodate ( io_tree , start_pos , end_of_last_block , GFP_NOFS ) ;
2007-08-28 00:49:44 +04:00
/* FIXME...EIEIO, ENOSPC and more */
/* insert any holes we need to create */
if ( inode - > i_size < start_pos ) {
u64 last_pos_in_file ;
u64 hole_size ;
2007-10-16 00:14:19 +04:00
u64 mask = root - > sectorsize - 1 ;
2007-08-28 00:49:44 +04:00
last_pos_in_file = ( isize + mask ) & ~ mask ;
2008-01-23 00:47:59 +03:00
hole_size = ( end_pos - last_pos_in_file + mask ) & ~ mask ;
2007-08-30 19:54:02 +04:00
2007-08-28 00:49:44 +04:00
if ( last_pos_in_file < start_pos ) {
2007-08-30 19:54:02 +04:00
err = btrfs_drop_extents ( trans , root , inode ,
last_pos_in_file ,
last_pos_in_file + hole_size ,
2007-10-16 00:18:25 +04:00
last_pos_in_file ,
2007-10-16 00:15:53 +04:00
& hint_byte ) ;
2007-08-30 19:54:02 +04:00
if ( err )
goto failed ;
2007-08-28 00:49:44 +04:00
err = btrfs_insert_file_extent ( trans , root ,
inode - > i_ino ,
last_pos_in_file ,
0 , 0 , hole_size ) ;
2008-01-25 00:13:08 +03:00
btrfs_drop_extent_cache ( inode , last_pos_in_file ,
last_pos_in_file + hole_size - 1 ) ;
2008-01-23 00:47:59 +03:00
btrfs_check_file ( root , inode ) ;
2007-08-28 00:49:44 +04:00
}
if ( err )
2007-06-12 14:35:45 +04:00
goto failed ;
2007-08-28 00:49:44 +04:00
}
/*
* either allocate an extent for the new bytes or setup the key
* to show we are doing inline data in the extent
*/
2007-10-16 00:18:25 +04:00
inline_size = end_pos ;
if ( isize > = BTRFS_MAX_INLINE_DATA_SIZE ( root ) | |
2008-01-30 00:03:38 +03:00
inline_size > root - > fs_info - > max_inline | |
( inline_size & ( root - > sectorsize - 1 ) ) = = 0 | |
2007-10-16 00:18:25 +04:00
inline_size > = BTRFS_MAX_INLINE_DATA_SIZE ( root ) ) {
2007-08-28 00:49:44 +04:00
u64 last_end ;
2007-12-22 00:27:21 +03:00
u64 existing_delalloc = 0 ;
2007-10-16 00:18:25 +04:00
2007-08-28 00:49:44 +04:00
for ( i = 0 ; i < num_pages ; i + + ) {
struct page * p = pages [ i ] ;
SetPageUptodate ( p ) ;
2007-08-28 00:49:44 +04:00
set_page_dirty ( p ) ;
2007-06-12 14:35:45 +04:00
}
2007-10-30 23:56:53 +03:00
last_end = ( u64 ) ( pages [ num_pages - 1 ] - > index ) < <
PAGE_CACHE_SHIFT ;
2007-08-28 00:49:44 +04:00
last_end + = PAGE_CACHE_SIZE - 1 ;
2007-12-22 00:27:21 +03:00
if ( start_pos < isize ) {
u64 delalloc_start = start_pos ;
2008-01-25 00:13:08 +03:00
existing_delalloc = count_range_bits ( io_tree ,
2007-12-22 00:27:21 +03:00
& delalloc_start ,
end_of_last_block , ( u64 ) - 1 ,
EXTENT_DELALLOC ) ;
}
2008-01-25 00:13:08 +03:00
set_extent_delalloc ( io_tree , start_pos , end_of_last_block ,
2007-08-28 00:49:44 +04:00
GFP_NOFS ) ;
2008-01-08 23:46:30 +03:00
btrfs_add_ordered_inode ( inode ) ;
2007-08-28 00:49:44 +04:00
} else {
2007-10-16 00:18:25 +04:00
u64 aligned_end ;
2007-08-28 00:49:44 +04:00
/* step one, delete the existing extents in this range */
2007-10-16 00:18:25 +04:00
aligned_end = ( pos + write_bytes + root - > sectorsize - 1 ) &
~ ( ( u64 ) root - > sectorsize - 1 ) ;
2007-08-30 19:54:02 +04:00
err = btrfs_drop_extents ( trans , root , inode , start_pos ,
2007-11-01 18:28:41 +03:00
aligned_end , aligned_end , & hint_byte ) ;
2007-08-30 19:54:02 +04:00
if ( err )
goto failed ;
2007-11-06 18:26:26 +03:00
if ( isize > inline_size )
inline_size = min_t ( u64 , isize , aligned_end ) ;
inline_size - = start_pos ;
2007-08-28 00:49:44 +04:00
err = insert_inline_extent ( trans , root , inode , start_pos ,
2007-11-06 18:26:26 +03:00
inline_size , pages , 0 , num_pages ) ;
2008-01-25 00:13:08 +03:00
btrfs_drop_extent_cache ( inode , start_pos , aligned_end - 1 ) ;
2007-08-28 00:49:44 +04:00
BUG_ON ( err ) ;
}
if ( end_pos > isize ) {
i_size_write ( inode , end_pos ) ;
btrfs_update_inode ( trans , root , inode ) ;
2007-06-12 14:35:45 +04:00
}
failed :
2007-08-28 00:49:44 +04:00
err = btrfs_end_transaction ( trans , root ) ;
out_unlock :
mutex_unlock ( & root - > fs_info - > fs_mutex ) ;
2008-01-25 00:13:08 +03:00
unlock_extent ( io_tree , start_pos , end_of_last_block , GFP_NOFS ) ;
2007-06-12 14:35:45 +04:00
return err ;
}
2007-08-28 00:49:44 +04:00
int btrfs_drop_extent_cache ( struct inode * inode , u64 start , u64 end )
{
struct extent_map * em ;
struct extent_map_tree * em_tree = & BTRFS_I ( inode ) - > extent_tree ;
while ( 1 ) {
2008-01-25 00:13:08 +03:00
spin_lock ( & em_tree - > lock ) ;
2007-08-28 00:49:44 +04:00
em = lookup_extent_mapping ( em_tree , start , end ) ;
2008-01-25 00:13:08 +03:00
if ( ! em ) {
spin_unlock ( & em_tree - > lock ) ;
2007-08-28 00:49:44 +04:00
break ;
2008-01-25 00:13:08 +03:00
}
2007-08-28 00:49:44 +04:00
remove_extent_mapping ( em_tree , em ) ;
2008-01-25 00:13:08 +03:00
spin_unlock ( & em_tree - > lock ) ;
2007-08-28 00:49:44 +04:00
/* once for us */
free_extent_map ( em ) ;
/* once for the tree*/
free_extent_map ( em ) ;
}
return 0 ;
}
2008-01-23 00:47:59 +03:00
int btrfs_check_file ( struct btrfs_root * root , struct inode * inode )
{
return 0 ;
#if 0
struct btrfs_path * path ;
struct btrfs_key found_key ;
struct extent_buffer * leaf ;
struct btrfs_file_extent_item * extent ;
u64 last_offset = 0 ;
int nritems ;
int slot ;
int found_type ;
int ret ;
int err = 0 ;
u64 extent_end = 0 ;
path = btrfs_alloc_path ( ) ;
ret = btrfs_lookup_file_extent ( NULL , root , path , inode - > i_ino ,
last_offset , 0 ) ;
while ( 1 ) {
nritems = btrfs_header_nritems ( path - > nodes [ 0 ] ) ;
if ( path - > slots [ 0 ] > = nritems ) {
ret = btrfs_next_leaf ( root , path ) ;
if ( ret )
goto out ;
nritems = btrfs_header_nritems ( path - > nodes [ 0 ] ) ;
}
slot = path - > slots [ 0 ] ;
leaf = path - > nodes [ 0 ] ;
btrfs_item_key_to_cpu ( leaf , & found_key , slot ) ;
if ( found_key . objectid ! = inode - > i_ino )
break ;
if ( found_key . type ! = BTRFS_EXTENT_DATA_KEY )
goto out ;
if ( found_key . offset ! = last_offset ) {
WARN_ON ( 1 ) ;
btrfs_print_leaf ( root , leaf ) ;
printk ( " inode %lu found offset %Lu expected %Lu \n " ,
inode - > i_ino , found_key . offset , last_offset ) ;
err = 1 ;
goto out ;
}
extent = btrfs_item_ptr ( leaf , slot ,
struct btrfs_file_extent_item ) ;
found_type = btrfs_file_extent_type ( leaf , extent ) ;
if ( found_type = = BTRFS_FILE_EXTENT_REG ) {
extent_end = found_key . offset +
btrfs_file_extent_num_bytes ( leaf , extent ) ;
} else if ( found_type = = BTRFS_FILE_EXTENT_INLINE ) {
struct btrfs_item * item ;
item = btrfs_item_nr ( leaf , slot ) ;
extent_end = found_key . offset +
btrfs_file_extent_inline_len ( leaf , item ) ;
extent_end = ( extent_end + root - > sectorsize - 1 ) &
~ ( ( u64 ) root - > sectorsize - 1 ) ;
}
last_offset = extent_end ;
path - > slots [ 0 ] + + ;
}
if ( last_offset < inode - > i_size ) {
WARN_ON ( 1 ) ;
btrfs_print_leaf ( root , leaf ) ;
printk ( " inode %lu found offset %Lu size %Lu \n " , inode - > i_ino ,
last_offset , inode - > i_size ) ;
err = 1 ;
}
out :
btrfs_free_path ( path ) ;
return err ;
# endif
}
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 .
*/
int btrfs_drop_extents ( struct btrfs_trans_handle * trans ,
struct btrfs_root * root , struct inode * inode ,
2007-11-30 18:09:33 +03:00
u64 start , u64 end , u64 inline_limit , u64 * hint_byte )
2007-06-12 14:35:45 +04:00
{
2007-11-30 18:09:33 +03:00
u64 extent_end = 0 ;
u64 search_start = start ;
2007-10-16 00:14:19 +04:00
struct extent_buffer * leaf ;
2007-06-12 14:35:45 +04:00
struct btrfs_file_extent_item * extent ;
struct btrfs_path * path ;
2007-11-30 18:09:33 +03:00
struct btrfs_key key ;
struct btrfs_file_extent_item old ;
int keep ;
int slot ;
2007-06-12 14:35:45 +04:00
int bookend ;
int found_type ;
int found_extent ;
int found_inline ;
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
2007-08-28 00:49:44 +04:00
btrfs_drop_extent_cache ( inode , start , end - 1 ) ;
2007-06-12 14:35:45 +04:00
path = btrfs_alloc_path ( ) ;
if ( ! path )
return - ENOMEM ;
while ( 1 ) {
2007-06-28 23:57:36 +04:00
recow = 0 ;
2007-06-12 14:35:45 +04:00
btrfs_release_path ( root , path ) ;
ret = btrfs_lookup_file_extent ( trans , root , path , inode - > i_ino ,
search_start , - 1 ) ;
if ( ret < 0 )
goto out ;
if ( ret > 0 ) {
if ( path - > slots [ 0 ] = = 0 ) {
ret = 0 ;
goto out ;
}
path - > slots [ 0 ] - - ;
}
2007-06-18 17:57:58 +04:00
next_slot :
2007-06-12 14:35:45 +04:00
keep = 0 ;
bookend = 0 ;
found_extent = 0 ;
found_inline = 0 ;
extent = NULL ;
2007-10-16 00:14:19 +04:00
leaf = path - > nodes [ 0 ] ;
2007-06-12 14:35:45 +04:00
slot = path - > slots [ 0 ] ;
2007-06-18 17:57:58 +04:00
ret = 0 ;
2007-10-16 00:14:19 +04:00
btrfs_item_key_to_cpu ( leaf , & key , slot ) ;
2008-01-23 00:47:59 +03:00
2007-06-12 14:35:45 +04:00
if ( key . offset > = end | | key . objectid ! = inode - > i_ino ) {
goto out ;
}
2007-06-18 17:57:58 +04:00
if ( btrfs_key_type ( & key ) > BTRFS_EXTENT_DATA_KEY ) {
2007-06-12 14:35:45 +04:00
goto out ;
}
2007-06-28 23:57:36 +04:00
if ( recow ) {
search_start = key . offset ;
continue ;
}
2007-06-18 17:57:58 +04:00
if ( btrfs_key_type ( & key ) = = BTRFS_EXTENT_DATA_KEY ) {
extent = btrfs_item_ptr ( leaf , slot ,
struct btrfs_file_extent_item ) ;
2007-10-16 00:14:19 +04:00
found_type = btrfs_file_extent_type ( leaf , extent ) ;
2007-06-18 17:57:58 +04:00
if ( found_type = = BTRFS_FILE_EXTENT_REG ) {
2007-11-08 05:08:16 +03:00
extent_end =
btrfs_file_extent_disk_bytenr ( leaf ,
extent ) ;
if ( extent_end )
* hint_byte = extent_end ;
2007-06-18 17:57:58 +04:00
extent_end = key . offset +
2007-10-16 00:15:53 +04:00
btrfs_file_extent_num_bytes ( leaf , extent ) ;
2007-06-18 17:57:58 +04:00
found_extent = 1 ;
} else if ( found_type = = BTRFS_FILE_EXTENT_INLINE ) {
2007-10-16 00:14:19 +04:00
struct btrfs_item * item ;
item = btrfs_item_nr ( leaf , slot ) ;
2007-06-18 17:57:58 +04:00
found_inline = 1 ;
extent_end = key . offset +
2007-10-16 00:14:19 +04:00
btrfs_file_extent_inline_len ( leaf , item ) ;
2007-06-18 17:57:58 +04:00
}
} else {
extent_end = search_start ;
2007-06-12 14:35:45 +04:00
}
/* we found nothing we can drop */
2007-06-18 17:57:58 +04:00
if ( ( ! found_extent & & ! found_inline ) | |
search_start > = extent_end ) {
int nextret ;
u32 nritems ;
2007-10-16 00:14:19 +04:00
nritems = btrfs_header_nritems ( leaf ) ;
2007-06-18 17:57:58 +04:00
if ( slot > = nritems - 1 ) {
nextret = btrfs_next_leaf ( root , path ) ;
if ( nextret )
goto out ;
2007-06-28 23:57:36 +04:00
recow = 1 ;
2007-06-18 17:57:58 +04:00
} else {
path - > slots [ 0 ] + + ;
}
goto next_slot ;
2007-06-12 14:35:45 +04:00
}
if ( found_inline ) {
2007-10-16 00:14:19 +04:00
u64 mask = root - > sectorsize - 1 ;
2007-06-12 14:35:45 +04:00
search_start = ( extent_end + mask ) & ~ mask ;
} else
search_start = extent_end ;
2007-12-14 19:14:42 +03:00
if ( end < = extent_end & & start > = key . offset & & found_inline ) {
2007-11-01 18:28:41 +03:00
* hint_byte = EXTENT_MAP_INLINE ;
2007-12-14 19:14:42 +03:00
continue ;
2007-11-01 18:28:41 +03:00
}
2007-06-12 14:35:45 +04:00
if ( end < extent_end & & end > = key . offset ) {
if ( found_extent ) {
2007-10-16 00:15:53 +04:00
u64 disk_bytenr =
btrfs_file_extent_disk_bytenr ( leaf , extent ) ;
u64 disk_num_bytes =
btrfs_file_extent_disk_num_bytes ( leaf ,
2007-10-16 00:14:19 +04:00
extent ) ;
read_extent_buffer ( leaf , & old ,
( unsigned long ) extent ,
sizeof ( old ) ) ;
2007-10-16 00:15:53 +04:00
if ( disk_bytenr ! = 0 ) {
2007-06-12 14:35:45 +04:00
ret = btrfs_inc_extent_ref ( trans , root ,
2007-12-11 17:25:06 +03:00
disk_bytenr , disk_num_bytes ,
root - > root_key . objectid ,
trans - > transid ,
key . objectid , end ) ;
2007-06-12 14:35:45 +04:00
BUG_ON ( ret ) ;
}
}
2007-11-01 18:28:41 +03:00
bookend = 1 ;
2008-01-30 22:39:54 +03:00
if ( found_inline & & start < = key . offset )
2007-11-01 18:28:41 +03:00
keep = 1 ;
2007-06-12 14:35:45 +04:00
}
/* truncate existing extent */
if ( start > key . offset ) {
u64 new_num ;
u64 old_num ;
keep = 1 ;
2007-10-16 00:14:19 +04:00
WARN_ON ( start & ( root - > sectorsize - 1 ) ) ;
2007-06-12 14:35:45 +04:00
if ( found_extent ) {
2007-10-16 00:15:53 +04:00
new_num = start - key . offset ;
old_num = btrfs_file_extent_num_bytes ( leaf ,
extent ) ;
* hint_byte =
btrfs_file_extent_disk_bytenr ( leaf ,
extent ) ;
if ( btrfs_file_extent_disk_bytenr ( leaf ,
extent ) ) {
2007-06-12 14:35:45 +04:00
inode - > i_blocks - =
2007-10-16 00:15:53 +04:00
( old_num - new_num ) > > 9 ;
2007-06-12 14:35:45 +04:00
}
2007-10-16 00:15:53 +04:00
btrfs_set_file_extent_num_bytes ( leaf , extent ,
new_num ) ;
2007-10-16 00:14:19 +04:00
btrfs_mark_buffer_dirty ( leaf ) ;
2007-11-30 18:09:33 +03:00
} else if ( key . offset < inline_limit & &
( end > extent_end ) & &
( inline_limit < extent_end ) ) {
2007-10-16 00:18:25 +04:00
u32 new_size ;
new_size = btrfs_file_extent_calc_inline_size (
2007-11-30 18:09:33 +03:00
inline_limit - key . offset ) ;
2007-10-16 00:18:25 +04:00
btrfs_truncate_item ( trans , root , path ,
2007-11-01 18:28:41 +03:00
new_size , 1 ) ;
2007-06-12 14:35:45 +04:00
}
}
/* delete the entire extent */
if ( ! keep ) {
2007-10-16 00:15:53 +04:00
u64 disk_bytenr = 0 ;
u64 disk_num_bytes = 0 ;
u64 extent_num_bytes = 0 ;
2007-12-11 17:25:06 +03:00
u64 root_gen ;
2007-12-11 20:42:00 +03:00
u64 root_owner ;
2007-12-11 17:25:06 +03:00
2007-12-11 20:42:00 +03:00
root_gen = btrfs_header_generation ( leaf ) ;
root_owner = btrfs_header_owner ( leaf ) ;
2007-06-12 14:35:45 +04:00
if ( found_extent ) {
2007-10-16 00:15:53 +04:00
disk_bytenr =
btrfs_file_extent_disk_bytenr ( leaf ,
2007-10-16 00:14:19 +04:00
extent ) ;
2007-10-16 00:15:53 +04:00
disk_num_bytes =
btrfs_file_extent_disk_num_bytes ( leaf ,
2007-10-16 00:14:19 +04:00
extent ) ;
2007-10-16 00:15:53 +04:00
extent_num_bytes =
btrfs_file_extent_num_bytes ( leaf , extent ) ;
* hint_byte =
btrfs_file_extent_disk_bytenr ( leaf ,
extent ) ;
2007-06-12 14:35:45 +04:00
}
ret = btrfs_del_item ( trans , root , path ) ;
2007-06-22 22:16:25 +04:00
/* TODO update progress marker and return */
2007-06-12 14:35:45 +04:00
BUG_ON ( ret ) ;
btrfs_release_path ( root , path ) ;
extent = NULL ;
2007-10-16 00:15:53 +04:00
if ( found_extent & & disk_bytenr ! = 0 ) {
inode - > i_blocks - = extent_num_bytes > > 9 ;
2007-06-12 14:35:45 +04:00
ret = btrfs_free_extent ( trans , root ,
2007-12-11 17:25:06 +03:00
disk_bytenr ,
disk_num_bytes ,
2007-12-11 20:42:00 +03:00
root_owner ,
2007-12-11 17:25:06 +03:00
root_gen , inode - > i_ino ,
key . offset , 0 ) ;
2007-06-12 14:35:45 +04:00
}
BUG_ON ( ret ) ;
if ( ! bookend & & search_start > = end ) {
ret = 0 ;
goto out ;
}
if ( ! bookend )
continue ;
}
2008-01-30 22:39:54 +03:00
if ( bookend & & found_inline & & start < = key . offset ) {
2007-11-01 18:28:41 +03:00
u32 new_size ;
new_size = btrfs_file_extent_calc_inline_size (
2008-01-30 22:39:54 +03:00
extent_end - end ) ;
2007-11-01 18:28:41 +03:00
btrfs_truncate_item ( trans , root , path , new_size , 0 ) ;
}
2007-06-12 14:35:45 +04:00
/* create bookend, splitting the extent in two */
if ( bookend & & found_extent ) {
struct btrfs_key ins ;
ins . objectid = inode - > i_ino ;
ins . offset = end ;
btrfs_set_key_type ( & ins , BTRFS_EXTENT_DATA_KEY ) ;
btrfs_release_path ( root , path ) ;
ret = btrfs_insert_empty_item ( trans , root , path , & ins ,
sizeof ( * extent ) ) ;
2007-06-18 17:57:58 +04:00
2007-10-16 00:14:19 +04:00
leaf = path - > nodes [ 0 ] ;
2007-06-18 17:57:58 +04:00
if ( ret ) {
2007-10-16 00:14:19 +04:00
btrfs_print_leaf ( root , leaf ) ;
printk ( " got %d on inserting %Lu %u %Lu start %Lu end %Lu found %Lu %Lu keep was %d \n " , ret , ins . objectid , ins . type , ins . offset , start , end , key . offset , extent_end , keep ) ;
2007-06-18 17:57:58 +04:00
}
2007-06-12 14:35:45 +04:00
BUG_ON ( ret ) ;
2007-10-16 00:14:19 +04:00
extent = btrfs_item_ptr ( leaf , path - > slots [ 0 ] ,
struct btrfs_file_extent_item ) ;
write_extent_buffer ( leaf , & old ,
( unsigned long ) extent , sizeof ( old ) ) ;
btrfs_set_file_extent_offset ( leaf , extent ,
2007-10-16 00:15:53 +04:00
le64_to_cpu ( old . offset ) + end - key . offset ) ;
WARN_ON ( le64_to_cpu ( old . num_bytes ) <
( extent_end - end ) ) ;
btrfs_set_file_extent_num_bytes ( leaf , extent ,
extent_end - end ) ;
2007-10-16 00:14:19 +04:00
btrfs_set_file_extent_type ( leaf , extent ,
2007-06-12 14:35:45 +04:00
BTRFS_FILE_EXTENT_REG ) ;
2007-10-16 00:15:53 +04:00
2007-06-12 14:35:45 +04:00
btrfs_mark_buffer_dirty ( path - > nodes [ 0 ] ) ;
2007-10-16 00:15:53 +04:00
if ( le64_to_cpu ( old . disk_bytenr ) ! = 0 ) {
2007-06-12 14:35:45 +04:00
inode - > i_blocks + =
2007-10-16 00:15:53 +04:00
btrfs_file_extent_num_bytes ( leaf ,
extent ) > > 9 ;
2007-06-12 14:35:45 +04:00
}
ret = 0 ;
goto out ;
}
}
out :
btrfs_free_path ( path ) ;
return ret ;
}
/*
* this gets pages into the page cache and locks them down
*/
2008-01-03 18:01:48 +03:00
static int prepare_pages ( struct btrfs_root * root , struct file * file ,
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 ;
2007-10-16 00:14:19 +04:00
start_pos = pos & ~ ( ( u64 ) root - > sectorsize - 1 ) ;
2007-06-12 14:35:45 +04:00
memset ( pages , 0 , num_pages * sizeof ( struct page * ) ) ;
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-12-19 00:15:09 +03:00
# if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
ClearPageDirty ( pages [ i ] ) ;
# else
2007-06-28 23:57:36 +04:00
cancel_dirty_page ( pages [ i ] , PAGE_CACHE_SIZE ) ;
2007-12-19 00:15:09 +03:00
# endif
2007-06-28 23:57:36 +04:00
wait_on_page_writeback ( pages [ i ] ) ;
2007-09-17 19:25:58 +04:00
set_page_extent_mapped ( pages [ i ] ) ;
2007-08-28 00:49:44 +04:00
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 ;
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 ;
if ( file - > f_flags & O_DIRECT )
return - EINVAL ;
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 ) ;
current - > backing_dev_info = inode - > i_mapping - > backing_dev_info ;
err = generic_write_checks ( file , & pos , & count , S_ISBLK ( inode - > i_mode ) ) ;
if ( err )
2007-12-22 00:27:21 +03:00
goto out_nolock ;
2007-06-12 14:35:45 +04:00
if ( count = = 0 )
2007-12-22 00:27:21 +03:00
goto out_nolock ;
2007-12-19 00:15:09 +03:00
err = remove_suid ( fdentry ( file ) ) ;
2007-06-12 14:35:45 +04:00
if ( err )
2007-12-22 00:27:21 +03:00
goto out_nolock ;
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
mutex_lock ( & inode - > i_mutex ) ;
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 ] ) ;
}
}
while ( count > 0 ) {
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 ) ;
2007-06-12 14:35:45 +04:00
memset ( pages , 0 , sizeof ( pages ) ) ;
2007-12-22 00:27:21 +03:00
mutex_lock ( & root - > fs_info - > fs_mutex ) ;
ret = btrfs_check_free_space ( root , write_bytes , 0 ) ;
mutex_unlock ( & root - > fs_info - > fs_mutex ) ;
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 ) ;
2007-06-22 22:16:25 +04:00
if ( ret )
goto out ;
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 ) {
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 ) ;
2007-06-22 22:16:25 +04:00
if ( ret )
goto out ;
2007-06-12 14:35:45 +04:00
buf + = write_bytes ;
count - = write_bytes ;
pos + = write_bytes ;
num_written + = write_bytes ;
2007-06-18 17:57:58 +04:00
balance_dirty_pages_ratelimited_nr ( inode - > i_mapping , num_pages ) ;
2007-11-27 18:52:01 +03:00
if ( num_pages < ( root - > leafsize > > PAGE_CACHE_SHIFT ) + 1 )
btrfs_btree_balance_dirty ( root , 1 ) ;
2008-01-08 23:46:30 +03:00
btrfs_throttle ( root ) ;
2007-06-12 14:35:45 +04:00
cond_resched ( ) ;
}
out :
2007-12-22 00:27:21 +03:00
mutex_unlock ( & inode - > i_mutex ) ;
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
if ( num_written > 0 & & ( ( file - > f_flags & O_SYNC ) | | IS_SYNC ( inode ) ) ) {
err = sync_page_range ( inode , inode - > i_mapping ,
start_pos , num_written ) ;
if ( err < 0 )
num_written = err ;
}
2007-06-12 14:35:45 +04:00
current - > backing_dev_info = NULL ;
return num_written ? num_written : err ;
}
static int btrfs_sync_file ( struct file * file ,
struct dentry * dentry , int datasync )
{
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 ;
/*
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
*/
mutex_lock ( & root - > fs_info - > fs_mutex ) ;
2007-08-11 00:22:09 +04:00
if ( ! BTRFS_I ( inode ) - > last_trans )
goto out ;
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
*/
2007-06-12 14:35:45 +04:00
trans = btrfs_start_transaction ( root , 1 ) ;
if ( ! trans ) {
ret = - ENOMEM ;
goto out ;
}
ret = btrfs_commit_transaction ( trans , root ) ;
out :
2007-08-11 00:22:09 +04:00
mutex_unlock ( & root - > fs_info - > fs_mutex ) ;
2007-06-12 14:35:45 +04:00
return ret > 0 ? EIO : ret ;
}
2007-06-15 21:50:00 +04:00
static struct vm_operations_struct btrfs_file_vm_ops = {
2007-07-25 20:31:35 +04:00
# if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
. nopage = filemap_nopage ,
. populate = filemap_populate ,
# else
. fault = filemap_fault ,
# endif
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 ;
}
2007-06-12 14:35:45 +04:00
struct file_operations btrfs_file_operations = {
. 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-12-19 00:15:09 +03:00
# if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
. sendfile = generic_file_sendfile ,
# endif
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 ,
. 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
} ;