/*
* 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
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Simple nfs V4 ops
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <rpc/types.h>
#include <rpc/auth.h>
#include <sys/t_lock.h>
#include "clnt.h"
#include <sys/fcntl.h>
#include <sys/vfs.h>
#include <errno.h>
#include <sys/promif.h>
#include <rpc/xdr.h>
#include "nfs_inet.h"
#include <sys/stat.h>
#include <sys/bootvfs.h>
#include <sys/bootdebug.h>
#include <sys/salib.h>
#include <sys/sacache.h>
#include <rpc/rpc.h>
#include "brpc.h"
#include <rpcsvc/nfs4_prot.h>
#define dprintf if (boothowto & RB_DEBUG) printf
static struct timeval zero_timeout = {0, 0}; /* default */
/*
* NFS Version 4 specific functions
*/
ssize_t
nfs4read(struct nfs_file *filep, char *buf, size_t size)
{
enum clnt_stat status;
read4arg_t readargs;
read4res_t readres;
char *buf_offset;
uint_t count = 0;
uint_t readcnt = 0;
bool_t done = FALSE;
struct timeval timeout;
int framing_errs = 0;
static uint_t pos;
static char ind[] = "|/-\\";
static int blks_read;
utf8string str;
char tagname[] = "inetboot read";
bzero(&readres, sizeof (readres));
str.utf8string_len = sizeof (tagname) - 1;
str.utf8string_val = tagname;
/*
* read
*/
buf_offset = buf;
if (nfs_readsize == 0)
nfs_readsize = READ4_SIZE;
if (size < nfs_readsize)
readargs.r_count = size;
else
readargs.r_count = nfs_readsize;
if (filep->fh.fh4.len > 0)
compound_init(&readargs.r_arg, &str, 0, 2, &filep->fh.fh4);
else
compound_init(&readargs.r_arg, &str, 0, 2, NULL);
readargs.r_opread = OP_READ;
/*
* zero out the stateid field
*/
bzero(&readargs.r_stateid, sizeof (readargs.r_stateid));
readargs.r_offset = filep->offset;
do {
readres.r_data_val = buf_offset;
if ((count + readargs.r_count) > size)
readargs.r_count = size - count;
timeout.tv_sec = NFS_REXMIT_MIN;
timeout.tv_usec = 0;
do {
status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND,
xdr_read4_args, (caddr_t)&readargs,
xdr_read4_res, (caddr_t)&readres,
timeout);
if (status == RPC_TIMEDOUT) {
dprintf("NFS read(%d) timed out. Retrying...\n", readargs.r_count);
if (errno == ETIMEDOUT)
framing_errs++;
if (framing_errs > NFS_MAX_FERRS &&
readargs.r_count > NFS_READ_DECR) {
readargs.r_count /= 2;
nfs_readsize /= 2;
dprintf("NFS read size now %d.\n",
nfs_readsize);
timeout.tv_sec = NFS_REXMIT_MIN;
framing_errs = 0;
} else {
if (timeout.tv_sec < NFS_REXMIT_MAX)
timeout.tv_sec++;
else
timeout.tv_sec = 0;
}
}
} while (status == RPC_TIMEDOUT);
if (status != RPC_SUCCESS)
return (-1);
if (readres.r_status != NFS4_OK) {
nfs4_error(readres.r_status);
return (-1);
}
readcnt = readres.r_data_len;
if (readres.r_eof == TRUE)
done = TRUE;
if (readcnt < readargs.r_count) {
#ifdef NFS_OPS_DEBUG
if ((boothowto & DBFLAGS) == DBFLAGS)
printf("nfs4read: partial read %d instead "
"of %d\n", readcnt, readargs.count);
#endif
done = TRUE;
}
count += readcnt;
filep->offset += readcnt;
buf_offset += readcnt;
readargs.r_offset += readcnt;
if ((blks_read++ & 0x3) == 0)
printf("%c\b", ind[pos++ & 3]);
} while (count < size && !done);
return (count);
}
static vtype_t nf4_to_vt[] = {
VBAD, VREG, VDIR, VBLK, VCHR, VLNK, VSOCK, VFIFO
};
int
nfs4getattr(struct nfs_file *nfp, struct vattr *vap)
{
enum clnt_stat status;
attr4_bitmap1_t bitmap1;
attr4_bitmap2_t bitmap2;
getattr4arg_t getattrargs;
getattr4res_t getattrres;
b_fattr4_t *bfattr4;
utf8string str;
char tagname[] = "inetboot getattr";
bzero(&getattrres, sizeof (getattrres));
/*
* Putfh
*/
str.utf8string_len = sizeof (tagname) - 1;
str.utf8string_val = tagname;
if (nfp->fh.fh4.len > 0)
compound_init(&getattrargs.ga_arg, &str, 0, 2, &nfp->fh.fh4);
else
compound_init(&getattrargs.ga_arg, &str, 0, 2, NULL);
/*
* getattr
*/
getattrargs.ga_opgetattr = OP_GETATTR;
/*
* Set up the attribute bitmap. We pretty much need everything
* except for the filehandle and supported attrs.
*/
bitmap1.word = 0;
bitmap1.bm_fattr4_type = 1;
bitmap1.bm_fattr4_size = 1;
bitmap1.bm_fattr4_fileid = 1;
bitmap2.word = 0;
bitmap2.bm_fattr4_mode = 1;
bitmap2.bm_fattr4_time_access = 1;
bitmap2.bm_fattr4_time_metadata = 1;
bitmap2.bm_fattr4_time_modify = 1;
getattrargs.ga_attr_req.b_bitmap_len = NFS4_MAX_BITWORDS;
getattrargs.ga_attr_req.b_bitmap_val[0] = bitmap1.word;
getattrargs.ga_attr_req.b_bitmap_val[1] = bitmap2.word;
status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_getattr4_args,
(caddr_t)&getattrargs, xdr_getattr4_res,
(caddr_t)&getattrres, zero_timeout);
if (status != RPC_SUCCESS) {
dprintf("nfs4getattr: RPC error %d\n", status);
return (-1);
}
if (getattrres.gr_attr_status != NFS4_OK) {
nfs4_error(getattrres.gr_attr_status);
return (getattrres.gr_attr_status);
}
bfattr4 = &getattrres.gr_attrs;
if (vap->va_mask & AT_TYPE) {
if (bfattr4->b_fattr4_type < NF4REG ||
bfattr4->b_fattr4_type > NF4FIFO)
vap->va_type = VBAD;
else
vap->va_type = nf4_to_vt[bfattr4->b_fattr4_type];
}
if (vap->va_mask & AT_MODE)
vap->va_mode = (mode_t)bfattr4->b_fattr4_mode;
if (vap->va_mask & AT_SIZE)
vap->va_size = (u_offset_t)bfattr4->b_fattr4_size;
if (vap->va_mask & AT_NODEID)
vap->va_nodeid = (uint64_t)bfattr4->b_fattr4_fileid;
/*
* XXX - may need to do something more here.
*/
if (vap->va_mask & AT_ATIME) {
vap->va_atime.tv_sec = bfattr4->b_fattr4_time_access.seconds;
vap->va_atime.tv_nsec = bfattr4->b_fattr4_time_access.nseconds;
}
if (vap->va_mask & AT_CTIME) {
vap->va_ctime.tv_sec = bfattr4->b_fattr4_time_metadata.seconds;
vap->va_ctime.tv_nsec =
bfattr4->b_fattr4_time_metadata.nseconds;
}
if (vap->va_mask & AT_MTIME) {
vap->va_mtime.tv_sec = bfattr4->b_fattr4_time_modify.seconds;
vap->va_mtime.tv_nsec = bfattr4->b_fattr4_time_modify.nseconds;
}
return (NFS4_OK);
}
/*
* Display nfs error messages.
*/
/*ARGSUSED*/
void
nfs4_error(enum nfsstat4 status)
{
if (!(boothowto & RB_DEBUG))
return;
switch (status) {
case NFS4_OK:
printf("NFS: No error.\n");
break;
case NFS4ERR_PERM:
printf("NFS: Not owner.\n");
break;
case NFS4ERR_NOENT:
#ifdef NFS_OPS_DEBUG
printf("NFS: No such file or directory.\n");
#endif /* NFS_OPS_DEBUG */
break;
case NFS4ERR_IO:
printf("NFS: IO ERROR occurred on NFS server.\n");
break;
case NFS4ERR_NXIO:
printf("NFS: No such device or address.\n");
break;
case NFS4ERR_ACCESS:
printf("NFS: Permission denied.\n");
break;
case NFS4ERR_EXIST:
printf("NFS: File exists.\n");
break;
case NFS4ERR_XDEV:
printf("NFS: Cross device hard link.\n");
break;
case NFS4ERR_NOTDIR:
printf("NFS: Not a directory.\n");
break;
case NFS4ERR_ISDIR:
printf("NFS: Is a directory.\n");
break;
case NFS4ERR_INVAL:
printf("NFS: Invalid argument.\n");
break;
case NFS4ERR_FBIG:
printf("NFS: File too large.\n");
break;
case NFS4ERR_NOSPC:
printf("NFS: No space left on device.\n");
break;
case NFS4ERR_ROFS:
printf("NFS: Read-only filesystem.\n");
break;
case NFS4ERR_MLINK:
printf("NFS: Too many hard links.\n");
break;
case NFS4ERR_NAMETOOLONG:
printf("NFS: File name too long.\n");
break;
case NFS4ERR_NOTEMPTY:
printf("NFS: Directory not empty.\n");
break;
case NFS4ERR_DQUOT:
printf("NFS: Disk quota exceeded.\n");
break;
case NFS4ERR_STALE:
printf("NFS: Stale file handle.\n");
break;
case NFS4ERR_BADHANDLE:
printf("NFS: Illegal NFS file handle.\n");
break;
case NFS4ERR_BAD_COOKIE:
printf("NFS: Stale Cookie.\n");
break;
case NFS4ERR_NOTSUPP:
printf("NFS: Operation is not supported.\n");
break;
case NFS4ERR_TOOSMALL:
printf("NFS: Buffer too small.\n");
break;
case NFS4ERR_SERVERFAULT:
printf("NFS: Server fault.\n");
break;
case NFS4ERR_BADTYPE:
printf("NFS: Unsupported object type.\n");
break;
case NFS4ERR_BAD_STATEID:
printf("NFS: Bad stateid\n");
break;
case NFS4ERR_BAD_SEQID:
printf("NFS: Bad seqid\n");
break;
default:
printf("NFS: unknown error.\n");
break;
}
}
/*
* lookup one component. for multicomponent lookup use a driver like lookup().
*/
struct nfs_file *
nfs4lookup(struct nfs_file *dir, char *name, int *nstat)
{
static struct nfs_file cd;
attr4_bitmap1_t bitmap1;
lookup4arg_t lookupargs;
lookup4res_t lookupres;
enum clnt_stat status;
utf8string str;
char tagname[] = "inetboot lookup";
/*
* NFSv4 uses a special LOOKUPP op
* for looking up the parent directory.
*/
if (strcmp(name, "..") == 0)
return (nfs4lookupp(dir, nstat, NULL));
*nstat = (int)NFS4_OK;
bzero(&lookupres, sizeof (lookupres));
/*
* Check if we have a filehandle and initialize the compound
* with putfh or putrootfh appropriately.
*/
str.utf8string_len = sizeof (tagname) - 1;
str.utf8string_val = tagname;
if (dir->fh.fh4.len > 0)
compound_init(&lookupargs.la_arg, &str, 0, 3, &dir->fh.fh4);
else
compound_init(&lookupargs.la_arg, &str, 0, 3, NULL);
/*
* lookup
*/
lookupargs.la_oplookup = OP_LOOKUP;
/*
* convert the pathname from char * to utf8string
*/
lookupargs.la_pathname.utf8string_len = strlen(name);
lookupargs.la_pathname.utf8string_val =
bkmem_alloc(lookupargs.la_pathname.utf8string_len);
if (lookupargs.la_pathname.utf8string_val == NULL) {
dprintf("nfs4lookup: bkmem_alloc failed\n");
return (NULL);
}
bcopy(name, lookupargs.la_pathname.utf8string_val,
lookupargs.la_pathname.utf8string_len);
/*
* Setup the attr bitmap. All we need is the type and filehandle info
*/
lookupargs.la_opgetattr = OP_GETATTR;
bitmap1.word = 0;
bitmap1.bm_fattr4_type = 1;
bitmap1.bm_fattr4_filehandle = 1;
lookupargs.la_attr_req.b_bitmap_len = 1;
lookupargs.la_attr_req.b_bitmap_val[0] = bitmap1.word;
lookupargs.la_attr_req.b_bitmap_val[1] = 0;
status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_lookup4_args,
(caddr_t)&lookupargs, xdr_lookup4_res,
(caddr_t)&lookupres, zero_timeout);
if (status != RPC_SUCCESS) {
dprintf("nfs4lookup: RPC error. status %d\n", status);
return (NULL);
}
if (lookupres.lr_lookup_status != NFS4_OK) {
#ifdef DEBUG
dprintf("nfs4lookup: lookup status = %d\n",
lookupres.lr_lookup_status);
#endif
nfs4_error(lookupres.lr_lookup_status);
*nstat = (int)lookupres.lr_lookup_status;
if (lookupargs.la_pathname.utf8string_val != NULL)
bkmem_free(lookupargs.la_pathname.utf8string_val,
lookupargs.la_pathname.utf8string_len);
return (NULL);
}
if (lookupres.lr_attr_status != NFS4_OK) {
#ifdef DEBUG
dprintf("nfs4lookup: getattr status = %d\n",
lookupres.lr_attr_status);
#endif
nfs4_error(lookupres.lr_attr_status);
*nstat = (int)lookupres.lr_attr_status;
if (lookupargs.la_pathname.utf8string_val != NULL)
bkmem_free(lookupargs.la_pathname.utf8string_val,
lookupargs.la_pathname.utf8string_len);
return (NULL);
}
/*
* We have all the information we need to update the file pointer
*/
bzero((caddr_t)&cd, sizeof (struct nfs_file));
cd.version = NFS_V4;
cd.ftype.type4 = lookupres.lr_attrs.b_fattr4_type;
cd.fh.fh4.len = lookupres.lr_attrs.b_fattr4_filehandle.len;
bcopy(lookupres.lr_attrs.b_fattr4_filehandle.data, cd.fh.fh4.data,
cd.fh.fh4.len);
/*
* Free the arg string
*/
if (lookupargs.la_pathname.utf8string_val != NULL)
bkmem_free(lookupargs.la_pathname.utf8string_val,
lookupargs.la_pathname.utf8string_len);
return (&cd);
}
/*
* lookup parent directory.
*/
struct nfs_file *
nfs4lookupp(struct nfs_file *dir, int *nstat, uint64_t *fileid)
{
static struct nfs_file cd;
attr4_bitmap1_t bitmap1;
lookupp4arg_t lookuppargs;
lookup4res_t lookupres;
enum clnt_stat status;
utf8string str;
char tagname[] = "inetboot lookupp";
*nstat = (int)NFS4_OK;
bzero(&lookupres, sizeof (lookupres));
/*
* Check if we have a filehandle and initialize the compound
* with putfh or putrootfh appropriately.
*/
str.utf8string_len = sizeof (tagname) - 1;
str.utf8string_val = tagname;
if (dir->fh.fh4.len > 0)
compound_init(&lookuppargs.la_arg, &str, 0, 3, &dir->fh.fh4);
else
compound_init(&lookuppargs.la_arg, &str, 0, 3, NULL);
/*
* lookupp
*/
lookuppargs.la_oplookupp = OP_LOOKUPP;
/*
* Setup the attr bitmap. Normally, all we need is the type and
* filehandle info, but getdents might require the fileid of the
* parent.
*/
lookuppargs.la_opgetattr = OP_GETATTR;
bitmap1.word = 0;
bitmap1.bm_fattr4_type = 1;
bitmap1.bm_fattr4_filehandle = 1;
if (fileid != NULL)
bitmap1.bm_fattr4_fileid = 1;
lookuppargs.la_attr_req.b_bitmap_len = 1;
lookuppargs.la_attr_req.b_bitmap_val[0] = bitmap1.word;
lookuppargs.la_attr_req.b_bitmap_val[1] = 0;
status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_lookupp4_args,
(caddr_t)&lookuppargs, xdr_lookup4_res,
(caddr_t)&lookupres, zero_timeout);
if (status != RPC_SUCCESS) {
dprintf("nfs4lookupp: RPC error. status %d\n", status);
return (NULL);
}
if (lookupres.lr_lookup_status != NFS4_OK) {
#ifdef DEBUG
dprintf("nfs4lookupp: lookupp status = %d\n",
lookupres.lr_lookup_status);
#endif
nfs4_error(lookupres.lr_lookup_status);
*nstat = (int)lookupres.lr_lookup_status;
return (NULL);
}
if (lookupres.lr_attr_status != NFS4_OK) {
#ifdef DEBUG
dprintf("nfs4lookupp: getattr status = %d\n",
lookupres.lr_attr_status);
#endif
nfs4_error(lookupres.lr_attr_status);
*nstat = (int)lookupres.lr_attr_status;
return (NULL);
}
/*
* We have all the information we need to update the file pointer
*/
bzero((caddr_t)&cd, sizeof (struct nfs_file));
cd.version = NFS_V4;
cd.ftype.type4 = lookupres.lr_attrs.b_fattr4_type;
cd.fh.fh4.len = lookupres.lr_attrs.b_fattr4_filehandle.len;
bcopy(lookupres.lr_attrs.b_fattr4_filehandle.data, cd.fh.fh4.data,
cd.fh.fh4.len);
/*
* Fill in the fileid if the user passed in one
*/
if (fileid != NULL)
*fileid = lookupres.lr_attrs.b_fattr4_fileid;
return (&cd);
}
/*
* Gets symbolic link into pathname.
*/
int
nfs4getsymlink(struct nfs_file *cfile, char **path)
{
enum clnt_stat status;
readlink4arg_t readlinkargs;
readlink4res_t readlinkres;
static char symlink_path[NFS_MAXPATHLEN];
int spathlen;
utf8string str;
char tagname[] = "inetboot getsymlink";
int error = NFS4_OK;
bzero(&readlinkres, sizeof (readlinkres));
/*
* readlink
*/
str.utf8string_len = sizeof (tagname) - 1;
str.utf8string_val = tagname;
if (cfile->fh.fh4.len > 0)
compound_init(&readlinkargs.rl_arg, &str, 0, 2,
&cfile->fh.fh4);
else
compound_init(&readlinkargs.rl_arg, &str, 0, 2, NULL);
readlinkargs.rl_opreadlink = OP_READLINK;
status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_readlink4_args,
(caddr_t)&readlinkargs, xdr_readlink4_res,
(caddr_t)&readlinkres, zero_timeout);
if (status != RPC_SUCCESS) {
dprintf("nfs4getsymlink: RPC readlink error %d\n", status);
error = -1;
goto out;
}
if (readlinkres.rl_status != NFS4_OK) {
nfs4_error(readlinkres.rl_status);
error = readlinkres.rl_status;
goto out;
}
/*
* Convert the utf8string to a normal character string
*/
spathlen = readlinkres.rl_link.utf8string_len;
if (spathlen <= 0 || readlinkres.rl_link.utf8string_val == NULL) {
*path = NULL;
error = readlinkres.rl_status;
goto out;
}
bcopy(readlinkres.rl_link.utf8string_val, symlink_path, spathlen);
symlink_path[spathlen] = '\0';
*path = symlink_path;
out:
/*
* Free the results
*/
if (readlinkres.rl_link.utf8string_val != NULL)
bkmem_free(readlinkres.rl_link.utf8string_val, spathlen);
return (error);
}
/*
* Should just forget about the tag, but will leave in support for the time
* being.
*/
void
compound_init(b_compound_t *cp, utf8string *str, uint_t mvers, uint_t arglen,
struct nfs_bfh4 *pfh)
{
if (str == NULL || str->utf8string_len == 0) {
cp->ca_tag.utf8string_len = 0;
cp->ca_tag.utf8string_val = NULL;
} else {
cp->ca_tag.utf8string_len = str->utf8string_len;
cp->ca_tag.utf8string_val = str->utf8string_val;
}
cp->ca_minorversion = mvers;
cp->ca_argarray_len = arglen;
if (pfh == NULL) {
cp->ca_isputrootfh = TRUE;
cp->ca_opputfh.pf_opnum = OP_PUTROOTFH;
} else {
cp->ca_isputrootfh = FALSE;
cp->ca_opputfh.pf_opnum = OP_PUTFH;
cp->ca_opputfh.pf_filehandle.len = pfh->len;
bcopy(pfh->data, cp->ca_opputfh.pf_filehandle.data, pfh->len);
}
}