DragonFlyBSD Kernel Audit
sys/vfs/fuse/fuse_device.c
← back
/*-
 * Copyright (c) 2019 Tomohiro Kusumi <tkusumi@netbsd.org>
 * Copyright (c) 2019 The DragonFly Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "fuse.h"

#include <sys/conf.h>
#include <sys/device.h>
#include <sys/devfs.h>
#include <sys/uio.h>

static int fuse_cdevpriv_close(struct fuse_mount*);
static struct cdev *fuse_dev;

static void
fuse_cdevpriv_dtor(void *data)
{
	struct fuse_mount *fmp = data;

	if (!fuse_cdevpriv_close(fmp))
		fuse_mount_free(fmp);
}

static int
fuse_device_open(struct dev_open_args *ap)
{
	struct fuse_mount *fmp;
	struct file *fp = ap->a_fpp ? *ap->a_fpp : NULL;

	fmp = kmalloc(sizeof(*fmp), M_TEMP, M_WAITOK | M_ZERO);
	KKASSERT(fmp);

	refcount_init(&fmp->refcnt, 1);
	devfs_set_cdevpriv(fp, fmp, fuse_cdevpriv_dtor);
	fuse_dbg("open %s\n", ap->a_head.a_dev->si_name);

	return 0;
}

static int
fuse_device_close(struct dev_close_args *ap)
{
	struct fuse_mount *fmp;
	int error;

	error = devfs_get_cdevpriv(ap->a_fp, (void**)&fmp);
	if (error)
		return error;
	KKASSERT(fmp);

	/* XXX Can't call this on device close due to devfs bug... */
	//fuse_cdevpriv_close(fmp);
	fuse_dbg("close %s\n", ap->a_head.a_dev->si_name);

	return 0;
}

static int
fuse_cdevpriv_close(struct fuse_mount *fmp)
{
	if (!fmp->devvp) {
		fuse_print("/dev/%s not associated with FUSE mount\n",
		    fuse_dev->si_name);
		return ENODEV;
	}

	mtx_lock(&fmp->mnt_lock);
	if (fuse_mount_kill(fmp) == -1)
		KNOTE(&fmp->kq.ki_note, 0);
	KKASSERT(fmp->devvp);
	mtx_unlock(&fmp->mnt_lock);

	return 0;
}

/* Call with ->ipc_lock held. */
static void
fuse_device_clear(struct fuse_mount *fmp)
{
	struct fuse_ipc *fip;

	while ((fip = TAILQ_FIRST(&fmp->request_head))) {
		TAILQ_REMOVE(&fmp->request_head, fip, request_entry);
		if (atomic_swap_int(&fip->sent, 1) == -1)
			wakeup(fip);
	}

	while ((fip = TAILQ_FIRST(&fmp->reply_head))) {
		TAILQ_REMOVE(&fmp->reply_head, fip, reply_entry);
		if (fuse_ipc_test_and_set_replied(fip))
			wakeup(fip);
	}
}

static int
fuse_device_read(struct dev_read_args *ap)
{
	struct uio *uio = ap->a_uio;
	struct fuse_mount *fmp;
	struct fuse_ipc *fip;
	int error;

	error = devfs_get_cdevpriv(ap->a_fp, (void**)&fmp);
	if (error)
		return error;

	if (fuse_test_dead(fmp))
		return ENOTCONN;

	mtx_lock(&fmp->ipc_lock);
	while (!(fip = TAILQ_FIRST(&fmp->request_head))) {
		error = mtxsleep(fmp, &fmp->ipc_lock, PCATCH, "ftxc", 0);
		if (fuse_test_dead(fmp)) {
			fuse_device_clear(fmp);
			mtx_unlock(&fmp->ipc_lock);
			fuse_dbg("error=%d dead\n", error);
			return ENOTCONN;
		}
		if (error) {
			mtx_unlock(&fmp->ipc_lock);
			fuse_dbg("error=%d\n", error);
			return error;
		}
	}
	TAILQ_REMOVE(&fmp->request_head, fip, request_entry);
	mtx_unlock(&fmp->ipc_lock);

	fuse_dbgipc(fip, 0, "");

	if (uio->uio_resid < fuse_in_size(fip))
		error = EILSEQ;
	else
		error = uiomove(fuse_in(fip), fuse_in_size(fip), uio);

	if (atomic_swap_int(&fip->sent, 1) == -1)
		wakeup(fip);

	return error;
}

