#include <sys/cred.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/disp.h>
#include <sys/atomic.h>
#include <rpc/types.h>
#include <nfs/nfs.h>
#include <nfs/nfssys.h>
#include <nfs/export.h>
#include <nfs/rnode.h>
#include <rpc/auth.h>
#include <rpc/svc.h>
#include <rpc/xdr.h>
#include <rpc/clnt.h>
#include <nfs/nfs_log.h>
#define NUM_RECORDS_TO_WRITE 256
#define NUM_BYTES_TO_WRITE 65536
static int nfslog_num_records_to_write = NUM_RECORDS_TO_WRITE;
static int nfslog_num_bytes_to_write = NUM_BYTES_TO_WRITE;
struct lr_alloc {
struct lr_alloc *next;
struct lr_alloc *prev;
#define LR_ALLOC_NOFREE 0x1
int lr_flags;
caddr_t log_record;
size_t size;
struct kmem_cache *alloc_cache;
struct exportinfo *exi;
struct log_buffer *lb;
};
struct flush_thread_params {
struct nfsl_flush_args tp_args;
int tp_error;
};
static int log_file_create(caddr_t, struct log_file **);
static void log_file_rele(struct log_file *);
static struct log_buffer *log_buffer_create(caddr_t);
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 create_buffer_header(caddr_t *, size_t *, size_t *);
static int nfslog_write_logrecords(struct log_file *, struct lr_alloc *, int);
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 *);
static krwlock_t nfslog_buffer_list_lock;
struct log_buffer *nfslog_buffer_list = NULL;
#define LOG_BUFFER_HOLD(lbp) { \
mutex_enter(&(lbp)->lb_lock); \
(lbp)->lb_refcnt++; \
mutex_exit(&(lbp)->lb_lock); \
}
#define LOG_FILE_HOLD(lfp) { \
mutex_enter(&(lfp)->lf_lock); \
(lfp)->lf_refcnt++; \
mutex_exit(&(lfp)->lf_lock); \
}
#define LOG_FILE_RELE(lfp) { \
log_file_rele(lfp); \
}
#define LOG_FILE_LOCK_TO_WRITE(lfp) { \
mutex_enter(&(lfp)->lf_lock); \
(lfp)->lf_refcnt++; \
(lfp)->lf_writers++; \
}
#define LOG_FILE_UNLOCK_FROM_WRITE(lfp) { \
(lfp)->lf_writers--; \
if ((lfp)->lf_writers == 0 && ((lfp)->lf_flags & L_WAITING)) { \
(lfp)->lf_flags &= ~L_WAITING; \
cv_broadcast(&(lfp)->lf_cv_waiters); \
} \
mutex_exit(&(lfp)->lf_lock); \
log_file_rele(lfp); \
}
int rfsl_log_buffer = 0;
static int rfsl_log_file = 0;
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),
NULL, NFSLOG_SMALL_REC_NAME },
#define MEDIUM_INDX 1
{ NFSLOG_MEDIUM_RECORD_SIZE - sizeof (struct lr_alloc),
NULL, NFSLOG_MEDIUM_REC_NAME },
#define LARGE_INDX 2
{ NFSLOG_LARGE_RECORD_SIZE - sizeof (struct lr_alloc),
NULL, NFSLOG_LARGE_REC_NAME },
{ (-1), NULL }
};
#define ALLOC_SIZE(index) \
(nfslog_mem_alloc[index].size + sizeof (struct lr_alloc))
void
nfslog_init()
{
int indx;
rw_init(&nfslog_buffer_list_lock, NULL, RW_DEFAULT, NULL);
for (indx = 0; nfslog_mem_alloc[indx].size != (-1); indx++) {
nfslog_mem_alloc[indx].mem_cache =
kmem_cache_create(nfslog_mem_alloc[indx].cache_name,
ALLOC_SIZE(indx), 0, NULL, NULL, NULL, NULL, NULL, 0);
}
}
int
nfslog_setup(struct exportinfo *exi)
{
struct exportdata *kex;
struct log_buffer *lbp;
struct log_buffer *nlbp;
kex = &exi->exi_export;
ASSERT(kex->ex_flags & EX_LOG);
rw_enter(&nfslog_buffer_list_lock, RW_READER);
for (lbp = nfslog_buffer_list; lbp != NULL; lbp = lbp->lb_next) {
LOGGING_DPRINT((10,
"searching for buffer... found log_buffer '%s'\n",
lbp->lb_path));
if (strcmp(lbp->lb_path, kex->ex_log_buffer) == 0) {
LOG_BUFFER_HOLD(lbp);
exi->exi_logbuffer = lbp;
LOGGING_DPRINT((10, "\tfound log_buffer for '%s'\n",
kex->ex_log_buffer));
rw_exit(&nfslog_buffer_list_lock);
return (0);
}
}
rw_exit(&nfslog_buffer_list_lock);
if ((nlbp = log_buffer_create(kex->ex_log_buffer)) == NULL) {
return (EIO);
}
rw_enter(&nfslog_buffer_list_lock, RW_WRITER);
for (lbp = nfslog_buffer_list; lbp != NULL;
lbp = lbp->lb_next) {
if (strcmp(lbp->lb_path, kex->ex_log_buffer) == 0) {
LOG_BUFFER_HOLD(lbp);
exi->exi_logbuffer = lbp;
LOGGING_DPRINT((10, "found log_buffer for '%s' "
"after allocation\n", kex->ex_log_buffer));
rw_exit(&nfslog_buffer_list_lock);
log_buffer_rele(nlbp);
return (0);
}
}
LOGGING_DPRINT((10, "exportfs: adding nlbp=%p to list\n",
(void *)nlbp));
nlbp->lb_next = nfslog_buffer_list;
nfslog_buffer_list = nlbp;
LOG_BUFFER_HOLD(nlbp);
exi->exi_logbuffer = nlbp;
rw_exit(&nfslog_buffer_list_lock);
return (0);
}
void
nfslog_disable(struct exportinfo *exi)
{
log_buffer_rele(exi->exi_logbuffer);
}
static struct log_buffer *
log_buffer_create(caddr_t name)
{
struct log_buffer *buffer;
struct log_file *logfile;
int namelen = strlen(name);
LOGGING_DPRINT((10, "log_buffer_create: %s\n", name));
if (log_file_create(name, &logfile))
return (NULL);
buffer = (struct log_buffer *)kmem_alloc(sizeof (*buffer), KM_SLEEP);
buffer->lb_refcnt = 1;
buffer->lb_rec_id = 0;
buffer->lb_path = (caddr_t)kmem_alloc(namelen + 1, KM_SLEEP);
bcopy(name, buffer->lb_path, namelen + 1);
buffer->lb_logfile = logfile;
buffer->lb_records = NULL;
buffer->lb_num_recs = 0;
buffer->lb_size_queued = 0;
mutex_init(&buffer->lb_lock, NULL, MUTEX_DEFAULT, NULL);
rfsl_log_buffer++;
return (buffer);
}
static void
log_buffer_rele(struct log_buffer *lbp)
{
int len;
mutex_enter(&lbp->lb_lock);
if (--lbp->lb_refcnt > 1) {
mutex_exit(&lbp->lb_lock);
return;
}
if (lbp->lb_refcnt < 0) {
panic("log_rele: log_buffer refcnt < 0");
}
if (lbp->lb_refcnt == 1) {
LOGGING_DPRINT((10,
"log_buffer_rele lbp=%p disconnecting\n", (void *)lbp));
lbp->lb_refcnt++;
mutex_exit(&lbp->lb_lock);
(void) nfslog_records_flush_to_disk(lbp);
rw_enter(&nfslog_buffer_list_lock, RW_WRITER);
mutex_enter(&lbp->lb_lock);
if (--lbp->lb_refcnt > 1) {
mutex_exit(&lbp->lb_lock);
rw_exit(&nfslog_buffer_list_lock);
return;
}
if (lbp == nfslog_buffer_list) {
nfslog_buffer_list = lbp->lb_next;
} else {
struct log_buffer *tlbp;
for (tlbp = nfslog_buffer_list; tlbp->lb_next != NULL;
tlbp = tlbp->lb_next) {
if (tlbp->lb_next == lbp) {
tlbp->lb_next = lbp->lb_next;
break;
}
}
}
mutex_exit(&lbp->lb_lock);
rw_exit(&nfslog_buffer_list_lock);
}
LOGGING_DPRINT((10, "log_buffer_rele lbp=%p freeing\n", (void *)lbp));
log_file_rele(lbp->lb_logfile);
len = strlen(lbp->lb_path) + 1;
kmem_free(lbp->lb_path, len);
kmem_free(lbp, sizeof (*lbp));
rfsl_log_buffer--;
}
static int
log_file_create(caddr_t origname, struct log_file **lfpp)
{
vnode_t *vp = NULL;
char *name;
int namelen;
int error;
struct log_file *logfile = NULL;
vattr_t va;
caddr_t loghdr = NULL;
size_t loghdr_len = 0;
size_t loghdr_free = 0;
namelen = strlen(origname) + strlen(LOG_INPROG_STRING);
name = (caddr_t)kmem_alloc(namelen + 1, KM_SLEEP);
(void) sprintf(name, "%s%s", origname, LOG_INPROG_STRING);
LOGGING_DPRINT((3, "log_file_create: %s\n", name));
error = vn_open(name, UIO_SYSSPACE, FCREAT|FWRITE|FOFFMAX,
LOG_MODE, &vp, CRCREAT, 0);
if (error != 0) {
nfs_cmn_err(error, CE_WARN,
"log_file_create: Can not open %s - error %m", name);
goto out;
}
LOGGING_DPRINT((3, "log_file_create: %s vp=%p v_count=%d\n",
name, (void *)vp, vp->v_count));
logfile = (struct log_file *)kmem_zalloc(sizeof (*logfile), KM_SLEEP);
logfile->lf_path = name;
logfile->lf_vp = vp;
logfile->lf_refcnt = 1;
mutex_init(&logfile->lf_lock, NULL, MUTEX_DEFAULT, NULL);
rfsl_log_file++;
va.va_mask = AT_SIZE;
error = VOP_GETATTR(vp, &va, 0, CRED(), NULL);
if (error) {
nfs_cmn_err(error, CE_WARN,
"log_file_create: Can not stat %s - error = %m", name);
goto out;
}
if (va.va_size == 0) {
struct lr_alloc lr;
create_buffer_header(&loghdr, &loghdr_len, &loghdr_free);
lr.next = lr.prev = &lr;
lr.lr_flags = 0;
lr.log_record = loghdr;
lr.size = loghdr_len;
lr.alloc_cache = NULL;
lr.exi = NULL;
lr.lb = NULL;
mutex_enter(&logfile->lf_lock);
error = nfslog_write_logrecords(logfile, &lr, 1);
mutex_exit(&logfile->lf_lock);
if (error != 0) {
nfs_cmn_err(error, CE_WARN,
"log_file_create: Can not write header "
"on %s - error = %m", name);
goto out;
}
}
*lfpp = logfile;
if (loghdr != NULL)
kmem_free(loghdr, loghdr_free);
return (0);
out:
if (vp != NULL) {
int error1;
error1 = VOP_CLOSE(vp, FCREAT|FWRITE|FOFFMAX, 1, (offset_t)0,
CRED(), NULL);
if (error1) {
nfs_cmn_err(error1, CE_WARN,
"log_file_create: Can not close %s - "
"error = %m", name);
}
VN_RELE(vp);
}
kmem_free(name, namelen + 1);
if (logfile != NULL) {
mutex_destroy(&logfile->lf_lock);
kmem_free(logfile, sizeof (*logfile));
rfsl_log_file--;
}
if (loghdr != NULL)
kmem_free(loghdr, loghdr_free);
return (error);
}
static void
log_file_rele(struct log_file *lfp)
{
int len;
int error;
mutex_enter(&lfp->lf_lock);
if (--lfp->lf_refcnt > 0) {
LOGGING_DPRINT((10,
"log_file_rele lfp=%p decremented refcnt to %d\n",
(void *)lfp, lfp->lf_refcnt));
mutex_exit(&lfp->lf_lock);
return;
}
if (lfp->lf_refcnt < 0) {
panic("log_file_rele: log_file refcnt < 0");
}
LOGGING_DPRINT((10, "log_file_rele lfp=%p freeing node\n",
(void *)lfp));
lfp->lf_flags &= ~(L_PRINTED | L_ERROR);
ASSERT(lfp->lf_flags == 0);
ASSERT(lfp->lf_writers == 0);
error = VOP_CLOSE(lfp->lf_vp, FCREAT|FWRITE|FOFFMAX, 1, (offset_t)0,
CRED(), NULL);
if (error != 0) {
nfs_cmn_err(error, CE_WARN,
"NFS: Could not close log buffer %s - error = %m",
lfp->lf_path);
#ifdef DEBUG
} else {
LOGGING_DPRINT((3,
"log_file_rele: %s has been closed vp=%p v_count=%d\n",
lfp->lf_path, (void *)lfp->lf_vp, lfp->lf_vp->v_count));
#endif
}
VN_RELE(lfp->lf_vp);
len = strlen(lfp->lf_path) + 1;
kmem_free(lfp->lf_path, len);
kmem_free(lfp, sizeof (*lfp));
rfsl_log_file--;
}
void *
nfslog_record_alloc(struct exportinfo *exi, int alloc_indx, void **cookie,
int flags)
{
struct lr_alloc *lrp;
lrp = (struct lr_alloc *)
kmem_cache_alloc(nfslog_mem_alloc[alloc_indx].mem_cache,
KM_NOSLEEP);
if (lrp == NULL) {
*cookie = NULL;
return (NULL);
}
lrp->next = lrp;
lrp->prev = lrp;
lrp->lr_flags = 0;
lrp->log_record = (caddr_t)((uintptr_t)lrp +
(uintptr_t)sizeof (struct lr_alloc));
lrp->size = nfslog_mem_alloc[alloc_indx].size;
lrp->alloc_cache = nfslog_mem_alloc[alloc_indx].mem_cache;
lrp->exi = exi;
if (exi->exi_export.ex_flags & EX_LOG) {
LOG_BUFFER_HOLD(exi->exi_logbuffer);
lrp->lb = exi->exi_logbuffer;
} else {
lrp->lb = NULL;
}
*cookie = (void *)lrp;
LOGGING_DPRINT((3,
"nfslog_record_alloc(log_buffer=%p mem=%p size=%lu)\n",
(void *)exi->exi_logbuffer, (void *)lrp->log_record, lrp->size));
return (lrp->log_record);
}
void
nfslog_record_put(void *cookie, size_t size, bool_t sync,
unsigned int which_buffers)
{
struct lr_alloc *lrp = (struct lr_alloc *)cookie;
struct log_buffer *lbp = lrp->lb;
if (size == 0 || size > lrp->size) {
nfslog_free_logrecords(lrp);
return;
}
lrp->size = size;
if (which_buffers == NFSLOG_ALL_BUFFERS) {
(void) nfslog_record_append2all(lrp);
nfslog_free_logrecords(lrp);
return;
}
mutex_enter(&lbp->lb_lock);
if (lbp->lb_records == NULL) {
lbp->lb_records = (caddr_t)lrp;
lbp->lb_num_recs = 1;
lbp->lb_size_queued = lrp->size;
} else {
insque(lrp, ((struct lr_alloc *)lbp->lb_records)->prev);
lbp->lb_num_recs++;
lbp->lb_size_queued += lrp->size;
}
if (lbp->lb_size_queued >= nfslog_num_bytes_to_write ||
lbp->lb_num_recs > nfslog_num_records_to_write || sync == TRUE) {
mutex_exit(&lbp->lb_lock);
(void) nfslog_records_flush_to_disk(lbp);
} else {
mutex_exit(&lbp->lb_lock);
}
}
static int
nfslog_records_flush_to_disk(struct log_buffer *lbp)
{
mutex_enter(&lbp->lb_lock);
if (lbp->lb_records == NULL) {
mutex_exit(&lbp->lb_lock);
return (0);
}
return (nfslog_records_flush_to_disk_nolock(lbp));
}
static int
nfslog_records_flush_to_disk_nolock(struct log_buffer *lbp)
{
struct log_file *lfp = NULL;
struct lr_alloc *lrp_writers;
int num_recs;
int error = 0;
ASSERT(MUTEX_HELD(&lbp->lb_lock));
lfp = lbp->lb_logfile;
LOG_FILE_LOCK_TO_WRITE(lfp);
ASSERT(lbp->lb_records != NULL);
lrp_writers = (struct lr_alloc *)lbp->lb_records;
lbp->lb_records = NULL;
num_recs = lbp->lb_num_recs;
lbp->lb_num_recs = 0;
lbp->lb_size_queued = 0;
mutex_exit(&lbp->lb_lock);
error = nfslog_write_logrecords(lfp, lrp_writers, num_recs);
LOG_FILE_UNLOCK_FROM_WRITE(lfp);
nfslog_free_logrecords(lrp_writers);
return (error);
}
static int
nfslog_write_logrecords(struct log_file *lfp, struct lr_alloc *lrp_writers,
int num_recs)
{
struct uio uio;
struct iovec *iovp;
int size_iovecs;
vnode_t *vp;
struct vattr va;
struct lr_alloc *lrp;
int i;
ssize_t len;
int ioflag = FAPPEND;
int error = 0;
ASSERT(MUTEX_HELD(&lfp->lf_lock));
vp = lfp->lf_vp;
size_iovecs = sizeof (struct iovec) * num_recs;
iovp = (struct iovec *)kmem_alloc(size_iovecs, KM_NOSLEEP);
if (iovp == NULL) {
error = ENOMEM;
goto out;
}
i = 0;
len = 0;
lrp = lrp_writers;
do {
iovp[i].iov_base = lrp->log_record;
iovp[i].iov_len = lrp->size;
len += lrp->size;
lrp = lrp->next;
i++;
} while (lrp != lrp_writers);
ASSERT(i == num_recs);
uio.uio_iov = iovp;
uio.uio_iovcnt = num_recs;
uio.uio_loffset = 0;
uio.uio_segflg = (short)UIO_SYSSPACE;
uio.uio_resid = len;
uio.uio_llimit = (rlim64_t)MAXOFFSET_T;
uio.uio_fmode = FWRITE;
uio.uio_extflg = UIO_COPY_DEFAULT;
va.va_mask = AT_SIZE;
(void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
if ((error = VOP_GETATTR(vp, &va, 0, CRED(), NULL)) == 0) {
if ((len + va.va_size) < (MAXOFF32_T)) {
error = VOP_WRITE(vp, &uio, ioflag, CRED(), NULL);
VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
if (uio.uio_resid)
error = ENOSPC;
if (error)
(void) VOP_SETATTR(vp, &va, 0, CRED(), NULL);
} else {
VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
if (!(lfp->lf_flags & L_PRINTED)) {
cmn_err(CE_WARN,
"NFS Logging: buffer file %s exceeds 2GB; "
"stopped writing buffer \n", lfp->lf_path);
}
error = ENOSPC;
}
} else {
VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
}
kmem_free(iovp, size_iovecs);
out:
if (error) {
if (!(lfp->lf_flags & L_PRINTED)) {
nfs_cmn_err(error, CE_WARN,
"NFS Logging disabled for buffer %s - "
"write error = %m\n", lfp->lf_path);
lfp->lf_flags |= L_PRINTED;
}
} else if (lfp->lf_flags & (L_ERROR | L_PRINTED)) {
lfp->lf_flags &= ~(L_ERROR | L_PRINTED);
cmn_err(CE_WARN,
"NFS Logging re-enabled for buffer %s\n", lfp->lf_path);
}
return (error);
}
static void
nfslog_free_logrecords(struct lr_alloc *lrp_writers)
{
struct lr_alloc *lrp = lrp_writers;
struct lr_alloc *lrp_free;
do {
lrp_free = lrp;
lrp = lrp->next;
if ((lrp_free->lr_flags & LR_ALLOC_NOFREE) == 0) {
if (lrp_free->lb != NULL)
log_buffer_rele(lrp_free->lb);
if (lrp_free->alloc_cache)
kmem_cache_free(lrp_free->alloc_cache,
(void *)lrp_free);
} else {
lrp_free->next = lrp_free;
lrp_free->prev = lrp_free;
}
} while (lrp != lrp_writers);
}
static int
nfslog_logbuffer_rename(struct log_buffer *lbp)
{
struct log_file *lf;
int error;
struct log_file *logfile;
(void) nfslog_records_flush_to_disk(lbp);
mutex_enter(&(lbp)->lb_lock);
lf = lbp->lb_logfile;
mutex_enter(&(lf)->lf_lock);
mutex_exit(&(lbp)->lb_lock);
lf->lf_refcnt++;
mutex_exit(&(lf)->lf_lock);
LOGGING_DPRINT((10, "nfslog_logbuffer_rename: renaming %s to %s\n",
lf->lf_path, lbp->lb_path));
error = nfslog_logfile_rename(lf->lf_path, lbp->lb_path);
if (error != 0)
goto out;
error = log_file_create(lbp->lb_path, &logfile);
if (error != 0) {
(void) nfslog_logfile_rename(lbp->lb_path, lf->lf_path);
goto out;
}
mutex_enter(&(lbp)->lb_lock);
lbp->lb_logfile = logfile;
mutex_exit(&(lbp)->lb_lock);
LOG_FILE_RELE(lf);
nfslog_logfile_wait(lf);
out:
LOG_FILE_RELE(lf);
return (error);
}
static int
nfslog_logfile_rename(char *from, char *new)
{
int error;
error = vn_rename(from, new, UIO_SYSSPACE);
if (error != 0) {
cmn_err(CE_WARN,
"nfslog_logfile_rename: couldn't rename %s to %s\n",
from, new);
}
return (error);
}
static void
nfslog_logfile_wait(struct log_file *lf)
{
mutex_enter(&lf->lf_lock);
while (lf->lf_writers > 0) {
lf->lf_flags |= L_WAITING;
(void) cv_wait_sig(&lf->lf_cv_waiters, &lf->lf_lock);
}
mutex_exit(&lf->lf_lock);
}
static int
nfslog_record_append2all(struct lr_alloc *lrp)
{
struct log_buffer *lbp, *nlbp;
int error, ret_error = 0;
int lr_flags = lrp->lr_flags;
rw_enter(&nfslog_buffer_list_lock, RW_READER);
if ((lbp = nfslog_buffer_list) != NULL)
LOG_BUFFER_HOLD(lbp);
for (nlbp = NULL; lbp != NULL; lbp = nlbp) {
if ((nlbp = lbp->lb_next) != NULL) {
LOG_BUFFER_HOLD(nlbp);
}
rw_exit(&nfslog_buffer_list_lock);
lrp->lr_flags = LR_ALLOC_NOFREE;
ASSERT(lbp != NULL);
mutex_enter(&lbp->lb_lock);
if (lbp->lb_records == NULL) {
lbp->lb_records = (caddr_t)lrp;
lbp->lb_num_recs = 1;
lbp->lb_size_queued = lrp->size;
} else {
insque(lrp, ((struct lr_alloc *)lbp->lb_records)->prev);
lbp->lb_num_recs++;
lbp->lb_size_queued += lrp->size;
}
error = nfslog_records_flush_to_disk_nolock(lbp);
if (error) {
ret_error = -1;
nfs_cmn_err(error, CE_WARN,
"rfsl_log_pubfh: could not append record to "
"\"%s\" error = %m\n", lbp->lb_path);
}
log_buffer_rele(lbp);
rw_enter(&nfslog_buffer_list_lock, RW_READER);
}
rw_exit(&nfslog_buffer_list_lock);
lrp->lr_flags = lr_flags;
return (ret_error);
}
#ifdef DEBUG
static int logging_debug = 0;
void
nfslog_dprint(const int level, const char *fmt, ...)
{
va_list args;
if (logging_debug == level ||
(logging_debug > 10 && (logging_debug - 10) >= level)) {
va_start(args, fmt);
(void) vprintf(fmt, args);
va_end(args);
}
}
#endif
int
nfsl_flush(struct nfsl_flush_args *args, model_t model)
{
struct flush_thread_params *tparams;
struct nfsl_flush_args *nfsl_args;
int error = 0;
ulong_t buffer_len;
STRUCT_HANDLE(nfsl_flush_args, uap);
STRUCT_SET_HANDLE(uap, model, args);
tparams = (struct flush_thread_params *)
kmem_zalloc(sizeof (*tparams), KM_SLEEP);
nfsl_args = &tparams->tp_args;
nfsl_args->version = STRUCT_FGET(uap, version);
if (nfsl_args->version != NFSL_FLUSH_ARGS_VERS) {
cmn_err(CE_WARN, "nfsl_flush: exected version %d, got %d",
NFSL_FLUSH_ARGS_VERS, nfsl_args->version);
return (EIO);
}
nfsl_args->directive = STRUCT_FGET(uap, directive);
if ((nfsl_args->directive & NFSL_ALL) == 0) {
nfsl_args->buff_len = STRUCT_FGET(uap, buff_len);
nfsl_args->buff = (char *)
kmem_alloc(nfsl_args->buff_len, KM_NOSLEEP);
if (nfsl_args->buff == NULL)
return (ENOMEM);
error = copyinstr((const char *)STRUCT_FGETP(uap, buff),
nfsl_args->buff, nfsl_args->buff_len, &buffer_len);
if (error)
return (EFAULT);
if (nfsl_args->buff_len != buffer_len)
return (EFAULT);
}
LOGGING_DPRINT((10, "nfsl_flush: Flushing %s buffer(s)\n",
nfsl_args->directive & NFSL_ALL ? "all" : nfsl_args->buff));
if (nfsl_args->directive & NFSL_SYNC) {
nfslog_do_flush(tparams);
error = tparams->tp_error;
kmem_free(nfsl_args->buff, nfsl_args->buff_len);
kmem_free(tparams, sizeof (*tparams));
} else {
(void) zthread_create(NULL, 0, nfslog_do_flush,
tparams, 0, minclsyspri);
}
return (error);
}
static void
nfslog_do_flush(struct flush_thread_params *tparams)
{
struct nfsl_flush_args *args;
struct log_buffer *lbp, *nlbp;
int error = ENOENT;
int found = 0;
char *buf_inprog;
int buf_inprog_len;
if (!tparams)
return;
args = &tparams->tp_args;
if (!args)
return;
rw_enter(&nfslog_buffer_list_lock, RW_READER);
if ((lbp = nfslog_buffer_list) != NULL) {
LOG_BUFFER_HOLD(lbp);
}
for (nlbp = NULL; lbp != NULL; lbp = nlbp) {
if ((nlbp = lbp->lb_next) != NULL) {
LOG_BUFFER_HOLD(nlbp);
}
rw_exit(&nfslog_buffer_list_lock);
if (args->directive & NFSL_ALL) {
(void) nfslog_records_flush_to_disk(lbp);
} else {
if ((strcmp(lbp->lb_path, args->buff) == 0) &&
(args->directive & NFSL_RENAME)) {
error = nfslog_logbuffer_rename(lbp);
found++;
if (nlbp != NULL)
log_buffer_rele(nlbp);
log_buffer_rele(lbp);
break;
}
}
log_buffer_rele(lbp);
rw_enter(&nfslog_buffer_list_lock, RW_READER);
}
if (!found)
rw_exit(&nfslog_buffer_list_lock);
if (!found && ((args->directive & NFSL_ALL) == 0) &&
(args->directive & NFSL_RENAME)) {
buf_inprog_len = strlen(args->buff) +
strlen(LOG_INPROG_STRING) + 1;
buf_inprog = (caddr_t)kmem_alloc(buf_inprog_len, KM_SLEEP);
(void) sprintf(buf_inprog, "%s%s",
args->buff, LOG_INPROG_STRING);
error = nfslog_logfile_rename(buf_inprog, args->buff);
kmem_free(buf_inprog, buf_inprog_len);
}
if ((args->directive & NFSL_SYNC) == 0) {
kmem_free(args->buff, args->buff_len);
kmem_free(tparams, sizeof (*tparams));
zthread_exit();
}
tparams->tp_error = error;
}
static void
create_buffer_header(caddr_t *loghdr, size_t *reclen, size_t *freesize)
{
timestruc_t now;
nfslog_buffer_header lh;
XDR xdrs;
unsigned int final_size;
*freesize = NFSLOG_SMALL_RECORD_SIZE;
lh.bh_length = 0;
lh.bh_version = NFSLOG_BUF_VERSION;
lh.bh_flags = 0;
lh.bh_offset = 0;
gethrestime(&now);
TIMESPEC_TO_TIMESPEC32(&lh.bh_timestamp, &now);
*loghdr = (caddr_t)kmem_alloc(*freesize, KM_SLEEP);
xdrmem_create(&xdrs, *loghdr, *freesize, XDR_ENCODE);
(void) xdr_nfslog_buffer_header(&xdrs, &lh);
final_size = xdr_getpos(&xdrs);
xdr_setpos(&xdrs, 0);
(void) xdr_u_int(&xdrs, &final_size);
*reclen = (size_t)final_size;
}
struct nfslog_proc_disp {
bool_t (*xdrargs)();
bool_t (*xdrres)();
bool_t affects_transactions;
};
struct nfslog_vers_disp {
int nfslog_dis_nprocs;
struct nfslog_proc_disp *nfslog_dis_proc_table;
};
struct nfslog_prog_disp {
int nfslog_dis_prog;
int nfslog_dis_versmin;
int nfslog_dis_nvers;
struct nfslog_vers_disp *nfslog_dis_vers_table;
};
static int rfs_log_bad = 0;
static int rfs_log_good = 0;
static struct nfslog_proc_disp nfslog_proc_v2[] = {
{xdr_void, xdr_void, FALSE},
{xdr_fhandle, xdr_nfslog_getattrres, FALSE},
{xdr_nfslog_setattrargs, xdr_nfsstat, TRUE},
{xdr_void, xdr_void, FALSE},
{xdr_nfslog_diropargs, xdr_nfslog_diropres, TRUE},
{xdr_fhandle, xdr_nfslog_rdlnres, FALSE},
{xdr_nfslog_nfsreadargs, xdr_nfslog_rdresult, TRUE},
{xdr_void, xdr_void, FALSE},
{xdr_nfslog_writeargs, xdr_nfslog_writeresult, TRUE},
{xdr_nfslog_createargs, xdr_nfslog_diropres, TRUE},
{xdr_nfslog_diropargs, xdr_nfsstat, TRUE},
{xdr_nfslog_rnmargs, xdr_nfsstat, TRUE},
{xdr_nfslog_linkargs, xdr_nfsstat, TRUE},
{xdr_nfslog_symlinkargs, xdr_nfsstat, TRUE},
{xdr_nfslog_createargs, xdr_nfslog_diropres, TRUE},
{xdr_nfslog_diropargs, xdr_nfsstat, TRUE},
{xdr_nfslog_rddirargs, xdr_nfslog_rddirres, TRUE},
{xdr_fhandle, xdr_nfslog_statfs, FALSE},
};
static struct nfslog_proc_disp nfslog_proc_v3[] = {
{xdr_void, xdr_void, FALSE},
{xdr_nfslog_nfs_fh3, xdr_nfslog_GETATTR3res, FALSE},
{xdr_nfslog_SETATTR3args, xdr_nfslog_SETATTR3res, TRUE},
{xdr_nfslog_diropargs3, xdr_nfslog_LOOKUP3res, TRUE},
{xdr_nfslog_ACCESS3args, xdr_nfslog_ACCESS3res, FALSE},
{xdr_nfslog_nfs_fh3, xdr_nfslog_READLINK3res, FALSE},
{xdr_nfslog_READ3args, xdr_nfslog_READ3res, TRUE},
{xdr_nfslog_WRITE3args, xdr_nfslog_WRITE3res, TRUE},
{xdr_nfslog_CREATE3args, xdr_nfslog_CREATE3res, TRUE},
{xdr_nfslog_MKDIR3args, xdr_nfslog_MKDIR3res, TRUE},
{xdr_nfslog_SYMLINK3args, xdr_nfslog_SYMLINK3res, TRUE},
{xdr_nfslog_MKNOD3args, xdr_nfslog_MKNOD3res, TRUE},
{xdr_nfslog_REMOVE3args, xdr_nfslog_REMOVE3res, TRUE},
{xdr_nfslog_RMDIR3args, xdr_nfslog_RMDIR3res, TRUE},
{xdr_nfslog_RENAME3args, xdr_nfslog_RENAME3res, TRUE},
{xdr_nfslog_LINK3args, xdr_nfslog_LINK3res, TRUE},
{xdr_nfslog_READDIR3args, xdr_nfslog_READDIR3res, TRUE},
{xdr_nfslog_READDIRPLUS3args, xdr_nfslog_READDIRPLUS3res, TRUE},
{xdr_nfslog_FSSTAT3args, xdr_nfslog_FSSTAT3res, FALSE},
{xdr_nfslog_FSINFO3args, xdr_nfslog_FSINFO3res, FALSE},
{xdr_nfslog_PATHCONF3args, xdr_nfslog_PATHCONF3res, FALSE},
{xdr_nfslog_COMMIT3args, xdr_nfslog_COMMIT3res, FALSE},
};
static struct nfslog_proc_disp nfslog_proc_v1[] = {
{xdr_void, xdr_void, TRUE},
{xdr_nfslog_sharefsargs, xdr_nfslog_sharefsres, TRUE},
{xdr_nfslog_sharefsargs, xdr_nfslog_sharefsres, TRUE},
{xdr_nfslog_diropargs3, xdr_nfslog_LOOKUP3res, TRUE},
{xdr_nfslog_getfhargs, xdr_nfsstat, TRUE},
};
static struct nfslog_vers_disp nfslog_vers_disptable[] = {
{sizeof (nfslog_proc_v2) / sizeof (nfslog_proc_v2[0]),
nfslog_proc_v2},
{sizeof (nfslog_proc_v3) / sizeof (nfslog_proc_v3[0]),
nfslog_proc_v3},
};
static struct nfslog_vers_disp nfslog_nfslog_vers_disptable[] = {
{sizeof (nfslog_proc_v1) / sizeof (nfslog_proc_v1[0]),
nfslog_proc_v1},
};
static struct nfslog_prog_disp nfslog_dispatch_table[] = {
{NFS_PROGRAM, NFS_VERSMIN,
(sizeof (nfslog_vers_disptable) /
sizeof (nfslog_vers_disptable[0])),
nfslog_vers_disptable},
{NFSLOG_PROGRAM, NFSLOG_VERSMIN,
(sizeof (nfslog_nfslog_vers_disptable) /
sizeof (nfslog_nfslog_vers_disptable[0])),
nfslog_nfslog_vers_disptable},
};
static int nfslog_dispatch_table_arglen = sizeof (nfslog_dispatch_table) /
sizeof (nfslog_dispatch_table[0]);
struct exportinfo *
nfslog_get_exi(
nfs_export_t *ne,
struct exportinfo *exi,
struct svc_req *req,
caddr_t res,
unsigned int *nfslog_rec_id)
{
struct log_buffer *lb;
struct exportinfo *exi_ret = NULL;
fhandle_t *fh;
nfs_fh3 *fh3;
if (exi == NULL)
return (NULL);
if (exi->exi_export.ex_flags & EX_LOG) {
lb = exi->exi_logbuffer;
*nfslog_rec_id = atomic_add_32_nv(&lb->lb_rec_id, (int32_t)1);
exi_hold(exi);
return (exi);
}
if (exi != ne->exi_public)
return (NULL);
if (req->rq_prog == NFS_PROGRAM) {
switch (req->rq_vers) {
case NFS_V3:
if ((req->rq_proc == NFSPROC3_LOOKUP) &&
(((LOOKUP3res *)res)->status == NFS3_OK)) {
fh3 = &((LOOKUP3res *)res)->res_u.ok.object;
exi_ret = checkexport(&fh3->fh3_fsid,
FH3TOXFIDP(fh3));
}
break;
case NFS_VERSION:
if ((req->rq_proc == RFS_LOOKUP) &&
(((struct nfsdiropres *)
res)->dr_status == NFS_OK)) {
fh = &((struct nfsdiropres *)res)->
dr_u.dr_drok_u.drok_fhandle;
exi_ret = checkexport(&fh->fh_fsid,
(fid_t *)&fh->fh_xlen);
}
break;
default:
break;
}
}
if (exi_ret != NULL && exi_ret->exi_export.ex_flags & EX_LOG) {
lb = exi_ret->exi_logbuffer;
*nfslog_rec_id = atomic_add_32_nv(&lb->lb_rec_id, (int32_t)1);
return (exi_ret);
}
return (NULL);
}
#ifdef DEBUG
static long long rfslog_records_ignored = 0;
#endif
void
nfslog_write_record(struct exportinfo *exi, struct svc_req *req,
caddr_t args, caddr_t res, cred_t *cr, struct netbuf *pnb,
unsigned int record_id, unsigned int which_buffers)
{
struct nfslog_prog_disp *progtable;
struct nfslog_vers_disp *verstable;
struct nfslog_proc_disp *disp = NULL;
int i, vers;
void *log_cookie;
caddr_t buffer;
XDR xdrs;
unsigned int final_size;
int encode_ok;
int alloc_indx;
ASSERT(exi != NULL); ASSERT(req != NULL); ASSERT(args != NULL);
ASSERT(res != NULL); ASSERT(cr != NULL);
for (i = 0; (i < nfslog_dispatch_table_arglen); i++) {
if (req->rq_prog == nfslog_dispatch_table[i].nfslog_dis_prog)
break;
}
if (i >= nfslog_dispatch_table_arglen) {
return;
}
progtable = &nfslog_dispatch_table[i];
vers = req->rq_vers - progtable->nfslog_dis_versmin;
verstable = &progtable->nfslog_dis_vers_table[vers];
disp = &verstable->nfslog_dis_proc_table[req->rq_proc];
if (!(exi->exi_export.ex_flags & EX_LOG_ALLOPS) &&
!disp->affects_transactions) {
#ifdef DEBUG
rfslog_records_ignored++;
#endif
return;
}
switch (req->rq_prog) {
case NFS_PROGRAM:
switch (req->rq_vers) {
case NFS_V3:
switch (req->rq_proc) {
case NFSPROC3_READDIRPLUS:
alloc_indx = MEDIUM_INDX;
break;
default:
alloc_indx = SMALL_INDX;
break;
}
break;
default:
alloc_indx = SMALL_INDX;
break;
}
break;
case NFSLOG_PROGRAM:
alloc_indx = MEDIUM_INDX;
break;
default:
alloc_indx = SMALL_INDX;
break;
}
do {
encode_ok = FALSE;
if (nfslog_mem_alloc[alloc_indx].size == (-1)) {
cmn_err(CE_WARN,
"NFSLOG: unable to encode record - prog=%d "
"proc = %d", req->rq_prog, req->rq_proc);
return;
}
buffer = nfslog_record_alloc(exi, alloc_indx, &log_cookie, 0);
if (buffer == NULL) {
rfs_log_bad++;
cmn_err(CE_WARN, "NFSLOG: can't get record");
return;
}
xdrmem_create(&xdrs, buffer,
nfslog_mem_alloc[alloc_indx].size, XDR_ENCODE);
if (xdr_nfslog_request_record(&xdrs, exi, req, cr, pnb,
nfslog_mem_alloc[alloc_indx].size, record_id) &&
(*disp->xdrargs)(&xdrs, args) &&
(*disp->xdrres)(&xdrs, res)) {
encode_ok = TRUE;
rfs_log_good++;
final_size = xdr_getpos(&xdrs);
xdr_setpos(&xdrs, 0);
(void) xdr_u_int(&xdrs, &final_size);
} else {
nfslog_record_put(log_cookie, 0, FALSE, which_buffers);
alloc_indx++;
}
} while (encode_ok == FALSE);
nfslog_record_put(log_cookie,
final_size, (record_id == 0), which_buffers);
}
static char *
get_publicfh_path(int *alloc_length)
{
char *pubpath;
nfs_export_t *ne = nfs_get_export();
rw_enter(&ne->exported_lock, RW_READER);
*alloc_length = ne->exi_public->exi_export.ex_pathlen + 1;
pubpath = kmem_alloc(*alloc_length, KM_SLEEP);
(void) strcpy(pubpath, ne->exi_public->exi_export.ex_path);
rw_exit(&ne->exported_lock);
return (pubpath);
}
static void
log_public_record(struct exportinfo *exi, cred_t *cr)
{
struct svc_req req;
struct netbuf nb = {0, 0, NULL};
int free_length = 0;
diropargs3 args;
LOOKUP3res res;
bzero(&req, sizeof (req));
req.rq_prog = NFSLOG_PROGRAM;
req.rq_vers = NFSLOG_VERSION;
req.rq_proc = NFSLOG_LOOKUP;
req.rq_cred.oa_flavor = AUTH_NONE;
bzero(&args, sizeof (diropargs3));
bzero(&res, sizeof (LOOKUP3res));
args.dir.fh3_length = 0;
if ((args.name = get_publicfh_path(&free_length)) == NULL)
return;
args.dirp = &args.dir;
res.status = NFS3_OK;
res.res_u.ok.object.fh3_length = 0;
nfslog_write_record(exi, &req,
(caddr_t)&args, (caddr_t)&res, cr, &nb, 0, NFSLOG_ALL_BUFFERS);
kmem_free(args.name, free_length);
}
void
nfslog_share_record(struct exportinfo *exi, cred_t *cr)
{
struct svc_req req;
int res = 0;
struct netbuf nb = {0, 0, NULL};
ASSERT(exi != NULL);
if (nfslog_buffer_list == NULL)
return;
if (exi->exi_export.ex_flags & EX_LOG) {
bzero(&req, sizeof (req));
req.rq_prog = NFSLOG_PROGRAM;
req.rq_vers = NFSLOG_VERSION;
req.rq_proc = NFSLOG_SHARE;
req.rq_cred.oa_flavor = AUTH_NONE;
nfslog_write_record(exi, &req, (caddr_t)exi, (caddr_t)&res, cr,
&nb, 0, NFSLOG_ONE_BUFFER);
}
log_public_record(exi, cr);
}
void
nfslog_unshare_record(struct exportinfo *exi, cred_t *cr)
{
struct svc_req req;
int res = 0;
struct netbuf nb = {0, 0, NULL};
ASSERT(exi != NULL);
ASSERT(exi->exi_export.ex_flags & EX_LOG);
bzero(&req, sizeof (req));
req.rq_prog = NFSLOG_PROGRAM;
req.rq_vers = NFSLOG_VERSION;
req.rq_proc = NFSLOG_UNSHARE;
req.rq_cred.oa_flavor = AUTH_NONE;
nfslog_write_record(exi, &req,
(caddr_t)exi, (caddr_t)&res, cr, &nb, 0, NFSLOG_ONE_BUFFER);
}
void
nfslog_getfh(struct exportinfo *exi, fhandle *fh, char *fname, enum uio_seg seg,
cred_t *cr)
{
struct svc_req req;
int res = 0;
struct netbuf nb = {0, 0, NULL};
int error = 0;
char *namebuf;
size_t len;
nfslog_getfhargs gfh;
ASSERT(exi != NULL);
ASSERT(exi->exi_export.ex_flags & EX_LOG);
bzero(&req, sizeof (req));
req.rq_prog = NFSLOG_PROGRAM;
req.rq_vers = NFSLOG_VERSION;
req.rq_proc = NFSLOG_GETFH;
req.rq_cred.oa_flavor = AUTH_NONE;
namebuf = kmem_alloc(MAXPATHLEN + 4, KM_SLEEP);
if (seg == UIO_USERSPACE) {
error = copyinstr(fname, namebuf, MAXPATHLEN, &len);
} else {
error = copystr(fname, namebuf, MAXPATHLEN, &len);
}
if (!error) {
gfh.gfh_fh_buf = *fh;
gfh.gfh_path = namebuf;
nfslog_write_record(exi, &req, (caddr_t)&gfh, (caddr_t)&res,
cr, &nb, 0, NFSLOG_ONE_BUFFER);
}
kmem_free(namebuf, MAXPATHLEN + 4);
}