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