Samba and MS Shadow Copy
Ken Cross
kcross at nssolutions.com
Tue Aug 5 12:01:16 GMT 2003
metze:
OK, here is the guts of the VFS module related to Shadow Copy stuff. I
snipped out sections of code that are irrelevant, repetitive, or deal with
the internals of our file system that wouldn't make any sense to anybody.
This module contains 3 interesting elements:
1. The Shadow Copy routines (alfs_shadowdata and alfs_shadowreplace) could
be used as a template for others wishing to implement it. Other VFS
routines that have a path argument should invoke the SHADOW_CHECK macro.
2. The VFS routine for "stat" includes a cache for file stats. Samba has a
HUGE amount of stat requests going on. This code will keep the stat in
cache for one second. A lot of operations (open with create, unlink, etc.)
will invalidate the cache. Tests have shown NetBench operations get a 70%
cache hit. (!)
3. I've abused the "log level" parameter to add a new parameter to smb.conf
without mucking with loadparam. See the vfs_alfs_init routine. The
"statcache_size" isn't really a debug level, it's a value to override a
default value. So
log level = 2 statcache_size:100
will set statcache_size in the DEBUGLEVEL_CLASS array (see debug.c). Then I
use:
i = ( DEBUGLEVEL_CLASS_ISSET[ alfs_statcache_index ] ?
DEBUGLEVEL_CLASS[ alfs_statcache_index ] : DEFAULT_STATCACHE_SIZE );
to retrieve the value or, if not set, use a default value.
Hope others find some of this useful.
Ken
________________________________
Ken Cross
Network Storage Solutions
Phone 865.675.4070 ext 31
kcross at nssolutions.com
> -----Original Message-----
> From: Stefan (metze) Metzmacher [mailto:metze at metzemix.de]
> Sent: Tuesday, August 05, 2003 1:10 AM
> To: Jeremy Allison; Ken Cross
> Cc: 'Multiple recipients of list SAMBA-TECHNICAL'
> Subject: Re: Samba and MS Shadow Copy
>
>
> At 17:59 04.08.2003 +0000, Jeremy Allison wrote:
> >On Mon, Aug 04, 2003 at 01:22:31PM -0400, Ken Cross wrote:
> > >
> > > -----------------------
> > >
> > > DISCLAIMER
> > >
> > > This information has been developed through the usual tedious
> > > process of reverse-engineering. I make no claims about
> its accuracy
> > > or whether it
> > will
> > > stay this way. All I can say is that it seems to work
> for me at the
> > moment.
> >
> >Ken,
> >
> > *VERY* cool work. I'll look at getting this into the tree
> > asap.
> >
> >Thanks,
> >
> > Jeremy.
>
> Jeremy,
>
> please change the macros to SMB_VFS_GETSHADOWDATA(...)
>
> +/* KJC Shadow Volume operations. */
> +#define SMB_VFS_SHADOW_DATA(conn,size,outbuf)
> ((conn)->vfs.ops.getshadowdata((conn)->vfs.handles.getshadowda
> ta,(conn),(size),(outbuf)))
> +
>
> because if the function pointer is named 'getshadowdata'
>
> the macros should be SMB_VFS_UPERCASEFUNCTIONNAME()
>
> We should be doing this in the same style for each function...
>
> Ken,
>
> cool work!!!
>
> is the module you wrote anywhere to download or look at:-)
>
> I maybe want to write a vfs_subversion module, and this stuff
> is really
> cool for that...:-)
>
>
>
> metze
> --------------------------------------------------------------
> ---------------
> Stefan "metze" Metzmacher <metze at metzemix.de>
>
-------------- next part --------------
/*
* ALFS VFS module for samba. Handles special ACL operations.
*
* Copyright (C) K.J. Cross, Network Storage Solutions, 2003
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (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
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/************************************************************************
*
* This module handles peculiarities associated with the ALFS filesystem
* from Network Storage Solutions. They primarily deal with ACLs and
* adding the ability to distinguish between inodes in snapshots.
*
***********************************************************************/
#include "includes.h"
#undef DBGC_CLASS
#define DBGC_CLASS vfs_alfs_debug_level
/* Macros to become_root and unbecome_root only if necessary. */
#define CHECK_IF_ROOT BOOL am_root = (geteuid() == (uid_t)0)
#define BECOME_ROOT if( !am_root ) become_root()
#define UNBECOME_ROOT if( !am_root ) unbecome_root()
/* Primarily used during development. */
#define KJC_ALFSDEBUG 1
#ifdef KJC_ALFSDEBUG
# define DEBUG_ENTER_PATH DEBUG( 5, ( "Entered routine " __FUNCTION__ ": '%s'\n", path ) );
# define DEBUG_ENTER DEBUG( 5, ( "Entered routine " __FUNCTION__ "\n" ) );
# define DEBUG_EXIT DEBUG( 5, ( "Exited routine " __FUNCTION__ "\n" ) );
#else
# define DEBUG_ENTER_PATH
# define DEBUG_ENTER
# define DEBUG_EXIT
#endif
/*
* The following string is defined in smbd if this module is built statically.
* If so, it will be displayed in "smbd -V".
*/
const char vfs_alfs_ver[] = "$Id: vfs_alfs.c,v 1.32 2003/08/03 21:21:20 root Exp $";
/*
* vfs_alfs_debug_level will be changed to the debug_level for "alfs".
* Thus, the debug level for this module can be set in smb.conf with:
*
* debug level = 2 alfs:5
*/
static int vfs_alfs_debug_level = DBGC_VFS;
/***********************************************************************
*
* Shadow Volume (Snapshot) data
*
**********************************************************************/
#define SHADOW_TEMPLATE "@GMT-YYYY.MM.DD-HH.MM.SS"
#define SHADOW_STRFTIME "@GMT-%Y.%m.%d-%H.%M.%S"
#define SHADOW_MAX_SNAPSHOTS 250 /* Maximum number of snapshots to send to the caller. */
#define SHADOW_TEMPLATE_LEN 24 /* Length of SHADOW_TEMPLATE */
#define SHADOW_TEMPLATE_LEN1 25 /* Length with terminating null */
#define SHADOW_LABEL_LEN ( SHADOW_TEMPLATE_LEN * sizeof(smb_ucs2_t) ) /* # Bytes in a label */
#define SHADOW_LABEL_LEN1 ( SHADOW_TEMPLATE_LEN1 * sizeof(smb_ucs2_t) ) /* # Bytes in a label w/ terminator */
#define SHADOW_CHECK if( path[0] == '@' ) path = alfs_shadowreplace( conn, path );
/* Data sent back to client for Shadow Copy info. */
typedef struct
{
uint32 num_volumes; /* Total number of shadow volumes currently mounted */
uint32 num_labels; /* Number of volume labels in the "labels" array */
uint32 data_size; /* Total bytes being sent back */
smb_ucs2_t labels[2]; /* Concatenated list of null-terminated UCS2 (2-byte) labels */
} SHADOW_COPY_DATA;
/***********************************************************************
*
* ALFS Stat Cache
*
* Testing has shown that "stat" is the most common operation being performed.
* Also, 75% of the time performing a "stat" is involved in lookups.
* Therefore, caching the status of files for a short time and using
* them should improve performance.
*
* The debug level "statcache_size" determines the size of the stat cache:
*
* statcache_size not set = DEFAULT_STATCACHE_SIZE
* statcache_size <= 0 means disable the stat cache
* statcache_size > 0 = size (number of entries) of the stat cache
*/
#define DEFAULT_STATCACHE_SIZE 50
static int alfs_statcache_index; /* Index of statcache_size in DEBUG array. */
static int alfs_statcache_size; /* Number of entries in the stat cache. */
typedef struct _xStatCache_t
{
time_t time; /* When this stat was read, >0 means entry is valid */
int result; /* Results from stat call. If non-zero, it is errno. */
char name[MAXPATHLEN]; /* Filename */
SMB_STRUCT_STAT sb; /* Output from stat call. */
}
xStatCache_t;
static xStatCache_t *pxStatCache=NULL; /* Allocated based on alfs_statcache_size. */
/* Indicator of whether the stat cache is valid. */
static BOOL bStatCacheOK = False;
/* Counters indicating performance. */
static u_int64_t statcache_count, statcache_hits;
/************************************************************************/
/* Fallback in case connection isn't provided to VFS routines. */
extern struct current_user current_user;
/* Function prototypes (listed alphabetically) */
static int alfs_acl_free_qualifier(vfs_handle_struct *handle, connection_struct *conn, void *qualifier, SMB_ACL_TAG_T tagtype);
static int alfs_acl_free_text(vfs_handle_struct *handle, connection_struct *conn, char *text);
static SMB_ACL_T alfs_acl_get_file(vfs_handle_struct *handle, connection_struct *conn, const char *path, SMB_ACL_TYPE_T);
static int alfs_acl_set_fd(vfs_handle_struct *handle, files_struct *fsp, int fd, SMB_ACL_T aclp);
static int alfs_acl_set_file(vfs_handle_struct *handle, connection_struct *conn, const char *path, SMB_ACL_TYPE_T, SMB_ACL_T aclp);
static int alfs_chdir(vfs_handle_struct *handle, connection_struct *conn, const char *path);
static int alfs_chmod(vfs_handle_struct *handle, connection_struct *conn, const char *path, mode_t mode);
static int alfs_chown(vfs_handle_struct *handle, connection_struct *conn, const char *path, uid_t uid, gid_t gid);
static int alfs_fchmod(vfs_handle_struct *handle, files_struct *fsp, int fd, mode_t mode);
static int alfs_fchown(vfs_handle_struct *handle, files_struct *fsp, int fd, uid_t uid, gid_t gid);
static int alfs_fstat(vfs_handle_struct *handle, files_struct *fsp, int fd, SMB_STRUCT_STAT *sbuf);
static int alfs_lstat(vfs_handle_struct *handle, connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf);
static int alfs_mkdir(vfs_handle_struct *handle, connection_struct *conn, const char *path, mode_t mode);
static int alfs_open(vfs_handle_struct *handle, connection_struct *conn, const char *path, int flags, mode_t mode);
static DIR *alfs_opendir(vfs_handle_struct *handle, connection_struct *conn, const char *path);
static int alfs_rename(vfs_handle_struct *handle, connection_struct *conn, const char *old, const char *new);
static int alfs_rmdir(vfs_handle_struct *handle, connection_struct *conn, const char *path);
static int alfs_shadowdata(vfs_handle_struct *handle, connection_struct *conn, int max_data_count, char *outbuf );
static const char *alfs_shadowreplace( connection_struct *conn, const char *path );
static int alfs_stat(vfs_handle_struct *handle, connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf);
static int alfs_stat_internal(const char *path, SMB_STRUCT_STAT *sbuf, BOOL check_cache);
static int alfs_unlink(vfs_handle_struct *handle, connection_struct *conn, const char *path);
/* VFS operations */
static vfs_op_tuple alfs_op_tuples[] =
{
{SMB_VFS_OP(alfs_acl_free_text), SMB_VFS_OP_SYS_ACL_FREE_TEXT, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_acl_free_qualifier), SMB_VFS_OP_SYS_ACL_FREE_QUALIFIER, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_acl_get_file), SMB_VFS_OP_SYS_ACL_GET_FILE, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_acl_set_fd), SMB_VFS_OP_SYS_ACL_SET_FD, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_acl_set_file), SMB_VFS_OP_SYS_ACL_SET_FILE, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_chdir), SMB_VFS_OP_CHDIR, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_chmod), SMB_VFS_OP_CHMOD, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_chown), SMB_VFS_OP_CHOWN, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_fchmod), SMB_VFS_OP_FCHMOD, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_fchown), SMB_VFS_OP_FCHOWN, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_fstat), SMB_VFS_OP_FSTAT, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_lstat), SMB_VFS_OP_LSTAT, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_mkdir), SMB_VFS_OP_MKDIR, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_open), SMB_VFS_OP_OPEN, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_opendir), SMB_VFS_OP_OPENDIR, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_rename), SMB_VFS_OP_RENAME, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_rmdir), SMB_VFS_OP_RMDIR, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_shadowdata), SMB_VFS_OP_SHADOW_DATA, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_stat), SMB_VFS_OP_STAT, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(alfs_unlink), SMB_VFS_OP_UNLINK, SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP}
};
/* Sort snapshot IDs in descending order. Note that they are never equal. */
static int snapsort( const void *id1, const void *id2 )
{
return ( *(ulong *)id1 > *(ulong *)id2 ? -1 : 1 );
}
/************************************************************************
*
* ALFS Shadow Volume Emulation Code
*
* The code below emulates Microsoft's Shadow Volume operations
* using the snapshot capability of ALFS volumes.
*
* The format of the files specification for a shadow volume is
* very specific:
*
* \@GMT-2003.03.03-03.04.04\File.txt
*
* would be the shadow copy of "File.txt" from a snapshot taken
* March 3, 2003 at 3:04:04 GMT.
*
***********************************************************************/
/*
* alfs_shadowdata -- get shadow volume (snapshot) data
*
* The data must be returned in outbuf as follows:
*
* uint32 num_volumes; Total number of shadow volumes currently mounted
* uint32 num_labels; Number of volume labels in the "labels" array
* uint32 data_size; Total bytes being sent back
* smb_ucs2_t labels[2]; Concatenated list of null-terminated UCS2 (2-byte) labels
* volume names in the form:
*
* @GMT-YYYY.MM.DD-HH.MM.SS<NULL>
* @GMT-2003.03.03-03.04.04<NULL>
*
* The buf_len parameter contains the maximum number of data bytes the
* client will accept. The client may request a minimal (16-byte)
* response in order to get the data_size to allocate. Because of
* this, it's important that buf_len not be exceeded, but
* data_size should be contain the length of data that *would*
* have been generated if there was sufficient room.
*
* Return the total length of the data, or -1 on an error.
*/
static int alfs_shadowdata(vfs_handle_struct *handle, connection_struct *conn, int buf_len, char *outbuf )
{
int ret, num_volumes, data_size, num_ints, num_labels;
int i, j, n, bufsize, snap_id_index = 0;
ulong temp_id, bytes_left;
ulong snap_ids[ SNAP_LIMIT ];
time_t snaptime;
char *buf_ptr;
char GMTstring[ SHADOW_TEMPLATE_LEN1 ];
struct alfs_snaplist snap_buf;
struct tm snaptm, *ptm;
struct statfs fs, *fslist;
BOOL size_only;
SHADOW_COPY_DATA *pdata = (SHADOW_COPY_DATA *)outbuf;
CHECK_IF_ROOT;
if( buf_len < sizeof( SHADOW_COPY_DATA ) )
return -1;
BECOME_ROOT;
/*
* Check if there is only enough room to calculate the size.
*/
size_only = ( buf_len == sizeof( SHADOW_COPY_DATA ) );
/*
* Get the mountpoint for the share.
*/
if( statfs( conn->connectpath, &fs ) != 0 )
{
UNBECOME_ROOT;
return -1;
}
/*
* Find all the snapshots that are currently mounted.
*/
if( ( n = getfsstat( NULL, 0, MNT_NOWAIT ) ) == -1 )
{
UNBECOME_ROOT;
return -1;
}
bufsize = (n+1) * sizeof( struct statfs );
if( ( fslist = malloc( bufsize ) ) == NULL )
{
UNBECOME_ROOT;
return -1;
}
if( ( n = getfsstat( fslist, bufsize, MNT_NOWAIT ) ) == -1 )
{
free( fslist );
UNBECOME_ROOT;
return -1;
}
/*
* Build a list of all the snapshot IDs
* that are currently mounted.
*/
num_volumes = 0;
for( i=0; i<n; i++ )
{
/*
* [SNIP]
*
* This loop needs to examine each mounted volume to
* determine if it is a snapshot of the current share.
*
* In our implementation, the result is a list of snapshot IDs
* that is saved in snap_ids. The number of elements in
* snap_ids is in snap_id_index.
*/
}
free( fslist );
/*
* The list of snapshots could be longer than what Windows
* can handle. Set a limit to what we send back.
*/
num_volumes = snap_id_index;
if( num_volumes > SHADOW_MAX_SNAPSHOTS )
{
DEBUG( 2, ("SHADOW Returning %d out of %d snapshots.\n",
SHADOW_MAX_SNAPSHOTS, num_volumes ) );
num_volumes = SHADOW_MAX_SNAPSHOTS;
}
memset( pdata, 0, sizeof( SHADOW_COPY_DATA ) );
if( size_only )
{
/*
* We are only returning the length of the data block
* to allocate to save the volume labels.
*/
pdata->num_volumes = num_volumes;
pdata->data_size = ( num_volumes * SHADOW_LABEL_LEN1 );
DEBUG( 5, ("SHADOW Compute size only: %d volumes need %d bytes\n",
num_volumes, pdata->data_size ) );
ret = ( sizeof( SHADOW_COPY_DATA ) );
UNBECOME_ROOT;
return ret;
}
/*
* We want to gather the volume labels and pass
* them back as UCS2 null-terminated strings.
*/
num_labels = 0;
buf_ptr = (char *) &pdata->labels;
bytes_left = buf_len - ( buf_ptr - outbuf );
/* Sort the list of snapshot IDs to have the most recent first. */
qsort( snap_ids, snap_id_index, sizeof( ulong ), snapsort );
/* Process num_volumes entries (*not* snap_id_index entries). */
for( i=0; i<num_volumes; i++ )
{
/*
* [SNIP]
*
* This loop takes each snapshot ID and determines the
* GMT timestamp associated with that snapshot. That
* value is stored in snaptime.
*/
/*
* Convert the snapshot time (which is in GMT)
* into the required template format.
*/
j = strftime( GMTstring, sizeof GMTstring, SHADOW_STRFTIME, gmtime( &snaptime ) );
SMB_ASSERT( j == SHADOW_TEMPLATE_LEN );
/* Store the label as a UCS2 string. */
j = dos_PutUniCode( buf_ptr, GMTstring, bytes_left, True );
bytes_left -= j;
buf_ptr += j;
num_labels++;
DEBUG( 3, ("SHADOW Added '%s' (snapshot %d) to list\n", GMTstring, temp_id ) );
}
/*
* We are returning real data.
*/
pdata->num_volumes = num_volumes;
pdata->num_labels = num_labels;
pdata->data_size = ( num_volumes * SHADOW_LABEL_LEN1 );
ret = ( buf_ptr - outbuf );
DEBUG( 5, ("SHADOW full data: %d bytes of %d bytes for %d volumes.\n", ret, buf_len, num_labels ) );
UNBECOME_ROOT;
return ret;
}
/*
* alfs_shadowreplace
*
* If a filename starts with a shadow volume template ("@GMT-yyyy.mm.dd-hh.mm.ss"),
* then it references a snapshot volume. Find the volume to see if it is
* mounted and, if so, replace the template with the mount point.
*
* Example: If the share path is "/Drive0/t1" and the mount point for the
* snapshot taken at 2003-07-23 12:27:17 is "/Snaps/latest", then:
*
* "@GMT-2003.07.23-12.27.17/t4/APInstall.log"
*
* becomes
*
* "/Snaps/latest/t1/t4/APInstall.log"
*
* It is possible that the snapshot timestamp may have changed. Therefore,
* if the exact timestamp isn't found, use the closest one that is later.
*
* If the volume isn't found or any error occurs, return the original path.
*
* If it is found, a pointer to a static string with the real path is returned.
* Note that the relative path will be replaced by an absolute path.
*
*/
static const char *alfs_shadowreplace( connection_struct *conn, const char *path )
{
static char snappath[ MAXPATHLEN ];
char *snap_mount, *cp;
int i, n;
int fullyear;
long bufsize;
ulong snap_id, closest_id, temp_id;
time_t snaptime, closest_time;
struct alfs_snaplist snap_buf;
struct tm snaptm;
struct statfs fs, *fslist=NULL;
BOOL am_root;
/* Quick check for a quick exit. */
if( path == NULL || path[0] != '@' )
return path;
/* Look for the template. */
n = strlen( path );
if( n < SHADOW_TEMPLATE_LEN || strncmp( path, "@GMT-", 5 ) != 0 )
return path;
DEBUG( 3, ("SHADOW checking label %s\n", path ) );
/*
* Extract the date/time values.
* Processing GMT is a PITA.
* Save the GMT offset from a bogus conversion.
*/
memset( &snaptm, 0, sizeof snaptm );
snaptm.tm_year = 70;
snaptm.tm_mday = 1;
mktime( &snaptm );
i = snaptm.tm_gmtoff; /* Save this */
if( n == SHADOW_TEMPLATE_LEN )
{
n = sscanf( path, "@GMT-%4d.%2d.%2d-%2d.%2d.%2d",
&fullyear, &snaptm.tm_mon, &snaptm.tm_mday,
&snaptm.tm_hour, &snaptm.tm_min, &snaptm.tm_sec );
if( n != 6 )
return path;
}
else
{
n = sscanf( path, "@GMT-%4d.%2d.%2d-%2d.%2d.%2d/%s",
&fullyear, &snaptm.tm_mon, &snaptm.tm_mday,
&snaptm.tm_hour, &snaptm.tm_min, &snaptm.tm_sec,
snappath );
if( n != 7 )
return path;
}
/*
* Adjust time parameters and convert to time_t.
* Note that the time is GMT, which is what we want.
* But mktime insists on adjusting for GMT, so we
* have to un-adjust it.
*/
snaptm.tm_mon--;
snaptm.tm_year = fullyear - 1900;
snaptime = mktime( &snaptm );
snaptime += i; /* Remove GMT offset. */
if( snaptime <= 0 )
return path;
/* Become root to ensure statfs will succeed. */
am_root = (geteuid() == (uid_t)0);
BECOME_ROOT;
/*
* Get the mountpoint for the share.
*/
if( statfs( conn->connectpath, &fs ) != 0 )
goto unbecome_and_exit;
/*
* [SNIP]
*
* The GMT timestamp is now in staptime.
* Find the snapshot that goes with that timestamp.
* Then, search the mounted filesystems for
* the mount point of that snapshot.
*/
/* The snapshot is not mounted. Sorry. */
if( snap_mount == NULL )
{
DEBUG( 3, ("SHADOW Snapshot %d (%s) is not mounted.\n", snap_id, path ) );
goto unbecome_and_exit;
}
/*
* We found the mount point for the snapshot we need.
* Construct a new path using this mount point.
* This consists of:
*
* 1. The mount point of the snapshot.
*
* 2. The directory of "path" excluding the
* mount point of the current share.
*
* 3. The remaining directory/file information
* from "path" after the template string.
*
*/
/*
* Find where the end of the mount point is
* in the path name of the current share.
*/
cp = conn->connectpath + strlen( fs.f_mntonname );
strlcpy( snappath, snap_mount, sizeof snappath );
strlcat( snappath, cp, sizeof snappath );
strlcat( snappath, &path[ SHADOW_TEMPLATE_LEN ], sizeof snappath );
DEBUG( 3, ("SHADOW converted %s to %s\n", path, snappath ) );
DEBUG( 10, ("SHADOW converted %s to '%s' '%s' '%s'\n",
path, snap_mount, cp, &path[ SHADOW_TEMPLATE_LEN ] ) );
path = snappath;
unbecome_and_exit:
SAFE_FREE( fslist );
UNBECOME_ROOT;
return path;
}
static int alfs_open(vfs_handle_struct *handle, connection_struct *conn, const char *path, int flags, mode_t mode)
{
int result;
uint32 desired_access;
BOOL created = False;
BOOL became_root = False;
SMB_STRUCT_STAT sbuf;
CHECK_IF_ROOT;
/* No status block yet - make sure VALID_STAT fails */
sbuf.st_nlink = 0;
DEBUG_ENTER_PATH;
SMB_ASSERT( !VALID_STAT( sbuf ) );
SMB_ASSERT( conn != NULL );
SHADOW_CHECK;
/* [snip -- lots of unrelated ACL stuff here...] */
/*
* The Stat Cache may reference this file.
* Since it has such a short lifetime, it's simpler and more
* foolproof (excluding the superior fool) to just invalidate
* the whole cache.
*/
if( result != -1 && created )
bStatCacheOK = False;
DEBUG_EXIT;
return result;
}
/*
* Get the stat of a file. This is (by far) the most common operation performed.
* Maintain a cache of file stats that is good for at most one second.
*
* If check_cache is True, then check the cache to see if it's present.
* If it is False, then the real stat will be obtained and saved in cache.
*/
static int alfs_stat_internal(const char *path, SMB_STRUCT_STAT *sbuf, BOOL check_cache)
{
const char *relative_path;
time_t now = time(NULL);
time_t oldest_time = INT_MAX;
int oldest_index = -1;
int result;
int i;
int pathlen;
BOOL use_cache = False;
relative_path = path;
/*
* Use the "statcache_size" debug class to set the size of the stat cache.
* If it is zero, that will disable the stat cache; if it is not set,
* it will default to DEFAULT_STATCACHE_SIZE.
*/
if( alfs_statcache_index > 0 )
{
i = (DEBUGLEVEL_CLASS_ISSET[ alfs_statcache_index ] ?
DEBUGLEVEL_CLASS[ alfs_statcache_index ] : DEFAULT_STATCACHE_SIZE );
if( i == alfs_statcache_size )
{
/*
* This is the normal case -- the stat cache has
* been created and is the correct size. If it
* is enabled (> 0), use it.
*/
use_cache = ( alfs_statcache_size > 0 );
}
else
{
/*
* The cache has either not been created yet or the
* size in the debug level "statcache_size" has changed,
* so it must be allocated and initialized.
*
* First, destroy any existing cache.
*/
SAFE_FREE( pxStatCache );
/* Allocate the cache. */
alfs_statcache_size = i;
if( alfs_statcache_size > 0 &&
( pxStatCache = malloc( alfs_statcache_size * sizeof( xStatCache_t ) ) ) != NULL )
{
/* The cache has been allocated. Set flag to initialize it. */
use_cache = True;
bStatCacheOK = False;
DEBUG( 2, ( "StatCache initialized for %d entries (%d bytes).\n",
alfs_statcache_size, alfs_statcache_size * sizeof( xStatCache_t ) ) );
}
else
{
DEBUG( 2, ( "StatCache is disabled.\n" ) );
}
}
}
/*
* If the path name starts with "./", remove it.
* The exception is if the full path == "./", in
* which case it should be changed to ".".
*/
if( relative_path[0] == '.' && relative_path[1] == '/' )
{
if( relative_path[2] == '\0' )
relative_path = ".";
else
relative_path += 2;
}
pathlen = strlen( relative_path );
statcache_count++;
/* Quick check for special (but common) case. */
if( relative_path[pathlen-1] == '*' )
{
/* Can't get stat of "*". Count it as a hit. */
statcache_hits++;
errno = ENOENT; /* ?? */
return -1;
}
if( use_cache )
{
/* If bStatCacheOK = False, clear all the entries in the cache. */
if( !bStatCacheOK )
{
for( i=0; i<alfs_statcache_size; i++ )
pxStatCache[i].time = 0;
bStatCacheOK = True;
oldest_index = 0;
}
else
{
/*
* The stat cache is valid. Look for this entry
* that has occurred within the same second.
* If check_cache is False, don't actually look.
*/
for( i=0; i<alfs_statcache_size; i++ )
{
if( pxStatCache[i].time == now && memcmp( relative_path, pxStatCache[i].name, pathlen+1 ) == 0 )
{
/* We found the entry. Use it if we're supposed to. */
if( !check_cache )
{
/* Don't use the contents of cache -- overwrite it. */
oldest_index = i;
break;
}
result = pxStatCache[i].result;
if( result == 0 )
{
/* Get the cached stat. */
*sbuf = pxStatCache[i].sb;
}
else
{
/* Set errno to the results. */
errno = result;
result = -1;
}
statcache_hits++;
DEBUG( 7, ( "StatCache hit: '%s' %llu/%llu = %4.1f%%\n",
relative_path, statcache_hits, statcache_count,
statcache_hits * 100.0 / statcache_count ) );
return result;
}
/* Keep track of the oldest time. */
if( pxStatCache[i].time < oldest_time )
{
oldest_time = pxStatCache[i].time;
oldest_index = i;
}
}
}
}
/* Get the stat. */
result = stat( path, sbuf );
if( use_cache )
{
SMB_ASSERT( oldest_index >= 0 );
SMB_ASSERT( result == 0 || result == -1 );
/*
* Save the results in the cache.
* If the results were an error (-1), save errno.
*/
pxStatCache[ oldest_index ].time = now;
pxStatCache[ oldest_index ].result = ( result ? errno : 0 );
memcpy( pxStatCache[ oldest_index ].name, relative_path, pathlen+1 );
if( result == 0 )
pxStatCache[ oldest_index ].sb = *sbuf;
DEBUG( 7, ( "StatCache miss: '%s' (%d/%d) %llu/%llu = %4.1f%%\n",
relative_path, result, pxStatCache[ oldest_index ].result,
statcache_hits, statcache_count,
statcache_hits * 100.0 / statcache_count ) );
}
return result;
}
/* Note: Since they aren't used, it's OK for handle and/or conn to be NULL. */
static int alfs_stat(vfs_handle_struct *handle, connection_struct *conn, const char *path, SMB_STRUCT_STAT *sbuf)
{
int result;
CHECK_IF_ROOT;
DEBUG_ENTER_PATH;
SHADOW_CHECK;
/* Get the stat. */
errno = 0;
result = alfs_stat_internal( path, sbuf, True );
/* Don't let it fail because of protection. */
if( result == -1 && ( errno == EPERM || errno == EACCES ) && !am_root )
{
int ibefore = errno;
become_root();
errno = 0;
result = alfs_stat_internal( path, sbuf, False );
unbecome_root();
if( result == -1 )
{
char *s1, *s2;
s1 = strdup( strerror( ibefore ) );
s2 = strdup( strerror( errno ) );
DEBUG( 4, ( "Stat Failed on %s with %s, now got %s\n", path, s1, s2 ) );
free( s1 );
free( s2 );
}
else
DEBUG( 4, ( "Stat Failed on %s, success as root\n", path ) );
}
return result;
}
/* Register this module. If loaded dynamically, this is redefined as "init_module". */
NTSTATUS vfs_alfs_init(void)
{
NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "alfs", alfs_op_tuples);
if (!NT_STATUS_IS_OK(ret))
return ret;
/* Add the "alfs" debug class. */
vfs_alfs_debug_level = debug_add_class("alfs");
if (vfs_alfs_debug_level == -1)
{
vfs_alfs_debug_level = DBGC_VFS;
DEBUG( 0, ("vfs_alfs: Couldn't register alfs custom debugging class!\n"));
}
else
{
DEBUG( 3, ("vfs_alfs: Debug class number of 'alfs': %d\n", vfs_alfs_debug_level));
}
/*
* Use the "statcache_size" debug class to set the size of the stat cache.
* If it is zero, that will disable the stat cache; if it is not set,
* it will default to DEFAULT_STATCACHE_SIZE.
*/
alfs_statcache_index = debug_add_class("statcache_size");
if (alfs_statcache_index == -1)
{
DEBUG( 0, ("vfs_alfs: Couldn't register statcache_size custom debugging class!\n"));
}
else
{
DEBUG( 3, ("vfs_alfs: Debug class number of 'statcache_size': %d\n", alfs_statcache_index));
}
return ret;
}
More information about the samba-technical
mailing list