4c4ecb7844
Some filesystems do not provide inode numbers through readdir (FUSE mounts without -o use_ino). We therefore have to lookup each directory entry to get the missing numbers. dot and double-dot are exceptions, as we already know the values. Moreover, the lookup code does not expect to get requests for dot and will abort perfused(8) when it gets some. In order to fix that, we just check for dot and double-dot special case and use the known values instead of sending a lookup.
3693 lines
90 KiB
C
3693 lines
90 KiB
C
/* $NetBSD: ops.c,v 1.84 2015/06/03 14:07:05 manu Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2010-2011 Emmanuel Dreyfus. 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``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 FOUNDATION OR CONTRIBUTORS
|
|
* 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.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <libgen.h>
|
|
#include <errno.h>
|
|
#include <err.h>
|
|
#include <sysexits.h>
|
|
#include <syslog.h>
|
|
#include <puffs.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/extattr.h>
|
|
#include <sys/time.h>
|
|
#include <machine/vmparam.h>
|
|
|
|
#include "perfuse_priv.h"
|
|
#include "fuse.h"
|
|
|
|
extern int perfuse_diagflags;
|
|
|
|
#if 0
|
|
static void print_node(const char *, puffs_cookie_t);
|
|
#endif
|
|
#ifdef PUFFS_KFLAG_CACHE_FS_TTL
|
|
static void perfuse_newinfo_setttl(struct puffs_newinfo *,
|
|
struct puffs_node *, struct fuse_entry_out *, struct fuse_attr_out *);
|
|
#endif /* PUFFS_KFLAG_CACHE_FS_TTL */
|
|
static int xchg_msg(struct puffs_usermount *, puffs_cookie_t,
|
|
perfuse_msg_t *, size_t, enum perfuse_xchg_pb_reply);
|
|
static int mode_access(puffs_cookie_t, const struct puffs_cred *, mode_t);
|
|
static int sticky_access(puffs_cookie_t, struct puffs_node *,
|
|
const struct puffs_cred *);
|
|
static void fuse_attr_to_vap(struct perfuse_state *,
|
|
struct vattr *, struct fuse_attr *);
|
|
static int node_lookup_common(struct puffs_usermount *, puffs_cookie_t,
|
|
struct puffs_newinfo *, const char *, const struct puffs_cred *,
|
|
struct puffs_node **);
|
|
static int node_mk_common(struct puffs_usermount *, puffs_cookie_t,
|
|
struct puffs_newinfo *, const struct puffs_cn *pcn, perfuse_msg_t *);
|
|
static uint64_t readdir_last_cookie(struct fuse_dirent *, size_t);
|
|
static ssize_t fuse_to_dirent(struct puffs_usermount *, puffs_cookie_t,
|
|
struct fuse_dirent *, size_t);
|
|
static void readdir_buffered(puffs_cookie_t, struct dirent *, off_t *,
|
|
size_t *);
|
|
static void node_ref(puffs_cookie_t);
|
|
static void node_rele(puffs_cookie_t);
|
|
static void requeue_request(struct puffs_usermount *,
|
|
puffs_cookie_t opc, enum perfuse_qtype);
|
|
static int dequeue_requests(puffs_cookie_t opc, enum perfuse_qtype, int);
|
|
#define DEQUEUE_ALL 0
|
|
|
|
/*
|
|
* From <sys/vnode>, inside #ifdef _KERNEL section
|
|
*/
|
|
#define IO_SYNC (0x40|IO_DSYNC)
|
|
#define IO_DSYNC 0x00200
|
|
#define IO_DIRECT 0x02000
|
|
|
|
/*
|
|
* From <fcntl>, inside #ifdef _KERNEL section
|
|
*/
|
|
#define F_WAIT 0x010
|
|
#define F_FLOCK 0x020
|
|
#define OFLAGS(fflags) ((fflags) - 1)
|
|
|
|
/*
|
|
* Borrowed from src/sys/kern/vfs_subr.c and src/sys/sys/vnode.h
|
|
*/
|
|
const enum vtype iftovt_tab[16] = {
|
|
VNON, VFIFO, VCHR, VNON, VDIR, VNON, VBLK, VNON,
|
|
VREG, VNON, VLNK, VNON, VSOCK, VNON, VNON, VBAD,
|
|
};
|
|
const int vttoif_tab[9] = {
|
|
0, S_IFREG, S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK,
|
|
S_IFSOCK, S_IFIFO, S_IFMT,
|
|
};
|
|
|
|
#define IFTOVT(mode) (iftovt_tab[((mode) & S_IFMT) >> 12])
|
|
#define VTTOIF(indx) (vttoif_tab[(int)(indx)])
|
|
|
|
#if 0
|
|
static void
|
|
print_node(const char *func, puffs_cookie_t opc)
|
|
{
|
|
struct puffs_node *pn;
|
|
struct perfuse_node_data *pnd;
|
|
struct vattr *vap;
|
|
|
|
pn = (struct puffs_node *)opc;
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
vap = &pn->pn_va;
|
|
|
|
printf("%s: \"%s\", opc = %p, nodeid = 0x%"PRIx64" ino = %"PRIu64"\n",
|
|
func, pnd->pnd_name, opc, pnd->pnd_nodeid, vap->va_fileid);
|
|
|
|
return;
|
|
}
|
|
#endif /* PERFUSE_DEBUG */
|
|
|
|
int
|
|
perfuse_node_close_common(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
int mode)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
int op;
|
|
uint64_t fh;
|
|
struct fuse_release_in *fri;
|
|
struct perfuse_node_data *pnd;
|
|
struct puffs_node *pn;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
pn = (struct puffs_node *)opc;
|
|
pnd = PERFUSE_NODE_DATA(pn);
|
|
|
|
if (puffs_pn_getvap(pn)->va_type == VDIR) {
|
|
op = FUSE_RELEASEDIR;
|
|
mode = FREAD;
|
|
} else {
|
|
op = FUSE_RELEASE;
|
|
}
|
|
|
|
/*
|
|
* Destroy the filehandle before sending the
|
|
* request to the FUSE filesystem, otherwise
|
|
* we may get a second close() while we wait
|
|
* for the reply, and we would end up closing
|
|
* the same fh twice instead of closng both.
|
|
*/
|
|
fh = perfuse_get_fh(opc, mode);
|
|
perfuse_destroy_fh(pn, fh);
|
|
|
|
/*
|
|
* release_flags may be set to FUSE_RELEASE_FLUSH
|
|
* to flush locks. lock_owner must be set in that case
|
|
*
|
|
* ps_new_msg() is called with NULL creds, which will
|
|
* be interpreted as FUSE superuser. We come here from the
|
|
* inactive method, which provides no creds, but obviously
|
|
* runs with kernel privilege.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, op, sizeof(*fri), NULL);
|
|
fri = GET_INPAYLOAD(ps, pm, fuse_release_in);
|
|
fri->fh = fh;
|
|
fri->flags = 0;
|
|
fri->release_flags = 0;
|
|
fri->lock_owner = pnd->pnd_lock_owner;
|
|
fri->flags = (fri->lock_owner != 0) ? FUSE_RELEASE_FLUSH : 0;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FH)
|
|
DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", fh = 0x%"PRIx64"\n",
|
|
__func__, (void *)opc, pnd->pnd_nodeid, fri->fh);
|
|
#endif
|
|
|
|
if ((error = xchg_msg(pu, opc, pm,
|
|
NO_PAYLOAD_REPLY_LEN, wait_reply)) != 0)
|
|
DERRX(EX_SOFTWARE, "%s: freed fh = 0x%"PRIx64" but filesystem "
|
|
"returned error = %d", __func__, fh, error);
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
xchg_msg(struct puffs_usermount *pu, puffs_cookie_t opc, perfuse_msg_t *pm,
|
|
size_t len, enum perfuse_xchg_pb_reply wait)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
struct perfuse_trace *pt = NULL;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
pnd = NULL;
|
|
if ((struct puffs_node *)opc != NULL)
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if ((perfuse_diagflags & PDF_FILENAME) && (opc != 0))
|
|
DPRINTF("file = \"%s\", ino = %"PRIu64" flags = 0x%x\n",
|
|
perfuse_node_path(ps, opc),
|
|
((struct puffs_node *)opc)->pn_va.va_fileid,
|
|
PERFUSE_NODE_DATA(opc)->pnd_flags);
|
|
#endif
|
|
ps->ps_xchgcount++;
|
|
if (pnd)
|
|
pnd->pnd_inxchg++;
|
|
|
|
/*
|
|
* Record FUSE call start if requested
|
|
*/
|
|
if (perfuse_diagflags & PDF_TRACE)
|
|
pt = perfuse_trace_begin(ps, opc, pm);
|
|
|
|
/*
|
|
* Do actual FUSE exchange
|
|
*/
|
|
if ((error = ps->ps_xchg_msg(pu, pm, len, wait)) != 0)
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
/*
|
|
* Record FUSE call end if requested
|
|
*/
|
|
if (pt != NULL)
|
|
perfuse_trace_end(ps, pt, error);
|
|
|
|
ps->ps_xchgcount--;
|
|
if (pnd) {
|
|
pnd->pnd_inxchg--;
|
|
(void)dequeue_requests(opc, PCQ_AFTERXCHG, DEQUEUE_ALL);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
mode_access(puffs_cookie_t opc, const struct puffs_cred *pcr, mode_t mode)
|
|
{
|
|
struct puffs_node *pn;
|
|
struct vattr *va;
|
|
|
|
/*
|
|
* pcr is NULL for self open through fsync or readdir.
|
|
* In both case, access control is useless, as it was
|
|
* done before, at open time.
|
|
*/
|
|
if (pcr == NULL)
|
|
return 0;
|
|
|
|
pn = (struct puffs_node *)opc;
|
|
va = puffs_pn_getvap(pn);
|
|
return puffs_access(va->va_type, va->va_mode,
|
|
va->va_uid, va->va_gid,
|
|
mode, pcr);
|
|
}
|
|
|
|
static int
|
|
sticky_access(puffs_cookie_t opc, struct puffs_node *targ,
|
|
const struct puffs_cred *pcr)
|
|
{
|
|
uid_t uid;
|
|
int sticky, owner, parent_owner;
|
|
|
|
/*
|
|
* This covers the case where the kernel requests a DELETE
|
|
* or RENAME on its own, and where puffs_cred_getuid would
|
|
* return -1. While such a situation should not happen,
|
|
* we allow it here.
|
|
*
|
|
* This also allows root to tamper with other users' files
|
|
* that have the sticky bit.
|
|
*/
|
|
if (puffs_cred_isjuggernaut(pcr))
|
|
return 0;
|
|
|
|
if (puffs_cred_getuid(pcr, &uid) != 0)
|
|
DERRX(EX_SOFTWARE, "puffs_cred_getuid fails in %s", __func__);
|
|
|
|
sticky = puffs_pn_getvap(opc)->va_mode & S_ISTXT;
|
|
owner = puffs_pn_getvap(targ)->va_uid == uid;
|
|
parent_owner = puffs_pn_getvap(opc)->va_uid == uid;
|
|
|
|
if (sticky && !owner && !parent_owner)
|
|
return EPERM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
fuse_attr_to_vap(struct perfuse_state *ps, struct vattr *vap,
|
|
struct fuse_attr *fa)
|
|
{
|
|
vap->va_type = IFTOVT(fa->mode);
|
|
vap->va_mode = fa->mode & ALLPERMS;
|
|
vap->va_nlink = fa->nlink;
|
|
vap->va_uid = fa->uid;
|
|
vap->va_gid = fa->gid;
|
|
vap->va_fsid = (long)ps->ps_fsid;
|
|
vap->va_fileid = fa->ino;
|
|
vap->va_size = fa->size;
|
|
vap->va_blocksize = fa->blksize;
|
|
vap->va_atime.tv_sec = (time_t)fa->atime;
|
|
vap->va_atime.tv_nsec = (long) fa->atimensec;
|
|
vap->va_mtime.tv_sec = (time_t)fa->mtime;
|
|
vap->va_mtime.tv_nsec = (long)fa->mtimensec;
|
|
vap->va_ctime.tv_sec = (time_t)fa->ctime;
|
|
vap->va_ctime.tv_nsec = (long)fa->ctimensec;
|
|
vap->va_birthtime.tv_sec = 0;
|
|
vap->va_birthtime.tv_nsec = 0;
|
|
vap->va_gen = 0;
|
|
vap->va_flags = 0;
|
|
vap->va_rdev = fa->rdev;
|
|
vap->va_bytes = fa->blocks * S_BLKSIZE;
|
|
vap->va_filerev = (u_quad_t)PUFFS_VNOVAL;
|
|
vap->va_vaflags = 0;
|
|
|
|
if (vap->va_blocksize == 0)
|
|
vap->va_blocksize = DEV_BSIZE;
|
|
|
|
if (vap->va_size == (size_t)PUFFS_VNOVAL) /* XXX */
|
|
vap->va_size = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef PUFFS_KFLAG_CACHE_FS_TTL
|
|
static void
|
|
perfuse_newinfo_setttl(struct puffs_newinfo *pni,
|
|
struct puffs_node *pn, struct fuse_entry_out *feo,
|
|
struct fuse_attr_out *fao)
|
|
{
|
|
#ifdef PERFUSE_DEBUG
|
|
if ((feo == NULL) && (fao == NULL))
|
|
DERRX(EX_SOFTWARE, "%s: feo and fao NULL", __func__);
|
|
|
|
if ((feo != NULL) && (fao != NULL))
|
|
DERRX(EX_SOFTWARE, "%s: feo and fao != NULL", __func__);
|
|
#endif /* PERFUSE_DEBUG */
|
|
|
|
if (fao != NULL) {
|
|
struct timespec va_ttl;
|
|
|
|
va_ttl.tv_sec = fao->attr_valid;
|
|
va_ttl.tv_nsec = fao->attr_valid_nsec;
|
|
|
|
puffs_newinfo_setvattl(pni, &va_ttl);
|
|
}
|
|
|
|
if (feo != NULL) {
|
|
struct timespec va_ttl;
|
|
struct timespec cn_ttl;
|
|
struct timespec now;
|
|
struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(pn);
|
|
|
|
va_ttl.tv_sec = feo->attr_valid;
|
|
va_ttl.tv_nsec = feo->attr_valid_nsec;
|
|
cn_ttl.tv_sec = feo->entry_valid;
|
|
cn_ttl.tv_nsec = feo->entry_valid_nsec;
|
|
|
|
puffs_newinfo_setvattl(pni, &va_ttl);
|
|
puffs_newinfo_setcnttl(pni, &cn_ttl);
|
|
|
|
if (clock_gettime(CLOCK_REALTIME, &now) != 0)
|
|
DERR(EX_OSERR, "clock_gettime failed");
|
|
|
|
timespecadd(&now, &cn_ttl, &pnd->pnd_cn_expire);
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif /* PUFFS_KFLAG_CACHE_FS_TTL */
|
|
|
|
static int
|
|
node_lookup_common(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct puffs_newinfo *pni, const char *path,
|
|
const struct puffs_cred *pcr, struct puffs_node **pnp)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *oldpnd;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_entry_out *feo;
|
|
struct puffs_node *pn;
|
|
size_t len;
|
|
int error;
|
|
|
|
/*
|
|
* Prevent further lookups if the parent was removed
|
|
*/
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED)
|
|
return ESTALE;
|
|
|
|
if (pnp == NULL)
|
|
DERRX(EX_SOFTWARE, "pnp must be != NULL");
|
|
|
|
ps = puffs_getspecific(pu);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FILENAME)
|
|
DPRINTF("%s: opc = %p, file = \"%s\" looking up \"%s\"\n",
|
|
__func__, (void *)opc,
|
|
perfuse_node_path(ps, opc), path);
|
|
|
|
if (strcmp(path, ".") == 0)
|
|
DERRX(EX_SOFTWARE, "unexpected dot-lookup");
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_RECLAIMED)
|
|
DERRX(EX_SOFTWARE,
|
|
"looking up reclaimed node opc = %p, name = \"%s\"",
|
|
opc, path);
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_INVALID)
|
|
DERRX(EX_SOFTWARE,
|
|
"looking up freed node opc = %p, name = \"%s\"",
|
|
opc, path);
|
|
#endif /* PERFUSE_DEBUG */
|
|
|
|
len = strlen(path) + 1;
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_LOOKUP, len, pcr);
|
|
(void)strlcpy(_GET_INPAYLOAD(ps, pm, char *), path, len);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, sizeof(*feo), wait_reply)) != 0)
|
|
return error;
|
|
|
|
feo = GET_OUTPAYLOAD(ps, pm, fuse_entry_out);
|
|
|
|
/*
|
|
* Starting with ABI 7.4, inode number 0 means ENOENT,
|
|
* with entry_valid / entry_valid_nsec giving negative
|
|
* cache timeout (which we do not implement yet).
|
|
*/
|
|
if (feo->attr.ino == 0) {
|
|
ps->ps_destroy_msg(pm);
|
|
return ENOENT;
|
|
}
|
|
|
|
/*
|
|
* Check for a known node, not reclaimed, with another name.
|
|
* It may have been moved, or we can lookup ../
|
|
*/
|
|
if (((oldpnd = perfuse_node_bynodeid(ps, feo->nodeid)) != NULL) &&
|
|
!(oldpnd->pnd_flags & PND_RECLAIMED)) {
|
|
/*
|
|
* Save the new node name if not ..
|
|
*/
|
|
if (strncmp(path, "..", len) != 0)
|
|
(void)strlcpy(oldpnd->pnd_name,
|
|
path, MAXPATHLEN);
|
|
pn = oldpnd->pnd_pn;
|
|
|
|
} else {
|
|
pn = perfuse_new_pn(pu, path, opc);
|
|
PERFUSE_NODE_DATA(pn)->pnd_nodeid = feo->nodeid;
|
|
perfuse_node_cache(ps, pn);
|
|
}
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (PERFUSE_NODE_DATA(pn)->pnd_flags & PND_RECLAIMED)
|
|
DERRX(EX_SOFTWARE,
|
|
"reclaimed in lookup opc = %p, name = \"%s\", ck = %p",
|
|
opc, path, pn);
|
|
|
|
if (PERFUSE_NODE_DATA(pn)->pnd_flags & PND_INVALID)
|
|
DERRX(EX_SOFTWARE,
|
|
"freed in lookup opc = %p, name = \"%s\", ck = %p",
|
|
opc, path, pn);
|
|
#endif /* PERFUSE_DEBUG */
|
|
|
|
fuse_attr_to_vap(ps, &pn->pn_va, &feo->attr);
|
|
pn->pn_va.va_gen = (u_long)(feo->generation);
|
|
PERFUSE_NODE_DATA(pn)->pnd_fuse_nlookup++;
|
|
|
|
*pnp = pn;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FILENAME)
|
|
DPRINTF("%s: opc = %p, looked up opc = %p, "
|
|
"nodeid = 0x%"PRIx64" file = \"%s\"\n", __func__,
|
|
(void *)opc, pn, feo->nodeid, path);
|
|
#endif
|
|
|
|
if (pni != NULL) {
|
|
#ifdef PUFFS_KFLAG_CACHE_FS_TTL
|
|
puffs_newinfo_setva(pni, &pn->pn_va);
|
|
perfuse_newinfo_setttl(pni, pn, feo, NULL);
|
|
#endif /* PUFFS_KFLAG_CACHE_FS_TTL */
|
|
puffs_newinfo_setcookie(pni, pn);
|
|
puffs_newinfo_setvtype(pni, pn->pn_va.va_type);
|
|
puffs_newinfo_setsize(pni, (voff_t)pn->pn_va.va_size);
|
|
puffs_newinfo_setrdev(pni, pn->pn_va.va_rdev);
|
|
}
|
|
|
|
if (PERFUSE_NODE_DATA(pn)->pnd_flags & PND_NODELEAK) {
|
|
PERFUSE_NODE_DATA(pn)->pnd_flags &= ~PND_NODELEAK;
|
|
ps->ps_nodeleakcount--;
|
|
}
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Common code for methods that create objects:
|
|
* perfuse_node_mkdir
|
|
* perfuse_node_mknod
|
|
* perfuse_node_symlink
|
|
*/
|
|
static int
|
|
node_mk_common(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct puffs_newinfo *pni, const struct puffs_cn *pcn,
|
|
perfuse_msg_t *pm)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct puffs_node *pn;
|
|
struct fuse_entry_out *feo;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, sizeof(*feo), wait_reply)) != 0)
|
|
return error;
|
|
|
|
feo = GET_OUTPAYLOAD(ps, pm, fuse_entry_out);
|
|
if (feo->nodeid == PERFUSE_UNKNOWN_NODEID)
|
|
DERRX(EX_SOFTWARE, "%s: no nodeid", __func__);
|
|
|
|
pn = perfuse_new_pn(pu, pcn->pcn_name, opc);
|
|
PERFUSE_NODE_DATA(pn)->pnd_nodeid = feo->nodeid;
|
|
PERFUSE_NODE_DATA(pn)->pnd_puffs_nlookup++;
|
|
perfuse_node_cache(ps, pn);
|
|
|
|
fuse_attr_to_vap(ps, &pn->pn_va, &feo->attr);
|
|
pn->pn_va.va_gen = (u_long)(feo->generation);
|
|
|
|
puffs_newinfo_setcookie(pni, pn);
|
|
#ifdef PUFFS_KFLAG_CACHE_FS_TTL
|
|
puffs_newinfo_setva(pni, &pn->pn_va);
|
|
perfuse_newinfo_setttl(pni, pn, feo, NULL);
|
|
#endif /* PUFFS_KFLAG_CACHE_FS_TTL */
|
|
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FILENAME)
|
|
DPRINTF("%s: opc = %p, file = \"%s\", flags = 0x%x "
|
|
"nodeid = 0x%"PRIx64"\n",
|
|
__func__, (void *)pn, pcn->pcn_name,
|
|
PERFUSE_NODE_DATA(pn)->pnd_flags, feo->nodeid);
|
|
#endif
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
/* Parents is now dirty */
|
|
PERFUSE_NODE_DATA(opc)->pnd_flags |= PND_DIRTY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t
|
|
readdir_last_cookie(struct fuse_dirent *fd, size_t fd_len)
|
|
{
|
|
size_t len;
|
|
size_t seen = 0;
|
|
char *ndp;
|
|
|
|
do {
|
|
len = FUSE_DIRENT_ALIGN(sizeof(*fd) + fd->namelen);
|
|
seen += len;
|
|
|
|
if (seen >= fd_len)
|
|
break;
|
|
|
|
ndp = (char *)(void *)fd + (size_t)len;
|
|
fd = (struct fuse_dirent *)(void *)ndp;
|
|
} while (1 /* CONSTCOND */);
|
|
|
|
return fd->off;
|
|
}
|
|
|
|
static ssize_t
|
|
fuse_to_dirent(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct fuse_dirent *fd, size_t fd_len)
|
|
{
|
|
struct dirent *dents;
|
|
size_t dents_len;
|
|
ssize_t written;
|
|
uint64_t fd_offset;
|
|
struct fuse_dirent *fd_base;
|
|
size_t len;
|
|
|
|
fd_base = fd;
|
|
fd_offset = 0;
|
|
written = 0;
|
|
dents = PERFUSE_NODE_DATA(opc)->pnd_dirent;
|
|
dents_len = (size_t)PERFUSE_NODE_DATA(opc)->pnd_dirent_len;
|
|
|
|
do {
|
|
char *ndp;
|
|
size_t reclen;
|
|
char name[MAXPATHLEN];
|
|
|
|
reclen = _DIRENT_RECLEN(dents, fd->namelen);
|
|
|
|
/*
|
|
* Check we do not overflow the output buffer
|
|
* struct fuse_dirent is bigger than struct dirent,
|
|
* so we should always use fd_len and never reallocate
|
|
* later.
|
|
* If we have to reallocate,try to double the buffer
|
|
* each time so that we do not have to do it too often.
|
|
*/
|
|
if (written + reclen > dents_len) {
|
|
if (dents_len == 0)
|
|
dents_len = fd_len;
|
|
else
|
|
dents_len =
|
|
MAX(2 * dents_len, written + reclen);
|
|
|
|
dents = PERFUSE_NODE_DATA(opc)->pnd_dirent;
|
|
if ((dents = realloc(dents, dents_len)) == NULL)
|
|
DERR(EX_OSERR, "%s: malloc failed", __func__);
|
|
|
|
PERFUSE_NODE_DATA(opc)->pnd_dirent = dents;
|
|
PERFUSE_NODE_DATA(opc)->pnd_dirent_len = dents_len;
|
|
|
|
/*
|
|
* (void *) for delint
|
|
*/
|
|
ndp = (char *)(void *)dents + written;
|
|
dents = (struct dirent *)(void *)ndp;
|
|
}
|
|
|
|
strncpy(name, fd->name, fd->namelen);
|
|
name[fd->namelen] = '\0';
|
|
|
|
/*
|
|
* Filesystem was mounted without -o use_ino
|
|
* Perform a lookup to find it.
|
|
*/
|
|
if (fd->ino == PERFUSE_UNKNOWN_INO) {
|
|
struct puffs_node *pn;
|
|
struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
if (strcmp(name, "..") == 0) {
|
|
/*
|
|
* Avoid breaking out of fs
|
|
* by lookup to .. on root
|
|
*/
|
|
if (pnd->pnd_nodeid == FUSE_ROOT_ID)
|
|
fd->ino = FUSE_ROOT_ID;
|
|
else
|
|
fd->ino = pnd->pnd_parent_nodeid;
|
|
} else if (strcmp(name, ".") == 0 ) {
|
|
fd->ino = pnd->pnd_nodeid;
|
|
} else {
|
|
int error;
|
|
|
|
error = node_lookup_common(pu, opc, NULL,
|
|
name, NULL, &pn);
|
|
if (error != 0) {
|
|
DWARNX("node_lookup_common %s "
|
|
"failed: %d", name, error);
|
|
} else {
|
|
fd->ino = pn->pn_va.va_fileid;
|
|
(void)perfuse_node_reclaim(pu, pn);
|
|
}
|
|
}
|
|
}
|
|
|
|
dents->d_fileno = fd->ino;
|
|
dents->d_reclen = (unsigned short)reclen;
|
|
dents->d_namlen = fd->namelen;
|
|
dents->d_type = fd->type;
|
|
strlcpy(dents->d_name, name, fd->namelen + 1);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_READDIR)
|
|
DPRINTF("%s: translated \"%s\" ino = %"PRIu64"\n",
|
|
__func__, dents->d_name, dents->d_fileno);
|
|
#endif
|
|
|
|
dents = _DIRENT_NEXT(dents);
|
|
written += reclen;
|
|
|
|
/*
|
|
* Move to the next record.
|
|
* fd->off is not the offset, it is an opaque cookie
|
|
* given by the filesystem to keep state across multiple
|
|
* readdir() operation.
|
|
* Use record alignement instead.
|
|
*/
|
|
len = FUSE_DIRENT_ALIGN(sizeof(*fd) + fd->namelen);
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_READDIR)
|
|
DPRINTF("%s: record at %"PRId64"/0x%"PRIx64" "
|
|
"length = %zd/0x%zx. "
|
|
"next record at %"PRId64"/0x%"PRIx64" "
|
|
"max %zd/0x%zx\n",
|
|
__func__, fd_offset, fd_offset, len, len,
|
|
fd_offset + len, fd_offset + len,
|
|
fd_len, fd_len);
|
|
#endif
|
|
fd_offset += len;
|
|
|
|
/*
|
|
* Check if next record is still within the packet
|
|
* If it is not, we reached the end of the buffer.
|
|
*/
|
|
if (fd_offset >= fd_len)
|
|
break;
|
|
|
|
/*
|
|
* (void *) for delint
|
|
*/
|
|
ndp = (char *)(void *)fd_base + (size_t)fd_offset;
|
|
fd = (struct fuse_dirent *)(void *)ndp;
|
|
|
|
} while (1 /* CONSTCOND */);
|
|
|
|
/*
|
|
* Adjust the dirent output length
|
|
*/
|
|
if (written != -1)
|
|
PERFUSE_NODE_DATA(opc)->pnd_dirent_len = written;
|
|
|
|
return written;
|
|
}
|
|
|
|
static void
|
|
readdir_buffered(puffs_cookie_t opc, struct dirent *dent, off_t *readoff,
|
|
size_t *reslen)
|
|
{
|
|
struct dirent *fromdent;
|
|
struct perfuse_node_data *pnd;
|
|
char *ndp;
|
|
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
while (*readoff < pnd->pnd_dirent_len) {
|
|
/*
|
|
* (void *) for delint
|
|
*/
|
|
ndp = (char *)(void *)pnd->pnd_dirent + (size_t)*readoff;
|
|
fromdent = (struct dirent *)(void *)ndp;
|
|
|
|
if (*reslen < _DIRENT_SIZE(fromdent))
|
|
break;
|
|
|
|
memcpy(dent, fromdent, _DIRENT_SIZE(fromdent));
|
|
*readoff += _DIRENT_SIZE(fromdent);
|
|
*reslen -= _DIRENT_SIZE(fromdent);
|
|
|
|
dent = _DIRENT_NEXT(dent);
|
|
}
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_READDIR)
|
|
DPRINTF("%s: readoff = %"PRId64", "
|
|
"pnd->pnd_dirent_len = %"PRId64"\n",
|
|
__func__, *readoff, pnd->pnd_dirent_len);
|
|
#endif
|
|
if (*readoff >= pnd->pnd_dirent_len) {
|
|
free(pnd->pnd_dirent);
|
|
pnd->pnd_dirent = NULL;
|
|
pnd->pnd_dirent_len = 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
static void
|
|
node_ref(puffs_cookie_t opc)
|
|
{
|
|
struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (pnd->pnd_flags & PND_INVALID)
|
|
DERRX(EX_SOFTWARE, "Use of freed node opc = %p", opc);
|
|
#endif /* PERFUSE_DEBUG */
|
|
|
|
pnd->pnd_ref++;
|
|
return;
|
|
}
|
|
|
|
static void
|
|
node_rele(puffs_cookie_t opc)
|
|
{
|
|
struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (pnd->pnd_flags & PND_INVALID)
|
|
DERRX(EX_SOFTWARE, "Use of freed node opc = %p", opc);
|
|
#endif /* PERFUSE_DEBUG */
|
|
|
|
pnd->pnd_ref--;
|
|
|
|
if (pnd->pnd_ref == 0)
|
|
(void)dequeue_requests(opc, PCQ_REF, DEQUEUE_ALL);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
requeue_request(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
enum perfuse_qtype type)
|
|
{
|
|
struct perfuse_cc_queue pcq;
|
|
struct perfuse_node_data *pnd;
|
|
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
pcq.pcq_type = type;
|
|
pcq.pcq_cc = puffs_cc_getcc(pu);
|
|
TAILQ_INSERT_TAIL(&pnd->pnd_pcq, &pcq, pcq_next);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_REQUEUE)
|
|
DPRINTF("%s: REQUEUE opc = %p, pcc = %p (%s)\n",
|
|
__func__, (void *)opc, pcq.pcq_cc,
|
|
perfuse_qtypestr[type]);
|
|
#endif
|
|
|
|
puffs_cc_yield(pcq.pcq_cc);
|
|
TAILQ_REMOVE(&pnd->pnd_pcq, &pcq, pcq_next);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_REQUEUE)
|
|
DPRINTF("%s: RESUME opc = %p, pcc = %p (%s)\n",
|
|
__func__, (void *)opc, pcq.pcq_cc,
|
|
perfuse_qtypestr[type]);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
static int
|
|
dequeue_requests(puffs_cookie_t opc, enum perfuse_qtype type, int max)
|
|
{
|
|
struct perfuse_cc_queue *pcq;
|
|
struct perfuse_node_data *pnd;
|
|
int dequeued;
|
|
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
dequeued = 0;
|
|
TAILQ_FOREACH(pcq, &pnd->pnd_pcq, pcq_next) {
|
|
if (pcq->pcq_type != type)
|
|
continue;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_REQUEUE)
|
|
DPRINTF("%s: SCHEDULE opc = %p, pcc = %p (%s)\n",
|
|
__func__, (void *)opc, pcq->pcq_cc,
|
|
perfuse_qtypestr[type]);
|
|
#endif
|
|
puffs_cc_schedule(pcq->pcq_cc);
|
|
|
|
if (++dequeued == max)
|
|
break;
|
|
}
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_REQUEUE)
|
|
DPRINTF("%s: DONE opc = %p\n", __func__, (void *)opc);
|
|
#endif
|
|
|
|
return dequeued;
|
|
}
|
|
|
|
void
|
|
perfuse_fs_init(struct puffs_usermount *pu)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_init_in *fii;
|
|
struct fuse_init_out *fio;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
|
|
if (puffs_mount(pu, ps->ps_target, ps->ps_mountflags, ps->ps_root) != 0)
|
|
DERR(EX_OSERR, "%s: puffs_mount failed", __func__);
|
|
|
|
/*
|
|
* Linux 2.6.34.1 sends theses flags:
|
|
* FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_ATOMIC_O_TRUNC
|
|
* FUSE_EXPORT_SUPPORT | FUSE_BIG_WRITES | FUSE_DONT_MASK
|
|
*
|
|
* Linux also sets max_readahead at 32 pages (128 kB)
|
|
*
|
|
* ps_new_msg() is called with NULL creds, which will
|
|
* be interpreted as FUSE superuser.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, 0, FUSE_INIT, sizeof(*fii), NULL);
|
|
fii = GET_INPAYLOAD(ps, pm, fuse_init_in);
|
|
fii->major = FUSE_KERNEL_VERSION;
|
|
fii->minor = FUSE_KERNEL_MINOR_VERSION;
|
|
fii->max_readahead = (unsigned int)(32 * sysconf(_SC_PAGESIZE));
|
|
fii->flags = (FUSE_ASYNC_READ|FUSE_POSIX_LOCKS|FUSE_ATOMIC_O_TRUNC);
|
|
|
|
if ((error = xchg_msg(pu, 0, pm, sizeof(*fio), wait_reply)) != 0)
|
|
DERRX(EX_SOFTWARE, "init message exchange failed (%d)", error);
|
|
|
|
fio = GET_OUTPAYLOAD(ps, pm, fuse_init_out);
|
|
ps->ps_max_readahead = fio->max_readahead;
|
|
ps->ps_max_write = fio->max_write;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
return;
|
|
}
|
|
|
|
int
|
|
perfuse_fs_unmount(struct puffs_usermount *pu, int flags)
|
|
{
|
|
perfuse_msg_t *pm;
|
|
struct perfuse_state *ps;
|
|
puffs_cookie_t opc;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
opc = (puffs_cookie_t)puffs_getroot(pu);
|
|
|
|
/*
|
|
* ps_new_msg() is called with NULL creds, which will
|
|
* be interpreted as FUSE superuser.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_DESTROY, 0, NULL);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0){
|
|
DWARN("unmount %s", ps->ps_target);
|
|
if (!(flags & MNT_FORCE))
|
|
return error;
|
|
else
|
|
error = 0;
|
|
} else {
|
|
ps->ps_destroy_msg(pm);
|
|
}
|
|
|
|
ps->ps_umount(pu);
|
|
|
|
if (perfuse_diagflags & PDF_MISC)
|
|
DPRINTF("%s unmounted, exit\n", ps->ps_target);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
perfuse_fs_statvfs(struct puffs_usermount *pu, struct statvfs *svfsb)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
puffs_cookie_t opc;
|
|
struct fuse_statfs_out *fso;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
opc = (puffs_cookie_t)puffs_getroot(pu);
|
|
|
|
/*
|
|
* ps_new_msg() is called with NULL creds, which will
|
|
* be interpreted as FUSE superuser.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_STATFS, 0, NULL);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, sizeof(*fso), wait_reply)) != 0)
|
|
return error;
|
|
|
|
fso = GET_OUTPAYLOAD(ps, pm, fuse_statfs_out);
|
|
svfsb->f_flag = ps->ps_mountflags;
|
|
svfsb->f_bsize = fso->st.bsize;
|
|
svfsb->f_frsize = fso->st.frsize;
|
|
svfsb->f_iosize = ((struct puffs_node *)opc)->pn_va.va_blocksize;
|
|
svfsb->f_blocks = fso->st.blocks;
|
|
svfsb->f_bfree = fso->st.bfree;
|
|
svfsb->f_bavail = fso->st.bavail;
|
|
svfsb->f_bresvd = fso->st.bfree - fso->st.bavail;
|
|
svfsb->f_files = fso->st.files;
|
|
svfsb->f_ffree = fso->st.ffree;
|
|
svfsb->f_favail = fso->st.ffree;/* files not reserved for root */
|
|
svfsb->f_fresvd = 0; /* files reserved for root */
|
|
|
|
svfsb->f_syncreads = ps->ps_syncreads;
|
|
svfsb->f_syncwrites = ps->ps_syncwrites;
|
|
|
|
svfsb->f_asyncreads = ps->ps_asyncreads;
|
|
svfsb->f_asyncwrites = ps->ps_asyncwrites;
|
|
|
|
(void)memcpy(&svfsb->f_fsidx, &ps->ps_fsid, sizeof(ps->ps_fsid));
|
|
svfsb->f_fsid = (unsigned long)ps->ps_fsid;
|
|
svfsb->f_namemax = MAXPATHLEN; /* XXX */
|
|
svfsb->f_owner = ps->ps_owner_uid;
|
|
|
|
(void)strlcpy(svfsb->f_mntonname, ps->ps_target, _VFS_NAMELEN);
|
|
|
|
if (ps->ps_filesystemtype != NULL)
|
|
(void)strlcpy(svfsb->f_fstypename,
|
|
ps->ps_filesystemtype, _VFS_NAMELEN);
|
|
else
|
|
(void)strlcpy(svfsb->f_fstypename, "fuse", _VFS_NAMELEN);
|
|
|
|
if (ps->ps_source != NULL)
|
|
strlcpy(svfsb->f_mntfromname, ps->ps_source, _VFS_NAMELEN);
|
|
else
|
|
strlcpy(svfsb->f_mntfromname, _PATH_FUSE, _VFS_NAMELEN);
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
perfuse_fs_sync(struct puffs_usermount *pu, int waitfor,
|
|
const struct puffs_cred *pcr)
|
|
{
|
|
/*
|
|
* FUSE does not seem to have a FS sync callback.
|
|
* Maybe do not even register this callback
|
|
*/
|
|
return puffs_fsnop_sync(pu, waitfor, pcr);
|
|
}
|
|
|
|
/* ARGSUSED0 */
|
|
int
|
|
perfuse_fs_fhtonode(struct puffs_usermount *pu, void *fid, size_t fidsize,
|
|
struct puffs_newinfo *pni)
|
|
{
|
|
DERRX(EX_SOFTWARE, "%s: UNIMPLEMENTED (FATAL)", __func__);
|
|
return 0;
|
|
}
|
|
|
|
/* ARGSUSED0 */
|
|
int
|
|
perfuse_fs_nodetofh(struct puffs_usermount *pu, puffs_cookie_t cookie,
|
|
void *fid, size_t *fidsize)
|
|
{
|
|
DERRX(EX_SOFTWARE, "%s: UNIMPLEMENTED (FATAL)", __func__);
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
/* ARGSUSED0 */
|
|
void
|
|
perfuse_fs_extattrctl(struct puffs_usermount *pu, int cmd,
|
|
puffs_cookie_t *cookie, int flags, int namespace, const char *attrname)
|
|
{
|
|
DERRX(EX_SOFTWARE, "%s: UNIMPLEMENTED (FATAL)", __func__);
|
|
return 0;
|
|
}
|
|
#endif /* 0 */
|
|
|
|
/* ARGSUSED0 */
|
|
void
|
|
perfuse_fs_suspend(struct puffs_usermount *pu, int status)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
int
|
|
perfuse_node_lookup(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct puffs_newinfo *pni, const struct puffs_cn *pcn)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct puffs_node *pn;
|
|
mode_t mode;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
node_ref(opc);
|
|
|
|
/*
|
|
* Check permissions
|
|
*/
|
|
switch(pcn->pcn_nameiop) {
|
|
case NAMEI_DELETE: /* FALLTHROUGH */
|
|
case NAMEI_RENAME: /* FALLTHROUGH */
|
|
case NAMEI_CREATE:
|
|
if (pcn->pcn_flags & NAMEI_ISLASTCN)
|
|
mode = PUFFS_VEXEC|PUFFS_VWRITE;
|
|
else
|
|
mode = PUFFS_VEXEC;
|
|
break;
|
|
case NAMEI_LOOKUP: /* FALLTHROUGH */
|
|
default:
|
|
mode = PUFFS_VEXEC;
|
|
break;
|
|
}
|
|
|
|
if ((error = mode_access(opc, pcn->pcn_cred, mode)) != 0)
|
|
goto out;
|
|
|
|
error = node_lookup_common(pu, (puffs_cookie_t)opc, pni,
|
|
pcn->pcn_name, pcn->pcn_cred, &pn);
|
|
|
|
if (error != 0)
|
|
goto out;
|
|
|
|
/*
|
|
* Kernel would kill us if the filesystem returned the parent
|
|
* itself. If we want to live, hide that!
|
|
*/
|
|
if ((opc == (puffs_cookie_t)pn) && (strcmp(pcn->pcn_name, ".") != 0)) {
|
|
DERRX(EX_SOFTWARE, "lookup \"%s\" in \"%s\" returned parent",
|
|
pcn->pcn_name, perfuse_node_path(ps, opc));
|
|
/* NOTREACHED */
|
|
error = ESTALE;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Removed node
|
|
*/
|
|
if (PERFUSE_NODE_DATA(pn)->pnd_flags & PND_REMOVED) {
|
|
error = ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Check for sticky bit. Unfortunately there is no way to
|
|
* do this before creating the puffs_node, since we require
|
|
* this operation to get the node owner.
|
|
*/
|
|
switch (pcn->pcn_nameiop) {
|
|
case NAMEI_DELETE: /* FALLTHROUGH */
|
|
case NAMEI_RENAME:
|
|
error = sticky_access(opc, pn, pcn->pcn_cred);
|
|
if (error != 0) {
|
|
(void)perfuse_node_reclaim(pu, pn);
|
|
goto out;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
PERFUSE_NODE_DATA(pn)->pnd_puffs_nlookup++;
|
|
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_create(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct puffs_newinfo *pni, const struct puffs_cn *pcn,
|
|
const struct vattr *vap)
|
|
{
|
|
perfuse_msg_t *pm;
|
|
struct perfuse_state *ps;
|
|
struct fuse_create_in *fci;
|
|
struct fuse_entry_out *feo;
|
|
struct fuse_open_out *foo;
|
|
struct puffs_node *pn;
|
|
const char *name;
|
|
size_t namelen;
|
|
size_t len;
|
|
int error;
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED)
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
|
|
/*
|
|
* If create is unimplemented: Check that it does not
|
|
* already exists, and if not, do mknod and open
|
|
*/
|
|
ps = puffs_getspecific(pu);
|
|
if (ps->ps_flags & PS_NO_CREAT) {
|
|
error = node_lookup_common(pu, opc, NULL, pcn->pcn_name,
|
|
pcn->pcn_cred, &pn);
|
|
if (error == 0) {
|
|
(void)perfuse_node_reclaim(pu, pn);
|
|
error = EEXIST;
|
|
goto out;
|
|
}
|
|
|
|
error = perfuse_node_mknod(pu, opc, pni, pcn, vap);
|
|
if (error != 0)
|
|
goto out;
|
|
|
|
error = node_lookup_common(pu, opc, NULL, pcn->pcn_name,
|
|
pcn->pcn_cred, &pn);
|
|
if (error != 0)
|
|
goto out;
|
|
|
|
/*
|
|
* FUSE does the open at create time, while
|
|
* NetBSD will open in a subsequent operation.
|
|
* We need to open now, in order to retain FUSE
|
|
* semantics. The calling process will not get
|
|
* a file descriptor before the kernel sends
|
|
* the open operation.
|
|
*/
|
|
error = perfuse_node_open(pu, (puffs_cookie_t)pn,
|
|
FWRITE, pcn->pcn_cred);
|
|
goto out;
|
|
}
|
|
|
|
name = pcn->pcn_name;
|
|
namelen = pcn->pcn_namelen + 1;
|
|
len = sizeof(*fci) + namelen;
|
|
|
|
/*
|
|
* flags should use O_WRONLY instead of O_RDWR, but it
|
|
* breaks when the caller tries to read from file.
|
|
*
|
|
* mode must contain file type (ie: S_IFREG), use VTTOIF(vap->va_type)
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_CREATE, len, pcn->pcn_cred);
|
|
fci = GET_INPAYLOAD(ps, pm, fuse_create_in);
|
|
fci->flags = O_CREAT | O_TRUNC | O_RDWR;
|
|
fci->mode = vap->va_mode | VTTOIF(vap->va_type);
|
|
fci->umask = 0; /* Seems unused by libfuse */
|
|
(void)strlcpy((char*)(void *)(fci + 1), name, namelen);
|
|
|
|
len = sizeof(*feo) + sizeof(*foo);
|
|
if ((error = xchg_msg(pu, opc, pm, len, wait_reply)) != 0) {
|
|
/*
|
|
* create is unimplmented, remember it for later,
|
|
* and start over using mknod and open instead.
|
|
*/
|
|
if (error == ENOSYS) {
|
|
ps->ps_flags |= PS_NO_CREAT;
|
|
error = perfuse_node_create(pu, opc, pni, pcn, vap);
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
feo = GET_OUTPAYLOAD(ps, pm, fuse_entry_out);
|
|
foo = (struct fuse_open_out *)(void *)(feo + 1);
|
|
if (feo->nodeid == PERFUSE_UNKNOWN_NODEID)
|
|
DERRX(EX_SOFTWARE, "%s: no nodeid", __func__);
|
|
|
|
/*
|
|
* Save the file handle and inode in node private data
|
|
* so that we can reuse it later
|
|
*/
|
|
pn = perfuse_new_pn(pu, name, opc);
|
|
perfuse_new_fh((puffs_cookie_t)pn, foo->fh, FWRITE);
|
|
PERFUSE_NODE_DATA(pn)->pnd_nodeid = feo->nodeid;
|
|
PERFUSE_NODE_DATA(pn)->pnd_puffs_nlookup++;
|
|
perfuse_node_cache(ps, pn);
|
|
|
|
fuse_attr_to_vap(ps, &pn->pn_va, &feo->attr);
|
|
pn->pn_va.va_gen = (u_long)(feo->generation);
|
|
|
|
puffs_newinfo_setcookie(pni, pn);
|
|
#ifdef PUFFS_KFLAG_CACHE_FS_TTL
|
|
puffs_newinfo_setva(pni, &pn->pn_va);
|
|
perfuse_newinfo_setttl(pni, pn, feo, NULL);
|
|
#endif /* PUFFS_KFLAG_CACHE_FS_TTL */
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & (PDF_FH|PDF_FILENAME))
|
|
DPRINTF("%s: opc = %p, file = \"%s\", flags = 0x%x "
|
|
"nodeid = 0x%"PRIx64", wfh = 0x%"PRIx64"\n",
|
|
__func__, (void *)pn, pcn->pcn_name,
|
|
PERFUSE_NODE_DATA(pn)->pnd_flags, feo->nodeid,
|
|
foo->fh);
|
|
#endif
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
|
|
int
|
|
perfuse_node_mknod(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct puffs_newinfo *pni, const struct puffs_cn *pcn,
|
|
const struct vattr *vap)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_mknod_in *fmi;
|
|
const char* path;
|
|
size_t len;
|
|
int error;
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED)
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
|
|
/*
|
|
* Only superuser can mknod objects other than
|
|
* directories, files, socks, fifo and links.
|
|
*
|
|
* Create an object require -WX permission in the parent directory
|
|
*/
|
|
switch (vap->va_type) {
|
|
case VDIR: /* FALLTHROUGH */
|
|
case VREG: /* FALLTHROUGH */
|
|
case VFIFO: /* FALLTHROUGH */
|
|
case VSOCK:
|
|
break;
|
|
default: /* VNON, VBLK, VCHR, VBAD */
|
|
if (!puffs_cred_isjuggernaut(pcn->pcn_cred)) {
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
ps = puffs_getspecific(pu);
|
|
path = pcn->pcn_name;
|
|
len = sizeof(*fmi) + pcn->pcn_namelen + 1;
|
|
|
|
/*
|
|
* mode must contain file type (ie: S_IFREG), use VTTOIF(vap->va_type)
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_MKNOD, len, pcn->pcn_cred);
|
|
fmi = GET_INPAYLOAD(ps, pm, fuse_mknod_in);
|
|
fmi->mode = vap->va_mode | VTTOIF(vap->va_type);
|
|
fmi->rdev = (uint32_t)vap->va_rdev;
|
|
fmi->umask = 0; /* Seems unused bu libfuse */
|
|
(void)strlcpy((char *)(void *)(fmi + 1), path, len - sizeof(*fmi));
|
|
|
|
error = node_mk_common(pu, opc, pni, pcn, pm);
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
|
|
int
|
|
perfuse_node_open(struct puffs_usermount *pu, puffs_cookie_t opc, int mode,
|
|
const struct puffs_cred *pcr)
|
|
{
|
|
return perfuse_node_open2(pu, opc, mode, pcr, NULL);
|
|
}
|
|
|
|
int
|
|
perfuse_node_open2(struct puffs_usermount *pu, puffs_cookie_t opc, int mode,
|
|
const struct puffs_cred *pcr, int *oflags)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
perfuse_msg_t *pm;
|
|
mode_t fmode;
|
|
int op;
|
|
struct fuse_open_in *foi;
|
|
struct fuse_open_out *foo;
|
|
struct puffs_node *pn;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
pn = (struct puffs_node *)opc;
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
error = 0;
|
|
|
|
if (pnd->pnd_flags & PND_REMOVED)
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
|
|
if (puffs_pn_getvap(pn)->va_type == VDIR)
|
|
op = FUSE_OPENDIR;
|
|
else
|
|
op = FUSE_OPEN;
|
|
|
|
/*
|
|
* libfuse docs says
|
|
* - O_CREAT and O_EXCL should never be set.
|
|
* - O_TRUNC may be used if mount option atomic_o_trunc is used XXX
|
|
*
|
|
* O_APPEND makes no sense since FUSE always sends
|
|
* the file offset for write operations. If the
|
|
* filesystem uses pwrite(), O_APPEND would cause
|
|
* the offset to be ignored and cause file corruption.
|
|
*/
|
|
mode &= ~(O_CREAT|O_EXCL|O_APPEND);
|
|
|
|
/*
|
|
* Do not open twice, and do not reopen for reading
|
|
* if we already have write handle.
|
|
*/
|
|
switch (mode & (FREAD|FWRITE)) {
|
|
case FREAD:
|
|
if (pnd->pnd_flags & (PND_RFH|PND_WFH))
|
|
goto out;
|
|
break;
|
|
case FWRITE:
|
|
if (pnd->pnd_flags & PND_WFH)
|
|
goto out;
|
|
break;
|
|
case FREAD|FWRITE:
|
|
if (pnd->pnd_flags & PND_WFH)
|
|
goto out;
|
|
|
|
/*
|
|
* Corner case: if already open for reading (PND_RFH)
|
|
* and re-opening FREAD|FWRITE, we need to reopen,
|
|
* but only for writing. Note the change on mode
|
|
* will only affect perfuse_new_fh()
|
|
*/
|
|
if (pnd->pnd_flags & PND_RFH)
|
|
mode &= ~FREAD;
|
|
break;
|
|
default:
|
|
DWARNX("open without either FREAD nor FWRITE");
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Queue open on a node so that we do not open
|
|
* twice. This would be better with read and
|
|
* write distinguished.
|
|
*/
|
|
while (pnd->pnd_flags & PND_INOPEN)
|
|
requeue_request(pu, opc, PCQ_OPEN);
|
|
pnd->pnd_flags |= PND_INOPEN;
|
|
|
|
/*
|
|
* Convert PUFFS mode to FUSE mode: convert FREAD/FWRITE
|
|
* to O_RDONLY/O_WRONLY while perserving the other options.
|
|
*/
|
|
fmode = mode & ~(FREAD|FWRITE);
|
|
fmode |= (mode & FWRITE) ? O_RDWR : O_RDONLY;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, op, sizeof(*foi), pcr);
|
|
foi = GET_INPAYLOAD(ps, pm, fuse_open_in);
|
|
foi->flags = fmode;
|
|
foi->unused = 0;
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, sizeof(*foo), wait_reply)) != 0)
|
|
goto out;
|
|
|
|
foo = GET_OUTPAYLOAD(ps, pm, fuse_open_out);
|
|
|
|
/*
|
|
* Save the file handle in node private data
|
|
* so that we can reuse it later
|
|
*/
|
|
perfuse_new_fh(opc, foo->fh, mode);
|
|
|
|
/*
|
|
* Set direct I/O if the filesystems forces it
|
|
*/
|
|
if ((foo->open_flags & FUSE_FOPEN_DIRECT_IO) && (oflags != NULL))
|
|
*oflags |= PUFFS_OPEN_IO_DIRECT;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & (PDF_FH|PDF_FILENAME))
|
|
DPRINTF("%s: opc = %p, file = \"%s\", "
|
|
"nodeid = 0x%"PRIx64", %s%sfh = 0x%"PRIx64"\n",
|
|
__func__, (void *)opc, perfuse_node_path(ps, opc),
|
|
pnd->pnd_nodeid, mode & FREAD ? "r" : "",
|
|
mode & FWRITE ? "w" : "", foo->fh);
|
|
#endif
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
out:
|
|
|
|
pnd->pnd_flags &= ~PND_INOPEN;
|
|
(void)dequeue_requests(opc, PCQ_OPEN, DEQUEUE_ALL);
|
|
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
/* ARGSUSED0 */
|
|
int
|
|
perfuse_node_close(struct puffs_usermount *pu, puffs_cookie_t opc, int flags,
|
|
const struct puffs_cred *pcr)
|
|
{
|
|
struct perfuse_node_data *pnd;
|
|
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
if (!(pnd->pnd_flags & PND_OPEN))
|
|
return EBADF;
|
|
|
|
/*
|
|
* Actual close is postponed at inactive time.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
perfuse_node_access(struct puffs_usermount *pu, puffs_cookie_t opc, int mode,
|
|
const struct puffs_cred *pcr)
|
|
{
|
|
perfuse_msg_t *pm;
|
|
struct perfuse_state *ps;
|
|
struct fuse_access_in *fai;
|
|
int error;
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED)
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
|
|
/*
|
|
* If we previously detected the filesystem does not
|
|
* implement access(), short-circuit the call and skip
|
|
* to libpuffs access() emulation.
|
|
*/
|
|
ps = puffs_getspecific(pu);
|
|
if (ps->ps_flags & PS_NO_ACCESS) {
|
|
const struct vattr *vap;
|
|
|
|
vap = puffs_pn_getvap((struct puffs_node *)opc);
|
|
|
|
error = puffs_access(IFTOVT(vap->va_mode),
|
|
vap->va_mode & ACCESSPERMS,
|
|
vap->va_uid, vap->va_gid,
|
|
(mode_t)mode, pcr);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Plain access call
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_ACCESS, sizeof(*fai), pcr);
|
|
fai = GET_INPAYLOAD(ps, pm, fuse_access_in);
|
|
fai->mask = 0;
|
|
fai->mask |= (mode & PUFFS_VREAD) ? R_OK : 0;
|
|
fai->mask |= (mode & PUFFS_VWRITE) ? W_OK : 0;
|
|
fai->mask |= (mode & PUFFS_VEXEC) ? X_OK : 0;
|
|
|
|
error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply);
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
/*
|
|
* If unimplemented, start over with emulation
|
|
*/
|
|
if (error == ENOSYS) {
|
|
ps->ps_flags |= PS_NO_ACCESS;
|
|
error = perfuse_node_access(pu, opc, mode, pcr);
|
|
}
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_getattr(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct vattr *vap, const struct puffs_cred *pcr)
|
|
{
|
|
return perfuse_node_getattr_ttl(pu, opc, vap, pcr, NULL);
|
|
}
|
|
|
|
int
|
|
perfuse_node_getattr_ttl(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct vattr *vap, const struct puffs_cred *pcr,
|
|
struct timespec *va_ttl)
|
|
{
|
|
perfuse_msg_t *pm = NULL;
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(opc);
|
|
struct fuse_getattr_in *fgi;
|
|
struct fuse_attr_out *fao;
|
|
int error = 0;
|
|
|
|
if ((pnd->pnd_flags & PND_REMOVED) && !(pnd->pnd_flags & PND_OPEN))
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
|
|
/*
|
|
* Serialize size access, see comment in perfuse_node_setattr().
|
|
*/
|
|
while (pnd->pnd_flags & PND_INRESIZE)
|
|
requeue_request(pu, opc, PCQ_RESIZE);
|
|
pnd->pnd_flags |= PND_INRESIZE;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
|
|
/*
|
|
* FUSE_GETATTR_FH must be set in fgi->flags
|
|
* if we use for fgi->fh
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_GETATTR, sizeof(*fgi), pcr);
|
|
fgi = GET_INPAYLOAD(ps, pm, fuse_getattr_in);
|
|
fgi->getattr_flags = 0;
|
|
fgi->dummy = 0;
|
|
fgi->fh = 0;
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_OPEN) {
|
|
fgi->fh = perfuse_get_fh(opc, FREAD);
|
|
fgi->getattr_flags |= FUSE_GETATTR_FH;
|
|
}
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_RESIZE)
|
|
DPRINTF(">> %s %p %" PRIu64 "\n", __func__, (void *)opc,
|
|
vap->va_size);
|
|
#endif
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, sizeof(*fao), wait_reply)) != 0)
|
|
goto out;
|
|
|
|
fao = GET_OUTPAYLOAD(ps, pm, fuse_attr_out);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_RESIZE)
|
|
DPRINTF("<< %s %p %" PRIu64 " -> %" PRIu64 "\n", __func__,
|
|
(void *)opc, vap->va_size, fao->attr.size);
|
|
#endif
|
|
|
|
/*
|
|
* We set birthtime, flags, filerev,vaflags to 0.
|
|
* This seems the best bet, since the information is
|
|
* not available from filesystem.
|
|
*/
|
|
fuse_attr_to_vap(ps, vap, &fao->attr);
|
|
|
|
if (va_ttl != NULL) {
|
|
va_ttl->tv_sec = fao->attr_valid;
|
|
va_ttl->tv_nsec = fao->attr_valid_nsec;
|
|
}
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
out:
|
|
|
|
pnd->pnd_flags &= ~PND_INRESIZE;
|
|
(void)dequeue_requests(opc, PCQ_RESIZE, DEQUEUE_ALL);
|
|
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_setattr(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
const struct vattr *vap, const struct puffs_cred *pcr)
|
|
{
|
|
return perfuse_node_setattr_ttl(pu, opc,
|
|
__UNCONST(vap), pcr, NULL, 0);
|
|
}
|
|
|
|
int
|
|
perfuse_node_setattr_ttl(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct vattr *vap, const struct puffs_cred *pcr,
|
|
struct timespec *va_ttl, int xflag)
|
|
{
|
|
perfuse_msg_t *pm;
|
|
uint64_t fh;
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
struct fuse_setattr_in *fsi;
|
|
struct fuse_attr_out *fao;
|
|
struct vattr *old_va;
|
|
enum perfuse_xchg_pb_reply reply;
|
|
int error;
|
|
#ifdef PERFUSE_DEBUG
|
|
struct vattr *old_vap;
|
|
int resize_debug = 0;
|
|
#endif
|
|
ps = puffs_getspecific(pu);
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
/*
|
|
* The only operation we can do once the file is removed
|
|
* is to resize it, and we can do it only if it is open.
|
|
* Do not even send the operation to the filesystem: the
|
|
* file is not there anymore.
|
|
*/
|
|
if (pnd->pnd_flags & PND_REMOVED) {
|
|
if (!(pnd->pnd_flags & PND_OPEN))
|
|
return ENOENT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
old_va = puffs_pn_getvap((struct puffs_node *)opc);
|
|
|
|
/*
|
|
* Check for permission to change size
|
|
* It is always allowed if we already have a write file handle
|
|
*/
|
|
if ((vap->va_size != (u_quad_t)PUFFS_VNOVAL) &&
|
|
!(pnd->pnd_flags & PND_WFH) &&
|
|
(error = mode_access(opc, pcr, PUFFS_VWRITE)) != 0)
|
|
return error;
|
|
|
|
/*
|
|
* Check for permission to change dates
|
|
*/
|
|
if (((vap->va_atime.tv_sec != (time_t)PUFFS_VNOVAL) ||
|
|
(vap->va_mtime.tv_sec != (time_t)PUFFS_VNOVAL)) &&
|
|
(puffs_access_times(old_va->va_uid, old_va->va_gid,
|
|
old_va->va_mode, 0, pcr) != 0))
|
|
return EPERM;
|
|
|
|
/*
|
|
* Check for permission to change owner and group
|
|
*/
|
|
if (((vap->va_uid != (uid_t)PUFFS_VNOVAL) ||
|
|
(vap->va_gid != (gid_t)PUFFS_VNOVAL)) &&
|
|
(puffs_access_chown(old_va->va_uid, old_va->va_gid,
|
|
vap->va_uid, vap->va_gid, pcr)) != 0)
|
|
return EPERM;
|
|
|
|
/*
|
|
* Check for sticky bit on non-directory by non root user
|
|
*/
|
|
if ((vap->va_mode != (mode_t)PUFFS_VNOVAL) &&
|
|
(vap->va_mode & S_ISTXT) && (old_va->va_type != VDIR) &&
|
|
!puffs_cred_isjuggernaut(pcr))
|
|
return EFTYPE;
|
|
|
|
/*
|
|
* Check for permission to change permissions
|
|
*/
|
|
if ((vap->va_mode != (mode_t)PUFFS_VNOVAL) &&
|
|
(puffs_access_chmod(old_va->va_uid, old_va->va_gid,
|
|
old_va->va_type, vap->va_mode, pcr)) != 0)
|
|
return EPERM;
|
|
|
|
node_ref(opc);
|
|
|
|
if (pnd->pnd_flags & PND_WFH)
|
|
fh = perfuse_get_fh(opc, FWRITE);
|
|
else
|
|
fh = FUSE_UNKNOWN_FH;
|
|
|
|
/*
|
|
* fchmod() sets mode and fh, and it may carry
|
|
* a resize as well. That may break if the
|
|
* filesystem does chmod then resize, and fails
|
|
* because it does not have permission anymore.
|
|
* We work this around by splitting into two setattr.
|
|
*/
|
|
if ((vap->va_size != (u_quad_t)PUFFS_VNOVAL) &&
|
|
(vap->va_mode != (mode_t)PUFFS_VNOVAL) &&
|
|
(fh != FUSE_UNKNOWN_FH)) {
|
|
struct vattr resize_va;
|
|
|
|
(void)memcpy(&resize_va, vap, sizeof(resize_va));
|
|
resize_va.va_mode = (mode_t)PUFFS_VNOVAL;
|
|
if ((error = perfuse_node_setattr_ttl(pu, opc, &resize_va,
|
|
pcr, va_ttl, xflag)) != 0)
|
|
goto out2;
|
|
|
|
vap->va_size = (u_quad_t)PUFFS_VNOVAL;
|
|
}
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_SETATTR, sizeof(*fsi), pcr);
|
|
fsi = GET_INPAYLOAD(ps, pm, fuse_setattr_in);
|
|
fsi->valid = 0;
|
|
|
|
/*
|
|
* Get a fh if the node is open for writing
|
|
*/
|
|
if (fh != FUSE_UNKNOWN_FH) {
|
|
fsi->fh = fh;
|
|
fsi->valid |= FUSE_FATTR_FH;
|
|
}
|
|
|
|
|
|
if (vap->va_size != (u_quad_t)PUFFS_VNOVAL) {
|
|
fsi->size = vap->va_size;
|
|
fsi->valid |= FUSE_FATTR_SIZE;
|
|
|
|
/*
|
|
* Serialize anything that can touch file size
|
|
* to avoid reordered GETATTR and SETATTR.
|
|
* Out of order SETATTR can report stale size,
|
|
* which will cause the kernel to truncate the file.
|
|
* XXX Probably useless now we have a lock on GETATTR
|
|
*/
|
|
while (pnd->pnd_flags & PND_INRESIZE)
|
|
requeue_request(pu, opc, PCQ_RESIZE);
|
|
pnd->pnd_flags |= PND_INRESIZE;
|
|
}
|
|
|
|
/*
|
|
* When not sending a time field, still fill with
|
|
* current value, as the filesystem may just reset
|
|
* the field to Epoch even if fsi->valid bit is
|
|
* not set (GlusterFS does that).
|
|
*/
|
|
if (vap->va_atime.tv_sec != (time_t)PUFFS_VNOVAL) {
|
|
fsi->atime = vap->va_atime.tv_sec;
|
|
fsi->atimensec = (uint32_t)vap->va_atime.tv_nsec;
|
|
fsi->valid |= FUSE_FATTR_ATIME;
|
|
} else {
|
|
fsi->atime = old_va->va_atime.tv_sec;
|
|
fsi->atimensec = (uint32_t)old_va->va_atime.tv_nsec;
|
|
}
|
|
|
|
if (vap->va_mtime.tv_sec != (time_t)PUFFS_VNOVAL) {
|
|
fsi->mtime = vap->va_mtime.tv_sec;
|
|
fsi->mtimensec = (uint32_t)vap->va_mtime.tv_nsec;
|
|
fsi->valid |= FUSE_FATTR_MTIME;
|
|
} else {
|
|
fsi->mtime = old_va->va_mtime.tv_sec;
|
|
fsi->mtimensec = (uint32_t)old_va->va_mtime.tv_nsec;
|
|
}
|
|
|
|
if (vap->va_mode != (mode_t)PUFFS_VNOVAL) {
|
|
fsi->mode = vap->va_mode;
|
|
fsi->valid |= FUSE_FATTR_MODE;
|
|
}
|
|
|
|
if (vap->va_uid != (uid_t)PUFFS_VNOVAL) {
|
|
fsi->uid = vap->va_uid;
|
|
fsi->valid |= FUSE_FATTR_UID;
|
|
}
|
|
|
|
if (vap->va_gid != (gid_t)PUFFS_VNOVAL) {
|
|
fsi->gid = vap->va_gid;
|
|
fsi->valid |= FUSE_FATTR_GID;
|
|
}
|
|
|
|
if (pnd->pnd_lock_owner != 0) {
|
|
fsi->lock_owner = pnd->pnd_lock_owner;
|
|
fsi->valid |= FUSE_FATTR_LOCKOWNER;
|
|
}
|
|
|
|
#ifndef PUFFS_KFLAG_NOFLUSH_META
|
|
/*
|
|
* ftruncate() sends only va_size, and metadata cache
|
|
* flush adds va_atime and va_mtime. Some FUSE
|
|
* filesystems will attempt to detect ftruncate by
|
|
* checking for FATTR_SIZE being set without
|
|
* FATTR_UID|FATTR_GID|FATTR_ATIME|FATTR_MTIME|FATTR_MODE
|
|
*
|
|
* Try to adapt and remove FATTR_ATIME|FATTR_MTIME
|
|
* if we suspect a ftruncate().
|
|
*/
|
|
if ((vap->va_size != (u_quad_t)PUFFS_VNOVAL) &&
|
|
((vap->va_mode == (mode_t)PUFFS_VNOVAL) &&
|
|
(vap->va_uid == (uid_t)PUFFS_VNOVAL) &&
|
|
(vap->va_gid == (gid_t)PUFFS_VNOVAL))) {
|
|
fsi->atime = 0;
|
|
fsi->atimensec = 0;
|
|
fsi->mtime = 0;
|
|
fsi->mtimensec = 0;
|
|
fsi->valid &= ~(FUSE_FATTR_ATIME|FUSE_FATTR_MTIME);
|
|
}
|
|
|
|
/*
|
|
* If only atime is changed, discard the operation: it
|
|
* happens after read, and in that case the filesystem
|
|
* already updaed atime. NB: utimes() also change mtime.
|
|
*/
|
|
if (fsi->valid == FUSE_FATTR_ATIME)
|
|
fsi->valid &= ~FUSE_FATTR_ATIME;
|
|
#endif /* PUFFS_KFLAG_NOFLUSH_META */
|
|
|
|
/*
|
|
* If nothing remain, discard the operation.
|
|
*/
|
|
if (!(fsi->valid & (FUSE_FATTR_SIZE|FUSE_FATTR_ATIME|FUSE_FATTR_MTIME|
|
|
FUSE_FATTR_MODE|FUSE_FATTR_UID|FUSE_FATTR_GID))) {
|
|
error = 0;
|
|
ps->ps_destroy_msg(pm);
|
|
goto out;
|
|
}
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
old_vap = puffs_pn_getvap((struct puffs_node *)opc);
|
|
|
|
if ((perfuse_diagflags & PDF_RESIZE) &&
|
|
(old_vap->va_size != (u_quad_t)PUFFS_VNOVAL)) {
|
|
resize_debug = 1;
|
|
|
|
DPRINTF(">> %s %p %" PRIu64 " -> %" PRIu64 "\n", __func__,
|
|
(void *)opc,
|
|
puffs_pn_getvap((struct puffs_node *)opc)->va_size,
|
|
fsi->size);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Do not honour FAF when changing size. How do
|
|
* you want such a thing to work?
|
|
*/
|
|
reply = wait_reply;
|
|
#ifdef PUFFS_SETATTR_FAF
|
|
if ((xflag & PUFFS_SETATTR_FAF) && !(fsi->valid & FUSE_FATTR_SIZE))
|
|
reply = no_reply;
|
|
#endif
|
|
if ((error = xchg_msg(pu, opc, pm, sizeof(*fao), reply)) != 0)
|
|
goto out;
|
|
|
|
if (reply == no_reply)
|
|
goto out;
|
|
|
|
/*
|
|
* Copy back the new values
|
|
*/
|
|
fao = GET_OUTPAYLOAD(ps, pm, fuse_attr_out);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (resize_debug)
|
|
DPRINTF("<< %s %p %" PRIu64 " -> %" PRIu64 "\n", __func__,
|
|
(void *)opc, old_vap->va_size, fao->attr.size);
|
|
#endif
|
|
|
|
fuse_attr_to_vap(ps, old_va, &fao->attr);
|
|
|
|
if (va_ttl != NULL) {
|
|
va_ttl->tv_sec = fao->attr_valid;
|
|
va_ttl->tv_nsec = fao->attr_valid_nsec;
|
|
(void)memcpy(vap, old_va, sizeof(*vap));
|
|
}
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
if (pnd->pnd_flags & PND_INRESIZE) {
|
|
pnd->pnd_flags &= ~PND_INRESIZE;
|
|
(void)dequeue_requests(opc, PCQ_RESIZE, DEQUEUE_ALL);
|
|
}
|
|
|
|
out2:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_poll(struct puffs_usermount *pu, puffs_cookie_t opc, int *events)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_poll_in *fpi;
|
|
struct fuse_poll_out *fpo;
|
|
int error;
|
|
|
|
node_ref(opc);
|
|
ps = puffs_getspecific(pu);
|
|
/*
|
|
* kh is set if FUSE_POLL_SCHEDULE_NOTIFY is set.
|
|
*
|
|
* XXX ps_new_msg() is called with NULL creds, which will
|
|
* be interpreted as FUSE superuser. We have no way to
|
|
* know the requesting process' credential, but since poll
|
|
* is supposed to operate on a file that has been open,
|
|
* permission should have already been checked at open time.
|
|
* That still may breaks on filesystems that provides odd
|
|
* semantics.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_POLL, sizeof(*fpi), NULL);
|
|
fpi = GET_INPAYLOAD(ps, pm, fuse_poll_in);
|
|
fpi->fh = perfuse_get_fh(opc, FREAD);
|
|
fpi->kh = 0;
|
|
fpi->flags = 0;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FH)
|
|
DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", "
|
|
"fh = 0x%"PRIx64"\n", __func__, (void *)opc,
|
|
PERFUSE_NODE_DATA(opc)->pnd_nodeid, fpi->fh);
|
|
#endif
|
|
if ((error = xchg_msg(pu, opc, pm, sizeof(*fpo), wait_reply)) != 0)
|
|
goto out;
|
|
|
|
fpo = GET_OUTPAYLOAD(ps, pm, fuse_poll_out);
|
|
*events = fpo->revents;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
/* ARGSUSED2 */
|
|
int
|
|
perfuse_node_fsync(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
const struct puffs_cred *pcr, int flags, off_t offlo, off_t offhi)
|
|
{
|
|
int op;
|
|
perfuse_msg_t *pm;
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
struct fuse_fsync_in *ffi;
|
|
uint64_t fh;
|
|
int error = 0;
|
|
|
|
pm = NULL;
|
|
ps = puffs_getspecific(pu);
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
/*
|
|
* No need to sync a removed node
|
|
*/
|
|
if (pnd->pnd_flags & PND_REMOVED)
|
|
return 0;
|
|
|
|
/*
|
|
* We do not sync closed files. They have been
|
|
* sync at inactive time already.
|
|
*/
|
|
if (!(pnd->pnd_flags & PND_OPEN))
|
|
return 0;
|
|
|
|
node_ref(opc);
|
|
|
|
if (puffs_pn_getvap((struct puffs_node *)opc)->va_type == VDIR)
|
|
op = FUSE_FSYNCDIR;
|
|
else /* VREG but also other types such as VLNK */
|
|
op = FUSE_FSYNC;
|
|
|
|
/*
|
|
* Do not sync if there are no change to sync
|
|
* XXX remove that test on files if we implement mmap
|
|
*/
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_SYNC)
|
|
DPRINTF("%s: TEST opc = %p, file = \"%s\" is %sdirty\n",
|
|
__func__, (void*)opc, perfuse_node_path(ps, opc),
|
|
pnd->pnd_flags & PND_DIRTY ? "" : "not ");
|
|
#endif
|
|
if (!(pnd->pnd_flags & PND_DIRTY))
|
|
goto out;
|
|
|
|
/*
|
|
* It seems NetBSD can call fsync without open first
|
|
* glusterfs complain in such a situation:
|
|
* "FSYNC() ERR => -1 (Invalid argument)"
|
|
* The file will be closed at inactive time.
|
|
*
|
|
* We open the directory for reading in order to sync.
|
|
* This sounds rather counterintuitive, but it works.
|
|
*/
|
|
if (!(pnd->pnd_flags & PND_WFH)) {
|
|
if ((error = perfuse_node_open(pu, opc, FREAD, pcr)) != 0)
|
|
goto out;
|
|
}
|
|
|
|
if (op == FUSE_FSYNCDIR)
|
|
fh = perfuse_get_fh(opc, FREAD);
|
|
else
|
|
fh = perfuse_get_fh(opc, FWRITE);
|
|
|
|
/*
|
|
* If fsync_flags is set, meta data should not be flushed.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, op, sizeof(*ffi), pcr);
|
|
ffi = GET_INPAYLOAD(ps, pm, fuse_fsync_in);
|
|
ffi->fh = fh;
|
|
ffi->fsync_flags = (flags & FFILESYNC) ? 0 : 1;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FH)
|
|
DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", fh = 0x%"PRIx64"\n",
|
|
__func__, (void *)opc,
|
|
PERFUSE_NODE_DATA(opc)->pnd_nodeid, ffi->fh);
|
|
#endif
|
|
|
|
if ((error = xchg_msg(pu, opc, pm,
|
|
NO_PAYLOAD_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
/*
|
|
* No reply beyond fuse_out_header: nothing to do on success
|
|
* just clear the dirty flag
|
|
*/
|
|
pnd->pnd_flags &= ~PND_DIRTY;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_SYNC)
|
|
DPRINTF("%s: CLEAR opc = %p, file = \"%s\"\n",
|
|
__func__, (void*)opc, perfuse_node_path(ps, opc));
|
|
#endif
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
/*
|
|
* ENOSYS is not returned to kernel,
|
|
*/
|
|
if (error == ENOSYS)
|
|
error = 0;
|
|
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_remove(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
puffs_cookie_t targ, const struct puffs_cn *pcn)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
perfuse_msg_t *pm;
|
|
char *path;
|
|
const char *name;
|
|
size_t len;
|
|
int error;
|
|
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
if ((pnd->pnd_flags & PND_REMOVED) ||
|
|
(PERFUSE_NODE_DATA(targ)->pnd_flags & PND_REMOVED))
|
|
return ENOENT;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (targ == NULL)
|
|
DERRX(EX_SOFTWARE, "%s: targ is NULL", __func__);
|
|
|
|
if (perfuse_diagflags & (PDF_FH|PDF_FILENAME))
|
|
DPRINTF("%s: opc = %p, remove opc = %p, file = \"%s\"\n",
|
|
__func__, (void *)opc, (void *)targ, pcn->pcn_name);
|
|
#endif
|
|
node_ref(opc);
|
|
node_ref(targ);
|
|
|
|
/*
|
|
* Await for all operations on the deleted node to drain,
|
|
* as the filesystem may be confused to have it deleted
|
|
* during a getattr
|
|
*/
|
|
while (PERFUSE_NODE_DATA(targ)->pnd_inxchg)
|
|
requeue_request(pu, targ, PCQ_AFTERXCHG);
|
|
|
|
ps = puffs_getspecific(pu);
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
name = pcn->pcn_name;
|
|
len = pcn->pcn_namelen + 1;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_UNLINK, len, pcn->pcn_cred);
|
|
path = _GET_INPAYLOAD(ps, pm, char *);
|
|
(void)strlcpy(path, name, len);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
perfuse_cache_flush(targ);
|
|
PERFUSE_NODE_DATA(targ)->pnd_flags |= PND_REMOVED;
|
|
|
|
if (!(PERFUSE_NODE_DATA(targ)->pnd_flags & PND_OPEN))
|
|
puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
|
|
|
|
/*
|
|
* The parent directory needs a sync
|
|
*/
|
|
PERFUSE_NODE_DATA(opc)->pnd_flags |= PND_DIRTY;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FILENAME)
|
|
DPRINTF("%s: remove nodeid = 0x%"PRIx64" file = \"%s\"\n",
|
|
__func__, PERFUSE_NODE_DATA(targ)->pnd_nodeid,
|
|
pcn->pcn_name);
|
|
#endif
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
node_rele(targ);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_link(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
puffs_cookie_t targ, const struct puffs_cn *pcn)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
const char *name;
|
|
size_t len;
|
|
struct puffs_node *pn;
|
|
struct fuse_link_in *fli;
|
|
int error;
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED)
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
node_ref(targ);
|
|
ps = puffs_getspecific(pu);
|
|
pn = (struct puffs_node *)targ;
|
|
name = pcn->pcn_name;
|
|
len = sizeof(*fli) + pcn->pcn_namelen + 1;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_LINK, len, pcn->pcn_cred);
|
|
fli = GET_INPAYLOAD(ps, pm, fuse_link_in);
|
|
fli->oldnodeid = PERFUSE_NODE_DATA(pn)->pnd_nodeid;
|
|
(void)strlcpy((char *)(void *)(fli + 1), name, len - sizeof(*fli));
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
node_rele(targ);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_rename(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
puffs_cookie_t src, const struct puffs_cn *pcn_src,
|
|
puffs_cookie_t targ_dir, puffs_cookie_t targ,
|
|
const struct puffs_cn *pcn_targ)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *dstdir_pnd;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_rename_in *fri;
|
|
const char *newname;
|
|
const char *oldname;
|
|
char *np;
|
|
int error;
|
|
size_t len;
|
|
size_t newname_len;
|
|
size_t oldname_len;
|
|
|
|
if ((PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) ||
|
|
(PERFUSE_NODE_DATA(src)->pnd_flags & PND_REMOVED) ||
|
|
(PERFUSE_NODE_DATA(targ_dir)->pnd_flags & PND_REMOVED))
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
node_ref(src);
|
|
|
|
/*
|
|
* Await for all operations on the deleted node to drain,
|
|
* as the filesystem may be confused to have it deleted
|
|
* during a getattr
|
|
*/
|
|
if ((struct puffs_node *)targ != NULL) {
|
|
node_ref(targ);
|
|
while (PERFUSE_NODE_DATA(targ)->pnd_inxchg)
|
|
requeue_request(pu, targ, PCQ_AFTERXCHG);
|
|
} else {
|
|
while (PERFUSE_NODE_DATA(src)->pnd_inxchg)
|
|
requeue_request(pu, src, PCQ_AFTERXCHG);
|
|
}
|
|
|
|
ps = puffs_getspecific(pu);
|
|
newname = pcn_targ->pcn_name;
|
|
newname_len = pcn_targ->pcn_namelen + 1;
|
|
oldname = pcn_src->pcn_name;
|
|
oldname_len = pcn_src->pcn_namelen + 1;
|
|
|
|
len = sizeof(*fri) + oldname_len + newname_len;
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_RENAME, len, pcn_targ->pcn_cred);
|
|
fri = GET_INPAYLOAD(ps, pm, fuse_rename_in);
|
|
fri->newdir = PERFUSE_NODE_DATA(targ_dir)->pnd_nodeid;
|
|
np = (char *)(void *)(fri + 1);
|
|
(void)strlcpy(np, oldname, oldname_len);
|
|
np += oldname_len;
|
|
(void)strlcpy(np, newname, newname_len);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
|
|
/*
|
|
* Record new parent nodeid
|
|
*/
|
|
dstdir_pnd = PERFUSE_NODE_DATA(targ_dir);
|
|
PERFUSE_NODE_DATA(src)->pnd_parent_nodeid = dstdir_pnd->pnd_nodeid;
|
|
|
|
if (opc != targ_dir)
|
|
dstdir_pnd->pnd_flags |= PND_DIRTY;
|
|
|
|
if (strcmp(newname, "..") != 0)
|
|
(void)strlcpy(PERFUSE_NODE_DATA(src)->pnd_name,
|
|
newname, MAXPATHLEN);
|
|
else
|
|
PERFUSE_NODE_DATA(src)->pnd_name[0] = 0; /* forget name */
|
|
|
|
PERFUSE_NODE_DATA(opc)->pnd_flags |= PND_DIRTY;
|
|
|
|
if ((struct puffs_node *)targ != NULL) {
|
|
perfuse_cache_flush(targ);
|
|
PERFUSE_NODE_DATA(targ)->pnd_flags |= PND_REMOVED;
|
|
}
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FILENAME)
|
|
DPRINTF("%s: nodeid = 0x%"PRIx64" file = \"%s\" renamed \"%s\" "
|
|
"nodeid = 0x%"PRIx64" -> nodeid = 0x%"PRIx64" \"%s\"\n",
|
|
__func__, PERFUSE_NODE_DATA(src)->pnd_nodeid,
|
|
pcn_src->pcn_name, pcn_targ->pcn_name,
|
|
PERFUSE_NODE_DATA(opc)->pnd_nodeid,
|
|
PERFUSE_NODE_DATA(targ_dir)->pnd_nodeid,
|
|
perfuse_node_path(ps, targ_dir));
|
|
#endif
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
node_rele(src);
|
|
if ((struct puffs_node *)targ != NULL)
|
|
node_rele(targ);
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_mkdir(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct puffs_newinfo *pni, const struct puffs_cn *pcn,
|
|
const struct vattr *vap)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_mkdir_in *fmi;
|
|
const char *path;
|
|
size_t len;
|
|
int error;
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED)
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
ps = puffs_getspecific(pu);
|
|
path = pcn->pcn_name;
|
|
len = sizeof(*fmi) + pcn->pcn_namelen + 1;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_MKDIR, len, pcn->pcn_cred);
|
|
fmi = GET_INPAYLOAD(ps, pm, fuse_mkdir_in);
|
|
fmi->mode = vap->va_mode;
|
|
fmi->umask = 0; /* Seems unused by libfuse? */
|
|
(void)strlcpy((char *)(void *)(fmi + 1), path, len - sizeof(*fmi));
|
|
|
|
error = node_mk_common(pu, opc, pni, pcn, pm);
|
|
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
|
|
int
|
|
perfuse_node_rmdir(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
puffs_cookie_t targ, const struct puffs_cn *pcn)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
perfuse_msg_t *pm;
|
|
char *path;
|
|
const char *name;
|
|
size_t len;
|
|
int error;
|
|
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
if ((pnd->pnd_flags & PND_REMOVED) ||
|
|
(PERFUSE_NODE_DATA(targ)->pnd_flags & PND_REMOVED))
|
|
return ENOENT;
|
|
|
|
/*
|
|
* Attempt to rmdir dir/.. shoud raise ENOTEMPTY
|
|
*/
|
|
if (PERFUSE_NODE_DATA(targ)->pnd_nodeid == pnd->pnd_parent_nodeid)
|
|
return ENOTEMPTY;
|
|
|
|
node_ref(opc);
|
|
node_ref(targ);
|
|
|
|
/*
|
|
* Await for all operations on the deleted node to drain,
|
|
* as the filesystem may be confused to have it deleted
|
|
* during a getattr
|
|
*/
|
|
while (PERFUSE_NODE_DATA(targ)->pnd_inxchg)
|
|
requeue_request(pu, targ, PCQ_AFTERXCHG);
|
|
|
|
ps = puffs_getspecific(pu);
|
|
name = pcn->pcn_name;
|
|
len = pcn->pcn_namelen + 1;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_RMDIR, len, pcn->pcn_cred);
|
|
path = _GET_INPAYLOAD(ps, pm, char *);
|
|
(void)strlcpy(path, name, len);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
perfuse_cache_flush(targ);
|
|
PERFUSE_NODE_DATA(targ)->pnd_flags |= PND_REMOVED;
|
|
|
|
if (!(PERFUSE_NODE_DATA(targ)->pnd_flags & PND_OPEN))
|
|
puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
|
|
|
|
/*
|
|
* The parent directory needs a sync
|
|
*/
|
|
PERFUSE_NODE_DATA(opc)->pnd_flags |= PND_DIRTY;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FILENAME)
|
|
DPRINTF("%s: remove nodeid = 0x%"PRIx64" file = \"%s\"\n",
|
|
__func__, PERFUSE_NODE_DATA(targ)->pnd_nodeid,
|
|
perfuse_node_path(ps, targ));
|
|
#endif
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
node_rele(targ);
|
|
return error;
|
|
}
|
|
|
|
/* vap is unused */
|
|
/* ARGSUSED4 */
|
|
int
|
|
perfuse_node_symlink(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct puffs_newinfo *pni, const struct puffs_cn *pcn_src,
|
|
const struct vattr *vap, const char *link_target)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
char *np;
|
|
const char *path;
|
|
size_t path_len;
|
|
size_t linkname_len;
|
|
size_t len;
|
|
int error;
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED)
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
ps = puffs_getspecific(pu);
|
|
path = pcn_src->pcn_name;
|
|
path_len = pcn_src->pcn_namelen + 1;
|
|
linkname_len = strlen(link_target) + 1;
|
|
len = path_len + linkname_len;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_SYMLINK, len, pcn_src->pcn_cred);
|
|
np = _GET_INPAYLOAD(ps, pm, char *);
|
|
(void)strlcpy(np, path, path_len);
|
|
np += path_len;
|
|
(void)strlcpy(np, link_target, linkname_len);
|
|
|
|
error = node_mk_common(pu, opc, pni, pcn_src, pm);
|
|
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
/* ARGSUSED4 */
|
|
int
|
|
perfuse_node_readdir(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
struct dirent *dent, off_t *readoff, size_t *reslen,
|
|
const struct puffs_cred *pcr, int *eofflag, off_t *cookies,
|
|
size_t *ncookies)
|
|
{
|
|
perfuse_msg_t *pm;
|
|
uint64_t fh;
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
struct fuse_read_in *fri;
|
|
struct fuse_out_header *foh;
|
|
struct fuse_dirent *fd;
|
|
size_t foh_len;
|
|
int error;
|
|
size_t fd_maxlen;
|
|
|
|
error = 0;
|
|
node_ref(opc);
|
|
ps = puffs_getspecific(pu);
|
|
|
|
/*
|
|
* readdir state is kept at node level, and several readdir
|
|
* requests can be issued at the same time on the same node.
|
|
* We need to queue requests so that only one is in readdir
|
|
* code at the same time.
|
|
*/
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
while (pnd->pnd_flags & PND_INREADDIR)
|
|
requeue_request(pu, opc, PCQ_READDIR);
|
|
pnd->pnd_flags |= PND_INREADDIR;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_READDIR)
|
|
DPRINTF("%s: READDIR opc = %p enter critical section\n",
|
|
__func__, (void *)opc);
|
|
#endif
|
|
/*
|
|
* Re-initialize pnd->pnd_fd_cookie on the first readdir for a node
|
|
*/
|
|
if (*readoff == 0)
|
|
pnd->pnd_fd_cookie = 0;
|
|
|
|
/*
|
|
* Do we already have the data bufered?
|
|
*/
|
|
if (pnd->pnd_dirent != NULL)
|
|
goto out;
|
|
pnd->pnd_dirent_len = 0;
|
|
|
|
/*
|
|
* It seems NetBSD can call readdir without open first
|
|
* libfuse will crash if it is done that way, hence open first.
|
|
*/
|
|
if (!(pnd->pnd_flags & PND_OPEN)) {
|
|
if ((error = perfuse_node_open(pu, opc, FREAD, pcr)) != 0)
|
|
goto out;
|
|
}
|
|
|
|
fh = perfuse_get_fh(opc, FREAD);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FH)
|
|
DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", "
|
|
"rfh = 0x%"PRIx64"\n", __func__, (void *)opc,
|
|
PERFUSE_NODE_DATA(opc)->pnd_nodeid, fh);
|
|
#endif
|
|
|
|
pnd->pnd_all_fd = NULL;
|
|
pnd->pnd_all_fd_len = 0;
|
|
fd_maxlen = ps->ps_max_readahead - sizeof(*foh);
|
|
|
|
do {
|
|
size_t fd_len;
|
|
char *afdp;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_READDIR, sizeof(*fri), pcr);
|
|
|
|
/*
|
|
* read_flags, lock_owner and flags are unused in libfuse
|
|
*/
|
|
fri = GET_INPAYLOAD(ps, pm, fuse_read_in);
|
|
fri->fh = fh;
|
|
fri->offset = pnd->pnd_fd_cookie;
|
|
fri->size = (uint32_t)fd_maxlen;
|
|
fri->read_flags = 0;
|
|
fri->lock_owner = 0;
|
|
fri->flags = 0;
|
|
|
|
if ((error = xchg_msg(pu, opc, pm,
|
|
UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
/*
|
|
* There are many puffs_framebufs calls later,
|
|
* therefore foh will not be valid for a long time.
|
|
* Just get the length and forget it.
|
|
*/
|
|
foh = GET_OUTHDR(ps, pm);
|
|
foh_len = foh->len;
|
|
|
|
/*
|
|
* Empty read: we reached the end of the buffer.
|
|
*/
|
|
if (foh_len == sizeof(*foh)) {
|
|
ps->ps_destroy_msg(pm);
|
|
*eofflag = 1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Check for corrupted message.
|
|
*/
|
|
if (foh_len < sizeof(*foh) + sizeof(*fd)) {
|
|
ps->ps_destroy_msg(pm);
|
|
DWARNX("readdir reply too short");
|
|
error = EIO;
|
|
goto out;
|
|
}
|
|
|
|
|
|
fd = GET_OUTPAYLOAD(ps, pm, fuse_dirent);
|
|
fd_len = foh_len - sizeof(*foh);
|
|
|
|
pnd->pnd_all_fd = realloc(pnd->pnd_all_fd,
|
|
pnd->pnd_all_fd_len + fd_len);
|
|
if (pnd->pnd_all_fd == NULL)
|
|
DERR(EX_OSERR, "%s: malloc failed", __func__);
|
|
|
|
afdp = (char *)(void *)pnd->pnd_all_fd + pnd->pnd_all_fd_len;
|
|
(void)memcpy(afdp, fd, fd_len);
|
|
|
|
pnd->pnd_all_fd_len += fd_len;
|
|
|
|
/*
|
|
* The fd->off field is used as a cookie for
|
|
* resuming the next readdir() where this one was left.
|
|
*/
|
|
pnd->pnd_fd_cookie = readdir_last_cookie(fd, fd_len);
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
} while (1 /* CONSTCOND */);
|
|
|
|
if (pnd->pnd_all_fd != NULL) {
|
|
if (fuse_to_dirent(pu, opc, pnd->pnd_all_fd,
|
|
pnd->pnd_all_fd_len) == -1)
|
|
error = EIO;
|
|
}
|
|
|
|
out:
|
|
if (pnd->pnd_all_fd != NULL) {
|
|
free(pnd->pnd_all_fd);
|
|
pnd->pnd_all_fd = NULL;
|
|
pnd->pnd_all_fd_len = 0;
|
|
}
|
|
|
|
if (error == 0)
|
|
readdir_buffered(opc, dent, readoff, reslen);
|
|
|
|
/*
|
|
* Schedule queued readdir requests
|
|
*/
|
|
pnd->pnd_flags &= ~PND_INREADDIR;
|
|
(void)dequeue_requests(opc, PCQ_READDIR, DEQUEUE_ALL);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_READDIR)
|
|
DPRINTF("%s: READDIR opc = %p exit critical section\n",
|
|
__func__, (void *)opc);
|
|
#endif
|
|
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_readlink(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
const struct puffs_cred *pcr, char *linkname, size_t *linklen)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
int error;
|
|
size_t len;
|
|
struct fuse_out_header *foh;
|
|
|
|
if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED)
|
|
return ENOENT;
|
|
|
|
node_ref(opc);
|
|
ps = puffs_getspecific(pu);
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_READLINK, 0, pcr);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
foh = GET_OUTHDR(ps, pm);
|
|
len = foh->len - sizeof(*foh);
|
|
if (len > *linklen)
|
|
DERRX(EX_PROTOCOL, "path len = %zd too long", len);
|
|
if (len == 0)
|
|
DERRX(EX_PROTOCOL, "path len = %zd too short", len);
|
|
|
|
(void)memcpy(linkname, _GET_OUTPAYLOAD(ps, pm, char *), len);
|
|
|
|
/*
|
|
* FUSE filesystems return a NUL terminated string, we
|
|
* do not want the trailing \0
|
|
*/
|
|
while (len > 0 && linkname[len - 1] == '\0')
|
|
len--;
|
|
|
|
*linklen = len;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
struct perfuse_node_data *pnd;
|
|
struct fuse_forget_in *ffi;
|
|
int nlookup;
|
|
struct timespec now;
|
|
|
|
if (opc == 0)
|
|
return 0;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
|
|
/*
|
|
* Never forget the root.
|
|
*/
|
|
if (pnd->pnd_nodeid == FUSE_ROOT_ID)
|
|
return 0;
|
|
|
|
/*
|
|
* There is a race condition between reclaim and lookup.
|
|
* When looking up an already known node, the kernel cannot
|
|
* hold a reference on the result until it gets the PUFFS
|
|
* reply. It mayy therefore reclaim the node after the
|
|
* userland looked it up, and before it gets the reply.
|
|
* On rely, the kernel re-creates the node, but at that
|
|
* time the node has been reclaimed in userland.
|
|
*
|
|
* In order to avoid this, we refuse reclaiming nodes that
|
|
* are too young since the last lookup - and that we do
|
|
* not have removed on our own, of course.
|
|
*/
|
|
if (clock_gettime(CLOCK_REALTIME, &now) != 0)
|
|
DERR(EX_OSERR, "clock_gettime failed");
|
|
|
|
if (timespeccmp(&pnd->pnd_cn_expire, &now, >) &&
|
|
!(pnd->pnd_flags & PND_REMOVED)) {
|
|
if (!(pnd->pnd_flags & PND_NODELEAK)) {
|
|
ps->ps_nodeleakcount++;
|
|
pnd->pnd_flags |= PND_NODELEAK;
|
|
}
|
|
DWARNX("possible leaked node:: opc = %p \"%s\"",
|
|
opc, pnd->pnd_name);
|
|
return 0;
|
|
}
|
|
|
|
node_ref(opc);
|
|
pnd->pnd_flags |= PND_RECLAIMED;
|
|
pnd->pnd_puffs_nlookup--;
|
|
nlookup = pnd->pnd_puffs_nlookup;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_RECLAIM)
|
|
DPRINTF("%s (nodeid %"PRId64") reclaimed\n",
|
|
perfuse_node_path(ps, opc), pnd->pnd_nodeid);
|
|
#endif
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_RECLAIM)
|
|
DPRINTF("%s (nodeid %"PRId64") is %sreclaimed, nlookup = %d "
|
|
"%s%s%s%s, pending ops:%s%s%s\n",
|
|
perfuse_node_path(ps, opc), pnd->pnd_nodeid,
|
|
pnd->pnd_flags & PND_RECLAIMED ? "" : "not ",
|
|
pnd->pnd_puffs_nlookup,
|
|
pnd->pnd_flags & PND_OPEN ? "open " : "not open",
|
|
pnd->pnd_flags & PND_RFH ? "r" : "",
|
|
pnd->pnd_flags & PND_WFH ? "w" : "",
|
|
pnd->pnd_flags & PND_BUSY ? "" : " none",
|
|
pnd->pnd_flags & PND_INREADDIR ? " readdir" : "",
|
|
pnd->pnd_flags & PND_INWRITE ? " write" : "",
|
|
pnd->pnd_flags & PND_INOPEN ? " open" : "");
|
|
#endif
|
|
/*
|
|
* Make sure it is not looked up again
|
|
*/
|
|
if (!(pnd->pnd_flags & PND_REMOVED))
|
|
perfuse_cache_flush(opc);
|
|
|
|
/*
|
|
* Purge any activity on the node, while checking
|
|
* that it remains eligible for a reclaim.
|
|
*/
|
|
while (pnd->pnd_ref > 1)
|
|
requeue_request(pu, opc, PCQ_REF);
|
|
|
|
/*
|
|
* reclaim cancel?
|
|
*/
|
|
if (pnd->pnd_puffs_nlookup > nlookup) {
|
|
pnd->pnd_flags &= ~PND_RECLAIMED;
|
|
perfuse_node_cache(ps, opc);
|
|
node_rele(opc);
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if ((pnd->pnd_flags & PND_OPEN) ||
|
|
!TAILQ_EMPTY(&pnd->pnd_pcq))
|
|
DERRX(EX_SOFTWARE, "%s: opc = %p \"%s\": still open",
|
|
__func__, opc, pnd->pnd_name);
|
|
|
|
if ((pnd->pnd_flags & PND_BUSY) ||
|
|
!TAILQ_EMPTY(&pnd->pnd_pcq))
|
|
DERRX(EX_SOFTWARE, "%s: opc = %p: queued operations",
|
|
__func__, opc);
|
|
|
|
if (pnd->pnd_inxchg != 0)
|
|
DERRX(EX_SOFTWARE, "%s: opc = %p: ongoing operations",
|
|
__func__, opc);
|
|
#endif
|
|
|
|
/*
|
|
* Send the FORGET message
|
|
*
|
|
* ps_new_msg() is called with NULL creds, which will
|
|
* be interpreted as FUSE superuser. This is obviously
|
|
* fine since we operate with kernel creds here.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_FORGET,
|
|
sizeof(*ffi), NULL);
|
|
ffi = GET_INPAYLOAD(ps, pm, fuse_forget_in);
|
|
ffi->nlookup = pnd->pnd_fuse_nlookup;
|
|
|
|
/*
|
|
* No reply is expected, pm is freed in xchg_msg
|
|
*/
|
|
(void)xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, no_reply);
|
|
|
|
perfuse_destroy_pn(pu, opc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
perfuse_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc)
|
|
{
|
|
struct perfuse_node_data *pnd;
|
|
int error;
|
|
|
|
if (opc == 0)
|
|
return 0;
|
|
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
if (!(pnd->pnd_flags & (PND_OPEN|PND_REMOVED)))
|
|
return 0;
|
|
|
|
node_ref(opc);
|
|
|
|
/*
|
|
* Make sure all operation are finished
|
|
* There can be an ongoing write. Other
|
|
* operation wait for all data before
|
|
* the close/inactive.
|
|
*/
|
|
while (pnd->pnd_flags & PND_INWRITE)
|
|
requeue_request(pu, opc, PCQ_AFTERWRITE);
|
|
|
|
/*
|
|
* The inactive operation may be cancelled,
|
|
* If no open is in progress, set PND_INOPEN
|
|
* so that a new open will be queued.
|
|
*/
|
|
if (pnd->pnd_flags & PND_INOPEN)
|
|
goto out;
|
|
|
|
pnd->pnd_flags |= PND_INOPEN;
|
|
|
|
/*
|
|
* Sync data
|
|
*/
|
|
if (pnd->pnd_flags & PND_DIRTY) {
|
|
if ((error = perfuse_node_fsync(pu, opc, NULL, 0, 0, 0)) != 0)
|
|
DWARN("%s: perfuse_node_fsync failed error = %d",
|
|
__func__, error);
|
|
}
|
|
|
|
|
|
/*
|
|
* Close handles
|
|
*/
|
|
if (pnd->pnd_flags & PND_WFH) {
|
|
if ((error = perfuse_node_close_common(pu, opc, FWRITE)) != 0)
|
|
DWARN("%s: close write FH failed error = %d",
|
|
__func__, error);
|
|
}
|
|
|
|
if (pnd->pnd_flags & PND_RFH) {
|
|
if ((error = perfuse_node_close_common(pu, opc, FREAD)) != 0)
|
|
DWARN("%s: close read FH failed error = %d",
|
|
__func__, error);
|
|
}
|
|
|
|
/*
|
|
* This will cause a reclaim to be sent
|
|
*/
|
|
if (pnd->pnd_flags & PND_REMOVED)
|
|
puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N1);
|
|
|
|
/*
|
|
* Schedule awaiting operations
|
|
*/
|
|
pnd->pnd_flags &= ~PND_INOPEN;
|
|
(void)dequeue_requests(opc, PCQ_OPEN, DEQUEUE_ALL);
|
|
|
|
/*
|
|
* errors are ignored, since the kernel ignores the return code.
|
|
*/
|
|
out:
|
|
node_rele(opc);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* ARGSUSED0 */
|
|
int
|
|
perfuse_node_print(struct puffs_usermount *pu, puffs_cookie_t opc)
|
|
{
|
|
DERRX(EX_SOFTWARE, "%s: UNIMPLEMENTED (FATAL)", __func__);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
perfuse_node_pathconf(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
int name, register_t *retval)
|
|
{
|
|
perfuse_msg_t *pm;
|
|
struct perfuse_state *ps;
|
|
struct fuse_statfs_out *fso;
|
|
int error = 0;
|
|
|
|
/*
|
|
* Static values copied from UFS
|
|
* in src/sys/ufs/ufs/ufs_vnops.c
|
|
*/
|
|
switch (name) {
|
|
case _PC_LINK_MAX:
|
|
*retval = LINK_MAX;
|
|
break;
|
|
case _PC_PATH_MAX:
|
|
*retval = PATH_MAX;
|
|
break;
|
|
case _PC_PIPE_BUF:
|
|
*retval = PIPE_BUF;
|
|
break;
|
|
case _PC_CHOWN_RESTRICTED:
|
|
*retval = 1;
|
|
break;
|
|
case _PC_NO_TRUNC:
|
|
*retval = 1;
|
|
break;
|
|
case _PC_SYNC_IO:
|
|
*retval = 1;
|
|
break;
|
|
case _PC_FILESIZEBITS:
|
|
*retval = 42;
|
|
break;
|
|
case _PC_SYMLINK_MAX:
|
|
*retval = MAXPATHLEN;
|
|
break;
|
|
case _PC_2_SYMLINKS:
|
|
*retval = 1;
|
|
break;
|
|
case _PC_NAME_MAX:
|
|
ps = puffs_getspecific(pu);
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_STATFS, 0, NULL);
|
|
|
|
error = xchg_msg(pu, opc, pm, sizeof(*fso), wait_reply);
|
|
if (error != 0)
|
|
return error;
|
|
|
|
fso = GET_OUTPAYLOAD(ps, pm, fuse_statfs_out);
|
|
*retval = fso->st.namelen;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
break;
|
|
default:
|
|
DWARN("Unimplemented pathconf for name = %d", name);
|
|
error = ENOSYS;
|
|
break;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_advlock(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
void *id, int op, struct flock *fl, int flags)
|
|
{
|
|
struct perfuse_state *ps;
|
|
int fop;
|
|
perfuse_msg_t *pm;
|
|
uint64_t fh;
|
|
struct fuse_lk_in *fli;
|
|
struct fuse_out_header *foh;
|
|
struct fuse_lk_out *flo;
|
|
uint32_t owner;
|
|
size_t len;
|
|
int error;
|
|
|
|
node_ref(opc);
|
|
|
|
/*
|
|
* Make sure we do have a filehandle, as the FUSE filesystem
|
|
* expect one. E.g.: if we provide none, GlusterFS logs an error
|
|
* "0-glusterfs-fuse: xl is NULL"
|
|
*
|
|
* We need the read file handle if the file is open read only,
|
|
* in order to support shared locks on read-only files.
|
|
* NB: The kernel always sends advlock for read-only
|
|
* files at exit time when the process used lock, see
|
|
* sys_exit -> exit1 -> fd_free -> fd_close -> VOP_ADVLOCK
|
|
*/
|
|
if ((fh = perfuse_get_fh(opc, FREAD)) == FUSE_UNKNOWN_FH) {
|
|
error = EBADF;
|
|
goto out;
|
|
}
|
|
|
|
ps = puffs_getspecific(pu);
|
|
|
|
if (op == F_GETLK)
|
|
fop = FUSE_GETLK;
|
|
else
|
|
fop = (flags & F_WAIT) ? FUSE_SETLKW : FUSE_SETLK;
|
|
|
|
/*
|
|
* XXX ps_new_msg() is called with NULL creds, which will
|
|
* be interpreted as FUSE superuser. We have no way to
|
|
* know the requesting process' credential, but since advlock()
|
|
* is supposed to operate on a file that has been open(),
|
|
* permission should have already been checked at open() time.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, fop, sizeof(*fli), NULL);
|
|
fli = GET_INPAYLOAD(ps, pm, fuse_lk_in);
|
|
fli->fh = fh;
|
|
fli->owner = (uint64_t)(vaddr_t)id;
|
|
fli->lk.start = fl->l_start;
|
|
fli->lk.end = fl->l_start + fl->l_len;
|
|
fli->lk.type = fl->l_type;
|
|
fli->lk.pid = fl->l_pid;
|
|
fli->lk_flags = (flags & F_FLOCK) ? FUSE_LK_FLOCK : 0;
|
|
|
|
owner = (uint32_t)(vaddr_t)id;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FH)
|
|
DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", fh = 0x%"PRIx64"\n",
|
|
__func__, (void *)opc,
|
|
PERFUSE_NODE_DATA(opc)->pnd_nodeid, fli->fh);
|
|
#endif
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
foh = GET_OUTHDR(ps, pm);
|
|
len = foh->len - sizeof(*foh);
|
|
|
|
/*
|
|
* Save or clear the lock
|
|
*/
|
|
switch (op) {
|
|
case F_GETLK:
|
|
if (len != sizeof(*flo))
|
|
DERRX(EX_SOFTWARE,
|
|
"%s: Unexpected lock reply len %zd",
|
|
__func__, len);
|
|
|
|
flo = GET_OUTPAYLOAD(ps, pm, fuse_lk_out);
|
|
fl->l_start = flo->lk.start;
|
|
fl->l_len = flo->lk.end - flo->lk.start;
|
|
fl->l_pid = flo->lk.pid;
|
|
fl->l_type = flo->lk.type;
|
|
fl->l_whence = SEEK_SET; /* libfuse hardcodes it */
|
|
|
|
PERFUSE_NODE_DATA(opc)->pnd_lock_owner = flo->lk.pid;
|
|
break;
|
|
case F_UNLCK:
|
|
owner = 0;
|
|
/* FALLTHROUGH */
|
|
case F_SETLK:
|
|
/* FALLTHROUGH */
|
|
case F_SETLKW:
|
|
if (error != 0)
|
|
PERFUSE_NODE_DATA(opc)->pnd_lock_owner = owner;
|
|
|
|
if (len != 0)
|
|
DERRX(EX_SOFTWARE,
|
|
"%s: Unexpected unlock reply len %zd",
|
|
__func__, len);
|
|
|
|
break;
|
|
default:
|
|
DERRX(EX_SOFTWARE, "%s: Unexpected op %d", __func__, op);
|
|
break;
|
|
}
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_read(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf,
|
|
off_t offset, size_t *resid, const struct puffs_cred *pcr, int ioflag)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
const struct vattr *vap;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_read_in *fri;
|
|
struct fuse_out_header *foh;
|
|
size_t readen;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
vap = puffs_pn_getvap((struct puffs_node *)opc);
|
|
|
|
/*
|
|
* NetBSD turns that into a getdents(2) output
|
|
* We just do a EISDIR as this feature is of little use.
|
|
*/
|
|
if (vap->va_type == VDIR)
|
|
return EISDIR;
|
|
|
|
do {
|
|
size_t max_read;
|
|
|
|
max_read = ps->ps_max_readahead - sizeof(*foh);
|
|
/*
|
|
* flags may be set to FUSE_READ_LOCKOWNER
|
|
* if lock_owner is provided.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_READ, sizeof(*fri), pcr);
|
|
fri = GET_INPAYLOAD(ps, pm, fuse_read_in);
|
|
fri->fh = perfuse_get_fh(opc, FREAD);
|
|
fri->offset = offset;
|
|
fri->size = (uint32_t)MIN(*resid, max_read);
|
|
fri->read_flags = 0; /* XXX Unused by libfuse? */
|
|
fri->lock_owner = pnd->pnd_lock_owner;
|
|
fri->flags = 0;
|
|
fri->flags |= (fri->lock_owner != 0) ? FUSE_READ_LOCKOWNER : 0;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FH)
|
|
DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", fh = 0x%"PRIx64"\n",
|
|
__func__, (void *)opc, pnd->pnd_nodeid, fri->fh);
|
|
#endif
|
|
error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply);
|
|
if (error != 0)
|
|
return error;
|
|
|
|
foh = GET_OUTHDR(ps, pm);
|
|
readen = foh->len - sizeof(*foh);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (readen > *resid)
|
|
DERRX(EX_SOFTWARE, "%s: Unexpected big read %zd",
|
|
__func__, readen);
|
|
#endif
|
|
|
|
(void)memcpy(buf, _GET_OUTPAYLOAD(ps, pm, char *), readen);
|
|
|
|
buf += readen;
|
|
offset += readen;
|
|
*resid -= readen;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
} while ((*resid != 0) && (readen != 0));
|
|
|
|
if (ioflag & (IO_SYNC|IO_DSYNC))
|
|
ps->ps_syncreads++;
|
|
else
|
|
ps->ps_asyncreads++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
perfuse_node_write(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
uint8_t *buf, off_t offset, size_t *resid,
|
|
const struct puffs_cred *pcr, int ioflag)
|
|
{
|
|
return perfuse_node_write2(pu, opc, buf, offset, resid, pcr, ioflag, 0);
|
|
}
|
|
|
|
/* ARGSUSED7 */
|
|
int
|
|
perfuse_node_write2(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
uint8_t *buf, off_t offset, size_t *resid,
|
|
const struct puffs_cred *pcr, int ioflag, int xflag)
|
|
{
|
|
struct perfuse_state *ps;
|
|
struct perfuse_node_data *pnd;
|
|
struct vattr *vap;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_write_in *fwi;
|
|
struct fuse_write_out *fwo;
|
|
size_t data_len;
|
|
size_t payload_len;
|
|
size_t written;
|
|
int inresize;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
pnd = PERFUSE_NODE_DATA(opc);
|
|
vap = puffs_pn_getvap((struct puffs_node *)opc);
|
|
written = 0;
|
|
inresize = 0;
|
|
error = 0;
|
|
|
|
if (vap->va_type == VDIR)
|
|
return EISDIR;
|
|
|
|
node_ref(opc);
|
|
|
|
/*
|
|
* We need to queue write requests in order to avoid
|
|
* dequeueing PCQ_AFTERWRITE when there are pending writes.
|
|
*/
|
|
while (pnd->pnd_flags & PND_INWRITE)
|
|
requeue_request(pu, opc, PCQ_WRITE);
|
|
pnd->pnd_flags |= PND_INWRITE;
|
|
|
|
/*
|
|
* append flag: re-read the file size so that
|
|
* we get the latest value.
|
|
*/
|
|
if (ioflag & PUFFS_IO_APPEND) {
|
|
if ((error = perfuse_node_getattr(pu, opc, vap, pcr)) != 0)
|
|
goto out;
|
|
|
|
offset = vap->va_size;
|
|
}
|
|
|
|
/*
|
|
* Serialize size access, see comment in perfuse_node_setattr().
|
|
*/
|
|
if ((u_quad_t)offset + *resid > vap->va_size) {
|
|
while (pnd->pnd_flags & PND_INRESIZE)
|
|
requeue_request(pu, opc, PCQ_RESIZE);
|
|
pnd->pnd_flags |= PND_INRESIZE;
|
|
inresize = 1;
|
|
}
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_RESIZE)
|
|
DPRINTF(">> %s %p %" PRIu64 "\n", __func__,
|
|
(void *)opc, vap->va_size);
|
|
#endif
|
|
|
|
do {
|
|
size_t max_write;
|
|
/*
|
|
* There is a writepage flag when data
|
|
* is aligned to page size. Use it for
|
|
* everything but the data after the last
|
|
* page boundary.
|
|
*/
|
|
max_write = ps->ps_max_write - sizeof(*fwi);
|
|
|
|
data_len = MIN(*resid, max_write);
|
|
if (data_len > (size_t)sysconf(_SC_PAGESIZE))
|
|
data_len = data_len & ~(sysconf(_SC_PAGESIZE) - 1);
|
|
|
|
payload_len = data_len + sizeof(*fwi);
|
|
|
|
/*
|
|
* flags may be set to FUSE_WRITE_CACHE (XXX usage?)
|
|
* or FUSE_WRITE_LOCKOWNER, if lock_owner is provided.
|
|
* write_flags is set to 1 for writepage.
|
|
*/
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_WRITE, payload_len, pcr);
|
|
fwi = GET_INPAYLOAD(ps, pm, fuse_write_in);
|
|
fwi->fh = perfuse_get_fh(opc, FWRITE);
|
|
fwi->offset = offset;
|
|
fwi->size = (uint32_t)data_len;
|
|
fwi->write_flags = (fwi->size % sysconf(_SC_PAGESIZE)) ? 0 : 1;
|
|
fwi->lock_owner = pnd->pnd_lock_owner;
|
|
fwi->flags = 0;
|
|
fwi->flags |= (fwi->lock_owner != 0) ? FUSE_WRITE_LOCKOWNER : 0;
|
|
fwi->flags |= (ioflag & IO_DIRECT) ? 0 : FUSE_WRITE_CACHE;
|
|
(void)memcpy((fwi + 1), buf, data_len);
|
|
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_FH)
|
|
DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", "
|
|
"fh = 0x%"PRIx64"\n", __func__,
|
|
(void *)opc, pnd->pnd_nodeid, fwi->fh);
|
|
#endif
|
|
if ((error = xchg_msg(pu, opc, pm,
|
|
sizeof(*fwo), wait_reply)) != 0)
|
|
goto out;
|
|
|
|
fwo = GET_OUTPAYLOAD(ps, pm, fuse_write_out);
|
|
written = fwo->size;
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (written > *resid)
|
|
DERRX(EX_SOFTWARE, "%s: Unexpected big write %zd",
|
|
__func__, written);
|
|
#endif
|
|
*resid -= written;
|
|
offset += written;
|
|
buf += written;
|
|
|
|
} while (*resid != 0);
|
|
|
|
/*
|
|
* puffs_ops(3) says
|
|
* "everything must be written or an error will be generated"
|
|
*/
|
|
if (*resid != 0)
|
|
error = EFBIG;
|
|
|
|
out:
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_RESIZE) {
|
|
if (offset > (off_t)vap->va_size)
|
|
DPRINTF("<< %s %p %" PRIu64 " -> %lld\n", __func__,
|
|
(void *)opc, vap->va_size, (long long)offset);
|
|
else
|
|
DPRINTF("<< %s %p \n", __func__, (void *)opc);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Update file size if we wrote beyond the end
|
|
*/
|
|
if (offset > (off_t)vap->va_size)
|
|
vap->va_size = offset;
|
|
|
|
/*
|
|
* Statistics
|
|
*/
|
|
if (ioflag & (IO_SYNC|IO_DSYNC))
|
|
ps->ps_syncwrites++;
|
|
else
|
|
ps->ps_asyncwrites++;
|
|
|
|
/*
|
|
* Remember to sync the file
|
|
*/
|
|
pnd->pnd_flags |= PND_DIRTY;
|
|
|
|
#ifdef PERFUSE_DEBUG
|
|
if (perfuse_diagflags & PDF_SYNC)
|
|
DPRINTF("%s: DIRTY opc = %p, file = \"%s\"\n",
|
|
__func__, (void*)opc, perfuse_node_path(ps, opc));
|
|
#endif
|
|
|
|
if (inresize) {
|
|
#ifdef PERFUSE_DEBUG
|
|
if (!(pnd->pnd_flags & PND_INRESIZE))
|
|
DERRX(EX_SOFTWARE, "file write grow without resize");
|
|
#endif
|
|
pnd->pnd_flags &= ~PND_INRESIZE;
|
|
(void)dequeue_requests(opc, PCQ_RESIZE, DEQUEUE_ALL);
|
|
}
|
|
|
|
/*
|
|
* VOP_PUTPAGE causes FAF write where kernel does not
|
|
* check operation result. At least warn if it failed.
|
|
*/
|
|
#ifdef PUFFS_WRITE_FAF
|
|
if (error && (xflag & PUFFS_WRITE_FAF))
|
|
DWARN("Data loss caused by FAF write failed on \"%s\"",
|
|
pnd->pnd_name);
|
|
#endif /* PUFFS_WRITE_FAF */
|
|
|
|
/*
|
|
* If there are no more queued write, we can resume
|
|
* an operation awaiting write completion.
|
|
*/
|
|
pnd->pnd_flags &= ~PND_INWRITE;
|
|
if (dequeue_requests(opc, PCQ_WRITE, 1) == 0)
|
|
(void)dequeue_requests(opc, PCQ_AFTERWRITE, DEQUEUE_ALL);
|
|
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
/* ARGSUSED0 */
|
|
void
|
|
perfuse_cache_write(struct puffs_usermount *pu, puffs_cookie_t opc, size_t size,
|
|
struct puffs_cacherun *runs)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* ARGSUSED4 */
|
|
int
|
|
perfuse_node_getextattr(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
int attrns, const char *attrname, size_t *attrsize, uint8_t *attr,
|
|
size_t *resid, const struct puffs_cred *pcr)
|
|
{
|
|
struct perfuse_state *ps;
|
|
char fuse_attrname[LINUX_XATTR_NAME_MAX + 1];
|
|
perfuse_msg_t *pm;
|
|
struct fuse_getxattr_in *fgi;
|
|
struct fuse_getxattr_out *fgo;
|
|
struct fuse_out_header *foh;
|
|
size_t attrnamelen;
|
|
size_t len;
|
|
char *np;
|
|
int error;
|
|
|
|
/* system namespace attrs are not accessible to non root users */
|
|
if (attrns == EXTATTR_NAMESPACE_SYSTEM && !puffs_cred_isjuggernaut(pcr))
|
|
return EPERM;
|
|
|
|
node_ref(opc);
|
|
ps = puffs_getspecific(pu);
|
|
attrname = perfuse_native_ns(attrns, attrname, fuse_attrname);
|
|
attrnamelen = strlen(attrname) + 1;
|
|
len = sizeof(*fgi) + attrnamelen;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_GETXATTR, len, pcr);
|
|
fgi = GET_INPAYLOAD(ps, pm, fuse_getxattr_in);
|
|
fgi->size = (unsigned int)((resid != NULL) ? *resid : 0);
|
|
np = (char *)(void *)(fgi + 1);
|
|
(void)strlcpy(np, attrname, attrnamelen);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
/*
|
|
* We just get fuse_getattr_out with list size if we requested
|
|
* a null size.
|
|
*/
|
|
if (resid == NULL) {
|
|
fgo = GET_OUTPAYLOAD(ps, pm, fuse_getxattr_out);
|
|
|
|
if (attrsize != NULL)
|
|
*attrsize = fgo->size;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* And with a non null requested size, we get the list just
|
|
* after the header
|
|
*/
|
|
foh = GET_OUTHDR(ps, pm);
|
|
np = (char *)(void *)(foh + 1);
|
|
len = foh->len - sizeof(*foh);
|
|
|
|
if (attrsize != NULL)
|
|
*attrsize = len;
|
|
|
|
if (resid != NULL) {
|
|
if (*resid < len) {
|
|
error = ERANGE;
|
|
ps->ps_destroy_msg(pm);
|
|
goto out;
|
|
}
|
|
|
|
(void)memcpy(attr, np, len);
|
|
*resid -= len;
|
|
}
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_setextattr(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
int attrns, const char *attrname, uint8_t *attr, size_t *resid,
|
|
const struct puffs_cred *pcr)
|
|
{
|
|
struct perfuse_state *ps;
|
|
char fuse_attrname[LINUX_XATTR_NAME_MAX + 1];
|
|
perfuse_msg_t *pm;
|
|
struct fuse_setxattr_in *fsi;
|
|
size_t attrnamelen;
|
|
size_t datalen;
|
|
size_t len;
|
|
char *np;
|
|
int error;
|
|
|
|
/* system namespace attrs are not accessible to non root users */
|
|
if (attrns == EXTATTR_NAMESPACE_SYSTEM && !puffs_cred_isjuggernaut(pcr))
|
|
return EPERM;
|
|
|
|
node_ref(opc);
|
|
ps = puffs_getspecific(pu);
|
|
attrname = perfuse_native_ns(attrns, attrname, fuse_attrname);
|
|
attrnamelen = strlen(attrname) + 1;
|
|
|
|
datalen = (resid != NULL) ? *resid : 0;
|
|
len = sizeof(*fsi) + attrnamelen + datalen;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_SETXATTR, len, pcr);
|
|
fsi = GET_INPAYLOAD(ps, pm, fuse_setxattr_in);
|
|
fsi->size = (unsigned int)datalen;
|
|
fsi->flags = 0;
|
|
np = (char *)(void *)(fsi + 1);
|
|
(void)strlcpy(np, attrname, attrnamelen);
|
|
np += attrnamelen;
|
|
if (datalen)
|
|
(void)memcpy(np, (char *)attr, datalen);
|
|
|
|
if ((error = xchg_msg(pu, opc, pm,
|
|
NO_PAYLOAD_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
if (resid)
|
|
*resid = 0;
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
/* ARGSUSED2 */
|
|
int
|
|
perfuse_node_listextattr(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
int attrns, size_t *attrsize, uint8_t *attrs, size_t *resid, int flag,
|
|
const struct puffs_cred *pcr)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_getxattr_in *fgi;
|
|
struct fuse_getxattr_out *fgo;
|
|
struct fuse_out_header *foh;
|
|
char *np;
|
|
size_t len, puffs_len, i, attrlen, outlen;
|
|
int error;
|
|
|
|
/* system namespace attrs are not accessible to non root users */
|
|
if (attrns == EXTATTR_NAMESPACE_SYSTEM && !puffs_cred_isjuggernaut(pcr))
|
|
return EPERM;
|
|
|
|
node_ref(opc);
|
|
|
|
ps = puffs_getspecific(pu);
|
|
len = sizeof(*fgi);
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_LISTXATTR, len, pcr);
|
|
fgi = GET_INPAYLOAD(ps, pm, fuse_getxattr_in);
|
|
if (resid != NULL)
|
|
fgi->size = (unsigned int)*resid;
|
|
else
|
|
fgi->size = 0;
|
|
|
|
if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0)
|
|
goto out;
|
|
|
|
/*
|
|
* We just get fuse_getattr_out with list size if we requested
|
|
* a null size.
|
|
*/
|
|
if (resid == NULL) {
|
|
fgo = GET_OUTPAYLOAD(ps, pm, fuse_getxattr_out);
|
|
|
|
if (attrsize != NULL)
|
|
*attrsize = fgo->size;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
error = 0;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* And with a non null requested size, we get the list just
|
|
* after the header
|
|
*/
|
|
foh = GET_OUTHDR(ps, pm);
|
|
np = (char *)(void *)(foh + 1);
|
|
puffs_len = foh->len - sizeof(*foh);
|
|
|
|
if (attrsize != NULL)
|
|
*attrsize = puffs_len;
|
|
|
|
if (attrs != NULL) {
|
|
if (*resid < puffs_len) {
|
|
error = ERANGE;
|
|
ps->ps_destroy_msg(pm);
|
|
goto out;
|
|
}
|
|
|
|
outlen = 0;
|
|
|
|
for (i = 0; i < puffs_len; i += attrlen + 1) {
|
|
attrlen = strlen(np + i);
|
|
|
|
/*
|
|
* Filter attributes per namespace
|
|
*/
|
|
if (!perfuse_ns_match(attrns, np + i))
|
|
continue;
|
|
|
|
#ifdef PUFFS_EXTATTR_LIST_LENPREFIX
|
|
/*
|
|
* Convert the FUSE reply to length prefixed strings
|
|
* if this is what the kernel wants.
|
|
*/
|
|
if (flag & PUFFS_EXTATTR_LIST_LENPREFIX) {
|
|
(void)memcpy(attrs + outlen + 1,
|
|
np + i, attrlen);
|
|
*(attrs + outlen) = (uint8_t)attrlen;
|
|
} else
|
|
#endif /* PUFFS_EXTATTR_LIST_LENPREFIX */
|
|
(void)memcpy(attrs + outlen, np + i, attrlen + 1);
|
|
outlen += attrlen + 1;
|
|
}
|
|
|
|
*resid -= outlen;
|
|
}
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
error = 0;
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_deleteextattr(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
int attrns, const char *attrname, const struct puffs_cred *pcr)
|
|
{
|
|
struct perfuse_state *ps;
|
|
char fuse_attrname[LINUX_XATTR_NAME_MAX + 1];
|
|
perfuse_msg_t *pm;
|
|
size_t attrnamelen;
|
|
char *np;
|
|
int error;
|
|
|
|
/* system namespace attrs are not accessible to non root users */
|
|
if (attrns == EXTATTR_NAMESPACE_SYSTEM && !puffs_cred_isjuggernaut(pcr))
|
|
return EPERM;
|
|
|
|
node_ref(opc);
|
|
|
|
ps = puffs_getspecific(pu);
|
|
attrname = perfuse_native_ns(attrns, attrname, fuse_attrname);
|
|
attrnamelen = strlen(attrname) + 1;
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_REMOVEXATTR, attrnamelen, pcr);
|
|
np = _GET_INPAYLOAD(ps, pm, char *);
|
|
(void)strlcpy(np, attrname, attrnamelen);
|
|
|
|
error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply);
|
|
if (error != 0)
|
|
goto out;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
perfuse_node_fallocate(struct puffs_usermount *pu, puffs_cookie_t opc,
|
|
off_t off, off_t len)
|
|
{
|
|
struct perfuse_state *ps;
|
|
perfuse_msg_t *pm;
|
|
struct fuse_fallocate_in *fai;
|
|
int error;
|
|
|
|
ps = puffs_getspecific(pu);
|
|
if (ps->ps_flags & PS_NO_FALLOCATE)
|
|
return EOPNOTSUPP;
|
|
|
|
node_ref(opc);
|
|
|
|
pm = ps->ps_new_msg(pu, opc, FUSE_FALLOCATE, sizeof(*fai), NULL);
|
|
|
|
fai = GET_INPAYLOAD(ps, pm, fuse_fallocate_in);
|
|
fai->fh = perfuse_get_fh(opc, FWRITE);
|
|
fai->offset = off;
|
|
fai->length = len;
|
|
fai->mode = 0;
|
|
|
|
error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply);
|
|
if (error == EOPNOTSUPP || error == ENOSYS) {
|
|
ps->ps_flags |= PS_NO_FALLOCATE;
|
|
error = EOPNOTSUPP;
|
|
}
|
|
if (error != 0)
|
|
goto out;
|
|
|
|
ps->ps_destroy_msg(pm);
|
|
|
|
out:
|
|
node_rele(opc);
|
|
return error;
|
|
}
|