root/usr/src/stand/lib/fs/nfs/nfs3ops.c
/*
 * 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 V3 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/nfs_prot.h>

#define dprintf if (boothowto & RB_DEBUG) printf

/*
 * NFS Version 3 specific functions
 */

ssize_t
nfs3read(struct nfs_file *filep, char *buf, size_t size)
{
        READ3args               read_args;
        READ3res                read_res;
        enum clnt_stat          read_stat;
        uint_t                  readcnt = 0;    /* # bytes read by nfs */
        uint_t                  count = 0;      /* # bytes transferred to buf */
        int                     done = FALSE;   /* last block has come in */
        int                     framing_errs = 0;       /* stack errors */
        char                    *buf_offset;    /* current buffer offset */
        struct timeval          timeout;
        static uint_t           pos;            /* progress indicator counter */
        static char             ind[] = "|/-\\";        /* progress indicator */
        static int              blks_read;

        read_args.file.data.data_len = filep->fh.fh3.len;
        read_args.file.data.data_val = filep->fh.fh3.data;
        read_args.offset = filep->offset;

        bzero(&read_res, sizeof (read_res));

        buf_offset = buf;

        /* Optimize for reads of less than one block size */

        if (nfs_readsize == 0)
                nfs_readsize = READ3_SIZE;

        if (size < nfs_readsize)
                read_args.count = size;
        else
                read_args.count = nfs_readsize;

        do {
                /* use the user's buffer to stuff the data into. */
                read_res.READ3res_u.resok.data.data_val = buf_offset;

                /*
                 * Handle the case where the file does not end
                 * on a block boundary.
                 */
                if ((count + read_args.count) > size)
                        read_args.count = size - count;

                timeout.tv_sec = NFS_REXMIT_MIN; /* Total wait for call */
                timeout.tv_usec = 0;
                do {
                        read_stat = CLNT_CALL(root_CLIENT, NFSPROC3_READ,
                            xdr_READ3args, (caddr_t)&read_args,
                            xdr_READ3res, (caddr_t)&read_res, timeout);

                        if (read_stat == RPC_TIMEDOUT) {
                                dprintf("NFS read(%d) timed out. Retrying...\n",
                                    read_args.count);
                                /*
                                 * If the remote is there and trying to respond,
                                 * but our stack is having trouble reassembling
                                 * the reply, reduce the read size in an
                                 * attempt to compensate. Reset the
                                 * transmission and reply wait timers.
                                 */
                                if (errno == ETIMEDOUT)
                                        framing_errs++;

                                if (framing_errs > NFS_MAX_FERRS &&
                                    read_args.count > NFS_READ_DECR) {
                                        read_args.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;
                                                        /* default RPC */
                                }
                        }
                } while (read_stat == RPC_TIMEDOUT);

                if (read_stat != RPC_SUCCESS)
                        return (-1);

                if (read_res.status != NFS3_OK)
                        return (-1);

                readcnt = read_res.READ3res_u.resok.data.data_len;
                /*
                 * If we are at EOF, update counts and exit
                 */
                if (read_res.READ3res_u.resok.eof == TRUE)
                        done = TRUE;

                /*
                 * Handle the case where the file is smaller than
                 * the size of the read request, thus the request
                 * couldn't be completely filled.
                 */
                if (readcnt < read_args.count) {
#ifdef NFS_OPS_DEBUG
                        if ((boothowto & DBFLAGS) == DBFLAGS)
                                printf("nfs3read(): partial read %d"
                                    " instead of %d\n",
                                    readcnt, read_args.count);
#endif
                        done = TRUE; /* update the counts and exit */
                }

                /* update various offsets */
                count += readcnt;
                filep->offset += readcnt;
                buf_offset += readcnt;
                read_args.offset += readcnt;
                /*
                 * round and round she goes (though not on every block..
                 * - OBP's take a fair bit of time to actually print stuff)
                 */
                if ((blks_read++ & 0x3) == 0)
                        printf("%c\b", ind[pos++ & 3]);
        } while (count < size && !done);

        return (count);
}

