nfs_dump.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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
* or http://www.opensolaris.org/os/licensing.
* 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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Dump memory to NFS swap file after a panic.
* We have no timeouts, context switches, etc.
*/
#include <rpc/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/vnode.h>
#include <sys/bootconf.h>
#include <nfs/nfs.h>
#include <rpc/auth.h>
#include <rpc/xdr.h>
#include <rpc/rpc_msg.h>
#include <rpc/clnt.h>
#include <netinet/in.h>
#include <sys/tiuser.h>
#include <nfs/nfs_clnt.h>
#include <sys/t_kuser.h>
#include <sys/file.h>
#include <sys/netconfig.h>
#include <sys/utsname.h>
#include <sys/sysmacros.h>
#include <sys/thread.h>
#include <sys/cred.h>
#include <sys/strsubr.h>
#include <nfs/rnode.h>
#include <sys/varargs.h>
#include <sys/cmn_err.h>
#include <sys/systm.h>
#include <sys/dumphdr.h>
#include <sys/debug.h>
#include <sys/sunddi.h>
#define TIMEOUT (2 * hz)
#define RETRIES (5)
#define HDR_SIZE (256)
static struct knetconfig nfsdump_cf;
static struct netbuf nfsdump_addr;
static fhandle_t nfsdump_fhandle2;
static nfs_fh3 nfsdump_fhandle3;
static int nfsdump_maxcount;
static rpcvers_t nfsdump_version;
/*
* nonzero dumplog enables nd_log messages
*/
static int dumplog = 0;
static int nd_init(vnode_t *, TIUSER **);
static int nd_poll(TIUSER *, int, int *);
static int nd_send_data(TIUSER *, caddr_t, int, XDR *, uint32_t *);
static int nd_get_reply(TIUSER *, XDR *, uint32_t, int *);
static int nd_auth_marshall(XDR *);
static void nd_log(const char *, ...) __KPRINTFLIKE(1);
/*PRINTFLIKE1*/
static void
nd_log(const char *fmt, ...)
{
if (dumplog) {
va_list adx;
va_start(adx, fmt);
vprintf(fmt, adx);
va_end(adx);
}
}
int
nfs_dump(vnode_t *dumpvp, caddr_t addr, int bn, int count)
{
static TIUSER *tiptr;
XDR xdrs;
int reply;
int badmsg;
uint32_t call_xid;
int retry = 0;
int error;
int i;
nd_log("nfs_dump: addr=%p bn=%d count=%d\n", (void *)addr, bn, count);
if (error = nd_init(dumpvp, &tiptr))
return (error);
for (i = 0; i < count; i += ptod(1), addr += ptob(1)) {
do {
error = nd_send_data(tiptr, addr, (int)dbtob(bn + i),
&xdrs, &call_xid);
if (error)
return (error);
do {
if (error = nd_poll(tiptr, retry, &reply))
return (error);
if (!reply) {
retry++;
break;
}
retry = 0;
error = nd_get_reply(tiptr, &xdrs, call_xid,
&badmsg);
if (error)
return (error);
} while (badmsg);
} while (retry);
}
return (0);
}
static int
nd_init(vnode_t *dumpvp, TIUSER **tiptr)
{
int error;
if (*tiptr)
return (0);
/*
* If dump info hasn't yet been initialized (because dump
* device was chosen at user-level, rather than at boot time
* in nfs_swapvp) fill it in now.
*/
if (nfsdump_maxcount == 0) {
nfsdump_version = VTOMI(dumpvp)->mi_vers;
switch (nfsdump_version) {
case NFS_VERSION:
nfsdump_fhandle2 = *VTOFH(dumpvp);
break;
case NFS_V3:
nfsdump_fhandle3 = *VTOFH3(dumpvp);
break;
default:
return (EIO);
}
nfsdump_maxcount = (int)dumpvp_size;
nfsdump_addr = VTOMI(dumpvp)->mi_curr_serv->sv_addr;
nfsdump_cf = *(VTOMI(dumpvp)->mi_curr_serv->sv_knconf);
if (nfsdump_cf.knc_semantics != NC_TPI_CLTS) {
int v6 = 1;
nd_log("nfs_dump: not connectionless!\n");
if ((strcmp(nfsdump_cf.knc_protofmly, NC_INET) == 0) ||
((v6 = strcmp(nfsdump_cf.knc_protofmly, NC_INET6))\
== 0)) {
major_t clone_maj;
nfsdump_cf.knc_proto = NC_UDP;
nfsdump_cf.knc_semantics = NC_TPI_CLTS;
nd_log("nfs_dump: grabbing UDP major number\n");
clone_maj = ddi_name_to_major("clone");
nd_log("nfs_dump: making UDP device\n");
nfsdump_cf.knc_rdev = makedevice(clone_maj,
ddi_name_to_major(v6?"udp":"udp6"));
} else {
error = EIO;
nfs_perror(error, "\nnfs_dump: cannot dump over"
" protocol %s: %m\n", nfsdump_cf.knc_proto);
return (error);
}
}
}
nd_log("nfs_dump: calling t_kopen\n");
if (error = t_kopen(NULL, nfsdump_cf.knc_rdev,
FREAD|FWRITE|FNDELAY, tiptr, CRED())) {
nfs_perror(error, "\nnfs_dump: t_kopen failed: %m\n");
return (EIO);
}
if ((strcmp(nfsdump_cf.knc_protofmly, NC_INET) == 0) ||
(strcmp(nfsdump_cf.knc_protofmly, NC_INET6) == 0)) {
nd_log("nfs_dump: calling bindresvport\n");
if (error = bindresvport(*tiptr, NULL, NULL, FALSE)) {
nfs_perror(error,
"\nnfs_dump: bindresvport failed: %m\n");
return (EIO);
}
} else {
nd_log("nfs_dump: calling t_kbind\n");
if ((error = t_kbind(*tiptr, NULL, NULL)) != 0) {
nfs_perror(error, "\nnfs_dump: t_kbind failed: %m\n");
return (EIO);
}
}
return (0);
}
static int
nd_send_data(TIUSER *tiptr, caddr_t addr, int offset, XDR *xdrp, uint32_t *xidp)
{
static struct rpc_msg call_msg;
static uchar_t header[HDR_SIZE];
static struct t_kunitdata sudata;
static uchar_t *dumpbuf;
int procnum;
stable_how stable = FILE_SYNC;
mblk_t *mblk_p;
int error;
int tsize = ptob(1);
uint64 offset3;
if (!dumpbuf) {
call_msg.rm_direction = CALL;
call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
call_msg.rm_call.cb_prog = NFS_PROGRAM;
call_msg.rm_call.cb_vers = nfsdump_version;
if (!(dumpbuf = kmem_alloc(ptob(1), KM_NOSLEEP))) {
cmn_err(CE_WARN, "\tnfs_dump: cannot allocate dump buffer");
return (ENOMEM);
}
}
nd_log("nfs_dump: calling esballoc for header\n");
if (!(mblk_p = esballoc(header, HDR_SIZE, BPRI_HI, &frnop))) {
cmn_err(CE_WARN, "\tnfs_dump: out of mblks");
return (ENOBUFS);
}
xdrmem_create(xdrp, (caddr_t)header, HDR_SIZE, XDR_ENCODE);
call_msg.rm_xid = alloc_xid();
*xidp = call_msg.rm_xid;
if (!xdr_callhdr(xdrp, &call_msg)) {
cmn_err(CE_WARN, "\tnfs_dump: cannot serialize header");
return (EIO);
}
if (nfsdump_maxcount) {
/*
* Do not extend the dump file if it is also
* the swap file.
*/
if (offset >= nfsdump_maxcount) {
cmn_err(CE_WARN, "\tnfs_dump: end of file");
return (EIO);
}
if (offset + tsize > nfsdump_maxcount)
tsize = nfsdump_maxcount - offset;
}
switch (nfsdump_version) {
case NFS_VERSION:
procnum = RFS_WRITE;
if (!XDR_PUTINT32(xdrp, (int32_t *)&procnum) ||
!nd_auth_marshall(xdrp) ||
!xdr_fhandle(xdrp, &nfsdump_fhandle2) ||
/*
* Following four values are:
* beginoffset
* offset
* length
* bytes array length
*/
!XDR_PUTINT32(xdrp, (int32_t *)&offset) ||
!XDR_PUTINT32(xdrp, (int32_t *)&offset) ||
!XDR_PUTINT32(xdrp, (int32_t *)&tsize) ||
!XDR_PUTINT32(xdrp, (int32_t *)&tsize)) {
cmn_err(CE_WARN, "\tnfs_dump: serialization failed");
return (EIO);
}
break;
case NFS_V3:
procnum = NFSPROC3_WRITE;
offset3 = offset;
if (!XDR_PUTINT32(xdrp, (int32_t *)&procnum) ||
!nd_auth_marshall(xdrp) ||
!xdr_nfs_fh3(xdrp, &nfsdump_fhandle3) ||
/*
* Following four values are:
* offset
* count
* stable
* bytes array length
*/
!xdr_u_longlong_t(xdrp, &offset3) ||
!XDR_PUTINT32(xdrp, (int32_t *)&tsize) ||
!XDR_PUTINT32(xdrp, (int32_t *)&stable) ||
!XDR_PUTINT32(xdrp, (int32_t *)&tsize)) {
cmn_err(CE_WARN, "\tnfs_dump: serialization failed");
return (EIO);
}
break;
default:
return (EIO);
}
bcopy(addr, (caddr_t)dumpbuf, tsize);
mblk_p->b_wptr += (int)XDR_GETPOS(xdrp);
mblk_p->b_cont = esballoc((uchar_t *)dumpbuf, ptob(1), BPRI_HI, &frnop);
if (!mblk_p->b_cont) {
cmn_err(CE_WARN, "\tnfs_dump: out of mblks");
return (ENOBUFS);
}
mblk_p->b_cont->b_wptr += ptob(1);
sudata.addr = nfsdump_addr; /* structure copy */
sudata.udata.buf = (char *)NULL;
sudata.udata.maxlen = 0;
sudata.udata.len = 1; /* needed for t_ksndudata */
sudata.udata.udata_mp = mblk_p;
nd_log("nfs_dump: calling t_ksndudata\n");
if (error = t_ksndudata(tiptr, &sudata, (frtn_t *)NULL)) {
nfs_perror(error, "\nnfs_dump: t_ksndudata failed: %m\n");
return (error);
}
return (0);
}
static int
nd_get_reply(TIUSER *tiptr, XDR *xdrp, uint32_t call_xid, int *badmsg)
{
static struct rpc_msg reply_msg;
static struct rpc_err rpc_err;
static struct nfsattrstat na;
static struct WRITE3res wres;
static struct t_kunitdata rudata;
int uderr;
int type;
int error;
*badmsg = 0;
rudata.addr.maxlen = 0;
rudata.opt.maxlen = 0;
rudata.udata.udata_mp = (mblk_t *)NULL;
nd_log("nfs_dump: calling t_krcvudata\n");
if (error = t_krcvudata(tiptr, &rudata, &type, &uderr)) {
nfs_perror(error, "\nnfs_dump: t_krcvudata failed: %m\n");
return (EIO);
}
if (type != T_DATA) {
cmn_err(CE_WARN, "\tnfs_dump: received type %d", type);
*badmsg = 1;
return (0);
}
if (!rudata.udata.udata_mp) {
cmn_err(CE_WARN, "\tnfs_dump: null receive");
*badmsg = 1;
return (0);
}
/*
* Decode results.
*/
xdrmblk_init(xdrp, rudata.udata.udata_mp, XDR_DECODE, 0);
reply_msg.acpted_rply.ar_verf = _null_auth;
switch (nfsdump_version) {
case NFS_VERSION:
reply_msg.acpted_rply.ar_results.where = (caddr_t)&na;
reply_msg.acpted_rply.ar_results.proc = xdr_attrstat;
break;
case NFS_V3:
reply_msg.acpted_rply.ar_results.where = (caddr_t)&wres;
reply_msg.acpted_rply.ar_results.proc = xdr_WRITE3res;
break;
default:
return (EIO);
}
if (!xdr_replymsg(xdrp, &reply_msg)) {
cmn_err(CE_WARN, "\tnfs_dump: xdr_replymsg failed");
return (EIO);
}
if (reply_msg.rm_xid != call_xid) {
*badmsg = 1;
return (0);
}
_seterr_reply(&reply_msg, &rpc_err);
if (rpc_err.re_status != RPC_SUCCESS) {
cmn_err(CE_WARN, "\tnfs_dump: RPC error %d (%s)",
rpc_err.re_status, clnt_sperrno(rpc_err.re_status));
return (EIO);
}
switch (nfsdump_version) {
case NFS_VERSION:
if (na.ns_status) {
cmn_err(CE_WARN, "\tnfs_dump: status %d", na.ns_status);
return (EIO);
}
break;
case NFS_V3:
if (wres.status != NFS3_OK) {
cmn_err(CE_WARN, "\tnfs_dump: status %d", wres.status);
return (EIO);
}
break;
default:
return (EIO);
}
if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) {
/* free auth handle */
xdrp->x_op = XDR_FREE;
(void) xdr_opaque_auth(xdrp, &(reply_msg.acpted_rply.ar_verf));
}
freemsg(rudata.udata.udata_mp);
return (0);
}
static int
nd_poll(TIUSER *tiptr, int retry, int *eventp)
{
clock_t start_bolt = lbolt;
clock_t timout = TIMEOUT * (retry + 1);
int error;
nd_log("nfs_dump: calling t_kspoll\n");
*eventp = 0;
while (!*eventp && ((lbolt - start_bolt) < timout)) {
/*
* Briefly enable interrupts before checking for a reply;
* the network transports do not yet support do_polled_io.
*/
int s = spl0();
splx(s);
if (error = t_kspoll(tiptr, 0, READWAIT, eventp)) {
nfs_perror(error,
"\nnfs_dump: t_kspoll failed: %m\n");
return (EIO);
}
runqueues();
}
if (retry == RETRIES && !*eventp) {
cmn_err(CE_WARN, "\tnfs_dump: server not responding");
return (EIO);
}
return (0);
}
static int
nd_auth_marshall(XDR *xdrp)
{
int credsize;
int32_t *ptr;
int hostnamelen;
hostnamelen = (int)strlen(utsname.nodename);
credsize = 4 + 4 + roundup(hostnamelen, 4) + 4 + 4 + 4;
ptr = XDR_INLINE(xdrp, 4 + 4 + credsize + 4 + 4);
if (!ptr) {
cmn_err(CE_WARN, "\tnfs_dump: auth_marshall failed");
return (0);
}
/*
* We can do the fast path.
*/
IXDR_PUT_INT32(ptr, AUTH_UNIX); /* cred flavor */
IXDR_PUT_INT32(ptr, credsize); /* cred len */
IXDR_PUT_INT32(ptr, gethrestime_sec());
IXDR_PUT_INT32(ptr, hostnamelen);
bcopy(utsname.nodename, ptr, hostnamelen);
ptr += roundup(hostnamelen, 4) / 4;
IXDR_PUT_INT32(ptr, 0); /* uid */
IXDR_PUT_INT32(ptr, 0); /* gid */
IXDR_PUT_INT32(ptr, 0); /* gid list length (empty) */
IXDR_PUT_INT32(ptr, AUTH_NULL); /* verf flavor */
IXDR_PUT_INT32(ptr, 0); /* verf len */
return (1);
}