nfs_log.c revision 27242a7c712b654e22134a70356231efc69c8202
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#define NUM_RECORDS_TO_WRITE 256
#define NUM_BYTES_TO_WRITE 65536
extern krwlock_t exported_lock;
static int nfslog_num_records_to_write = NUM_RECORDS_TO_WRITE;
static int nfslog_num_bytes_to_write = NUM_BYTES_TO_WRITE;
/*
* This struct is used to 'hide' the details of managing the log
* records internally to the logging code. Allocation routines
* are used to obtain pieces of memory for XDR encoding. This struct
* is a 'header' to those areas and a opaque cookie is used to pass
* this data structure between the allocating function and the put
* function.
*/
struct lr_alloc {
int lr_flags;
struct log_buffer *lb;
};
struct flush_thread_params {
struct nfsl_flush_args tp_args;
int tp_error;
};
static void log_file_rele(struct log_file *);
static void log_buffer_rele(struct log_buffer *);
static int nfslog_record_append2all(struct lr_alloc *);
static int nfslog_logbuffer_rename(struct log_buffer *);
static void nfslog_logfile_wait(struct log_file *);
static int nfslog_logfile_rename(char *, char *);
static void nfslog_do_flush(struct flush_thread_params *);
static void nfslog_free_logrecords(struct lr_alloc *);
static int nfslog_records_flush_to_disk(struct log_buffer *);
static int nfslog_records_flush_to_disk_nolock(struct log_buffer *);
/*
* This lock must be held when searching or modifying 'nfslog_buffer_list'.
*/
static krwlock_t nfslog_buffer_list_lock;
/*
* The list of "log_buffer" structures.
*/
#define LOG_BUFFER_HOLD(lbp) { \
}
#define LOG_FILE_HOLD(lfp) { \
}
#define LOG_FILE_RELE(lfp) { \
log_file_rele(lfp); \
}
/*
* These two macros are used to prep a logfile data structure and
* associated file for writing data. Note that the lf_lock is
* held as a result of the call to the first macro. This is used
* for serialization correctness between the logbuffer struct and
* the logfile struct.
*/
#define LOG_FILE_LOCK_TO_WRITE(lfp) { \
(lfp)->lf_writers++; \
}
#define LOG_FILE_UNLOCK_FROM_WRITE(lfp) { \
(lfp)->lf_writers--; \
} \
log_file_rele(lfp); \
}
int rfsl_log_buffer = 0;
static int rfsl_log_file = 0;
/* This array is used for memory allocation of record encoding spaces */
static struct {
int size;
struct kmem_cache *mem_cache;
char *cache_name;
} nfslog_mem_alloc[] = {
#define SMALL_INDX 0
{ NFSLOG_SMALL_RECORD_SIZE - sizeof (struct lr_alloc),
#define MEDIUM_INDX 1
{ NFSLOG_MEDIUM_RECORD_SIZE - sizeof (struct lr_alloc),
#define LARGE_INDX 2
{ NFSLOG_LARGE_RECORD_SIZE - sizeof (struct lr_alloc),
{ (-1), NULL }
};
/* Used to calculate the 'real' allocation size */
#define ALLOC_SIZE(index) \
/*
* Initialize logging data buffer cache
*/
void
{
int indx;
/*
* Initialize the kmem caches for encoding
*/
}
}
/*
* Sets up the necessary log file and related buffers to enable logging
* on the given export point.
* Returns 0 on success, non-zero on failure.
*/
int
{
struct exportdata *kex;
struct log_buffer *lbp;
struct log_buffer *nlbp;
/*
* Logging is enabled for the new export point, check
* the existing log_buffer structures to see if the
* desired buffer has already been opened. If so, point
* the new exportinfo's exi_logbuffer to the existing
* one.
*/
LOGGING_DPRINT((10,
"searching for buffer... found log_buffer '%s'\n",
/* Found our match. Ref it and return */
kex->ex_log_buffer));
return (0);
}
}
/*
* New buffer needed, allocate it.
* The buffer list lock has been dropped so we will need to search
* the list again to ensure that another thread has not added
* a matching buffer.
*/
/*
* Failed the buffer creation for some reason so we
* will need to return.
*/
return (EIO);
}
/*
* A log_buffer already exists for the
* indicated buffer, use it instead.
*/
LOGGING_DPRINT((10,
"found log_buffer for '%s' "
"after allocation\n",
kex->ex_log_buffer));
return (0);
}
}
/*
* Didn't find an existing log_buffer for this buffer,
* use the the newly created one, and add to list. We
* increment the reference count because the node is
* entered into the global list.
*/
nlbp));
return (0);
}
/*
* Disables logging for the given export point.
*/
void
{
}
/*
* Creates the corresponding log_buffer and log_file structures
* for the the buffer named 'name'.
* Returns a pointer to the log_buffer structure with reference one.
*/
static struct log_buffer *
{
struct log_buffer *buffer;
return (NULL);
buffer->lb_num_recs = 0;
buffer->lb_size_queued = 0;
return (buffer);
}
/*
* Release a log_buffer structure
*/
static void
{
int len;
return;
}
panic("log_rele: log_buffer refcnt < 0");
/*NOTREACHED*/
}
/*
* Need to drop the lb_lock before acquiring the
* nfslog_buffer_list_lock. To avoid double free we need
* to hold an additional reference to the log buffer.
* This will ensure that no two threads will simultaneously
* be trying to free the same log buffer.
*/
/*
* If the ref count is 1, then the last
* clean up the buffer and remove it from the buffer
* list.
*/
LOGGING_DPRINT((10,
"log_buffer_rele lbp=%p disconnecting\n", lbp));
/*
* Hold additional reference before dropping the lb_lock
*/
/*
* Make sure that all of the buffered records are written.
* Don't bother checking the write return value since there
* isn't much we can do at this point.
*/
(void) nfslog_records_flush_to_disk(lbp);
/*
* Drop the reference count held above.
* If the ref count is still > 1 then someone has
* stepped in to use this log buffer. unlock and return.
*/
return;
}
if (lbp == nfslog_buffer_list) {
} else {
struct log_buffer *tlbp;
/* Drop the log_buffer from the master list */
break;
}
}
}
}
/*
* ref count zero; finish clean up.
*/
}
/*
* Creates the corresponding log_file structure for the buffer
* named 'log_file_name'.
* 'log_file_name' is created by concatenating 'origname' and LOG_INPROG_STRING.
* 'logfile' is set to be the log_file structure with reference one.
*/
static int
{
char *name;
int namelen;
int error;
size_t loghdr_len = 0;
size_t loghdr_free = 0;
"log_file_create: Can not open %s - error %m", name);
goto out;
}
/*
* No need to bump the vnode reference count since it is set
* to one by vn_open().
*/
if (error) {
"log_file_create: Can not stat %s - error = %m",
name);
goto out;
}
/*
* Write Header.
*/
/*
* Dummy up a lr_alloc struct for the write
*/
if (error != 0) {
"log_file_create: Can not write header "
"on %s - error = %m", name);
goto out;
}
}
return (0);
out:
int error1;
CRED());
if (error1) {
"log_file_create: Can not close %s - "
"error = %m", name);
}
}
}
return (error);
}
/*
* Release a log_file structure
*/
static void
{
int len;
int error;
LOGGING_DPRINT((10,
"log_file_rele lfp=%p decremented refcnt to %d\n",
return;
}
panic("log_file_rele: log_file refcnt < 0");
/*NOTREACHED*/
}
CRED())) {
"NFS: Could not close log buffer %s - error = %m",
#ifdef DEBUG
} else {
LOGGING_DPRINT((3,
"log_file_rele: %s has been closed vp=%p "
"v_count=%d\n",
#endif
}
}
/*
* Allocates a record of the size specified.
* 'exi' identifies the exportinfo structure being logged.
* 'size' indicates how much memory should be allocated
* 'cookie' is used to store an opaque value for the caller for later use
* 'flags' currently ignored.
*
* Returns a pointer to the beginning of the allocated memory.
* 'cookie' is a pointer to the 'lr_alloc' struct; this will be used
* to keep track of the encoded record and contains all the info
* for enqueuing the record on the log buffer for later writing.
*
* nfslog_record_put() must be used to 'free' this record or allocation.
*/
/* ARGSUSED */
void *
struct exportinfo *exi,
int alloc_indx,
void **cookie,
int flags)
{
return (NULL);
}
} else {
}
LOGGING_DPRINT((3,
"nfslog_record_alloc(log_buffer=%p mem=%p size=%lu)\n",
return (lrp->log_record);
}
/*
* After the above nfslog_record_alloc() has been called and a record
* encoded into the buffer that was returned, this function is called
* to handle appropriate disposition of the newly created record.
* The cookie value is the one that was returned from nfslog_record_alloc().
* Size is the actual size of the record that was encoded. This is
* passed in because the size used for the alloc was just an approximation.
* The sync parameter is used to tell us if we need to force this record
* to disk and if not it will be queued for later writing.
*
* Note that if the size parameter has a value of 0, then the record is
* not written to the log and the associated data structures are released.
*/
void
unsigned int which_buffers)
{
/*
* If the caller has nothing to write or if there is
* an apparent error, rele the buffer and free.
*/
return;
}
/*
* Reset the size to what actually needs to be written
* This is used later on when the iovec is built for
* writing the records to the log file.
*/
/* append to all if public exi */
if (which_buffers == NFSLOG_ALL_BUFFERS) {
(void) nfslog_record_append2all(lrp);
return;
}
/* Insert the record on the list to be written */
} else {
lbp->lb_num_recs++;
}
/*
* Determine if the queue for this log buffer should be flushed.
* This is done by either the number of records queued, the total
* size of all records queued or by the request of the caller
* via the sync parameter.
*/
(void) nfslog_records_flush_to_disk(lbp);
} else {
}
}
/*
* Examine the log_buffer struct to see if there are queue log records
* that need to be written to disk. If some exist, pull them off of
* the log buffer and write them to the log file.
*/
static int
{
return (0);
}
return (nfslog_records_flush_to_disk_nolock(lbp));
}
/*
* Function requires that the caller holds lb_lock.
* Function flushes any records in the log buffer to the disk.
* Function drops the lb_lock on return.
*/
static int
{
struct lr_alloc *lrp_writers;
int num_recs;
int error = 0;
lbp->lb_num_recs = 0;
lbp->lb_size_queued = 0;
return (error);
}
/*
* Take care of writing the provided log record(s) to the log file.
* We group the log records with an iovec and use VOP_WRITE to append
* them to the end of the log file.
*/
static int
{
int size_iovecs;
int i;
int error = 0;
goto out;
}
/* Build the iovec based on the list of log records */
i = 0;
len = 0;
lrp = lrp_writers;
do {
i++;
} while (lrp != lrp_writers);
uio.uio_loffset = 0;
/*
* Save the size. If the write fails, reset the size to avoid
* corrupted log buffer files.
*/
if (error)
} else {
"NFS Logging: buffer file %s exceeds 2GB; "
}
}
}
out:
if (error) {
"NFS Logging disabled for buffer %s - "
}
}
return (error);
}
static void
{
do {
/*
* Check to see if we are supposed to free this structure
* and relese the log_buffer ref count.
* It may be the case that the caller does not want this
* structure and its record contents freed just yet.
*/
(void *)lrp_free);
} else {
/*
* after being pulled from the list the
* pointers need to be reinitialized.
*/
}
} while (lrp != lrp_writers);
}
/*
* Rename lbp->lb_logfile to reflect the true name requested by 'share'
*/
static int
{
int error;
/*
* Try our best to get the cache records into the log file
* before the rename occurs.
*/
(void) nfslog_records_flush_to_disk(lbp);
/*
* Hold lb_lock before retrieving
* lb_logfile.
* Hold a reference to the
* "lf" structure. this is
* same as LOG_FILE_HOLD()
*/
/*
* rename the current buffer to what the daemon expects
*/
goto out;
/*
* Create a new working buffer file and have all new data sent there.
*/
/* Attempt to rename to original */
goto out;
}
/*
* Hold the lb_lock here, this will make
* all the threads trying to access lb->logfile block
* and get a new logfile structure instead of old one.
*/
/*
* Wait for log_file to be in a quiescent state before we
* return to our caller to let it proceed with the reading of
* this file.
*/
out:
/*
* Release our reference on "lf" in two different cases.
* 1. Error condition, release only the reference
* that we held at the begining of this
* routine on "lf" structure.
* 2. Fall through condition, no errors but the old
* logfile structure "lf" has been replaced with
* the new "logfile" structure, so release the
* reference that was part of the creation of
* "lf" structure to free up the resources.
*/
return (error);
}
/*
* Renames the 'from' file to 'new'.
*/
static int
{
int error;
"nfslog_logfile_rename: couldn't rename %s to %s\n",
}
return (error);
}
/*
* Wait for the log_file writers to finish before returning
*/
static void
{
while (lf->lf_writers > 0) {
}
}
static int
{
/*
* Remember next element in the list
*/
}
/*
* Insert the record on the buffer's list to be written
* and then flush the records to the log file.
* Make sure to set the no free flag so that the
* record can be used for the next write
*/
} else {
lbp->lb_num_recs++;
}
/*
* Flush log records to disk.
* Function is called with lb_lock held.
* Function drops the lb_lock on return.
*/
if (error) {
ret_error = -1;
"rfsl_log_pubfh: could not append record to "
}
}
return (ret_error);
}
#ifdef DEBUG
static int logging_debug = 0;
/*
* 0) no debugging
* 3) current test software
* 10) random stuff
*/
void
{
if (logging_debug == level ||
}
}
#endif /* DEBUG */
/*
* NFS Log Flush system call
* Caller must check privileges.
*/
/* ARGSUSED */
int
{
struct flush_thread_params *tparams;
struct nfsl_flush_args *nfsl_args;
int error = 0;
tparams = (struct flush_thread_params *)
return (EIO);
}
/*
* Process a specific buffer
*/
return (ENOMEM);
if (error)
return (EFAULT);
return (EFAULT);
}
/*
* Do the work synchronously
*/
} else {
/*
* Do the work asynchronously
*/
}
return (error);
}
/*
* This is where buffer flushing would occur, but there is no buffering
* at this time.
* Possibly rename the log buffer for processing.
* Sets tparams->ta_error equal to the value of the error that occured,
* 0 otherwise.
* Returns ENOENT if the buffer is not found.
*/
static void
{
struct nfsl_flush_args *args;
struct log_buffer *lbp;
int found = 0;
char *buf_inprog; /* name of buff in progress */
int buf_inprog_len;
/*
* Sanity check on the arguments.
*/
if (!tparams)
return;
if (!args)
return;
(void) nfslog_records_flush_to_disk(lbp);
} else {
found++;
break;
}
}
}
/*
* The specified buffer is not currently in use,
* simply rename the file indicated.
*/
}
out:
/*
* Work was performed asynchronously, the caller is
* no longer waiting for us.
* Free the thread arguments and exit.
*/
thread_exit();
/* NOTREACHED */
}
}
/*
* Generate buffer_header.
* 'loghdr' points the the buffer_header, and *reclen
* contains the length of the buffer.
*/
static void
{
unsigned int final_size;
/* pick some size that will hold the buffer_header */
/*
* Fill header
*/
gethrestime(&now);
/*
* Encode the header
*/
/*
* Reset with final size of the encoded data
*/
xdr_setpos(&xdrs, 0);
}
/*
* ****************************************************************
* RPC dispatch table for logging
* Indexed by program, version, proc
* Based on NFS dispatch table.
*/
struct nfslog_proc_disp {
/* processing */
};
struct nfslog_vers_disp {
int nfslog_dis_nprocs; /* number of procs */
};
struct nfslog_prog_disp {
int nfslog_dis_prog; /* program number */
int nfslog_dis_versmin; /* Minimum version value */
int nfslog_dis_nvers; /* Number of version values */
};
static int rfs_log_bad = 0; /* incremented on bad log attempts */
static int rfs_log_good = 0; /* incremented on successful log attempts */
/*
*
* In some cases, the nl types are the same as the nfs types and a simple
* bcopy should suffice. Rather that define tens of identical procedures,
* simply define these to bcopy. Similarly this takes care of different
* procs that use same parameter struct.
*/
static struct nfslog_proc_disp nfslog_proc_v2[] = {
/*
* NFS VERSION 2
*/
/* RFS_NULL = 0 */
/* RFS_GETATTR = 1 */
/* RFS_SETATTR = 2 */
/* RFS_ROOT = 3 *** NO LONGER SUPPORTED *** */
/* RFS_LOOKUP = 4 */
/* RFS_READLINK = 5 */
/* RFS_READ = 6 */
/* RFS_WRITECACHE = 7 *** NO LONGER SUPPORTED *** */
/* RFS_WRITE = 8 */
/* RFS_CREATE = 9 */
/* RFS_REMOVE = 10 */
/* RFS_RENAME = 11 */
/* RFS_LINK = 12 */
/* RFS_SYMLINK = 13 */
/* RFS_MKDIR = 14 */
/* RFS_RMDIR = 15 */
/* RFS_READDIR = 16 */
/* RFS_STATFS = 17 */
};
/*
* NFS VERSION 3
*/
static struct nfslog_proc_disp nfslog_proc_v3[] = {
/* NFSPROC3_NULL = 0 */
/* NFSPROC3_GETATTR = 1 */
/* NFSPROC3_SETATTR = 2 */
/* NFSPROC3_LOOKUP = 3 */
/* NFSPROC3_ACCESS = 4 */
/* NFSPROC3_READLINK = 5 */
/* NFSPROC3_READ = 6 */
/* NFSPROC3_WRITE = 7 */
/* NFSPROC3_CREATE = 8 */
/* NFSPROC3_MKDIR = 9 */
/* NFSPROC3_SYMLINK = 10 */
/* NFSPROC3_MKNOD = 11 */
/* NFSPROC3_REMOVE = 12 */
/* NFSPROC3_RMDIR = 13 */
/* NFSPROC3_RENAME = 14 */
/* NFSPROC3_LINK = 15 */
/* NFSPROC3_READDIR = 16 */
/* NFSPROC3_READDIRPLUS = 17 */
/* NFSPROC3_FSSTAT = 18 */
/* NFSPROC3_FSINFO = 19 */
/* NFSPROC3_PATHCONF = 20 */
/* NFSPROC3_COMMIT = 21 */
};
static struct nfslog_proc_disp nfslog_proc_v1[] = {
/*
* NFSLOG VERSION 1
*/
/* NFSLOG_NULL = 0 */
/* NFSLOG_SHARE = 1 */
/* NFSLOG_UNSHARE = 2 */
/* NFSLOG_LOOKUP = 3 */
/* NFSLOG_GETFH = 4 */
};
static struct nfslog_vers_disp nfslog_vers_disptable[] = {
{sizeof (nfslog_proc_v2) / sizeof (nfslog_proc_v2[0]),
{sizeof (nfslog_proc_v3) / sizeof (nfslog_proc_v3[0]),
};
static struct nfslog_vers_disp nfslog_nfslog_vers_disptable[] = {
{sizeof (nfslog_proc_v1) / sizeof (nfslog_proc_v1[0]),
};
static struct nfslog_prog_disp nfslog_dispatch_table[] = {
(sizeof (nfslog_vers_disptable) /
sizeof (nfslog_vers_disptable[0])),
(sizeof (nfslog_nfslog_vers_disptable) /
sizeof (nfslog_nfslog_vers_disptable[0])),
};
static int nfslog_dispatch_table_arglen = sizeof (nfslog_dispatch_table) /
sizeof (nfslog_dispatch_table[0]);
/*
* This function will determine the appropriate export info struct to use
* and allocate a record id to be used in the written log buffer.
* Usually this is a straightforward operation but the existence of the
* multicomponent lookup and its semantics of crossing file system
* boundaries add to the complexity. See the comments below...
*/
struct exportinfo *
struct exportinfo *exi,
unsigned int *nfslog_rec_id)
{
struct log_buffer *lb;
return (NULL);
/*
* If the exi is marked for logging, allocate a record id and return
*/
/* obtain the unique record id for the caller */
/*
* The caller will expect to be able to exi_rele() it,
* so exi->exi_count must be incremented before it can
* be returned, to make it uniform with exi_ret->exi_count
*/
return (exi);
}
if (exi != exi_public)
return (NULL);
/*
* Here we have an exi that is not marked for logging.
* It is possible that this request is a multicomponent lookup
* that was done from the public file handle (not logged) and
* the resulting file handle being returned to the client exists
* in a file system that is being logged. If this is the case
* we need to log this multicomponent lookup to the appropriate
* log buffer. This will allow for the appropriate path name
* mapping to occur at user level.
*/
case NFS_V3:
FH3TOXFIDP(fh3));
}
break;
case NFS_VERSION:
(((struct nfsdiropres *)
fh =
}
break;
default:
break;
}
}
/* obtain the unique record id for the caller */
return (exi_ret);
}
return (NULL);
}
#ifdef DEBUG
static long long rfslog_records_ignored = 0;
#endif
/*
* nfslog_write_record - Fill in the record buffer for writing out.
* If logrecp is null, log it, otherwise, malloc the record and return it.
*
* It is the responsibility of the caller to check whether this exportinfo
* has logging enabled.
* Note that nfslog_share_public_record() only needs to check for the
* existence of at least one logbuffer to which the public filehandle record
* needs to be logged.
*/
void
unsigned int record_id, unsigned int which_buffers)
{
int i, vers;
void *log_cookie; /* for logrecord if */
unsigned int final_size;
int encode_ok;
int alloc_indx;
/*
* Find program element
* Search the list since program can not be used as index
*/
for (i = 0; (i < nfslog_dispatch_table_arglen); i++) {
break;
}
if (i >= nfslog_dispatch_table_arglen) { /* program not logged */
/* not an error */
return;
}
/*
*/
progtable = &nfslog_dispatch_table[i];
!disp->affects_transactions) {
/*
* Only interested in logging operations affecting
* transaction generation. This is not one of them.
*/
#ifdef DEBUG
#endif
return;
}
case NFS_PROGRAM:
case NFS_V3:
case NFSPROC3_READDIRPLUS:
break;
default:
break;
}
break;
default:
break;
}
break;
case NFSLOG_PROGRAM:
break;
default:
break;
}
do {
/* Pick the size to alloc; end of the road - return */
"NFSLOG: unable to encode record - prog=%d "
return;
}
/* Error processing - no space alloced */
rfs_log_bad++;
return;
}
/*
* Encode the header, args and results of the record
*/
rfs_log_good++;
/*
* Get the final size of the encoded
* data and insert that length at the
* beginning.
*/
xdr_setpos(&xdrs, 0);
} else {
/* Oops, the encode failed so we need to free memory */
alloc_indx++;
}
/*
* Take the final log record and put it in the log file.
* This may be queued to the file internally and written
* later unless the last parameter is TRUE.
* request and it should be written synchronously to the log file.
*/
(record_id == 0), which_buffers);
}
static char *
{
extern struct exportinfo *exi_public;
char *pubpath;
return (pubpath);
}
static void
{
int free_length = 0;
return;
/*
* Calling this function with the exi_public
* will have the effect of appending the record
* to each of the open log buffers
*/
}
/*
* nfslog_share_record - logs a share request.
* This is not an NFS request, but we pretend here...
*/
void
{
int res = 0;
if (nfslog_buffer_list == NULL)
return;
&nb, 0, NFSLOG_ONE_BUFFER);
}
}
/*
* nfslog_unshare_record - logs an unshare request.
* This is not an NFS request, but we pretend here...
*/
void
{
int res = 0;
}
void
char *fname,
{
int res = 0;
int error = 0;
char *namebuf;
if (seg == UIO_USERSPACE) {
} else {
}
if (!error) {
}
}