freebsd-src/sys/fs/p9fs/p9fs_subr.c
Doug Rabson e97ad33a89 Add an implementation of the 9P filesystem
This is derived from swills@ fork of the Juniper virtfs with many
changes by me including bug fixes, style improvements, clearer layering
and more consistent logging. The filesystem is renamed to p9fs to better
reflect its function and to prevent possible future confusion with
virtio-fs.

Several updates and fixes from Juniper have been integrated into this
version by Val Packett and these contributions along with the original
Juniper authors are credited below.

To use this with bhyve, add 'virtio_p9fs_load=YES' to loader.conf. The
bhyve virtio-9p device allows access from the guest to files on the host
by mapping a 'sharename' to a host path. It is possible to use p9fs as a
root filesystem by adding this to /boot/loader.conf:

	vfs.root.mountfrom="p9fs:sharename"

for non-root filesystems add something like this to /etc/fstab:

	sharename /mnt p9fs rw 0 0

In both examples, substitute the share name used on the bhyve command
line.

The 9P filesystem protocol relies on stateful file opens which map
protocol-level FIDs to host file descriptors. The FreeBSD vnode
interface doesn't really support this and we use heuristics to guess the
right FID to use for file operations.  This can be confused by privilege
lowering and does not guarantee that the FID created for a given file
open is always used for file operations, even if the calling process is
using the file descriptor from the original open call. Improving this
would involve changes to the vnode interface which is out-of-scope for
this import.

Differential Revision: https://reviews.freebsd.org/D41844
Reviewed by: kib, emaste, dch
MFC after: 3 months
Co-authored-by: Val Packett <val@packett.cool>
Co-authored-by: Ka Ho Ng <kahon@juniper.net>
Co-authored-by: joyu <joyul@juniper.net>
Co-authored-by: Kumara Babu Narayanaswamy <bkumara@juniper.net>
2024-06-19 13:12:04 +01:00

412 lines
10 KiB
C