int
nfs3getattr(struct nfs_file *nfp, struct vattr *vap)
{
        enum clnt_stat getattr_stat;
        GETATTR3args getattr_args;
        GETATTR3res getattr_res;
        fattr3 *na;
        struct timeval timeout = {0, 0};        /* default */
        vtype_t nf3_to_vt[] =
                        { VBAD, VREG, VDIR, VBLK, VCHR, VLNK, VSOCK, VFIFO };


        bzero(&getattr_args, sizeof (getattr_args));
        getattr_args.object.data.data_len = nfp->fh.fh3.len;
        getattr_args.object.data.data_val = nfp->fh.fh3.data;

        bzero(&getattr_res, sizeof (getattr_res));

        getattr_stat = CLNT_CALL(root_CLIENT, NFSPROC3_GETATTR,
            xdr_GETATTR3args, (caddr_t)&getattr_args,
            xdr_GETATTR3res, (caddr_t)&getattr_res, timeout);

        if (getattr_stat != RPC_SUCCESS) {
                dprintf("nfs_getattr: RPC error %d\n", getattr_stat);
                return (-1);
        }
        if (getattr_res.status != NFS3_OK) {
                nfs3_error(getattr_res.status);
                return (getattr_res.status);
        }

        na = &getattr_res.GETATTR3res_u.resok.obj_attributes;
        if (vap->va_mask & AT_TYPE) {
                if (na->type < NF3REG || na->type > NF3FIFO)
                        vap->va_type = VBAD;
                else
                        vap->va_type = nf3_to_vt[na->type];
        }
        if (vap->va_mask & AT_MODE)
                vap->va_mode = (mode_t)na->mode;
        if (vap->va_mask & AT_SIZE)
                vap->va_size = (u_offset_t)na->size;
        if (vap->va_mask & AT_NODEID)
                vap->va_nodeid = (u_longlong_t)na->fileid;
        if (vap->va_mask & AT_ATIME) {
                vap->va_atime.tv_sec  = na->atime.seconds;
                vap->va_atime.tv_nsec = na->atime.nseconds;
        }
        if (vap->va_mask & AT_CTIME) {
                vap->va_ctime.tv_sec  = na->ctime.seconds;
                vap->va_ctime.tv_nsec = na->ctime.nseconds;
        }
        if (vap->va_mask & AT_MTIME) {
                vap->va_mtime.tv_sec  = na->mtime.seconds;
                vap->va_mtime.tv_nsec = na->mtime.nseconds;
        }

        return (NFS3_OK);
}

/*
 * Display nfs error messages.
 */
/*ARGSUSED*/
void
nfs3_error(enum nfsstat3 status)
{
        if (!(boothowto & RB_DEBUG))
                return;

        switch (status) {
        case NFS3_OK:
                printf("NFS: No error.\n");
                break;
        case NFS3ERR_PERM:
                printf("NFS: Not owner.\n");
                break;
        case NFS3ERR_NOENT:
#ifdef  NFS_OPS_DEBUG
                printf("NFS: No such file or directory.\n");
#endif  /* NFS_OPS_DEBUG */
                break;
        case NFS3ERR_IO:
                printf("NFS: IO ERROR occurred on NFS server.\n");
                break;
        case NFS3ERR_NXIO:
                printf("NFS: No such device or address.\n");
                break;
        case NFS3ERR_ACCES:
                printf("NFS: Permission denied.\n");
                break;
        case NFS3ERR_EXIST:
                printf("NFS: File exists.\n");
                break;
        case NFS3ERR_XDEV:
                printf("NFS: Cross device hard link.\n");
                break;
        case NFS3ERR_NODEV:
                printf("NFS: No such device.\n");
                break;
        case NFS3ERR_NOTDIR:
                printf("NFS: Not a directory.\n");
                break;
        case NFS3ERR_ISDIR:
                printf("NFS: Is a directory.\n");
                break;
        case NFS3ERR_INVAL:
                printf("NFS: Invalid argument.\n");
                break;
        case NFS3ERR_FBIG:
                printf("NFS: File too large.\n");
                break;
        case NFS3ERR_NOSPC:
                printf("NFS: No space left on device.\n");
                break;
        case NFS3ERR_ROFS:
                printf("NFS: Read-only filesystem.\n");
                break;
        case NFS3ERR_MLINK:
                printf("NFS: Too many hard links.\n");
                break;
        case NFS3ERR_NAMETOOLONG:
                printf("NFS: File name too long.\n");
                break;
        case NFS3ERR_NOTEMPTY:
                printf("NFS: Directory not empty.\n");
                break;
        case NFS3ERR_DQUOT:
                printf("NFS: Disk quota exceeded.\n");
                break;
        case NFS3ERR_STALE:
                printf("NFS: Stale file handle.\n");
                break;
        case NFS3ERR_REMOTE:
                printf("NFS: Remote file in path.\n");
                break;
        case NFS3ERR_BADHANDLE:
                printf("NFS: Illegal NFS file handle.\n");
                break;
        case NFS3ERR_NOT_SYNC:
                printf("NFS: Synchronization mismatch.\n");
                break;
        case NFS3ERR_BAD_COOKIE:
                printf("NFS: Stale Cookie.\n");
                break;
        case NFS3ERR_NOTSUPP:
                printf("NFS: Operation is not supported.\n");
                break;
        case NFS3ERR_TOOSMALL:
                printf("NFS: Buffer too small.\n");
                break;
        case NFS3ERR_SERVERFAULT:
                printf("NFS: Server fault.\n");
                break;
        case NFS3ERR_BADTYPE:
                printf("NFS: Unsupported object type.\n");
                break;
        case NFS3ERR_JUKEBOX:
                printf("NFS: Resource temporarily unavailable.\n");
                break;
        default:
                printf("NFS: unknown error.\n");
                break;
        }
}

