src/sys/miscfs/fuse/fuse_device.c

558 lines
12 KiB
C
Raw Normal View History

/* $OpenBSD: fuse_device.c,v 1.39 2023/09/08 20:00:28 mvs Exp $ */
/*
* Copyright (c) 2012-2013 Sylvestre Gallon <ccna.syl@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/vnode.h>
#include <sys/fusebuf.h>
#include <sys/selinfo.h>
#include "fusefs_node.h"
#include "fusefs.h"
#ifdef FUSE_DEBUG
#define DPRINTF(fmt, arg...) printf("%s: " fmt, "fuse", ##arg)
#else
#define DPRINTF(fmt, arg...)
#endif
SIMPLEQ_HEAD(fusebuf_head, fusebuf);
struct fuse_d {
struct fusefs_mnt *fd_fmp;
int fd_unit;
/*fusebufs queues*/
struct fusebuf_head fd_fbufs_in;
struct fusebuf_head fd_fbufs_wait;
/* kq fields */
struct selinfo fd_rsel;
LIST_ENTRY(fuse_d) fd_list;
};
int stat_fbufs_in = 0;
int stat_fbufs_wait = 0;
int stat_opened_fusedev = 0;
LIST_HEAD(, fuse_d) fuse_d_list;
struct fuse_d *fuse_lookup(int);
void fuseattach(int);
int fuseopen(dev_t, int, int, struct proc *);
int fuseclose(dev_t, int, int, struct proc *);
int fuseioctl(dev_t, u_long, caddr_t, int, struct proc *);
int fuseread(dev_t, struct uio *, int);
int fusewrite(dev_t, struct uio *, int);
int fusekqfilter(dev_t dev, struct knote *kn);
int filt_fuse_read(struct knote *, long);
void filt_fuse_rdetach(struct knote *);
static const struct filterops fuse_rd_filtops = {
.f_flags = FILTEROP_ISFD,
.f_attach = NULL,
.f_detach = filt_fuse_rdetach,
.f_event = filt_fuse_read,
};
#ifdef FUSE_DEBUG
static void
fuse_dump_buff(char *buff, int len)
{
char text[17];
int i;
if (len < 0) {
printf("invalid len: %d", len);
return;
}
if (buff == NULL) {
printf("invalid buff");
return;
}
memset(text, 0, 17);
for (i = 0; i < len; i++) {
if (i != 0 && (i % 16) == 0) {
printf(": %s\n", text);
memset(text, 0, 17);
}
printf("%.2x ", buff[i] & 0xff);
if (buff[i] > ' ' && buff[i] < '~')
text[i%16] = buff[i] & 0xff;
else
text[i%16] = '.';
}
if ((i % 16) != 0)
while ((i % 16) != 0) {
printf(" ");
i++;
}
printf(": %s\n", text);
}
#endif
struct fuse_d *
fuse_lookup(int unit)
{
struct fuse_d *fd;
LIST_FOREACH(fd, &fuse_d_list, fd_list)
if (fd->fd_unit == unit)
return (fd);
return (NULL);
}
/*
* Cleanup all msgs from sc_fbufs_in and sc_fbufs_wait.
*/
void
fuse_device_cleanup(dev_t dev)
{
struct fuse_d *fd;
struct fusebuf *f, *ftmp, *lprev;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return;
/* clear FIFO IN */
lprev = NULL;
SIMPLEQ_FOREACH_SAFE(f, &fd->fd_fbufs_in, fb_next, ftmp) {
DPRINTF("cleanup unprocessed msg in sc_fbufs_in\n");
if (lprev == NULL)
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_in, fb_next);
else
SIMPLEQ_REMOVE_AFTER(&fd->fd_fbufs_in, lprev,
fb_next);
stat_fbufs_in--;
f->fb_err = ENXIO;
wakeup(f);
lprev = f;
}
/* clear FIFO WAIT*/
lprev = NULL;
SIMPLEQ_FOREACH_SAFE(f, &fd->fd_fbufs_wait, fb_next, ftmp) {
DPRINTF("umount unprocessed msg in sc_fbufs_wait\n");
if (lprev == NULL)
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_wait, fb_next);
else
SIMPLEQ_REMOVE_AFTER(&fd->fd_fbufs_wait, lprev,
fb_next);
stat_fbufs_wait--;
f->fb_err = ENXIO;
wakeup(f);
lprev = f;
}
}
void
fuse_device_queue_fbuf(dev_t dev, struct fusebuf *fbuf)
{
struct fuse_d *fd;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return;
SIMPLEQ_INSERT_TAIL(&fd->fd_fbufs_in, fbuf, fb_next);
stat_fbufs_in++;
selwakeup(&fd->fd_rsel);
}
void
fuse_device_set_fmp(struct fusefs_mnt *fmp, int set)
{
struct fuse_d *fd;
fd = fuse_lookup(minor(fmp->dev));
if (fd == NULL)
return;
fd->fd_fmp = set ? fmp : NULL;
}
void
fuseattach(int num)
{
LIST_INIT(&fuse_d_list);
}
int
fuseopen(dev_t dev, int flags, int fmt, struct proc * p)
{
struct fuse_d *fd;
int unit = minor(dev);
if (flags & O_EXCL)
return (EBUSY); /* No exclusive opens */
if ((fd = fuse_lookup(unit)) != NULL)
return (EBUSY);
fd = malloc(sizeof(*fd), M_DEVBUF, M_WAITOK | M_ZERO);
fd->fd_unit = unit;
SIMPLEQ_INIT(&fd->fd_fbufs_in);
SIMPLEQ_INIT(&fd->fd_fbufs_wait);
LIST_INSERT_HEAD(&fuse_d_list, fd, fd_list);
stat_opened_fusedev++;
return (0);
}
int
fuseclose(dev_t dev, int flags, int fmt, struct proc *p)
{
struct fuse_d *fd;
int error;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (EINVAL);
if (fd->fd_fmp) {
printf("fuse: device close without umount\n");
fd->fd_fmp->sess_init = 0;
fuse_device_cleanup(dev);
if ((vfs_busy(fd->fd_fmp->mp, VB_WRITE | VB_NOWAIT)) != 0)
goto end;
error = dounmount(fd->fd_fmp->mp, MNT_FORCE, p);
if (error)
printf("fuse: unmount failed with error %d\n", error);
fd->fd_fmp = NULL;
}
end:
LIST_REMOVE(fd, fd_list);
free(fd, M_DEVBUF, sizeof(*fd));
stat_opened_fusedev--;
return (0);
}
/*
* FIOCGETFBDAT Get fusebuf data from kernel to user
* FIOCSETFBDAT Set fusebuf data from user to kernel
*/
int
fuseioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
{
struct fb_ioctl_xch *ioexch;
struct fusebuf *lastfbuf;
struct fusebuf *fbuf;
struct fuse_d *fd;
int error = 0;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (ENXIO);
switch (cmd) {
case FIOCGETFBDAT:
ioexch = (struct fb_ioctl_xch *)addr;
/* Looking for uuid in fd_fbufs_in */
SIMPLEQ_FOREACH(fbuf, &fd->fd_fbufs_in, fb_next) {
if (fbuf->fb_uuid == ioexch->fbxch_uuid)
break;
lastfbuf = fbuf;
}
if (fbuf == NULL) {
printf("fuse: Cannot find fusebuf\n");
return (EINVAL);
}
/* Remove the fbuf from fd_fbufs_in */
if (fbuf == SIMPLEQ_FIRST(&fd->fd_fbufs_in))
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_in, fb_next);
else
SIMPLEQ_REMOVE_AFTER(&fd->fd_fbufs_in, lastfbuf,
fb_next);
stat_fbufs_in--;
/* Do not handle fbufs with bad len */
if (fbuf->fb_len != ioexch->fbxch_len) {
printf("fuse: Bad fusebuf len\n");
return (EINVAL);
}
/* Update the userland fbuf */
error = copyout(fbuf->fb_dat, ioexch->fbxch_data,
ioexch->fbxch_len);
if (error) {
printf("fuse: cannot copyout\n");
return (error);
}
#ifdef FUSE_DEBUG
fuse_dump_buff(fbuf->fb_dat, fbuf->fb_len);
#endif
/* Adding fbuf in fd_fbufs_wait */
free(fbuf->fb_dat, M_FUSEFS, fbuf->fb_len);
fbuf->fb_dat = NULL;
SIMPLEQ_INSERT_TAIL(&fd->fd_fbufs_wait, fbuf, fb_next);
stat_fbufs_wait++;
break;
case FIOCSETFBDAT:
DPRINTF("SET BUFFER\n");
ioexch = (struct fb_ioctl_xch *)addr;
/* looking for uuid in fd_fbufs_wait */
SIMPLEQ_FOREACH(fbuf, &fd->fd_fbufs_wait, fb_next) {
if (fbuf->fb_uuid == ioexch->fbxch_uuid)
break;
lastfbuf = fbuf;
}
if (fbuf == NULL) {
printf("fuse: Cannot find fusebuf\n");
return (EINVAL);
}
/* Do not handle fbufs with bad len */
if (fbuf->fb_len != ioexch->fbxch_len) {
printf("fuse: Bad fusebuf size\n");
return (EINVAL);
}
/* fetching data from userland */
fbuf->fb_dat = malloc(ioexch->fbxch_len, M_FUSEFS,
M_WAITOK | M_ZERO);
error = copyin(ioexch->fbxch_data, fbuf->fb_dat,
ioexch->fbxch_len);
if (error) {
printf("fuse: Cannot copyin\n");
free(fbuf->fb_dat, M_FUSEFS, fbuf->fb_len);
fbuf->fb_dat = NULL;
return (error);
}
#ifdef FUSE_DEBUG
fuse_dump_buff(fbuf->fb_dat, fbuf->fb_len);
#endif
/* Remove fbuf from fd_fbufs_wait */
if (fbuf == SIMPLEQ_FIRST(&fd->fd_fbufs_wait))
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_wait, fb_next);
else
SIMPLEQ_REMOVE_AFTER(&fd->fd_fbufs_wait, lastfbuf,
fb_next);
stat_fbufs_wait--;
wakeup(fbuf);
break;
default:
error = EINVAL;
}
return (error);
}
int
fuseread(dev_t dev, struct uio *uio, int ioflag)
{
struct fuse_d *fd;
struct fusebuf *fbuf;
struct fb_hdr hdr;
void *tmpaddr;
int error = 0;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (ENXIO);
if (SIMPLEQ_EMPTY(&fd->fd_fbufs_in)) {
if (ioflag & O_NONBLOCK)
return (EAGAIN);
goto end;
}
fbuf = SIMPLEQ_FIRST(&fd->fd_fbufs_in);
/* We get the whole fusebuf or nothing */
if (uio->uio_resid != FUSEBUFSIZE)
return (EINVAL);
/* Do not send kernel pointers */
memcpy(&hdr.fh_next, &fbuf->fb_next, sizeof(fbuf->fb_next));
memset(&fbuf->fb_next, 0, sizeof(fbuf->fb_next));
tmpaddr = fbuf->fb_dat;
fbuf->fb_dat = NULL;
error = uiomove(fbuf, FUSEBUFSIZE, uio);
if (error)
goto end;
#ifdef FUSE_DEBUG
fuse_dump_buff((char *)fbuf, FUSEBUFSIZE);
#endif
/* Restore kernel pointers */
memcpy(&fbuf->fb_next, &hdr.fh_next, sizeof(fbuf->fb_next));
fbuf->fb_dat = tmpaddr;
/* Remove the fbuf if it does not contains data */
if (fbuf->fb_len == 0) {
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_in, fb_next);
stat_fbufs_in--;
SIMPLEQ_INSERT_TAIL(&fd->fd_fbufs_wait, fbuf, fb_next);
stat_fbufs_wait++;
}
end:
return (error);
}
int
fusewrite(dev_t dev, struct uio *uio, int ioflag)
{
struct fusebuf *lastfbuf;
struct fuse_d *fd;
struct fusebuf *fbuf;
struct fb_hdr hdr;
int error = 0;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (ENXIO);
/* We get the whole fusebuf or nothing */
if (uio->uio_resid != FUSEBUFSIZE)
return (EINVAL);
if ((error = uiomove(&hdr, sizeof(hdr), uio)) != 0)
return (error);
/* looking for uuid in fd_fbufs_wait */
SIMPLEQ_FOREACH(fbuf, &fd->fd_fbufs_wait, fb_next) {
if (fbuf->fb_uuid == hdr.fh_uuid)
break;
lastfbuf = fbuf;
}
if (fbuf == NULL)
return (EINVAL);
/* Update fb_hdr */
fbuf->fb_len = hdr.fh_len;
fbuf->fb_err = hdr.fh_err;
fbuf->fb_ino = hdr.fh_ino;
/* Check for corrupted fbufs */
if ((fbuf->fb_len && fbuf->fb_err) ||
SIMPLEQ_EMPTY(&fd->fd_fbufs_wait)) {
printf("fuse: dropping corrupted fusebuf\n");
error = EINVAL;
goto end;
}
/* Get the missing data from the fbuf */
error = uiomove(&fbuf->FD, uio->uio_resid, uio);
if (error)
return error;
fbuf->fb_dat = NULL;
#ifdef FUSE_DEBUG
fuse_dump_buff((char *)fbuf, FUSEBUFSIZE);
#endif
switch (fbuf->fb_type) {
case FBT_INIT:
fd->fd_fmp->sess_init = 1;
break ;
case FBT_DESTROY:
fd->fd_fmp = NULL;
break ;
}
end:
/* Remove the fbuf if it does not contains data */
if (fbuf->fb_len == 0) {
if (fbuf == SIMPLEQ_FIRST(&fd->fd_fbufs_wait))
SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_wait, fb_next);
else
SIMPLEQ_REMOVE_AFTER(&fd->fd_fbufs_wait, lastfbuf,
fb_next);
stat_fbufs_wait--;
if (fbuf->fb_type == FBT_INIT)
fb_delete(fbuf);
else
wakeup(fbuf);
}
return (error);
}
int
fusekqfilter(dev_t dev, struct knote *kn)
{
struct fuse_d *fd;
struct klist *klist;
fd = fuse_lookup(minor(dev));
if (fd == NULL)
return (EINVAL);
switch (kn->kn_filter) {
case EVFILT_READ:
klist = &fd->fd_rsel.si_note;
kn->kn_fop = &fuse_rd_filtops;
break;
case EVFILT_WRITE:
return (seltrue_kqfilter(dev, kn));
default:
return (EINVAL);
}
kn->kn_hook = fd;
klist_insert_locked(klist, kn);
return (0);
}
void
filt_fuse_rdetach(struct knote *kn)
{
struct fuse_d *fd = kn->kn_hook;
struct klist *klist = &fd->fd_rsel.si_note;
klist_remove_locked(klist, kn);
}
int
filt_fuse_read(struct knote *kn, long hint)
{
struct fuse_d *fd = kn->kn_hook;
int event = 0;
if (!SIMPLEQ_EMPTY(&fd->fd_fbufs_in))
event = 1;
return (event);
}