/*-
* Copyright (c) 2017 Juniper Networks, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/*-
* 9P filesystem subroutines. This file consists of all the Non VFS subroutines.
* It contains all of the functions related to the driver submission which form
* the upper layer i.e, p9fs driver. This will interact with the client to make
* sure we have correct API calls in the header.
*/
#include <sys/cdefs.h>
#include <sys/systm.h>
#include <sys/limits.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#include <sys/vnode.h>
#include "p9fs_proto.h"
#include <fs/p9fs/p9_client.h>
#include <fs/p9fs/p9_debug.h>
#include <fs/p9fs/p9_protocol.h>
#include <fs/p9fs/p9fs.h>
int
p9fs_proto_dotl(struct p9fs_session *vses)
{
return (vses->flags & P9FS_PROTO_2000L);
}
/* Initialize a p9fs session */
struct p9_fid *
p9fs_init_session(struct mount *mp, int *error)
{
struct p9fs_session *vses;
struct p9fs_mount *virtmp;
struct p9_fid *fid;
char *access;
virtmp = VFSTOP9(mp);
vses = &virtmp->p9fs_session;
vses->uid = P9_NONUNAME;
vses->uname = P9_DEFUNAME;
vses->aname = P9_DEFANAME;
/*
* Create the client structure. Call into the driver to create
* driver structures for the actual IO transfer.
*/
vses->clnt = p9_client_create(mp, error, virtmp->mount_tag);
if (vses->clnt == NULL) {
P9_DEBUG(ERROR, "%s: p9_client_create failed\n", __func__);
return (NULL);
}
/*
* Find the client version and cache the copy. We will use this copy
* throughout FS layer.
*/
if (p9_is_proto_dotl(vses->clnt))
vses->flags |= P9FS_PROTO_2000L;
else if (p9_is_proto_dotu(vses->clnt))
vses->flags |= P9FS_PROTO_2000U;
/* Set the access mode */
access = vfs_getopts(mp->mnt_optnew, "access", error);
if (access == NULL)
vses->flags |= P9_ACCESS_USER;
else if (!strcmp(access, "any"))
vses->flags |= P9_ACCESS_ANY;
else if (!strcmp(access, "single"))
vses->flags |= P9_ACCESS_SINGLE;
else if (!strcmp(access, "user"))
vses->flags |= P9_ACCESS_USER;
else {
P9_DEBUG(ERROR, "%s: unknown access mode\n", __func__);
*error = EINVAL;
goto out;
}
*error = 0;
/* Attach with the backend host*/
fid = p9_client_attach(vses->clnt, NULL, vses->uname, P9_NONUNAME,
vses->aname, error);
vses->mnt_fid = fid;
if (*error != 0) {
P9_DEBUG(ERROR, "%s: attach failed: %d\n", __func__, *error);
goto out;
}
P9_DEBUG(SUBR, "%s: attach successful fid :%p\n", __func__, fid);
fid->uid = vses->uid;
/* initialize the node list for the session */
STAILQ_INIT(&vses->virt_node_list);
P9FS_LOCK_INIT(vses);
P9_DEBUG(SUBR, "%s: INIT session successful\n", __func__);
return (fid);
out:
p9_client_destroy(vses->clnt);
return (NULL);
}
/* Begin to terminate a session */
void
p9fs_prepare_to_close(struct mount *mp)
{
struct p9fs_session *vses;
struct p9fs_mount *vmp;
struct p9fs_node *np, *pnp, *tmp;
vmp = VFSTOP9(mp);
vses = &vmp->p9fs_session;
/* break the node->parent references */
STAILQ_FOREACH_SAFE(np, &vses->virt_node_list, p9fs_node_next, tmp) {
if (np->parent && np->parent != np) {
pnp = np->parent;
np->parent = NULL;
vrele(P9FS_NTOV(pnp));
}
}
/* We are about to teardown, we dont allow anything other than clunk after this.*/
p9_client_begin_disconnect(vses->clnt);
}
/* Shutdown a session */
void
p9fs_complete_close(struct mount *mp)
{
struct p9fs_session *vses;
struct p9fs_mount *vmp;
vmp = VFSTOP9(mp);
vses = &vmp->p9fs_session;
/* Finish the close*/
p9_client_disconnect(vses->clnt);
}
/* Call from unmount. Close the session. */
void
p9fs_close_session(struct mount *mp)
{
struct p9fs_session *vses;
struct p9fs_mount *vmp;
vmp = VFSTOP9(mp);
vses = &vmp->p9fs_session;
p9fs_complete_close(mp);
/* Clean up the clnt structure. */
p9_client_destroy(vses->clnt);
P9FS_LOCK_DESTROY(vses);
P9_DEBUG(SUBR, "%s: Clean close session .\n", __func__);
}
/*
* Remove all the fids of a particular type from a p9fs node
* as well as destroy/clunk them.
*/
void
p9fs_fid_remove_all(struct p9fs_node *np, int leave_ofids)
{
struct p9_fid *fid, *tfid;
STAILQ_FOREACH_SAFE(fid, &np->vfid_list, fid_next, tfid) {
STAILQ_REMOVE(&np->vfid_list, fid, p9_fid, fid_next);
p9_client_clunk(fid);
}
if (!leave_ofids) {
STAILQ_FOREACH_SAFE(fid, &np->vofid_list, fid_next, tfid) {
STAILQ_REMOVE(&np->vofid_list, fid, p9_fid, fid_next);
p9_client_clunk(fid);
}
}
}
/* Remove a fid from its corresponding fid list */
void
p9fs_fid_remove(struct p9fs_node *np, struct p9_fid *fid, int fid_type)
{
switch (fid_type) {
case VFID:
P9FS_VFID_LOCK(np);
STAILQ_REMOVE(&np->vfid_list, fid, p9_fid, fid_next);
P9FS_VFID_UNLOCK(np);
break;
case VOFID:
P9FS_VOFID_LOCK(np);
STAILQ_REMOVE(&np->vofid_list, fid, p9_fid, fid_next);
P9FS_VOFID_UNLOCK(np);
break;
}
}
/* Add a fid to the corresponding fid list */
void
p9fs_fid_add(struct p9fs_node *np, struct p9_fid *fid, int fid_type)
{
switch (fid_type) {
case VFID:
P9FS_VFID_LOCK(np);
STAILQ_INSERT_TAIL(&np->vfid_list, fid, fid_next);
P9FS_VFID_UNLOCK(np);
break;
case VOFID:
P9FS_VOFID_LOCK(np);
STAILQ_INSERT_TAIL(&np->vofid_list, fid, fid_next);
P9FS_VOFID_UNLOCK(np);
break;
}
}
/* Build the path from root to current directory */
static int
p9fs_get_full_path(struct p9fs_node *np, char ***names)
{
int i, n;
struct p9fs_node *node;
char **wnames;
n = 0;
for (node = np ; (node != NULL) && !IS_ROOT(node) ; node = node->parent)
n++;
if (node == NULL)
return (0);
wnames = malloc(n * sizeof(char *), M_TEMP, M_ZERO|M_WAITOK);
for (i = n-1, node = np; i >= 0 ; i--, node = node->parent)
wnames[i] = node->inode.i_name;
*names = wnames;
return (n);
}
/*
* Return TRUE if this fid can be used for the requested mode.
*/
static int
p9fs_compatible_mode(struct p9_fid *fid, int mode)
{
/*
* Return TRUE for an exact match. For OREAD and OWRITE, allow
* existing ORDWR fids to match. Only check the low two bits
* of mode.
*
* TODO: figure out if this is correct for O_APPEND
*/
int fid_mode = fid->mode & 3;
if (fid_mode == mode)
return (TRUE);
if (fid_mode == P9PROTO_ORDWR)
return (mode == P9PROTO_OREAD || mode == P9PROTO_OWRITE);
return (FALSE);
}
/*
* Retrieve fid structure corresponding to a particular
* uid and fid type for a p9fs node
*/
static struct p9_fid *
p9fs_get_fid_from_uid(struct p9fs_node *np, uid_t uid, int fid_type, int mode)
{
struct p9_fid *fid;
switch (fid_type) {
case VFID:
P9FS_VFID_LOCK(np);
STAILQ_FOREACH(fid, &np->vfid_list, fid_next) {
if (fid->uid == uid) {
P9FS_VFID_UNLOCK(np);
return (fid);
}
}
P9FS_VFID_UNLOCK(np);
break;
case VOFID:
P9FS_VOFID_LOCK(np);
STAILQ_FOREACH(fid, &np->vofid_list, fid_next) {
if (fid->uid == uid && p9fs_compatible_mode(fid, mode)) {
P9FS_VOFID_UNLOCK(np);
return (fid);
}
}
P9FS_VOFID_UNLOCK(np);
break;
}
return (NULL);
}
/*
* Function returns the fid sturcture for a file corresponding to current user id.
* First it searches in the fid list of the corresponding p9fs node.
* New fid will be created if not already present and added in the corresponding
* fid list in the p9fs node.
* If the user is not already attached then this will attach the user first
* and then create a new fid for this particular file by doing dir walk.
*/
struct p9_fid *
p9fs_get_fid(struct p9_client *clnt, struct p9fs_node *np, struct ucred *cred,
int fid_type, int mode, int *error)
{
uid_t uid;
struct p9_fid *fid, *oldfid;
struct p9fs_node *root;
struct p9fs_session *vses;
int i, l, clone;
char **wnames = NULL;
uint16_t nwnames;
oldfid = NULL;
vses = np->p9fs_ses;
if (vses->flags & P9_ACCESS_ANY)
uid = vses->uid;
else if (cred)
uid = cred->cr_uid;
else
uid = 0;
/*
* Search for the fid in corresponding fid list.
* We should return NULL for VOFID if it is not present in the list.
* Because VOFID should have been created during the file open.
* If VFID is not present in the list then we should create one.
*/
fid = p9fs_get_fid_from_uid(np, uid, fid_type, mode);
if (fid != NULL || fid_type == VOFID)
return (fid);
/* Check root if the user is attached */
root = &np->p9fs_ses->rnp;
fid = p9fs_get_fid_from_uid(root, uid, fid_type, mode);
if(fid == NULL) {
/* Attach the user */
fid = p9_client_attach(clnt, NULL, NULL, uid,
vses->aname, error);
if (*error != 0)
return (NULL);
p9fs_fid_add(root, fid, fid_type);
}
/* If we are looking for root then return it */
if (IS_ROOT(np))
return (fid);
/* Get full path from root to p9fs node */
nwnames = p9fs_get_full_path(np, &wnames);
/*
* Could not get full path.
* If p9fs node is not deleted, parent should exist.
*/
KASSERT(nwnames != 0, ("%s: Directory of %s doesn't exist", __func__, np->inode.i_name));
clone = 1;
i = 0;
while (i < nwnames) {
l = MIN(nwnames - i, P9_MAXWELEM);
fid = p9_client_walk(fid, l, wnames, clone, error);
if (*error != 0) {
if (oldfid)
p9_client_clunk(oldfid);
fid = NULL;
goto bail_out;
}
oldfid = fid;
clone = 0;
i += l ;
}
p9fs_fid_add(np, fid, fid_type);
bail_out:
free(wnames, M_TEMP);
return (fid);
}