winfsp/src/dll/fuse3/fuse2to3.c
2019-02-28 10:39:31 -08:00

661 lines
20 KiB
C

/**
* @file dll/fuse3/fuse2to3.c
*
* @copyright 2015-2019 Bill Zissimopoulos
*/
/*
* This file is part of WinFsp.
*
* You can redistribute it and/or modify it under the terms of the GNU
* General Public License version 3 as published by the Free Software
* Foundation.
*
* Licensees holding a valid commercial license may use this software
* in accordance with the commercial license agreement provided in
* conjunction with the software. The terms and conditions of any such
* commercial license agreement shall govern, supersede, and render
* ineffective any application of the GPLv3 license to this software,
* notwithstanding of any reference thereto in the software or
* associated repository.
*/
#include <dll/fuse3/library.h>
static inline struct fuse3 *fuse2to3_getfuse3(void)
{
return fsp_fuse_get_context_internal()->fuse->fuse3;
}
static inline void fuse2to3_fi2from3(struct fuse_file_info *fi, struct fuse3_file_info *fi3)
{
memset(fi, 0, sizeof *fi);
fi->flags = fi3->flags;
fi->writepage = fi3->writepage;
fi->direct_io = fi3->direct_io;
fi->keep_cache = fi3->keep_cache;
fi->flush = fi3->flush;
fi->nonseekable = fi3->nonseekable;
fi->fh = fi3->fh;
fi->lock_owner = fi3->lock_owner;
}
static inline void fuse2to3_fi3from2(struct fuse3_file_info *fi3, struct fuse_file_info *fi)
{
memset(fi3, 0, sizeof *fi3);
fi3->flags = fi->flags;
fi3->writepage = fi->writepage;
fi3->direct_io = fi->direct_io;
fi3->keep_cache = fi->keep_cache;
fi3->flush = fi->flush;
fi3->nonseekable = fi->nonseekable;
fi3->fh = fi->fh;
fi3->lock_owner = fi->lock_owner;
}
static inline void fuse2to3_conn3from2(struct fuse3_conn_info *conn3, struct fuse_conn_info *conn)
{
memset(conn3, 0, sizeof *conn3);
conn3->proto_major = 7; /* pretend that we are FUSE kernel protocol 7.26 */
conn3->proto_minor = 26; /* which was current at the time of FUSE 3.2 */
conn3->max_write = conn->max_write;
conn3->max_read = conn->max_write;
conn3->max_readahead = conn->max_readahead;
conn3->capable = (conn->capable & ~FSP_FUSE_CAP_READDIR_PLUS) | FUSE_CAP_READDIRPLUS;
conn3->want = conn->want;
}
static int fuse2to3_getattr(const char *path, struct fuse_stat *stbuf)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.getattr(path, stbuf, 0);
}
static int fuse2to3_readlink(const char *path, char *buf, size_t size)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.readlink(path, buf, size);
}
static int fuse2to3_mknod(const char *path, fuse_mode_t mode, fuse_dev_t dev)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.mknod(path, mode, dev);
}
static int fuse2to3_mkdir(const char *path, fuse_mode_t mode)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.mkdir(path, mode);
}
static int fuse2to3_unlink(const char *path)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.unlink(path);
}
static int fuse2to3_rmdir(const char *path)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.rmdir(path);
}
static int fuse2to3_symlink(const char *dstpath, const char *srcpath)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.symlink(dstpath, srcpath);
}
static int fuse2to3_rename(const char *oldpath, const char *newpath)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.rename(oldpath, newpath, 0);
}
static int fuse2to3_link(const char *srcpath, const char *dstpath)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.link(srcpath, dstpath);
}
static int fuse2to3_chmod(const char *path, fuse_mode_t mode)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.chmod(path, mode, 0);
}
static int fuse2to3_chown(const char *path, fuse_uid_t uid, fuse_gid_t gid)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.chown(path, uid, gid, 0);
}
static int fuse2to3_truncate(const char *path, fuse_off_t size)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.truncate(path, size, 0);
}
static int fuse2to3_open(const char *path, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.open(path, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_read(const char *path, char *buf, size_t size, fuse_off_t off,
struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.read(path, buf, size, off, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_write(const char *path, const char *buf, size_t size, fuse_off_t off,
struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.write(path, buf, size, off, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_statfs(const char *path, struct fuse_statvfs *stbuf)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.statfs(path, stbuf);
}
static int fuse2to3_flush(const char *path, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.flush(path, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_release(const char *path, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.release(path, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_fsync(const char *path, int datasync, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.fsync(path, datasync, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_setxattr(const char *path, const char *name, const char *value, size_t size,
int flags)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.setxattr(path, name, value, size, flags);
}
static int fuse2to3_getxattr(const char *path, const char *name, char *value, size_t size)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.getxattr(path, name, value, size);
}
static int fuse2to3_listxattr(const char *path, char *namebuf, size_t size)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.listxattr(path, namebuf, size);
}
static int fuse2to3_removexattr(const char *path, const char *name)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.removexattr(path, name);
}
static int fuse2to3_opendir(const char *path, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.opendir(path, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_candel_filldir(void *buf, const char *name,
const struct fuse_stat *stbuf, fuse_off_t off,
enum fuse3_fill_dir_flags flags)
{
return fsp_fuse_intf_CanDeleteAddDirInfo(buf, name, 0, off);
}
static int fuse2to3_filldir(void *buf, const char *name,
const struct fuse_stat *stbuf, fuse_off_t off,
enum fuse3_fill_dir_flags flags)
{
return 0 != (flags & FUSE_FILL_DIR_PLUS) ?
fsp_fuse_intf_AddDirInfo(buf, name, stbuf, off) :
fsp_fuse_intf_AddDirInfo(buf, name, 0, off);
}
static int fuse2to3_readdir(const char *path, void *buf, fuse_fill_dir_t filler, fuse_off_t off,
struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse_dirhandle *dh = buf;
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res;
if (fsp_fuse_intf_CanDeleteAddDirInfo == filler)
res = f3->ops.readdir(path, buf, &fuse2to3_candel_filldir, off, &fi3, 0);
else if (fsp_fuse_intf_AddDirInfo == filler)
res = f3->ops.readdir(path, buf, &fuse2to3_filldir, off, &fi3,
dh->ReaddirPlus ? FUSE_READDIR_PLUS : 0);
else
{
FspDebugLog("fuse2to3_readdir = -ENOSYS (internal error: unknown filler)\n");
res = -ENOSYS_(f3->fuse->env);
}
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_releasedir(const char *path, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.releasedir(path, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_fsyncdir(const char *path, int datasync, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.fsyncdir(path, datasync, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static void *fuse2to3_init(struct fuse_conn_info *conn)
{
struct fuse_context *context = fsp_fuse_get_context_internal();
struct fuse *f = context->fuse;
struct fuse3 *f3 = f->fuse3;
struct fuse3_conn_info conn3;
fuse2to3_conn3from2(&conn3, conn);
struct fuse3_config conf3;
memset(&conf3, 0, sizeof conf3);
conf3.set_gid = f->set_gid;
conf3.gid = f->gid;
conf3.set_uid = f->set_uid;
conf3.uid = f->uid;
conf3.set_mode = f->set_umask;
conf3.umask = f->umask;
#if 0
/*
* Cannot set timeouts because of lack of floating point support.
*
* FUSE uses the `double` type for timeouts. This DLL does not use the standard library
* for a variety of reasons. This means that we cannot easily perform the computations
* below.
*
* If this becomes important (double) floating point values could perhaps be calculated
* using bit tricks. See below:
* - http://locklessinc.com/articles/i2f/
* - https://stackoverflow.com/a/20308114
*/
conf3.entry_timeout = f->VolumeParams.DirInfoTimeoutValid ?
f->VolumeParams.DirInfoTimeout / 1000 : f->VolumeParams.FileInfoTimeout / 1000;
conf3.negative_timeout = 0;
conf3.attr_timeout = f->VolumeParams.FileInfoTimeout / 1000;
conf3.ac_attr_timeout = conf3.attr_timeout;
#endif
void *res = f3->ops.init(&conn3, &conf3);
conn->max_write = conn3.max_write;
conn->max_readahead = conn3.max_readahead;
conn->want = 0 != (conn3.want & FUSE_CAP_READDIRPLUS) ? FSP_FUSE_CAP_READDIR_PLUS : 0;
conn->want |= conn3.want & ~FUSE_CAP_READDIRPLUS;
return res;
}
static void fuse2to3_destroy(void *data)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
f3->ops.destroy(data);
}
static int fuse2to3_access(const char *path, int mask)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.access(path, mask);
}
static int fuse2to3_create(const char *path, fuse_mode_t mode, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.create(path, mode, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_ftruncate(const char *path, fuse_off_t off, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.truncate(path, off, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_fgetattr(const char *path, struct fuse_stat *stbuf, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.getattr(path, stbuf, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_lock(const char *path,
struct fuse_file_info *fi, int cmd, struct fuse_flock *lock)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.lock(path, &fi3, cmd, lock);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_utimens(const char *path, const struct fuse_timespec tv[2])
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.utimens(path, tv, 0);
}
static int fuse2to3_bmap(const char *path, size_t blocksize, uint64_t *idx)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
return f3->ops.bmap(path, blocksize, idx);
}
static int fuse2to3_ioctl(const char *path, int cmd, void *arg, struct fuse_file_info *fi,
unsigned int flags, void *data)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.ioctl(path, cmd, arg, &fi3, flags, data);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_poll(const char *path, struct fuse_file_info *fi,
struct fuse_pollhandle *ph, unsigned *reventsp)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.poll(path, &fi3, (struct fuse3_pollhandle *)ph, reventsp);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_write_buf(const char *path,
struct fuse_bufvec *buf, fuse_off_t off, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.write_buf(path,
(struct fuse3_bufvec *)buf, /* revisit if we implement bufvec's */
off, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_read_buf(const char *path,
struct fuse_bufvec **bufp, size_t size, fuse_off_t off, struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.read_buf(path,
(struct fuse3_bufvec **)bufp, /* revisit if we implement bufvec's */
size, off, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_flock(const char *path, struct fuse_file_info *fi, int op)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.flock(path, &fi3, op);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fuse2to3_fallocate(const char *path, int mode, fuse_off_t off, fuse_off_t len,
struct fuse_file_info *fi)
{
struct fuse3 *f3 = fuse2to3_getfuse3();
struct fuse3_file_info fi3;
fuse2to3_fi3from2(&fi3, fi);
int res = f3->ops.fallocate(path, mode, off, len, &fi3);
fuse2to3_fi2from3(fi, &fi3);
return res;
}
static int fsp_fuse3_copy_args(struct fsp_fuse_env *env,
const struct fuse_args *args,
struct fuse_args *outargs)
{
outargs->argc = 0;
outargs->argv = 0;
outargs->allocated = 0;
for (int argi = 0; args->argc > argi; argi++)
if (-1 == fsp_fuse_opt_add_arg(env, outargs, args->argv[argi]))
goto fail;
return 0;
fail:
fsp_fuse_opt_free_args(env, outargs);
return -1;
}
static struct fuse3 *fsp_fuse3_new_common(struct fsp_fuse_env *env,
struct fuse_args *args,
const struct fuse3_operations *ops, size_t opsize, void *data,
int help)
{
/* preflight args */
struct fsp_fuse_core_opt_data opt_data;
struct fuse_args pfargs;
memset(&opt_data, 0, sizeof opt_data);
if (-1 == fsp_fuse3_copy_args(env, args, &pfargs))
return 0;
int optres = fsp_fuse_core_opt_parse(env, &pfargs, &opt_data, /*help=*/1);
fsp_fuse_opt_free_args(env, &pfargs);
if (-1 == optres)
return 0;
if (opt_data.help)
return 0;
struct fuse3 *f3 = 0;
if (opsize > sizeof(struct fuse3_operations))
opsize = sizeof(struct fuse3_operations);
f3 = fsp_fuse_obj_alloc(env, sizeof *f3);
if (0 == f3)
goto fail;
if (-1 == fsp_fuse3_copy_args(env, args, &f3->args))
goto fail;
memcpy(&f3->ops, ops, opsize);
f3->data = data;
return f3;
fail:
if (0 != f3)
fsp_fuse3_destroy(env, f3);
return 0;
}
FSP_FUSE_API struct fuse3 *fsp_fuse3_new_30(struct fsp_fuse_env *env,
struct fuse_args *args,
const struct fuse3_operations *ops, size_t opsize, void *data)
{
return fsp_fuse3_new_common(env, args, ops, opsize, data, /*help=*/1);
}
FSP_FUSE_API struct fuse3 *fsp_fuse3_new(struct fsp_fuse_env *env,
struct fuse_args *args,
const struct fuse3_operations *ops, size_t opsize, void *data)
{
return fsp_fuse3_new_common(env, args, ops, opsize, data, /*help=*/0);
}
FSP_FUSE_API void fsp_fuse3_destroy(struct fsp_fuse_env *env,
struct fuse3 *f3)
{
if (0 != f3->fuse)
fsp_fuse_destroy(env, f3->fuse);
fsp_fuse_opt_free_args(env, &f3->args);
fsp_fuse_obj_free(f3);
}
FSP_FUSE_API int fsp_fuse3_mount(struct fsp_fuse_env *env,
struct fuse3 *f3, const char *mountpoint)
{
struct fuse_chan *ch = 0;
struct fuse *f = 0;
struct fuse_operations fuse2to3_ops =
{
.getattr = 0 != f3->ops.getattr ? fuse2to3_getattr : 0,
.readlink = 0 != f3->ops.readlink ? fuse2to3_readlink : 0,
.mknod = 0 != f3->ops.mknod ? fuse2to3_mknod : 0,
.mkdir = 0 != f3->ops.mkdir ? fuse2to3_mkdir : 0,
.unlink = 0 != f3->ops.unlink ? fuse2to3_unlink : 0,
.rmdir = 0 != f3->ops.rmdir ? fuse2to3_rmdir : 0,
.symlink = 0 != f3->ops.symlink ? fuse2to3_symlink : 0,
.rename = 0 != f3->ops.rename ? fuse2to3_rename : 0,
.link = 0 != f3->ops.link ? fuse2to3_link : 0,
.chmod = 0 != f3->ops.chmod ? fuse2to3_chmod : 0,
.chown = 0 != f3->ops.chown ? fuse2to3_chown : 0,
.truncate = 0 != f3->ops.truncate ? fuse2to3_truncate : 0,
.open = 0 != f3->ops.open ? fuse2to3_open : 0,
.read = 0 != f3->ops.read ? fuse2to3_read : 0,
.write = 0 != f3->ops.write ? fuse2to3_write : 0,
.statfs = 0 != f3->ops.statfs ? fuse2to3_statfs : 0,
.flush = 0 != f3->ops.flush ? fuse2to3_flush : 0,
.release = 0 != f3->ops.release ? fuse2to3_release : 0,
.fsync = 0 != f3->ops.fsync ? fuse2to3_fsync : 0,
.setxattr = 0 != f3->ops.setxattr ? fuse2to3_setxattr : 0,
.getxattr = 0 != f3->ops.getxattr ? fuse2to3_getxattr : 0,
.listxattr = 0 != f3->ops.listxattr ? fuse2to3_listxattr : 0,
.removexattr = 0 != f3->ops.removexattr ? fuse2to3_removexattr : 0,
.opendir = 0 != f3->ops.opendir ? fuse2to3_opendir : 0,
.readdir = 0 != f3->ops.readdir ? fuse2to3_readdir : 0,
.releasedir = 0 != f3->ops.releasedir ? fuse2to3_releasedir : 0,
.fsyncdir = 0 != f3->ops.fsyncdir ? fuse2to3_fsyncdir : 0,
.init = 0 != f3->ops.init ? fuse2to3_init : 0,
.destroy = 0 != f3->ops.destroy ? fuse2to3_destroy : 0,
.access = 0 != f3->ops.access ? fuse2to3_access : 0,
.create = 0 != f3->ops.create ? fuse2to3_create : 0,
.ftruncate = 0 != f3->ops.truncate ? fuse2to3_ftruncate : 0,
.fgetattr = 0 != f3->ops.getattr ? fuse2to3_fgetattr : 0,
.lock = 0 != f3->ops.lock ? fuse2to3_lock : 0,
.utimens = 0 != f3->ops.utimens ? fuse2to3_utimens : 0,
.bmap = 0 != f3->ops.bmap ? fuse2to3_bmap : 0,
.ioctl = 0 != f3->ops.ioctl ? fuse2to3_ioctl : 0,
.poll = 0 != f3->ops.poll ? fuse2to3_poll : 0,
.write_buf = 0 != f3->ops.write_buf ? fuse2to3_write_buf : 0,
.read_buf = 0 != f3->ops.read_buf ? fuse2to3_read_buf : 0,
.flock = 0 != f3->ops.flock ? fuse2to3_flock : 0,
.fallocate = 0 != f3->ops.fallocate ? fuse2to3_fallocate : 0,
};
ch = fsp_fuse_mount(env, mountpoint, &f3->args);
if (0 == ch)
goto fail;
f = fsp_fuse_new(env, ch, &f3->args, &fuse2to3_ops, sizeof fuse2to3_ops, f3->data);
if (0 == f)
goto fail;
/*
* Free the fuse_chan which is no longer needed. Note that this behavior is WinFsp-FUSE
* specific, because WinFsp-FUSE only allocates/frees fuse_chan memory during fuse_mount/
* fuse_unmount and does not perform any actual mounting/unmounting. This would not work
* on a different FUSE implementation.
*
* (Store mountpoint and ch inside struct fuse3 so that they can be freed during fuse_destroy
* in that case.)
*/
fsp_fuse_unmount(env, mountpoint, ch);
/* Free the args which are no longer needed. */
fsp_fuse_opt_free_args(env, &f3->args);
f->fuse3 = f3;
f3->fuse = f;
return 0;
fail:
if (0 != f)
fsp_fuse_destroy(env, f);
if (0 != ch)
fsp_fuse_unmount(env, mountpoint, ch);
return -1;
}
FSP_FUSE_API void fsp_fuse3_unmount(struct fsp_fuse_env *env,
struct fuse3 *f3)
{
fsp_fuse_destroy(env, f3->fuse);
f3->fuse = 0;
}