2006-07-11 22:01:26 +04:00
/*
2007-11-21 03:45:33 +03:00
* Copyright ( c ) James Peach 2006 , 2007
* Copyright ( c ) David Losada Carballo 2007
2006-07-11 22:01:26 +04:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
2007-07-09 23:25:36 +04:00
* the Free Software Foundation ; either version 3 of the License , or
2006-07-11 22:01:26 +04:00
* ( at your option ) any later version .
*
* 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
2007-07-10 09:23:25 +04:00
* along with this program ; if not , see < http : //www.gnu.org/licenses/>.
2006-07-11 22:01:26 +04:00
*/
# include "includes.h"
2011-06-24 15:37:16 +04:00
# include "system/filesys.h"
2011-03-23 00:34:22 +03:00
# include "smbd/smbd.h"
2013-01-15 00:37:52 +04:00
# include "lib/util/tevent_unix.h"
2006-07-11 22:01:26 +04:00
/* Commit data module.
*
* The purpose of this module is to flush data to disk at regular intervals ,
* just like the NFS commit operation . There ' s two rationales for this . First ,
* it minimises the data loss in case of a power outage without incurring
* the poor performance of synchronous I / O . Second , a steady flush rate
* can produce better throughput than suddenly dumping massive amounts of
* writes onto a disk .
*
* Tunables :
*
* commit : dthresh Amount of dirty data that can accumulate
2007-11-21 03:45:33 +03:00
* before we commit ( sync ) it .
2006-07-11 22:01:26 +04:00
*
* commit : debug Debug level at which to emit messages .
*
2007-11-21 03:45:33 +03:00
* commit : eof mode String . Tunes how the module tries to guess when
* the client has written the last bytes of the file .
* Possible values ( default = hinted ) :
*
* ( * ) = hinted Some clients ( i . e . Windows Explorer ) declare the
* size of the file before transferring it . With this
* option , we remember that hint , and commit after
* writing in that file position . If the client
* doesn ' t declare the size of file , commiting on EOF
* is not triggered .
*
* = growth Commits after a write operation has made the file
* size grow . If the client declares a file size , it
* refrains to commit until the file has reached it .
* Useful for defeating writeback on NFS shares .
*
2006-07-11 22:01:26 +04:00
*/
# define MODULE "commit"
static int module_debug ;
2007-11-21 03:45:33 +03:00
enum eof_mode
{
EOF_NONE = 0x0000 ,
EOF_HINTED = 0x0001 ,
EOF_GROWTH = 0x0002
} ;
2006-07-11 22:01:26 +04:00
struct commit_info
{
2007-11-21 03:45:33 +03:00
/* For chunk-based commits */
2012-04-05 08:53:08 +04:00
off_t dbytes ; /* Dirty (uncommitted) bytes */
off_t dthresh ; /* Dirty data threshold */
2007-11-21 03:45:33 +03:00
/* For commits on EOF */
enum eof_mode on_eof ;
2012-04-05 08:53:08 +04:00
off_t eof ; /* Expected file size */
2006-07-11 22:01:26 +04:00
} ;
2007-11-21 03:45:33 +03:00
static int commit_do (
struct commit_info * c ,
int fd )
{
int result ;
DEBUG ( module_debug ,
( " %s: flushing %lu dirty bytes \n " ,
MODULE , ( unsigned long ) c - > dbytes ) ) ;
# if HAVE_FDATASYNC
result = fdatasync ( fd ) ;
# elif HAVE_FSYNC
result = fsync ( fd ) ;
# else
2009-12-13 23:56:28 +03:00
DEBUG ( 0 , ( " %s: WARNING: no commit support on this platform \n " ,
MODULE ) ) ;
2007-11-21 03:45:33 +03:00
result = 0
# endif
if ( result = = 0 ) {
c - > dbytes = 0 ; /* on success, no dirty bytes */
}
return result ;
}
static int commit_all (
2006-07-11 22:01:26 +04:00
struct vfs_handle_struct * handle ,
files_struct * fsp )
{
struct commit_info * c ;
2009-07-19 05:28:54 +04:00
if ( ( c = ( struct commit_info * ) VFS_FETCH_FSP_EXTENSION ( handle , fsp ) ) ) {
2006-07-11 22:01:26 +04:00
if ( c - > dbytes ) {
DEBUG ( module_debug ,
( " %s: flushing %lu dirty bytes \n " ,
MODULE , ( unsigned long ) c - > dbytes ) ) ;
2007-11-21 03:45:33 +03:00
return commit_do ( c , fsp - > fh - > fd ) ;
2006-07-11 22:01:26 +04:00
}
}
2007-11-21 03:45:33 +03:00
return 0 ;
2006-07-11 22:01:26 +04:00
}
2007-11-21 03:45:33 +03:00
static int commit (
2006-07-11 22:01:26 +04:00
struct vfs_handle_struct * handle ,
files_struct * fsp ,
2012-04-05 08:53:08 +04:00
off_t offset ,
2006-07-11 22:01:26 +04:00
ssize_t last_write )
{
struct commit_info * c ;
2009-07-19 05:28:54 +04:00
if ( ( c = ( struct commit_info * ) VFS_FETCH_FSP_EXTENSION ( handle , fsp ) )
= = NULL ) {
2007-11-21 03:45:33 +03:00
return 0 ;
}
2006-07-11 22:01:26 +04:00
2007-11-21 03:45:33 +03:00
c - > dbytes + = last_write ; /* dirty bytes always counted */
2006-07-11 22:01:26 +04:00
2007-11-21 03:45:33 +03:00
if ( c - > dthresh & & ( c - > dbytes > c - > dthresh ) ) {
return commit_do ( c , fsp - > fh - > fd ) ;
}
2006-07-11 22:01:26 +04:00
2007-11-21 03:45:33 +03:00
/* Return if we are not in EOF mode or if we have temporarily opted
* out of it .
*/
if ( c - > on_eof = = EOF_NONE | | c - > eof < 0 ) {
return 0 ;
}
/* This write hit or went past our cache the file size. */
if ( ( offset + last_write ) > = c - > eof ) {
if ( commit_do ( c , fsp - > fh - > fd ) = = - 1 ) {
return - 1 ;
}
/* Hinted mode only commits the first time we hit EOF. */
if ( c - > on_eof = = EOF_HINTED ) {
c - > eof = - 1 ;
} else if ( c - > on_eof = = EOF_GROWTH ) {
c - > eof = offset + last_write ;
}
}
return 0 ;
2006-07-11 22:01:26 +04:00
}
static int commit_connect (
struct vfs_handle_struct * handle ,
const char * service ,
const char * user )
{
2009-12-01 02:53:04 +03:00
int ret = SMB_VFS_NEXT_CONNECT ( handle , service , user ) ;
if ( ret < 0 ) {
return ret ;
}
2006-07-11 22:01:26 +04:00
module_debug = lp_parm_int ( SNUM ( handle - > conn ) , MODULE , " debug " , 100 ) ;
2009-12-01 02:53:04 +03:00
return 0 ;
2006-07-11 22:01:26 +04:00
}
static int commit_open (
vfs_handle_struct * handle ,
2009-06-16 23:01:13 +04:00
struct smb_filename * smb_fname ,
2006-07-11 22:01:26 +04:00
files_struct * fsp ,
int flags ,
mode_t mode )
{
2012-04-05 08:53:08 +04:00
off_t dthresh ;
2007-11-21 03:45:33 +03:00
const char * eof_mode ;
struct commit_info * c = NULL ;
int fd ;
2006-07-11 22:01:26 +04:00
/* Don't bother with read-only files. */
if ( ( flags & O_ACCMODE ) = = O_RDONLY ) {
2009-06-16 23:01:13 +04:00
return SMB_VFS_NEXT_OPEN ( handle , smb_fname , fsp , flags , mode ) ;
2006-07-11 22:01:26 +04:00
}
2007-11-21 03:45:33 +03:00
/* Read and check module configuration */
2006-07-11 22:01:26 +04:00
dthresh = conv_str_size ( lp_parm_const_string ( SNUM ( handle - > conn ) ,
MODULE , " dthresh " , NULL ) ) ;
2007-11-21 03:45:33 +03:00
eof_mode = lp_parm_const_string ( SNUM ( handle - > conn ) ,
MODULE , " eof mode " , " none " ) ;
if ( dthresh > 0 | | ! strequal ( eof_mode , " none " ) ) {
2009-07-19 05:28:54 +04:00
c = ( struct commit_info * ) VFS_ADD_FSP_EXTENSION (
handle , fsp , struct commit_info , NULL ) ;
2007-11-21 03:45:33 +03:00
/* Process main tunables */
2006-07-11 22:01:26 +04:00
if ( c ) {
c - > dthresh = dthresh ;
c - > dbytes = 0 ;
2007-11-21 03:45:33 +03:00
c - > on_eof = EOF_NONE ;
c - > eof = 0 ;
}
}
/* Process eof_mode tunable */
if ( c ) {
if ( strequal ( eof_mode , " hinted " ) ) {
c - > on_eof = EOF_HINTED ;
} else if ( strequal ( eof_mode , " growth " ) ) {
c - > on_eof = EOF_GROWTH ;
}
}
2009-06-16 23:01:13 +04:00
fd = SMB_VFS_NEXT_OPEN ( handle , smb_fname , fsp , flags , mode ) ;
2007-11-21 03:45:33 +03:00
if ( fd = = - 1 ) {
VFS_REMOVE_FSP_EXTENSION ( handle , fsp ) ;
return fd ;
}
/* EOF commit modes require us to know the initial file size. */
if ( c & & ( c - > on_eof ! = EOF_NONE ) ) {
SMB_STRUCT_STAT st ;
2015-10-07 22:44:11 +03:00
/*
* Setting the fd of the FSP is a hack
* but also practiced elsewhere -
* needed for calling the VFS .
*/
fsp - > fh - > fd = fd ;
if ( SMB_VFS_FSTAT ( fsp , & st ) = = - 1 ) {
int saved_errno = errno ;
SMB_VFS_CLOSE ( fsp ) ;
errno = saved_errno ;
2007-11-21 03:45:33 +03:00
return - 1 ;
2006-07-11 22:01:26 +04:00
}
2009-05-14 17:34:42 +04:00
c - > eof = st . st_ex_size ;
2006-07-11 22:01:26 +04:00
}
2011-11-30 21:51:27 +04:00
return fd ;
2006-07-11 22:01:26 +04:00
}
static ssize_t commit_write (
vfs_handle_struct * handle ,
files_struct * fsp ,
2009-07-19 05:29:15 +04:00
const void * data ,
2006-07-11 22:01:26 +04:00
size_t count )
{
ssize_t ret ;
2008-01-10 17:49:35 +03:00
ret = SMB_VFS_NEXT_WRITE ( handle , fsp , data , count ) ;
2007-11-21 03:45:33 +03:00
if ( ret > 0 ) {
if ( commit ( handle , fsp , fsp - > fh - > pos , ret ) = = - 1 ) {
return - 1 ;
}
}
2006-07-11 22:01:26 +04:00
return ret ;
}
static ssize_t commit_pwrite (
vfs_handle_struct * handle ,
files_struct * fsp ,
2009-07-19 05:29:15 +04:00
const void * data ,
2006-07-11 22:01:26 +04:00
size_t count ,
2012-04-05 08:53:08 +04:00
off_t offset )
2006-07-11 22:01:26 +04:00
{
ssize_t ret ;
2008-01-07 11:23:04 +03:00
ret = SMB_VFS_NEXT_PWRITE ( handle , fsp , data , count , offset ) ;
2007-11-21 03:45:33 +03:00
if ( ret > 0 ) {
if ( commit ( handle , fsp , offset , ret ) = = - 1 ) {
return - 1 ;
}
}
2006-07-11 22:01:26 +04:00
return ret ;
}
2013-01-15 00:37:52 +04:00
struct commit_pwrite_state {
struct vfs_handle_struct * handle ;
struct files_struct * fsp ;
ssize_t ret ;
int err ;
} ;
static void commit_pwrite_written ( struct tevent_req * subreq ) ;
static struct tevent_req * commit_pwrite_send ( struct vfs_handle_struct * handle ,
TALLOC_CTX * mem_ctx ,
struct tevent_context * ev ,
struct files_struct * fsp ,
const void * data ,
size_t n , off_t offset )
{
struct tevent_req * req , * subreq ;
struct commit_pwrite_state * state ;
req = tevent_req_create ( mem_ctx , & state , struct commit_pwrite_state ) ;
if ( req = = NULL ) {
return NULL ;
}
state - > handle = handle ;
state - > fsp = fsp ;
subreq = SMB_VFS_NEXT_PWRITE_SEND ( state , ev , handle , fsp , data ,
n , offset ) ;
if ( tevent_req_nomem ( subreq , req ) ) {
return tevent_req_post ( req , ev ) ;
}
tevent_req_set_callback ( subreq , commit_pwrite_written , req ) ;
return req ;
}
static void commit_pwrite_written ( struct tevent_req * subreq )
{
struct tevent_req * req = tevent_req_callback_data (
subreq , struct tevent_req ) ;
struct commit_pwrite_state * state = tevent_req_data (
req , struct commit_pwrite_state ) ;
int commit_ret ;
state - > ret = SMB_VFS_PWRITE_RECV ( subreq , & state - > err ) ;
TALLOC_FREE ( subreq ) ;
if ( state - > ret < = 0 ) {
tevent_req_done ( req ) ;
return ;
}
/*
* Ok , this is a sync fake . We should make the sync async as well , but
* I ' m too lazy for that right now - - vl
*/
commit_ret = commit ( state - > handle , state - > fsp , state - > fsp - > fh - > pos ,
state - > ret ) ;
if ( commit_ret = = - 1 ) {
state - > ret = - 1 ;
}
tevent_req_done ( req ) ;
}
static ssize_t commit_pwrite_recv ( struct tevent_req * req , int * err )
{
struct commit_pwrite_state * state =
tevent_req_data ( req , struct commit_pwrite_state ) ;
if ( tevent_req_is_unix_error ( req , err ) ) {
return - 1 ;
}
* err = state - > err ;
return state - > ret ;
}
2007-11-21 03:45:33 +03:00
static int commit_close (
2006-07-11 22:01:26 +04:00
vfs_handle_struct * handle ,
2008-01-11 16:19:28 +03:00
files_struct * fsp )
2006-07-11 22:01:26 +04:00
{
2007-11-21 03:45:33 +03:00
/* Commit errors not checked, close() will find them again */
2006-07-11 22:01:26 +04:00
commit_all ( handle , fsp ) ;
2008-01-11 16:19:28 +03:00
return SMB_VFS_NEXT_CLOSE ( handle , fsp ) ;
2006-07-11 22:01:26 +04:00
}
2007-11-21 03:45:33 +03:00
static int commit_ftruncate (
vfs_handle_struct * handle ,
files_struct * fsp ,
2012-04-05 08:53:08 +04:00
off_t len )
2007-11-21 03:45:33 +03:00
{
int result ;
2008-01-07 17:55:09 +03:00
result = SMB_VFS_NEXT_FTRUNCATE ( handle , fsp , len ) ;
2007-11-21 03:45:33 +03:00
if ( result = = 0 ) {
struct commit_info * c ;
2009-07-19 05:28:54 +04:00
if ( ( c = ( struct commit_info * ) VFS_FETCH_FSP_EXTENSION (
handle , fsp ) ) ) {
2007-11-21 03:45:33 +03:00
commit ( handle , fsp , len , 0 ) ;
c - > eof = len ;
}
}
return result ;
}
2009-07-24 04:28:58 +04:00
static struct vfs_fn_pointers vfs_commit_fns = {
2011-04-21 00:55:25 +04:00
. open_fn = commit_open ,
2009-07-24 04:28:58 +04:00
. close_fn = commit_close ,
2013-01-15 00:36:51 +04:00
. write_fn = commit_write ,
. pwrite_fn = commit_pwrite ,
2013-01-15 00:37:52 +04:00
. pwrite_send_fn = commit_pwrite_send ,
. pwrite_recv_fn = commit_pwrite_recv ,
2009-07-24 04:28:58 +04:00
. connect_fn = commit_connect ,
2013-01-15 00:36:51 +04:00
. ftruncate_fn = commit_ftruncate
2006-07-11 22:01:26 +04:00
} ;
2006-12-19 23:16:52 +03:00
NTSTATUS vfs_commit_init ( void ) ;
2006-07-11 22:01:26 +04:00
NTSTATUS vfs_commit_init ( void )
{
2009-07-24 04:28:58 +04:00
return smb_register_vfs ( SMB_VFS_INTERFACE_VERSION , MODULE ,
& vfs_commit_fns ) ;
2006-07-11 22:01:26 +04:00
}
2007-11-21 03:45:33 +03:00