/* @(#)rfs_misc.c 1.1 94/10/31 SMI */ /* * Miscellaneous routines needed by RFS */ #include <sys/varargs.h> #include <sys/param.h> #include <sys/types.h> #include <sys/user.h> #include <sys/uio.h> #include <sys/stream.h> #include <sys/proc.h> #include <sys/vnode.h> #include <sys/vfs.h> #include <sys/file.h> #include <sys/mount.h> #include <sys/pathname.h> #include <sys/debug.h> #include <sys/dirent.h> #include <sys/uio.h> #include <sys/kmem_alloc.h> #include <rfs/rfs_misc.h> #include <rfs/nserve.h> #include <rfs/cirmgr.h> #include <rfs/rdebug.h> #include <rfs/sema.h> #include <rfs/message.h> #include <rfs/comm.h> #include <rfs/rfs_node.h> #include <rfs/rfs_mnt.h> #include <rfs/idtab.h> #include <rfs/rfs_serve.h> #include <rfs/adv.h> #include <rfs/recover.h> extern struct vnodeops rfs_vnodeops; /* * Client-side routines */ /* * rfsnode lookup stuff. * These routines maintain a table of rfsnodes hashed by rd and * vfsp so that the rfsnode can be found if it already exists. * NOTE: RTABLESIZE must be a power of 2 for rftablehash to work! */ #define RFTABLESIZE 16 #define rftablehash(vfp, rdind) ((((u_int) vfp >> 8) + rdind) & (RFTABLESIZE-1)) static struct rfsnode *rftable[RFTABLESIZE]; char rfs_cache_flag; /* * Put a rfsnode in the table */ rf_save(rfp, vfsp) struct rfsnode *rfp; struct vfs *vfsp; { register int hashind = rftablehash(vfsp, rfp->rfs_sdp->sd_sindex); rfp->rfs_next = rftable[hashind]; rftable[hashind] = rfp; } /* * Remove a rfsnode from the table */ rf_unsave(rfp, vfsp) struct rfsnode *rfp; struct vfs *vfsp; { struct rfsnode *rt; struct rfsnode *rtprev = NULL; register int hashind = rftablehash(vfsp, rfp->rfs_sdp->sd_sindex); rt = rftable[hashind]; while (rt != NULL) { if (rt == rfp) { if (rtprev == NULL) { rftable[hashind] = rt->rfs_next; } else { rtprev->rfs_next = rt->rfs_next; } return; } rtprev = rt; rt = rt->rfs_next; } } /* * Lookup a rfsnode by vfs pointer and remote receive descriptor index. * If there is a node in the cache which also has a matching name * return it. Otherwise return one without a matching name. The node is * returned in *rfpp. The value returned by the routine is either * NO_MATCH, NAME_MATCH (for complete match) or OTHER_MATCH (for vfsp and * rdind match but different name). */ #define NO_MATCH 0 #define NAME_MATCH 1 #define OTHER_MATCH 2 int rf_find(vfsp, rdind, name, rfpp) struct vfs *vfsp; index_t rdind; char *name; struct rfsnode **rfpp; { register struct rfsnode *rt; *rfpp = (struct rfsnode *) NULL; rt = rftable[rftablehash(vfsp, rdind)]; while (rt != NULL) { if (rdind == rt->rfs_sdp->sd_sindex && vfsp == rfstov(rt)->v_vfsp) { *rfpp = rt; if (!strcmp(rt->rfs_name, name)) { rfstov(rt)->v_count++; return (NAME_MATCH); } } rt = rt->rfs_next; } return (*rfpp ? OTHER_MATCH : NO_MATCH); } /* * make an rfsnode corresponding to the vfs, parent send descriptor, * file component name, send descriptor and associated info for file. * 1) Look first for a node with matching remote reference in cache * 2) If found and name matches as well return the node. * 3) Otherwise, create a new node. * 4) If found matching node with different name in the cache, * copy state into new node. * 5) Cache the new node. */ int get_rfsnode(vfsp, psdp, name, sdp, nip, vpp) struct vfs *vfsp; sndd_t psdp; char *name; sndd_t sdp; struct nodeinfo *nip; struct vnode **vpp; { struct rfsnode *crfp, *rfp; register struct vnode *vp, *cvp; int nodesize; int match; struct rfs_stat rsb; int error = 0; extern char *strcpy(); DUPRINT5(DB_RFSNODE, "get_rfsnode: vfsp %x, psdp %x, name %s, sdp %x\n", vfsp, psdp, name, sdp); ASSERT(name && psdp && sdp && nip); rfs_cache_lock(); match = rf_find(vfsp, sdp->sd_sindex, name, &crfp); /* Found cached node with matching name: return it */ if (match == NAME_MATCH) { *vpp = rfstov(crfp); crfp->rfs_sdp->sd_stat &= ~SDCACHE_MASK; crfp->rfs_sdp->sd_stat |= sdp->sd_stat & SDCACHE_MASK; crfp->rfs_sdp->sd_fhandle = sdp->sd_fhandle; if (error = del_sndd(sdp)) rfstov(crfp)->v_count--; goto out; } /* Otherwise: make new node */ nodesize = sizeof (*rfp) + (strlen(name) + 1); rfp = (struct rfsnode *)new_kmem_alloc((u_int)nodesize, KMEM_SLEEP); bzero((caddr_t)rfp, (u_int) nodesize); rfp->rfs_alloc = nodesize; rfp->rfs_psdp = psdp; rfp->rfs_psdp->sd_refcnt++; /* New vnode refers to psd */ rfp->rfs_sdp = sdp; bcopy((caddr_t)nip, (caddr_t)&rfp->rfs_ninfo, (u_int) sizeof (struct nodeinfo)); (void) strcpy(rfp->rfs_name, name); vp = rfstov(rfp); vp->v_count = 1; vp->v_op = &rfs_vnodeops; vp->v_data = (caddr_t)rfp; vp->v_vfsp = vfsp; vp->v_type = RFSTOVT(nip->rni_ftype); vp->v_flag |= VNOMAP; /* Hack, for now, to let RFS run 413 files */ /* * Cache has matching node with other name: copy state from that node * Later (?) just hook up to shared state pointer */ if (match == OTHER_MATCH) { cvp = rfstov(crfp); ASSERT(vp->v_type == cvp->v_type); vp->v_flag = cvp->v_flag; /* * XXX Copying the following 3 fields doesn't really * make them work, but what the hell */ vp->v_shlockc = cvp->v_shlockc; vp->v_exlockc = cvp->v_exlockc; vp->v_vfsmountedhere = cvp->v_vfsmountedhere; vp->v_stream = cvp->v_stream; vp->v_rdev = cvp->v_rdev; /* Newly referenced device file: do stat to get device # for vnode */ } else if (vp->v_type==VBLK || vp->v_type==VCHR) { if (error = du_fstat(sdp, u.u_cred, &rsb)) { (void) del_sndd(rfp->rfs_psdp); (void) del_sndd(rfp->rfs_sdp); kmem_free((caddr_t)rfp, (u_int) rfp->rfs_alloc); goto out; } vp->v_rdev = rsb.st_rdev; } rf_save(rfp, vfsp); /* Cache the new node */ ((struct rfsmnt *)(vfsp->vfs_data))->rm_refcnt++; *vpp = rfstov(rfp); out: DUPRINT5(DB_RFSNODE, "get_rfsnode: error %d, match %d, vp %x, name %s\n", error, match, *vpp, error ? "" : vtorfs(*vpp)->rfs_name); rfs_cache_unlock(); if (error) *vpp = (struct vnode *) NULL; return (error); } /* Transform an RFS node from one without a remote reference into one with * a remote reference. This is essentially a no-op since we are * assuming a true lookup op which always returns a remote reference * to the file. Thus you don't need the remote reference you acquire * later as a side effect of some RFS system calls like DUOPEN. * Thus, what this routine does is to simply sanity check the new * remote reference ("gift") against the one already in the vnode to * make sure they really refer to the same remote file. It then throws * away the new reference. */ /*ARGSUSED*/ int transform_rfsnode(vpp, gift, nip) struct vnode **vpp; sndd_t gift; struct nodeinfo *nip; { register sndd_t sdp = vtorfs(*vpp)->rfs_sdp; int error = 0; if (!sdp || sdp->sd_sindex != gift->sd_sindex) ASSERT(sdp && (sdp->sd_sindex == gift->sd_sindex)); /* Transfer new info returned to old remote reference */ sdp->sd_fhandle = gift->sd_fhandle; error = del_sndd(gift); /* XXX what if it fails with error? */ return (error); } /* * Save and restore a uio structure -- so system calls involving data * movement can be retried from the start if they fail in the middle due * to server overload. */ uio_save(uiop, suiop, siov) register struct uio *uiop, *suiop; register struct iovec siov[]; { bcopy((caddr_t) uiop, (caddr_t) suiop, sizeof (struct uio)); bcopy((caddr_t) uiop->uio_iov, (caddr_t) siov, (u_int) uiop->uio_iovcnt * sizeof (struct iovec)); } uio_restore(uiop, suiop, siov) register struct uio *uiop, *suiop; register struct iovec siov[]; { bcopy((caddr_t) suiop, (caddr_t) uiop, sizeof (struct uio)); bcopy((caddr_t) suiop->uio_iov, (caddr_t) siov, (u_int) suiop->uio_iovcnt * sizeof (struct iovec)); } /* rfs_cache_check(vp, sd, vcode) */ /* Check and invalidate cache. If mandatory lock set or not caching * invalidate and turn cache off. * Else if vcode > vp->rfs_node->rfs_vcode invalidate cache, but don't * turn cache off. */ /* * Server side routines. */ /* * Server-side version of the lookupname code. This is essentially identical * to the routines in vfs_lookup.c. The big difference is that if you hit * a ".." you can end up backing out of the server RFS filesystem and you * then have to return the remaining pathname to the client to continue the * evaluation. */ /* * lookup the user file name, * Handle allocation and freeing of pathname buffer, return error. * On return, fnamep points to the remaining chunk of unevaluated pathname. */ rs_lookupname(fnamep, seg, followlink, sp, dirvpp, compvpp) char *fnamep; /* user pathname */ int seg; /* addr space that name is in */ enum symfollow followlink; /* follow sym links */ struct serv_proc *sp; /* server proc doing lookup */ struct vnode **dirvpp; /* ret for ptr to parent dir vnode */ struct vnode **compvpp; /* ret for ptr to component vnode */ { struct pathname lookpn; register int error; register char *cp; error = pn_get(fnamep, seg, &lookpn); if (error) return (error); error = rs_lookuppn(&lookpn, followlink, sp, dirvpp, compvpp); cp = fnamep; while (lookpn.pn_pathlen--) *cp++ = *lookpn.pn_path++; *cp = '\0'; pn_free(&lookpn); return (error); } /* * Starting at current directory, translate pathname pnp to end or until * you cross back out of the server mount point by virtue of "..". If * the latter happens, return the remaining pathname and an error of EDOTDOT. * Otherwise, leave pathname of final component in pnp, return the vnode * for the final component in *compvpp, and return the vnode * for the parent of the final component in dirvpp. * * This is the central routine in pathname translation and handles * multiple components in pathnames, separating them at /'s. It also * implements mounted file systems and processes symbolic links. */ rs_lookuppn(pnp, followlink, sp, dirvpp, compvpp) register struct pathname *pnp; /* pathaname to lookup */ enum symfollow followlink; /* (don't) follow sym links */ struct serv_proc *sp; /* server proc doing lookup */ struct vnode **dirvpp; /* ptr for parent vnode */ struct vnode **compvpp; /* ptr for entry vnode */ { register struct vnode *vp; /* current directory vp */ register struct vnode *cvp; /* current component vp */ struct vnode *tvp; /* non-reg temp ptr */ register struct vfs *vfsp; /* ptr to vfs for mount indir */ char component[MAXNAMLEN+1]; /* buffer for component */ register int error; register int nlink; extern struct vfsops rfs_vfsops; int lookup_flags; lookup_flags = dirvpp ? LOOKUP_DIR : 0; nlink = 0; cvp = (struct vnode *)0; /* * start at current directory. */ vp = u.u_cdir; VN_HOLD(vp); begin: /* * Each time we begin a new name interpretation (e.g. * when first called and after each symbolic link is * substituted), we allow the search to start at the * root directory if the name starts with a '/', otherwise * continuing from the current directory. */ component[0] = 0; if (pn_peekchar(pnp) == '/') { VN_RELE(vp); pn_skipslash(pnp); if (u.u_rdir) vp = u.u_rdir; else vp = rootdir; VN_HOLD(vp); } next: /* * Make sure we have a directory. */ if (vp->v_type != VDIR) { error = ENOTDIR; goto bad; } /* * Process the next component of the pathname. */ error = pn_stripcomponent(pnp, component); if (error) goto bad; /* * Check for degenerate name (e.g. / or "") * which is a way of talking about a directory, * e.g. "/." or ".". */ if (component[0] == 0) { /* * If the caller was interested in the parent then * return an error since we don't have the real parent */ if (dirvpp != (struct vnode **)0) { VN_RELE(vp); return (EEXIST); } (void) pn_set(pnp, "."); if (compvpp != (struct vnode **)0) { *compvpp = vp; } else { VN_RELE(vp); } return (0); } /* * Handle "..": three special cases. * 1. If at chroot() directory then ignore it so can't get out. * 2. If this is the root of an RFS resource the client has mounted, * then return to client to evaluate rest of pathname. * 3. If this vnode is the root of a mounted * file system, then replace it with the * vnode which was mounted on so we take the * .. in the other file system. */ if (strcmp(component, "..") == 0) { checkforroot: if (vp == u.u_rdir) { cvp = vp; VN_HOLD(cvp); goto skip; } if (VN_CMP(vp, srmount[sp->s_mntindx].sr_rootvnode)) { struct pathname dotdotpath; pn_alloc(&dotdotpath); (void) pn_set(&dotdotpath, ".."); (void) pn_combine(pnp, &dotdotpath); /* ".." for client */ pn_free(&dotdotpath); error = EDOTDOT; goto bad; } if (vp->v_flag & VROOT) { cvp = vp; vp = vp->v_vfsp->vfs_vnodecovered; VN_HOLD(vp); VN_RELE(cvp); cvp = (struct vnode *)0; goto checkforroot; } } /* * Perform a lookup in the current directory. */ error = VOP_LOOKUP(vp, component, &tvp, u.u_cred, pnp, lookup_flags); cvp = tvp; if (error) { cvp = (struct vnode *)0; /* * On error, if more pathname or if caller was not interested * in the parent directory then hard error. */ if (pn_pathleft(pnp) || dirvpp == (struct vnode **)0) goto bad; (void) pn_set(pnp, component); *dirvpp = vp; if (compvpp != (struct vnode **)0) { *compvpp = (struct vnode *)0; } return (0); } /* * If we hit a symbolic link and FOLLOW_LINK is set then place the * contents of the link at the front of the remaining pathname. */ if (cvp->v_type == VLNK && ((followlink == FOLLOW_LINK))) { struct pathname linkpath; nlink++; if (nlink > 20) { error = ELOOP; goto bad; } error = getsymlink(cvp, &linkpath); if (error) goto bad; if (pn_pathleft(&linkpath) == 0) (void) pn_set(&linkpath, "."); error = pn_combine(pnp, &linkpath); /* linkpath before pn */ pn_free(&linkpath); if (error) goto bad; VN_RELE(cvp); cvp = (struct vnode *)0; goto begin; } /* * If this vnode is mounted on, then we * transparently indirect to the vnode which * is the root of the mounted file system. * Before we do this we must check that an unmount is not * in progress on this vnode. This maintains the fs status * quo while a possibly lengthy unmount is going on. * If the mounted filesystem is an RFS type, return EMULTIHOP, * since RFS does not allow crossing multiple machines. */ mloop: while (vfsp = cvp->v_vfsmountedhere) { if (vfsp->vfs_op == &rfs_vfsops) { error = EMULTIHOP; goto bad; } while (vfsp->vfs_flag & VFS_MLOCK) { vfsp->vfs_flag |= VFS_MWAIT; (void) sleep((caddr_t)vfsp, PVFS); goto mloop; } error = VFS_ROOT(cvp->v_vfsmountedhere, &tvp); if (error) goto bad; VN_RELE(cvp); cvp = tvp; } skip: /* * Skip to next component of the pathname. * If no more components, return last directory (if wanted) and * last component (if wanted). */ if (pn_pathleft(pnp) == 0) { (void) pn_set(pnp, component); if (dirvpp != (struct vnode **)0) { /* * check that we have the real parent and not * an alias of the last component */ if (vp == cvp) { VN_RELE(vp); VN_RELE(cvp); return (EINVAL); } *dirvpp = vp; } else { VN_RELE(vp); } if (compvpp != (struct vnode **)0) { *compvpp = cvp; } else { VN_RELE(cvp); } return (0); } /* * skip over slashes from end of last component */ pn_skipslash(pnp); /* * Searched through another level of directory: * release previous directory handle and save new (result * of lookup) as current directory. */ VN_RELE(vp); vp = cvp; cvp = (struct vnode *)0; goto next; bad: /* * Error. Release vnodes and return. */ if (cvp) VN_RELE(cvp); VN_RELE(vp); return (error); } /* * rfs server version of vn_open (see sys/vfs_vnode.c). Basically * identical, but uses rfs server lookupname and vn_create routines. If the * pathname given, pnamep, backs out of a server mount point then this routine * returns EDOTDOT with the unevaluated remainder of the pathname in * pnamep. */ int rs_vnopen(pnamep, seg, filemode, createmode, sp, vpp) char *pnamep; int seg; register int filemode; int createmode; struct serv_proc *sp; struct vnode **vpp; { struct vnode *vp; /* ptr to file vnode */ register int mode; register int error; mode = 0; if (filemode & FREAD) mode |= VREAD; if (filemode & (FWRITE | FTRUNC)) mode |= VWRITE; if (filemode & FCREAT) { struct vattr vattr; enum vcexcl excl; /* * Wish to create a file. */ vattr_null(&vattr); vattr.va_type = VREG; vattr.va_mode = createmode; if (filemode & FTRUNC) vattr.va_size = 0; if (filemode & FEXCL) excl = EXCL; else excl = NONEXCL; filemode &= ~(FCREAT | FTRUNC | FEXCL); error = rs_vncreate(pnamep, seg, &vattr, excl, mode, sp, &vp); if (error) return (error); } else { /* * Wish to open a file. * Just look it up. */ error = rs_lookupname(pnamep, seg, NO_FOLLOW, sp, (struct vnode **)0, &vp); if (error) return (error); /* * cannnot write directories, active texts or * read only filesystems */ if (filemode & (FWRITE | FTRUNC)) { if (vp->v_type == VDIR) { error = EISDIR; goto out; } if (vp->v_vfsp->vfs_flag & VFS_RDONLY) { error = EROFS; goto out; } } /* * check permissions */ error = VOP_ACCESS(vp, mode, u.u_cred); if (error) goto out; /* * Sockets in filesystem name space are not supported (yet?) */ if (vp->v_type == VSOCK) { error = EOPNOTSUPP; goto out; } } /* * do opening protocol. */ error = VOP_OPEN(&vp, filemode, u.u_cred); /* * truncate if required */ if ((error == 0) && (filemode & FTRUNC)) { struct vattr vattr; filemode &= ~FTRUNC; vattr_null(&vattr); vattr.va_size = 0; error = VOP_SETATTR(vp, &vattr, u.u_cred); } out: if (error) { VN_RELE(vp); } else { *vpp = vp; } return (error); } /* * rfs server version of vn_create (see sys/vfs_vnode.c). Basically * identical, but uses rfs server lookupname routines. If the pathname * given, pnamep, backs out of a server mount point then this routine * returns EDOTDOT with the unevaluated remainder of the pathname in * pnamep. */ /*ARGSUSED*/ int rs_vncreate(pnamep, seg, vap, excl, mode, sp, vpp) char *pnamep; int seg; struct vattr *vap; enum vcexcl excl; int mode; struct serv_proc *sp; struct vnode **vpp; { struct vnode *dvp; /* ptr to parent dir vnode */ int error = 0; /* * Lookup directory. * If new object is a file, call lower level to create it. * Note that it is up to the lower level to enforce exclusive * creation, if the file is already there. * This allows the lower level to do whatever * locking or protocol that is needed to prevent races. * If the new object is directory call lower level to make * the new directory, with "." and "..". */ dvp = (struct vnode *)0; *vpp = (struct vnode *)0; /* * lookup will find the parent directory for the vnode. * When it is done the pn hold the name of the entry * in the directory. * If this is a non-exclusive create we also find the node itself. */ if (error = rs_lookupname(pnamep, UIOSEG_KERNEL, NO_FOLLOW, sp, &dvp, (excl == NONEXCL? vpp: (struct vnode **)0))) return (error); /* EEXIST is returned for open("."), so return EISDIR instead */ if (excl == NONEXCL && error == EEXIST) error = EISDIR; /* * Make sure filesystem is writeable */ if (dvp->v_vfsp->vfs_flag & VFS_RDONLY) { if (*vpp) { VN_RELE(*vpp); } error = EROFS; } else if (excl == NONEXCL && *vpp != (struct vnode *)0) { /* * we throw the vnode away to let VOP_CREATE truncate the * file in a non-racy manner. */ VN_RELE(*vpp); } if (error == 0) { /* * call mkdir if directory or create if other */ if (vap->va_type == VDIR) { error = VOP_MKDIR(dvp, pnamep, vap, vpp, u.u_cred); } else { error = VOP_CREATE( dvp, pnamep, vap, excl, mode, vpp, u.u_cred); } } VN_RELE(dvp); return (error); } /* Server-side remote unmount routine. Used by both server to perform a * DUSRUMOUNT op and by recovery. Release mount table, vnode and advertise * table resources associated with the mount. */ srumount(smp, vp) struct srmnt *smp; struct vnode *vp; { struct advertise *ap, *getadv(); DUPRINT3 (DB_MNT_ADV, "srumount: entry %x, vnode %x \n", smp, vp); /* still busy for client machine or recovery is going on */ if (smp->sr_refcnt != 1 || smp->sr_flags & MINTER) return (EBUSY); /* * Get advertise table entry for resource. Decrement count and free * entry and vnode if no references remain and resource no longer * advertised. */ ap = getadv(vp); ASSERT(ap != (struct advertise *) NULL); if ((--(ap->a_count) == 0) && (ap->a_flags & A_MINTER)) { ap->a_flags = A_FREE; VN_RELE(vp); ap->a_queue = (rcvd_t) NULL; } /* Now free the server-side mount table entry */ smp->sr_flags = MFREE; smp->sr_rootvnode = (struct vnode *) NULL; return (0); } /* * Set this server processes's working directory to the value requested * by the client, so that service will be done with respect to the * correct directory. rdp is receive descriptor for the current client. * rrdir is the client-supplied index for the remote root directory, if * any. */ set_dir(rdp, rrdir) rcvd_t rdp; long rrdir; { extern int nrcvd; u.u_cdir = rdp->rd_vnode; ASSERT(0 <= rrdir && rrdir < nrcvd); if (rrdir) u.u_rdir = rcvd[rrdir].rd_vnode; else u.u_rdir = (struct vnode *) NULL; } /* * Find the stream (queue) to client with sysid. */ queue_t * sysid_to_queue (sysid) sysid_t sysid; { register struct gdp *gdpp; DUPRINT2 (DB_MNT_ADV, "convert sysid %x to queue \n", sysid); for (gdpp = gdp; gdpp < &gdp[maxgdp]; gdpp++) { if (gdpp->sysid == sysid) { DUPRINT2 (DB_MNT_ADV, " queue is %x \n", gdpp->queue); return (gdpp->queue); } } return (NULL); } /* * nameptr returns a pointer to the last element of * a domain name. E.g., if name == a.b.c, nameptr * returns a ptr to c. If name == a, it would return * a ptr to a. */ char * nameptr(name) register char *name; { register char *ptr=name; ASSERT(name != NULL); while (*name) if (*name++ == SEPARATOR) ptr = name; return (ptr); } /* * Get the receive descriptor corresponding to a vnode by searching the * rcvd table for an entry with matching vnode. Returns a pointer to * the receive descriptor or NULL if not found. This routine replaces the * i_rcvd pointer in the inode. (This should be hashed.) */ rcvd_t get_rcvd(vp) register struct vnode *vp; { register struct rcvd *rdp; for (rdp = rcvd; rdp < &rcvd[nrcvd]; rdp++) { if ((rdp->rd_stat & RDUSED) && (rdp->rd_vnode == vp)) return (rdp); } return ((rcvd_t) NULL); } /* Find an empty entry in the smount table, make sure that this * machine doesn't already have this directory mounted. Returns 0 * and pointer to table entry in rsmp, or non-zero if error. */ int alocsrm(vp, sysid, rsmp) register struct vnode *vp; register sysid_t sysid; struct srmnt **rsmp; { struct srmnt *smp, *sfree; sfree = (struct srmnt *) NULL; for (smp = srmount; smp < &srmount [nsrmount]; smp++) { if (sfree == (struct srmnt *) NULL && smp->sr_flags == MFREE) { sfree = smp; continue; } if ((smp->sr_flags & MINUSE) && (smp->sr_rootvnode == vp) && (smp->sr_sysid == sysid)) return (EBUSY); } if (!(smp = sfree)) { DUPRINT1(DB_MNT_ADV, "srmount table overflow\n"); return (ENOSPC); } *rsmp = smp; return (0); } /* * Make a gift to give to a client. */ int make_gift(vp, qsize, sp, ret_gift) register struct vnode *vp; register char qsize; struct serv_proc *sp; rcvd_t *ret_gift; /* The gift (receive descriptor) */ { extern rcvd_t cr_rcvd(); register rcvd_t gift; extern struct rd_user *cr_rduser(); /* if sd went bad, forget it */ if (sp->s_sdp->sd_stat & SDLINKDOWN) return (ENOLINK); gift = get_rcvd(vp); if (gift == (rcvd_t) NULL) { if ((gift = cr_rcvd (qsize, GENERAL)) == (rcvd_t) NULL) return (ENOMEM); gift->rd_vnode = vp; } else gift->rd_refcnt++; /* keep track of who we gave it to */ if (cr_rduser(gift, sp) == (struct rd_user *) NULL) { del_rcvd (gift, (struct serv_proc *) NULL); return (ENOSPC); } *ret_gift = gift; return (0); } /* * Set a signal (SIGTERM) to the current server process, on behalf of the client * whose request is currently being serviced. This will interrupt the request * when it is serviced and produce an EINTR response to the client. */ void setrsig(sp, bp) struct serv_proc *sp; mblk_t *bp; { if (((struct message *)bp->b_rptr)->m_stat & SIGNAL) sp->s_proc->p_sig |= sigmask(SIGTERM); } /* * Check for and clear any client-generated remote signals (SIGTERM) that occurred * during servicing of the current request. */ void chkrsig(sp, error) struct serv_proc *sp; int *error; { register struct proc *p = sp->s_proc; if ((p->p_sig & sigmask(SIGUSR1)) || (p->p_cursig == SIGUSR1)) if (!(p->p_sig & sigmask(SIGTERM)) && (p->p_cursig != SIGTERM)) if (*error == EINTR) *error = ENOMEM; p->p_sig &= ~(sigmask(SIGTERM)); if (p->p_cursig == SIGTERM) p->p_cursig = 0; } /* * Construct a server response message for a remote system call request * and fill in some common fields. * sp -- server process structure for process servicing the request. * in_bp -- pointer to request message. May be NULL (but not if * error == EDOTDOT). This may be freed in this routine in which * case it will have a value of NULL on return. * error -- Code for error, if any, which occurred during request * processing. * vp -- vnode on which request is being performed. May be 0. * va -- attributes of vnode on which request is performed. May be 0. * out_bp -- returns pointer to response buffer. * outsize -- returns the size of the message. */ void make_response(sp, in_bp, error, vp, vap, out_bp, outsize) register struct serv_proc *sp; mblk_t **in_bp; int error; struct vnode *vp; register struct vattr *vap; mblk_t **out_bp; int *outsize; { register struct response *msg_out; struct request *msg_in; extern mblk_t *salocbuf(); extern char *strcpy(); /* Allocate response buffer */ *out_bp = salocbuf(sizeof (struct response), BPRI_MED); msg_in = (struct request *) (in_bp ? PTOMSG((*in_bp)->b_rptr) : NULL); /* Fill in some common fields */ msg_out = (struct response *)PTOMSG((*out_bp)->b_rptr); msg_out->rp_type = RESP_MSG; msg_out->rp_bp = (long) (*out_bp); msg_out->rp_subyte = 1; /* set to indicate no data */ msg_out->rp_rval = 0; msg_out->rp_sig = SIGTORFS(GET_SIGS(sp->s_proc)); msg_out->rp_sysid = sp->s_sysid; /* Return new vnode attributes to client, if any */ if (!error) { if (vp) msg_out->rp_ftype = VTTORFS(vp->v_type); if (vap) { msg_out->rp_size = vap->va_size; msg_out->rp_nlink = vap->va_nlink; msg_out->rp_uid = vap->va_uid; msg_out->rp_gid = vap->va_gid; } } if (error != EDOTDOT) { msg_out->rp_opcode = sp->s_op; msg_out->rp_mntindx = sp->s_mntindx; msg_out->rp_errno = ERRTORFS(error); *outsize = sizeof (struct response) - DATASIZE; /* Backed out of server mnt point, return remaining path to client */ } else { msg_out->rp_opcode = DUDOTDOT; msg_out->rp_mntindx = srmount[sp->s_mntindx].sr_mntindx; msg_out->rp_errno = 0; ASSERT(msg_in); (void) strcpy(msg_out->rp_data, msg_in->rq_data); *outsize = sizeof (struct response) - DATASIZE + strlen(msg_in->rq_data) + 1; } } /* * This routine is called to search a receive descriptor queue for pid * and sysid. Note that the sysid passed must be that of the client, * not the server. The message which matches will be removed from the * queue. */ #define rsmsg ((struct request *)PTOMSG(current->b_rptr)) mblk_t * chkrdq(que, pid, sysid) rcvd_t que; long pid, sysid; { mblk_t *current, *deque(); register int i; for (i = 0; i < que->rd_qcnt; i++) { current = deque(&que->rd_rcvdq); if ((rsmsg->rq_pid == pid) && (rsmsg->rq_sysid == sysid)) { DUPRINT3(DB_SIGNAL, "chkrdq: que %x pid %x\n", que, pid); que->rd_qcnt--; if (rcvdemp(que)) rm_msgs_list(que); return (current); } enque(&que->rd_rcvdq, current); } return (NULL); /* signal msg not on queue */ } /* * Server locking routines. */ static caddr_t rsl_mem; /* * Attach a new lock record to this file for the designated client, unless * there already is one. mntindx is the index of the server mount table for * this client and mount point. */ void rsl_alloc(rd, epid, mntindx, cred) rcvd_t rd; short epid; long mntindx; struct ucred *cred; { register struct rs_lock *lkp; register struct rs_lock *nlkp = (struct rs_lock *) NULL; register struct vnode *vp = rd->rd_vnode; register struct rd_user *rduptr; for (rduptr=rd->rd_user_list; rduptr; rduptr = rduptr->ru_next) { if (rduptr->ru_srmntindx == mntindx) break; } ASSERT(rduptr); DUPRINT5(DB_SERVE, "rsl_alloc: rduser %x, vp %x, epid %d, mntindx %d\n", rduptr, vp, epid, mntindx); rsl_lock(rduptr); /* If there's one already, just return */ for (lkp = rduptr->ru_lk; lkp; lkp = lkp->lk_next) { if (lkp->lk_epid == epid) goto out; } nlkp = (struct rs_lock *)new_kmem_fast_alloc( &rsl_mem, sizeof (struct rs_lock), 8, KMEM_SLEEP); nlkp->lk_epid = epid; nlkp->lk_uid = cred->cr_uid; nlkp->lk_gid = cred->cr_gid; nlkp->lk_next = rduptr->ru_lk; rduptr->ru_lk = nlkp; out: rsl_unlock(rduptr); DUPRINT3(DB_SERVE, "rsl_alloc: got %s lock record lk %x\n", nlkp ? "new" : "existing", nlkp ? nlkp : lkp); } /* * Release all locks on the designated file held by the designated client * process on the designated machine. If no client process is specified * (epid == 0), remove all locks on the file associated with the designated * machine. mntindx is the mount index for the server mount table corresponding * to this client mount. */ void rsl_lockrelease(rd, epid, mntindx) rcvd_t rd; short epid; long mntindx; { register struct rs_lock *lkp, *lkpp, *slkp; struct flock fl; struct ucred cr; register struct vnode *vp = rd->rd_vnode; register struct rd_user *rduptr; bzero((caddr_t) &cr, sizeof (struct ucred)); crhold(&cr); /* Set to unlock whole file */ fl.l_type = F_UNLCK; fl.l_whence = 0; fl.l_start = 0; fl.l_len = 0; for (rduptr = rd->rd_user_list; rduptr; rduptr = rduptr->ru_next) { if (rduptr->ru_srmntindx == mntindx) break; } ASSERT(rduptr); DUPRINT5(DB_SERVE, "rsl_lockrelease: rduser %x, vp %x, epid %d, mntindx %x\n", rduptr, vp, epid, mntindx); rsl_lock(rduptr); for (lkpp = NULL, lkp=rduptr->ru_lk; lkp; ) { if ((lkp->lk_epid == epid) || (epid == 0)) { slkp = lkp; if (lkp == rduptr->ru_lk) rduptr->ru_lk = lkp = lkp->lk_next; else lkpp->lk_next = lkp = lkp->lk_next; cr.cr_uid = slkp->lk_uid; cr.cr_gid = slkp->lk_gid; kmem_fast_free(&rsl_mem, (caddr_t) slkp); DUPRINT3(DB_SERVE, "rsl_lockrelease: rel lk %x, epid %d\n", slkp, epid); (void)VOP_LOCKCTL(vp, &fl, F_SETLK, &cr, RSL_PID(mntindx, epid)); if (epid) break; } else { lkpp = lkp; lkp = lkp->lk_next; } } rsl_unlock(rduptr); } /* * Check if a file is mandatorily lockable based on permission bits. */ mnd_lck(vp, cred) struct vnode *vp; struct ucred *cred; { struct vattr va; if (VOP_GETATTR(vp, &va, cred) != 0) return (0); return (MND_LCK(va.va_mode)); } /* * Other miscellaneous routines. */ /* * Read in pathname from user space * upath (from, to, maxbufsize); * Returns -2 if the pathname is too long, -1 if a bad user * address is supplied, otherwise returns the pathname length * (not including the trailing \0; i.e., null pathnames return * length 0. * WARNING: Unlike the ATT version, this code does not check to * see if you are a server and if so do the copy in-kernel, SO * CHECK BEFORE USING IT IN NEWLY PORTED MODULES. */ int upath(from, to, maxbufsize) register char *from, *to; int maxbufsize; { register int l; register int c; for (l = 0; l < maxbufsize; l++) { c = fubyte(from++); if (c == -1) return (-1); if (c & 0x80) return (-1); *to++ = c; if (c == 0) return (l); } return (-2); } /* * Allocate memory for RFS data structures. */ rfs_memalloc() { extern short *rfdev; register int i; rfheap = new_kmem_zalloc((u_int) rfsize, KMEM_SLEEP); advertise = (struct advertise *)new_kmem_zalloc( (u_int) nadvertise * sizeof (struct advertise), KMEM_SLEEP); rcvd = (struct rcvd *)new_kmem_zalloc( (u_int) nrcvd * sizeof (struct rcvd), KMEM_SLEEP); sndd = (struct sndd *)new_kmem_zalloc( (u_int) nsndd * sizeof (struct sndd), KMEM_SLEEP); gdp = (struct gdp *)new_kmem_zalloc( (u_int) maxgdp * sizeof (struct gdp), KMEM_SLEEP); rd_user = (struct rd_user *)new_kmem_zalloc( (u_int) nrduser * sizeof (struct rd_user), KMEM_SLEEP); rfsmount = (struct rfsmnt *)new_kmem_zalloc( (u_int) nrfsmount * sizeof (struct rfsmnt), KMEM_SLEEP); srmount = (struct srmnt *)new_kmem_zalloc( (u_int) nsrmount * sizeof (struct srmnt), KMEM_SLEEP); serv_proc = (struct serv_proc *)new_kmem_zalloc( (u_int) maxserve * sizeof (struct serv_proc), KMEM_SLEEP); rfdev = (short *)new_kmem_alloc( (u_int) RFDEVSIZE * sizeof (short), KMEM_SLEEP); for (i=0; i < RFDEVSIZE; i++) rfdev[i] = RFDEVEMPTY; } /* * Free memory for RFS data structures. */ rfs_memfree() { extern short *rfdev; kmem_free((caddr_t) rfheap, (u_int) rfsize); kmem_free((caddr_t) advertise, (u_int) nadvertise * sizeof (struct advertise)); kmem_free((caddr_t) rcvd, (u_int) nrcvd * sizeof (struct rcvd)); kmem_free((caddr_t) sndd, (u_int) nsndd * sizeof (struct sndd)); kmem_free((caddr_t) gdp, (u_int) maxgdp * sizeof (struct gdp)); kmem_free((caddr_t) rd_user, (u_int) nrduser * sizeof (struct rd_user)); kmem_free((caddr_t)rfsmount, (u_int) nrfsmount * sizeof (struct rfsmnt)); kmem_free((caddr_t) srmount, (u_int) nsrmount * sizeof (struct srmnt)); kmem_free((caddr_t) serv_proc, (u_int) maxserve * sizeof (struct serv_proc)); kmem_free((caddr_t) rfdev, (u_int) RFDEVSIZE * sizeof (short)); } /* * Make up a pseudo-device number for the filesystem of a file, to be * returned by the server as an st_dev value in response to a DUSTAT call. * The RFS client will use only the lower byte of the value returned so you * can't just return the lower byte of the server's real device number, as * this gives a high probability of collision. You will get collisions * anyways, if you go over 255 mounts on the server. * The algorithm just hashes into a table based on the lower byte of * the real device number. Collisions are handled by advancing to the * first free slot in the table. The pseudo-device number returned is * the hash table index. You don't have to worry about freeing table * entries, since the Sun kernel policies for device number allocation * are conservative, i.e., device numbers will tend to get re-used, * rather than be newly generated, across mounts. Note that when the * table starts getting full, performance will suffer. */ #define HASHNO(ID) ((ID) & 0x0ff) #define NEXTIND(H) ((H) == 0x0ff ? 0 : (H) + 1) /* * Return the pseudo device number given the real one. */ int rfsdev_pseudo(fsid) short fsid; { int hashno, i; extern short *rfdev; hashno = HASHNO(fsid); i = hashno; do { if (rfdev[i] == RFDEVEMPTY) rfdev[i] = fsid; if (rfdev[i] == fsid) break; i = NEXTIND(i); } while (i != hashno); return (i); } /* * Return the real device number given the pseudo one. * Remote ustat() needs this. */ short rfsdev_real(pseudo) int pseudo; { extern short *rfdev; return (rfdev[pseudo & 0x0ff]); } /*VARARGS1*/ rfs_printf(duval, numargs, va_alist) int duval; int numargs; va_dcl { va_list x1; char *fmt; int a2 = 0, a3 = 0, a4 = 0, a5 = 0, a6 = 0; int save; if (!(dudebug & duval)) return; va_start(x1); fmt = va_arg(x1, char *); if (numargs < 2) goto gotargs; a2 = va_arg(x1, int); if (numargs < 3) goto gotargs; a3 = va_arg(x1, int); if (numargs < 4) goto gotargs; a4 = va_arg(x1, int); if (numargs < 5) goto gotargs; a5 = va_arg(x1, int); if (numargs < 6) goto gotargs; a6 = va_arg(x1, int); gotargs: va_end(x1); if (dudebug & DB_NOPRINT) { save = noprintf; noprintf = 1; } printf(fmt, a2, a3, a4, a5, a6); if (dudebug & DB_NOPRINT) noprintf = save; }