static int
fuse_device_write(struct dev_write_args *ap)
{
	struct uio *uio = ap->a_uio;
	struct fuse_mount *fmp;
	struct fuse_ipc *fip;
	struct fuse_buf fb;
	struct fuse_in_header *ihd;
	struct fuse_out_header *ohd;
	int error;

	error = devfs_get_cdevpriv(ap->a_fp, (void**)&fmp);
	if (error)
		return error;

	if (uio->uio_resid < sizeof(*ohd))
		return EILSEQ;

	fuse_buf_alloc(&fb, uio->uio_resid);
	error = uiomove(fb.buf, uio->uio_resid, uio);
	if (error) {
		fuse_buf_free(&fb);
		return error;
	}
	ohd = fb.buf;

	mtx_lock(&fmp->ipc_lock);
	TAILQ_FOREACH(fip, &fmp->reply_head, reply_entry) {
		if (fip->unique == ohd->unique) {
			TAILQ_REMOVE(&fmp->reply_head, fip, reply_entry);
			break;
		}
	}
	mtx_unlock(&fmp->ipc_lock);

	if (!fip) {
		fuse_dbg("unique=%ju not found\n", ohd->unique);
		fuse_buf_free(&fb);
		return ENOMSG;
	}

	fip->reply = fb;
	ihd = fuse_in(fip);

	/* Non zero ohd->error is not /dev/fuse write error. */
	if (ohd->error == -ENOSYS) {
		fuse_set_nosys(fmp, ihd->opcode);
		fuse_dbgipc(fip, ohd->error, "ENOSYS");
	} else if (!ohd->error && fuse_audit_length(ihd, ohd)) {
		error = EPROTO;
		fuse_dbgipc(fip, error, "audit");
	} else
		fuse_dbgipc(fip, 0, "");

	/* Complete the IPC regardless of above result. */
	if (fuse_ipc_test_and_set_replied(fip))
		wakeup(fip);

	return error;
}

static void filt_fusedevdetach(struct knote*);
static int filt_fusedevread(struct knote*, long);
static int filt_fusedevwrite(struct knote*, long);

static struct filterops fusedevread_filterops =
	{ FILTEROP_ISFD,
	  NULL, filt_fusedevdetach, filt_fusedevread };
static struct filterops fusedevwrite_filterops =
	{ FILTEROP_ISFD,
	  NULL, filt_fusedevdetach, filt_fusedevwrite };

static int
fuse_device_kqfilter(struct dev_kqfilter_args *ap)
{
	struct knote *kn = ap->a_kn;
	struct klist *klist;
	struct fuse_mount *fmp;
	int error;

	error = devfs_get_cdevpriv(ap->a_fp, (void**)&fmp);
	if (error) {
		ap->a_result = error;
		return 0;
	}

	ap->a_result = 0;

	switch (kn->kn_filter) {
	case EVFILT_READ:
		kn->kn_fop = &fusedevread_filterops;
		kn->kn_hook = (caddr_t)fmp;
		break;
	case EVFILT_WRITE:
		kn->kn_fop = &fusedevwrite_filterops;
		kn->kn_hook = (caddr_t)fmp;
		break;
	default:
		ap->a_result = EOPNOTSUPP;
		return 0;
	}

	klist = &fmp->kq.ki_note;
	knote_insert(klist, kn);

	return 0;
}

static void
filt_fusedevdetach(struct knote *kn)
{
	struct fuse_mount *fmp = (struct fuse_mount*)kn->kn_hook;
	struct klist *klist = &fmp->kq.ki_note;

	knote_remove(klist, kn);
}

static int
filt_fusedevread(struct knote *kn, long hint)
{
	struct fuse_mount *fmp = (struct fuse_mount*)kn->kn_hook;
	int ready = 0;

	mtx_lock(&fmp->ipc_lock);
	if (!TAILQ_EMPTY(&fmp->request_head))
		ready = 1;
	mtx_unlock(&fmp->ipc_lock);

	return ready;
}

static int
filt_fusedevwrite(struct knote *kn, long hint)
{
	return 1;
}

static struct dev_ops fuse_device_cdevsw = {
	{ "fuse", 0, D_MPSAFE, },
	.d_open = fuse_device_open,
	.d_close = fuse_device_close,
	.d_read = fuse_device_read,
	.d_write = fuse_device_write,
	.d_kqfilter = fuse_device_kqfilter,
};

int
fuse_device_init(void)
{
	fuse_dev = make_dev(&fuse_device_cdevsw, 0, UID_ROOT, GID_OPERATOR,
	    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, "fuse");

	if (!fuse_dev)
		return ENOMEM;

	return 0;
}

void
fuse_device_cleanup(void)
{
	KKASSERT(fuse_dev);
	destroy_dev(fuse_dev);
}