struct nfs_file *
nfs3lookup(struct nfs_file *dir, char *name, int *nstat)
{
        struct timeval zero_timeout = {0, 0};   /* default */
        static struct nfs_file cd;
        LOOKUP3args dirop;
        LOOKUP3res res_lookup;
        enum clnt_stat status;

        *nstat = (int)NFS3_OK;

        bzero((caddr_t)&dirop, sizeof (LOOKUP3args));
        bzero((caddr_t)&res_lookup, sizeof (LOOKUP3res));

        dirop.what.dir.data.data_len = dir->fh.fh3.len;
        dirop.what.dir.data.data_val = dir->fh.fh3.data;
        dirop.what.name = name;

        status = CLNT_CALL(root_CLIENT, NFSPROC3_LOOKUP, xdr_LOOKUP3args,
            (caddr_t)&dirop, xdr_LOOKUP3res, (caddr_t)&res_lookup,
            zero_timeout);
        if (status != RPC_SUCCESS) {
                dprintf("lookup: RPC error.\n");
                return (NULL);
        }
        if (res_lookup.status != NFS3_OK) {
                nfs3_error(res_lookup.status);
                *nstat = (int)res_lookup.status;
                (void) CLNT_FREERES(root_CLIENT,
                    xdr_LOOKUP3res, (caddr_t)&res_lookup);
                return (NULL);
        }

        bzero((caddr_t)&cd, sizeof (struct nfs_file));
        cd.version = NFS_V3;
        /*
         * Server must supply post_op_attr's
         */
        if (res_lookup.LOOKUP3res_u.resok.obj_attributes.attributes_follow ==
            FALSE) {
                printf("nfs3lookup: server fails to return post_op_attr\n");
                (void) CLNT_FREERES(root_CLIENT,
                    xdr_LOOKUP3res, (caddr_t)&res_lookup);
                return (NULL);
        }

        cd.ftype.type3 = res_lookup.LOOKUP3res_u.resok.obj_attributes
            .post_op_attr_u.attributes.type;
        cd.fh.fh3.len = res_lookup.LOOKUP3res_u.resok.object.data.data_len;
        bcopy(res_lookup.LOOKUP3res_u.resok.object.data.data_val,
            cd.fh.fh3.data, cd.fh.fh3.len);
        (void) CLNT_FREERES(root_CLIENT, xdr_LOOKUP3res, (caddr_t)&res_lookup);
        return (&cd);
}

/*
 * Gets symbolic link into pathname.
 */
int
nfs3getsymlink(struct nfs_file *cfile, char **path)
{
        struct timeval zero_timeout = {0, 0};   /* default */
        enum clnt_stat status;
        struct READLINK3res linkres;
        struct READLINK3args linkargs;
        static char symlink_path[NFS_MAXPATHLEN];

        bzero(&linkargs, sizeof (linkargs));
        linkargs.symlink.data.data_len = cfile->fh.fh3.len;
        linkargs.symlink.data.data_val = cfile->fh.fh3.data;

        /*
         * linkres needs a zeroed buffer to place path data into:
         */
        bzero(&linkres, sizeof (linkres));
        bzero(symlink_path, NFS_MAXPATHLEN);
        linkres.READLINK3res_u.resok.data = symlink_path;

        status = CLNT_CALL(root_CLIENT, NFSPROC3_READLINK,
            xdr_READLINK3args, (caddr_t)&linkargs,
            xdr_READLINK3res, (caddr_t)&linkres, zero_timeout);
        if (status != RPC_SUCCESS) {
                dprintf("nfs3getsymlink: RPC call failed.\n");
                return (-1);
        }
        if (linkres.status != NFS3_OK) {
                nfs3_error(linkres.status);
                return (linkres.status);
        }

        *path = symlink_path;

        return (NFS3_OK);
}