2001-10-03 16:41:29 +04:00
/*
2004-03-30 23:35:44 +04:00
* Copyright ( C ) 2001 - 2004 Sistina Software , Inc . All rights reserved .
* Copyright ( C ) 2004 Red Hat , Inc . All rights reserved .
2001-10-03 16:41:29 +04:00
*
2004-03-30 23:35:44 +04:00
* This file is part of LVM2 .
*
* This copyrighted material is made available to anyone wishing to use ,
* modify , copy , or redistribute it subject to the terms and conditions
* of the GNU General Public License v .2 .
*
* 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 02111 - 1307 USA
2001-10-03 16:41:29 +04:00
*/
2002-11-18 17:01:16 +03:00
# include "lib.h"
2001-10-04 14:13:07 +04:00
# include "lvm-types.h"
2003-04-15 17:24:42 +04:00
# include "device.h"
2002-11-18 17:01:16 +03:00
# include "metadata.h"
2003-07-05 02:34:56 +04:00
# include "lvmcache.h"
# include "memlock.h"
2003-11-21 22:54:40 +03:00
# include "locking.h"
2001-10-04 14:13:07 +04:00
2002-12-20 02:25:55 +03:00
# include <limits.h>
2001-10-04 14:13:07 +04:00
# include <sys/stat.h>
2002-07-22 12:10:54 +04:00
# include <fcntl.h>
2002-08-14 18:58:00 +04:00
# include <unistd.h>
2001-10-04 14:13:07 +04:00
# include <sys/ioctl.h>
2003-04-15 17:24:42 +04:00
# ifdef linux
# define u64 uint64_t /* Missing without __KERNEL__ */
2003-07-05 02:34:56 +04:00
# undef WNOHANG /* Avoid redefinition */
# undef WUNTRACED /* Avoid redefinition */
2003-04-15 17:24:42 +04:00
# include <linux / fs.h> /* For block ioctl definitions */
# define BLKSIZE_SHIFT SECTOR_SHIFT
2004-01-27 23:53:57 +03:00
# ifndef BLKGETSIZE64 /* fs.h out-of-date */
# define BLKGETSIZE64 _IOR(0x12, 114, size_t)
# endif /* BLKGETSIZE64 */
2003-07-05 02:34:56 +04:00
# else
# include <sys / disk.h>
# define BLKBSZGET DKIOCGETBLOCKSIZE
# define BLKSSZGET DKIOCGETBLOCKSIZE
# define BLKGETSIZE64 DKIOCGETBLOCKCOUNT
# define BLKFLSBUF DKIOCSYNCHRONIZECACHE
# define BLKSIZE_SHIFT 0
2003-11-06 20:14:06 +03:00
# endif
# ifdef O_DIRECT_SUPPORT
2003-07-05 02:34:56 +04:00
# ifndef O_DIRECT
2003-11-06 20:14:06 +03:00
# error O_DIRECT support configured but O_DIRECT definition not found in headers
2003-07-05 02:34:56 +04:00
# endif
2003-04-15 17:24:42 +04:00
# endif
2001-11-13 21:52:52 +03:00
2003-07-05 02:34:56 +04:00
static LIST_INIT ( _open_devices ) ;
/*-----------------------------------------------------------------
* The standard io loop that keeps submitting an io until it ' s
* all gone .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static int _io ( struct device_area * where , void * buffer , int should_write )
{
int fd = dev_fd ( where - > dev ) ;
ssize_t n = 0 ;
size_t total = 0 ;
if ( fd < 0 ) {
log_error ( " Attempt to read an unopened device (%s). " ,
dev_name ( where - > dev ) ) ;
return 0 ;
}
/*
* Skip all writes in test mode .
*/
if ( should_write & & test_mode ( ) )
return 1 ;
if ( where - > size > SSIZE_MAX ) {
log_error ( " Read size too large: % " PRIu64 , where - > size ) ;
return 0 ;
}
if ( lseek ( fd , ( off_t ) where - > start , SEEK_SET ) < 0 ) {
2004-12-10 19:01:35 +03:00
log_error ( " %s: lseek % " PRIu64 " failed: %s " ,
dev_name ( where - > dev ) , ( uint64_t ) where - > start ,
strerror ( errno ) ) ;
2003-07-05 02:34:56 +04:00
return 0 ;
}
while ( total < ( size_t ) where - > size ) {
do
n = should_write ?
write ( fd , buffer , ( size_t ) where - > size - total ) :
read ( fd , buffer , ( size_t ) where - > size - total ) ;
while ( ( n < 0 ) & & ( ( errno = = EINTR ) | | ( errno = = EAGAIN ) ) ) ;
2004-12-10 19:01:35 +03:00
if ( n < 0 )
log_error ( " %s: %s failed after % " PRIu64 " of % " PRIu64
" at % " PRIu64 " : %s " , dev_name ( where - > dev ) ,
should_write ? " write " : " read " ,
( uint64_t ) total ,
( uint64_t ) where - > size ,
( uint64_t ) where - > start , strerror ( errno ) ) ;
2003-07-05 02:34:56 +04:00
if ( n < = 0 )
break ;
total + = n ;
buffer + = n ;
}
return ( total = = ( size_t ) where - > size ) ;
}
/*-----------------------------------------------------------------
* LVM2 uses O_DIRECT when performing metadata io , which requires
* block size aligned accesses . If any io is not aligned we have
* to perform the io via a bounce buffer , obviously this is quite
* inefficient .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* Get the sector size from an _open_ device .
*/
static int _get_block_size ( struct device * dev , unsigned int * size )
{
2004-12-10 19:01:35 +03:00
const char * name = dev_name ( dev ) ;
2003-07-05 02:34:56 +04:00
2004-12-10 19:01:35 +03:00
if ( ( dev - > block_size = = - 1 ) ) {
if ( ioctl ( dev_fd ( dev ) , BLKBSZGET , & dev - > block_size ) < 0 ) {
log_sys_error ( " ioctl BLKBSZGET " , name ) ;
return 0 ;
}
log_debug ( " %s: block size is %u bytes " , name , dev - > block_size ) ;
2003-07-05 02:34:56 +04:00
}
2004-12-10 19:01:35 +03:00
* size = ( unsigned int ) dev - > block_size ;
2003-07-05 02:34:56 +04:00
return 1 ;
}
/*
* Widens a region to be an aligned region .
*/
static void _widen_region ( unsigned int block_size , struct device_area * region ,
struct device_area * result )
{
uint64_t mask = block_size - 1 , delta ;
memcpy ( result , region , sizeof ( * result ) ) ;
/* adjust the start */
delta = result - > start & mask ;
if ( delta ) {
result - > start - = delta ;
result - > size + = delta ;
}
/* adjust the end */
delta = ( result - > start + result - > size ) & mask ;
if ( delta )
result - > size + = block_size - delta ;
}
static int _aligned_io ( struct device_area * where , void * buffer ,
int should_write )
{
void * bounce ;
unsigned int block_size = 0 ;
uintptr_t mask ;
struct device_area widened ;
if ( ! ( where - > dev - > flags & DEV_REGULAR ) & &
! _get_block_size ( where - > dev , & block_size ) ) {
stack ;
return 0 ;
}
if ( ! block_size )
2003-11-14 20:55:39 +03:00
block_size = getpagesize ( ) ;
2003-07-05 02:34:56 +04:00
_widen_region ( block_size , where , & widened ) ;
/* Do we need to use a bounce buffer? */
mask = block_size - 1 ;
if ( ! memcmp ( where , & widened , sizeof ( widened ) ) & &
! ( ( uintptr_t ) buffer & mask ) )
return _io ( where , buffer , should_write ) ;
/* Allocate a bounce buffer with an extra block */
if ( ! ( bounce = alloca ( ( size_t ) widened . size + block_size ) ) ) {
log_error ( " Bounce buffer alloca failed " ) ;
return 0 ;
}
/*
* Realign start of bounce buffer ( using the extra sector )
*/
if ( ( ( uintptr_t ) bounce ) & mask )
bounce = ( void * ) ( ( ( ( uintptr_t ) bounce ) + mask ) & ~ mask ) ;
/* channel the io through the bounce buffer */
if ( ! _io ( & widened , bounce , 0 ) ) {
if ( ! should_write ) {
stack ;
return 0 ;
}
/* FIXME pre-extend the file */
memset ( bounce , ' \n ' , widened . size ) ;
}
if ( should_write ) {
memcpy ( bounce + ( where - > start - widened . start ) , buffer ,
( size_t ) where - > size ) ;
/* ... then we write */
return _io ( & widened , bounce , 1 ) ;
}
memcpy ( buffer , bounce + ( where - > start - widened . start ) ,
( size_t ) where - > size ) ;
return 1 ;
}
/*-----------------------------------------------------------------
* Public functions
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2004-08-11 17:15:35 +04:00
int dev_get_size ( const struct device * dev , uint64_t * size )
2001-10-03 16:41:29 +04:00
{
int fd ;
2001-10-25 18:04:18 +04:00
const char * name = dev_name ( dev ) ;
2001-10-03 16:41:29 +04:00
2001-10-25 18:04:18 +04:00
if ( ( fd = open ( name , O_RDONLY ) ) < 0 ) {
log_sys_error ( " open " , name ) ;
2001-10-03 16:41:29 +04:00
return 0 ;
}
2003-04-15 17:24:42 +04:00
if ( ioctl ( fd , BLKGETSIZE64 , size ) < 0 ) {
log_sys_error ( " ioctl BLKGETSIZE64 " , name ) ;
2004-12-21 20:54:52 +03:00
if ( close ( fd ) )
log_sys_error ( " close " , name ) ;
2001-10-03 16:41:29 +04:00
return 0 ;
}
2003-04-30 19:21:10 +04:00
* size > > = BLKSIZE_SHIFT ; /* Convert to sectors */
2004-12-21 20:54:52 +03:00
if ( close ( fd ) )
log_sys_error ( " close " , name ) ;
2004-12-10 19:01:35 +03:00
log_very_verbose ( " %s: size is % " PRIu64 " sectors " , name , * size ) ;
2001-10-03 16:41:29 +04:00
return 1 ;
}
2004-12-10 19:01:35 +03:00
/* FIXME Unused
2002-11-18 17:01:16 +03:00
int dev_get_sectsize ( struct device * dev , uint32_t * size )
2001-12-11 13:18:49 +03:00
{
2002-08-14 18:58:00 +04:00
int fd ;
int s ;
const char * name = dev_name ( dev ) ;
if ( ( fd = open ( name , O_RDONLY ) ) < 0 ) {
log_sys_error ( " open " , name ) ;
return 0 ;
}
if ( ioctl ( fd , BLKSSZGET , & s ) < 0 ) {
log_sys_error ( " ioctl BLKSSZGET " , name ) ;
close ( fd ) ;
return 0 ;
}
close ( fd ) ;
* size = ( uint32_t ) s ;
2004-12-10 19:01:35 +03:00
log_very_verbose ( " %s: sector size is % " PRIu32 " bytes " , name , * size ) ;
2002-08-14 18:58:00 +04:00
return 1 ;
}
2004-12-10 19:01:35 +03:00
*/
2002-08-14 18:58:00 +04:00
2003-07-05 02:34:56 +04:00
void dev_flush ( struct device * dev )
2002-08-14 18:58:00 +04:00
{
2003-07-05 02:34:56 +04:00
if ( ! ( dev - > flags & DEV_REGULAR ) & & ioctl ( dev - > fd , BLKFLSBUF , 0 ) > = 0 )
2003-04-28 20:20:39 +04:00
return ;
2003-07-05 02:34:56 +04:00
if ( fsync ( dev - > fd ) > = 0 )
2003-04-28 20:20:39 +04:00
return ;
sync ( ) ;
2002-06-25 18:02:28 +04:00
}
2003-07-05 02:34:56 +04:00
int dev_open_flags ( struct device * dev , int flags , int direct , int quiet )
2001-11-14 13:01:52 +03:00
{
2002-01-23 21:55:01 +03:00
struct stat buf ;
2003-07-05 02:34:56 +04:00
const char * name ;
2002-01-25 02:16:19 +03:00
2003-07-05 02:34:56 +04:00
if ( dev - > fd > = 0 ) {
2004-12-13 00:47:14 +03:00
if ( ( dev - > flags & DEV_OPENED_RW ) | |
( ( flags & O_ACCMODE ) ! = O_RDWR ) ) {
dev - > open_count + + ;
return 1 ;
}
2004-12-21 23:23:16 +03:00
if ( dev - > open_count ) {
/* FIXME Ensure we never get here */
log_debug ( " WARNING: %s already opened read-only " ,
2004-12-10 19:01:35 +03:00
dev_name ( dev ) ) ;
2004-12-21 23:23:16 +03:00
dev - > open_count + + ;
}
dev_close_immediate ( dev ) ;
2002-01-25 02:16:19 +03:00
}
2001-11-14 13:01:52 +03:00
2003-07-05 02:34:56 +04:00
if ( memlock ( ) )
log_error ( " WARNING: dev_open(%s) called while suspended " ,
dev_name ( dev ) ) ;
if ( dev - > flags & DEV_REGULAR )
name = dev_name ( dev ) ;
else if ( ! ( name = dev_name_confirmed ( dev , quiet ) ) ) {
stack ;
2001-11-14 13:01:52 +03:00
return 0 ;
}
2003-07-05 02:34:56 +04:00
if ( ! ( dev - > flags & DEV_REGULAR ) & &
( ( stat ( name , & buf ) < 0 ) | | ( buf . st_rdev ! = dev - > dev ) ) ) {
2002-01-23 21:55:01 +03:00
log_error ( " %s: stat failed: Has device name changed? " , name ) ;
return 0 ;
}
2003-11-06 20:14:06 +03:00
# ifdef O_DIRECT_SUPPORT
2003-07-05 02:34:56 +04:00
if ( direct )
flags | = O_DIRECT ;
2003-11-06 20:14:06 +03:00
# endif
2003-07-05 02:34:56 +04:00
2004-09-15 02:23:23 +04:00
# ifdef O_NOATIME
/* Don't update atime on device inodes */
if ( ! ( dev - > flags & DEV_REGULAR ) )
flags | = O_NOATIME ;
# endif
2003-07-05 02:34:56 +04:00
if ( ( dev - > fd = open ( name , flags , 0777 ) ) < 0 ) {
2005-03-04 00:54:35 +03:00
if ( quiet )
log_sys_debug ( " open " , name ) ;
else
log_sys_error ( " open " , name ) ;
2001-11-14 13:01:52 +03:00
return 0 ;
}
2004-12-21 23:23:16 +03:00
dev - > open_count + + ;
2003-07-05 02:34:56 +04:00
dev - > flags & = ~ DEV_ACCESSED_W ;
2004-12-10 19:01:35 +03:00
if ( ( flags & O_ACCMODE ) = = O_RDWR )
dev - > flags | = DEV_OPENED_RW ;
else
dev - > flags & = ~ DEV_OPENED_RW ;
2003-07-05 02:34:56 +04:00
if ( ! ( dev - > flags & DEV_REGULAR ) & &
( ( fstat ( dev - > fd , & buf ) < 0 ) | | ( buf . st_rdev ! = dev - > dev ) ) ) {
2002-01-23 21:55:01 +03:00
log_error ( " %s: fstat failed: Has device name changed? " , name ) ;
2004-12-21 23:23:16 +03:00
dev_close_immediate ( dev ) ;
dev - > open_count = 0 ;
2002-01-23 21:55:01 +03:00
return 0 ;
}
2003-11-06 20:14:06 +03:00
# ifndef O_DIRECT_SUPPORT
2003-07-05 02:34:56 +04:00
if ( ! ( dev - > flags & DEV_REGULAR ) )
dev_flush ( dev ) ;
# endif
2004-12-10 19:01:35 +03:00
if ( ( flags & O_CREAT ) & & ! ( flags & O_TRUNC ) )
2003-07-05 02:34:56 +04:00
dev - > end = lseek ( dev - > fd , ( off_t ) 0 , SEEK_END ) ;
list_add ( & _open_devices , & dev - > open_list ) ;
2004-12-10 19:01:35 +03:00
2004-12-09 19:56:51 +03:00
log_debug ( " Opened %s %s " , dev_name ( dev ) ,
2004-12-10 19:01:35 +03:00
dev - > flags & DEV_OPENED_RW ? " RW " : " RO " ) ;
2002-04-24 22:20:51 +04:00
2001-11-14 13:01:52 +03:00
return 1 ;
}
2003-07-05 02:34:56 +04:00
int dev_open_quiet ( struct device * dev )
{
2003-11-21 22:54:40 +03:00
int flags ;
flags = vg_write_lock_held ( ) ? O_RDWR : O_RDONLY ;
return dev_open_flags ( dev , flags , 1 , 1 ) ;
2003-07-05 02:34:56 +04:00
}
int dev_open ( struct device * dev )
{
2003-11-21 22:54:40 +03:00
int flags ;
flags = vg_write_lock_held ( ) ? O_RDWR : O_RDONLY ;
return dev_open_flags ( dev , flags , 1 , 0 ) ;
2003-07-05 02:34:56 +04:00
}
static void _close ( struct device * dev )
{
if ( close ( dev - > fd ) )
log_sys_error ( " close " , dev_name ( dev ) ) ;
dev - > fd = - 1 ;
2004-12-10 19:01:35 +03:00
dev - > block_size = - 1 ;
2003-07-05 02:34:56 +04:00
list_del ( & dev - > open_list ) ;
log_debug ( " Closed %s " , dev_name ( dev ) ) ;
if ( dev - > flags & DEV_ALLOCED ) {
dbg_free ( ( void * ) list_item ( dev - > aliases . n , struct str_list ) - >
str ) ;
dbg_free ( dev - > aliases . n ) ;
dbg_free ( dev ) ;
}
}
2003-11-14 20:55:39 +03:00
static int _dev_close ( struct device * dev , int immediate )
2001-11-14 13:01:52 +03:00
{
if ( dev - > fd < 0 ) {
2001-11-14 16:52:38 +03:00
log_error ( " Attempt to close device '%s' "
" which is not open. " , dev_name ( dev ) ) ;
2001-11-14 13:01:52 +03:00
return 0 ;
}
2003-11-06 20:14:06 +03:00
# ifndef O_DIRECT_SUPPORT
2002-08-14 18:58:00 +04:00
if ( dev - > flags & DEV_ACCESSED_W )
2003-07-05 02:34:56 +04:00
dev_flush ( dev ) ;
# endif
2001-11-14 16:52:38 +03:00
2004-12-21 23:23:16 +03:00
if ( dev - > open_count > 0 )
dev - > open_count - - ;
2003-07-05 02:34:56 +04:00
/* FIXME lookup device in cache to get vgname and see if it's locked? */
2004-12-21 23:23:16 +03:00
if ( immediate | | ( dev - > open_count < 1 & & ! vgs_locked ( ) ) )
2003-07-05 02:34:56 +04:00
_close ( dev ) ;
2001-11-14 13:01:52 +03:00
return 1 ;
}
2003-11-14 20:55:39 +03:00
int dev_close ( struct device * dev )
{
return _dev_close ( dev , 0 ) ;
}
int dev_close_immediate ( struct device * dev )
{
return _dev_close ( dev , 1 ) ;
}
2003-07-05 02:34:56 +04:00
void dev_close_all ( void )
2001-10-08 20:08:16 +04:00
{
2003-07-05 02:34:56 +04:00
struct list * doh , * doht ;
struct device * dev ;
2002-12-20 02:25:55 +03:00
2003-07-05 02:34:56 +04:00
list_iterate_safe ( doh , doht , & _open_devices ) {
dev = list_struct_base ( doh , struct device , open_list ) ;
if ( dev - > open_count < 1 )
_close ( dev ) ;
2001-10-08 20:08:16 +04:00
}
}
2003-07-05 02:34:56 +04:00
int dev_read ( struct device * dev , uint64_t offset , size_t len , void * buffer )
2001-10-03 16:41:29 +04:00
{
2003-07-05 02:34:56 +04:00
struct device_area where ;
2001-10-08 20:08:16 +04:00
2004-12-21 23:23:16 +03:00
if ( ! dev - > open_count ) {
stack ;
2001-10-08 20:08:16 +04:00
return 0 ;
2004-12-21 23:23:16 +03:00
}
2001-10-08 20:08:16 +04:00
2003-07-05 02:34:56 +04:00
where . dev = dev ;
where . start = offset ;
where . size = len ;
2001-10-08 20:08:16 +04:00
2003-07-05 02:34:56 +04:00
return _aligned_io ( & where , buffer , 0 ) ;
2001-10-03 16:41:29 +04:00
}
2003-07-05 02:34:56 +04:00
/* FIXME If O_DIRECT can't extend file, dev_extend first; dev_truncate after.
* But fails if concurrent processes writing
*/
2001-11-28 21:03:11 +03:00
2003-07-05 02:34:56 +04:00
/* FIXME pre-extend the file */
int dev_append ( struct device * dev , size_t len , void * buffer )
{
int r ;
2001-10-10 17:03:10 +04:00
2004-12-21 23:23:16 +03:00
if ( ! dev - > open_count ) {
stack ;
2003-07-05 02:34:56 +04:00
return 0 ;
2004-12-21 23:23:16 +03:00
}
2001-10-10 17:03:10 +04:00
2003-07-05 02:34:56 +04:00
r = dev_write ( dev , dev - > end , len , buffer ) ;
dev - > end + = ( uint64_t ) len ;
2001-10-10 17:03:10 +04:00
2003-11-06 20:14:06 +03:00
# ifndef O_DIRECT_SUPPORT
2003-07-05 02:34:56 +04:00
dev_flush ( dev ) ;
# endif
return r ;
2001-10-10 17:03:10 +04:00
}
2003-07-05 02:34:56 +04:00
int dev_write ( struct device * dev , uint64_t offset , size_t len , void * buffer )
2001-10-03 16:41:29 +04:00
{
2003-07-05 02:34:56 +04:00
struct device_area where ;
2001-10-10 17:03:10 +04:00
2004-12-21 23:23:16 +03:00
if ( ! dev - > open_count ) {
stack ;
2001-10-10 17:03:10 +04:00
return 0 ;
2004-12-21 23:23:16 +03:00
}
2001-10-10 17:03:10 +04:00
2003-07-05 02:34:56 +04:00
where . dev = dev ;
where . start = offset ;
where . size = len ;
2001-10-10 17:03:10 +04:00
2002-04-24 22:20:51 +04:00
dev - > flags | = DEV_ACCESSED_W ;
2003-07-05 02:34:56 +04:00
return _aligned_io ( & where , buffer , 1 ) ;
2001-10-03 16:41:29 +04:00
}
2001-11-13 21:52:52 +03:00
2002-12-20 02:25:55 +03:00
int dev_zero ( struct device * dev , uint64_t offset , size_t len )
2001-11-13 21:52:52 +03:00
{
2002-12-20 02:25:55 +03:00
size_t s ;
2002-08-14 18:58:00 +04:00
char buffer [ 4096 ] ;
2002-11-18 17:01:16 +03:00
2003-07-05 02:34:56 +04:00
if ( ! dev_open ( dev ) ) {
2002-11-18 17:01:16 +03:00
stack ;
2001-11-13 21:52:52 +03:00
return 0 ;
}
2002-11-18 17:01:16 +03:00
if ( ( offset % SECTOR_SIZE ) | | ( len % SECTOR_SIZE ) )
2003-01-08 19:41:22 +03:00
log_debug ( " Wiping %s at % " PRIu64 " length % " PRIsize_t ,
2002-11-18 17:01:16 +03:00
dev_name ( dev ) , offset , len ) ;
else
2003-01-08 19:41:22 +03:00
log_debug ( " Wiping %s at sector % " PRIu64 " length % " PRIsize_t
2002-11-18 17:01:16 +03:00
" sectors " , dev_name ( dev ) , offset > > SECTOR_SHIFT ,
len > > SECTOR_SHIFT ) ;
2002-08-14 18:58:00 +04:00
memset ( buffer , 0 , sizeof ( buffer ) ) ;
2001-11-13 21:52:52 +03:00
while ( 1 ) {
2002-08-14 18:58:00 +04:00
s = len > sizeof ( buffer ) ? sizeof ( buffer ) : len ;
2003-07-05 02:34:56 +04:00
if ( ! dev_write ( dev , offset , s , buffer ) )
2001-11-13 21:52:52 +03:00
break ;
2003-07-05 02:34:56 +04:00
len - = s ;
if ( ! len )
2001-11-13 21:52:52 +03:00
break ;
2003-11-14 02:55:03 +03:00
offset + = s ;
2001-11-13 21:52:52 +03:00
}
2002-04-24 22:20:51 +04:00
dev - > flags | = DEV_ACCESSED_W ;
2003-07-05 02:34:56 +04:00
if ( ! dev_close ( dev ) )
2002-11-18 17:01:16 +03:00
stack ;
2001-11-14 13:01:52 +03:00
return ( len = = 0 ) ;
2001-11-13 21:52:52 +03:00
}