userlandfs: compatibility with FUSE 2.9 and FUSE lowlevel API

- Import latest version of files from FUSE 2.9.9 (our last
  synchronization was with 2.7.4)
- Adjust fuse pkgconfig file to use the POSIX error mapper
  automatically, since that's required for all FUSE software
- Implement the lowlevel API in addition to the highlevel one. The
  lowlevel API uses inode numbers to identify files, rather than paths,
  making it a better fit to the userlandfs architecture.

The FUSE 2.x branch is not maintained anymore by FUSE developers,
however, pretty much no one migrated to FUSE 3.x. So it is more
interesting to implement, rather than 3.x.

Confirmed still working with sshfs and curlftpfs.

Example use:

I tested this with github.com/whoozle/android-file-transfer-linux

- Build the fuse library and copy it to ~/config/non-packaged/add-ons/userlandfs/
- Start the server: /system/servers/userlandfs_server aft-mtp-mount
- Connect your Android phone and put it in USB file transfer mode
- Mount the device: mount -t userlandfs -p 'aft-mtp-mount /boot/home/MyPhone -d -o use_ino' ~/MyPhone
- You can now access your phone data

Change-Id: Ic3efda7ffbc33737e6f4958428fb3ec9939ef105
Reviewed-on: https://review.haiku-os.org/c/haiku/+/5198
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Reviewed-by: waddlesplash <waddlesplash@gmail.com>
This commit is contained in:
PulkoMandy 2022-06-06 21:48:05 +02:00 committed by waddlesplash
parent f28e05bce5
commit 0604d554e8
17 changed files with 2748 additions and 522 deletions

View File

@ -1,5 +1,5 @@
Name: fuse
Description: File System Userspace Executor
Version: 2.7-Haiku
Libs: -luserlandfs_fuse
CFlags: -I/system/develop/headers/userlandfs/fuse/
Version: 2.9-Haiku
Libs: -lposix_error_mapper -luserlandfs_fuse
CFlags: -I/system/develop/headers/userlandfs/fuse/ -DB_USE_POSITIVE_POSIX_ERRORS

View File

@ -31,6 +31,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/uio.h>
#ifdef __cplusplus
extern "C" {
@ -41,6 +42,7 @@ struct fs_info;
extern int gHasHaikuFuseExtensions;
#endif
/* ----------------------------------------------------------- *
* Basic FUSE API *
* ----------------------------------------------------------- */
@ -80,6 +82,14 @@ typedef int (*fuse_dirfil_t) (fuse_dirh_t h, const char *name, int type,
* releasedir, fsyncdir, access, create, ftruncate, fgetattr, lock,
* init and destroy are special purpose methods, without which a full
* featured filesystem can still be implemented.
*
* Almost all operations take a path which can be of any length.
*
* Changed in fuse 2.8.0 (regardless of API version)
* Previously, paths were limited to a length of PATH_MAX.
*
* See http://fuse.sourceforge.net/wiki/ for more information. There
* is also a snapshot of the relevant wiki pages in the doc/ folder.
*/
struct fuse_operations {
/** Get file attributes.
@ -111,7 +121,12 @@ struct fuse_operations {
*/
int (*mknod) (const char *, mode_t, dev_t);
/** Create a directory */
/** Create a directory
*
* Note that the mode argument may not have the type specification
* bits set, i.e. S_ISDIR(mode) can be false. To obtain the
* correct directory type bits use mode|S_IFDIR
* */
int (*mkdir) (const char *, mode_t);
/** Remove a file */
@ -146,11 +161,18 @@ struct fuse_operations {
/** File open operation
*
* No creation, or truncation flags (O_CREAT, O_EXCL, O_TRUNC)
* will be passed to open(). Open should check if the operation
* is permitted for the given flags. Optionally open may also
* return an arbitrary filehandle in the fuse_file_info structure,
* which will be passed to all file operations.
* No creation (O_CREAT, O_EXCL) and by default also no
* truncation (O_TRUNC) flags will be passed to open(). If an
* application specifies O_TRUNC, fuse first calls truncate()
* and then open(). Only if 'atomic_o_trunc' has been
* specified and kernel version is 2.6.24 or later, O_TRUNC is
* passed on to open.
*
* Unless the 'default_permissions' mount option is given,
* open should check if the operation is permitted for the
* given flags. Optionally open may also return an arbitrary
* filehandle in the fuse_file_info structure, which will be
* passed to all file operations.
*
* Changed in version 2.2
*/
@ -254,8 +276,11 @@ struct fuse_operations {
/** Open directory
*
* This method should check if the open operation is permitted for
* this directory
* Unless the 'default_permissions' mount option is given,
* this method should check if opendir is permitted for this
* directory. Optionally opendir may also return an arbitrary
* filehandle in the fuse_file_info structure, which will be
* passed to readdir, releasedir and fsyncdir.
*
* Introduced in version 2.3
*/
@ -393,7 +418,7 @@ struct fuse_operations {
* information without calling this method. This ensures, that
* for local locks the l_pid field is correctly filled in. The
* results may not be accurate in case of race conditions and in
* the presence of hard links, but it's unlikly that an
* the presence of hard links, but it's unlikely that an
* application would rely on accurate GETLK results in these
* cases. If a conflicting lock is not found, this method will be
* called, and the filesystem may fill out l_pid by a meaningful
@ -415,6 +440,11 @@ struct fuse_operations {
* Change the access and modification times of a file with
* nanosecond resolution
*
* This supersedes the old utime() interface. New applications
* should use this.
*
* See the utimensat(2) man page for details.
*
* Introduced in version 2.6
*/
int (*utimens) (const char *, const struct timespec tv[2]);
@ -432,6 +462,145 @@ struct fuse_operations {
#ifdef HAS_FUSE_HAIKU_EXTENSIONS
int (*get_fs_info) (struct fs_info*);
#endif
/**
* Flag indicating that the filesystem can accept a NULL path
* as the first argument for the following operations:
*
* read, write, flush, release, fsync, readdir, releasedir,
* fsyncdir, ftruncate, fgetattr, lock, ioctl and poll
*
* If this flag is set these operations continue to work on
* unlinked files even if "-ohard_remove" option was specified.
*/
unsigned int flag_nullpath_ok:1;
/**
* Flag indicating that the path need not be calculated for
* the following operations:
*
* read, write, flush, release, fsync, readdir, releasedir,
* fsyncdir, ftruncate, fgetattr, lock, ioctl and poll
*
* Closely related to flag_nullpath_ok, but if this flag is
* set then the path will not be calculaged even if the file
* wasn't unlinked. However the path can still be non-NULL if
* it needs to be calculated for some other reason.
*/
unsigned int flag_nopath:1;
/**
* Flag indicating that the filesystem accepts special
* UTIME_NOW and UTIME_OMIT values in its utimens operation.
*/
unsigned int flag_utime_omit_ok:1;
/**
* Reserved flags, don't set
*/
unsigned int flag_reserved:29;
/**
* Ioctl
*
* flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in
* 64bit environment. The size and direction of data is
* determined by _IOC_*() decoding of cmd. For _IOC_NONE,
* data will be NULL, for _IOC_WRITE data is out area, for
* _IOC_READ in area and if both are set in/out area. In all
* non-NULL cases, the area is of _IOC_SIZE(cmd) bytes.
*
* If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a
* directory file handle.
*
* Introduced in version 2.8
*/
int (*ioctl) (const char *, int cmd, void *arg,
struct fuse_file_info *, unsigned int flags, void *data);
/**
* Poll for IO readiness events
*
* Note: If ph is non-NULL, the client should notify
* when IO readiness events occur by calling
* fuse_notify_poll() with the specified ph.
*
* Regardless of the number of times poll with a non-NULL ph
* is received, single notification is enough to clear all.
* Notifying more times incurs overhead but doesn't harm
* correctness.
*
* The callee is responsible for destroying ph with
* fuse_pollhandle_destroy() when no longer in use.
*
* Introduced in version 2.8
*/
int (*poll) (const char *, struct fuse_file_info *,
struct fuse_pollhandle *ph, unsigned *reventsp);
/** Write contents of buffer to an open file
*
* Similar to the write() method, but data is supplied in a
* generic buffer. Use fuse_buf_copy() to transfer data to
* the destination.
*
* Introduced in version 2.9
*/
int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
struct fuse_file_info *);
/** Store data from an open file in a buffer
*
* Similar to the read() method, but data is stored and
* returned in a generic buffer.
*
* No actual copying of data has to take place, the source
* file descriptor may simply be stored in the buffer for
* later data transfer.
*
* The buffer must be allocated dynamically and stored at the
* location pointed to by bufp. If the buffer contains memory
* regions, they too must be allocated using malloc(). The
* allocated memory will be freed by the caller.
*
* Introduced in version 2.9
*/
int (*read_buf) (const char *, struct fuse_bufvec **bufp,
size_t size, off_t off, struct fuse_file_info *);
/**
* Perform BSD file locking operation
*
* The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN
*
* Nonblocking requests will be indicated by ORing LOCK_NB to
* the above operations
*
* For more information see the flock(2) manual page.
*
* Additionally fi->owner will be set to a value unique to
* this open file. This same value will be supplied to
* ->release() when the file is released.
*
* Note: if this method is not implemented, the kernel will still
* allow file locking to work locally. Hence it is only
* interesting for network filesystems and similar.
*
* Introduced in version 2.9
*/
int (*flock) (const char *, struct fuse_file_info *, int op);
/**
* Allocates space for an open file
*
* This function ensures that required space is allocated for specified
* file. If this function returns success then any subsequent write
* request to specified range is guaranteed not to fail because of lack
* of space on the file system media.
*
* Introduced in version 2.9.1
*/
int (*fallocate) (const char *, int, off_t, off_t,
struct fuse_file_info *);
};
/** Extra context that may be needed by some filesystems
@ -454,6 +623,9 @@ struct fuse_context {
/** Private filesystem data */
void *private_data;
/** Umask of the calling process (introduced in version 2.8) */
mode_t umask;
};
/**
@ -560,9 +732,28 @@ int fuse_loop_mt(struct fuse *f);
struct fuse_context *fuse_get_context(void);
/**
* Check if a request has already been interrupted
* Get the current supplementary group IDs for the current request
*
* Similar to the getgroups(2) system call, except the return value is
* always the total number of group IDs, even if it is larger than the
* specified size.
*
* The current fuse kernel module in linux (as of 2.6.30) doesn't pass
* the group list to userspace, hence this function needs to parse
* "/proc/$TID/task/$TID/status" to get the group IDs.
*
* This feature may not be supported on all operating systems. In
* such a case this function will return -ENOSYS.
*
* @param size size of given array
* @param list array of group IDs to be filled in
* @return the total number of supplementary group IDs or -errno on failure
*/
int fuse_getgroups(int size, gid_t list[]);
/**
* Check if the current request has already been interrupted
*
* @param req request handle
* @return 1 if the request has been interrupted, 0 otherwise
*/
int fuse_interrupted(void);
@ -585,6 +776,34 @@ int fuse_is_lib_option(const char *opt);
int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
size_t op_size, void *user_data);
/**
* Start the cleanup thread when using option "remember".
*
* This is done automatically by fuse_loop_mt()
* @param fuse struct fuse pointer for fuse instance
* @return 0 on success and -1 on error
*/
int fuse_start_cleanup_thread(struct fuse *fuse);
/**
* Stop the cleanup thread when using option "remember".
*
* This is done automatically by fuse_loop_mt()
* @param fuse struct fuse pointer for fuse instance
*/
void fuse_stop_cleanup_thread(struct fuse *fuse);
/**
* Iterate over cache removing stale entries
* use in conjunction with "-oremember"
*
* NOTE: This is already done for the standard sessions
*
* @param fuse struct fuse pointer for fuse instance
* @return the number of seconds until the next cleanup
*/
int fuse_clean_cache(struct fuse *fuse);
/*
* Stacking API
*/
@ -621,8 +840,14 @@ int fuse_fs_open(struct fuse_fs *fs, const char *path,
struct fuse_file_info *fi);
int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
off_t off, struct fuse_file_info *fi);
int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
struct fuse_bufvec **bufp, size_t size, off_t off,
struct fuse_file_info *fi);
int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
size_t size, off_t off, struct fuse_file_info *fi);
int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
struct fuse_bufvec *buf, off_t off,
struct fuse_file_info *fi);
int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
struct fuse_file_info *fi);
int fuse_fs_flush(struct fuse_fs *fs, const char *path,
@ -641,6 +866,8 @@ int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
struct fuse_file_info *fi);
int fuse_fs_lock(struct fuse_fs *fs, const char *path,
struct fuse_file_info *fi, int cmd, struct flock *lock);
int fuse_fs_flock(struct fuse_fs *fs, const char *path,
struct fuse_file_info *fi, int op);
int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode);
int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid);
int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size);
@ -664,9 +891,18 @@ int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
const char *name);
int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
uint64_t *idx);
int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg,
struct fuse_file_info *fi, unsigned int flags, void *data);
int fuse_fs_poll(struct fuse_fs *fs, const char *path,
struct fuse_file_info *fi, struct fuse_pollhandle *ph,
unsigned *reventsp);
int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
off_t offset, off_t length, struct fuse_file_info *fi);
void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn);
void fuse_fs_destroy(struct fuse_fs *fs);
int fuse_notify_poll(struct fuse_pollhandle *ph);
#ifdef HAS_FUSE_HAIKU_EXTENSIONS
int fuse_fs_get_fs_info(struct fuse_fs* fs, struct fs_info* info);
#endif

View File

@ -17,12 +17,13 @@
#include "fuse_opt.h"
#include <stdint.h>
#include <sys/types.h>
/** Major version of FUSE library interface */
#define FUSE_MAJOR_VERSION 2
/** Minor version of FUSE library interface */
#define FUSE_MINOR_VERSION 7
#define FUSE_MINOR_VERSION 9
#define FUSE_MAKE_VERSION(maj, min) ((maj) * 10 + (min))
#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
@ -65,8 +66,18 @@ struct fuse_file_info {
operation. Introduced in version 2.6 */
unsigned int flush : 1;
/** Can be filled in by open, to indicate that the file is not
seekable. Introduced in version 2.8 */
unsigned int nonseekable : 1;
/* Indicates that flock locks for this file should be
released. If set, lock_owner shall contain a valid value.
May only be set in ->release(). Introduced in version
2.9 */
unsigned int flock_release : 1;
/** Padding. Do not use*/
unsigned int padding : 29;
unsigned int padding : 27;
/** File handle. May be filled in by filesystem in open().
Available in all other file operations */
@ -76,6 +87,49 @@ struct fuse_file_info {
uint64_t lock_owner;
};
/**
* Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want'
*
* FUSE_CAP_ASYNC_READ: filesystem supports asynchronous read requests
* FUSE_CAP_POSIX_LOCKS: filesystem supports "remote" locking
* FUSE_CAP_ATOMIC_O_TRUNC: filesystem handles the O_TRUNC open flag
* FUSE_CAP_EXPORT_SUPPORT: filesystem handles lookups of "." and ".."
* FUSE_CAP_BIG_WRITES: filesystem can handle write size larger than 4kB
* FUSE_CAP_DONT_MASK: don't apply umask to file mode on create operations
* FUSE_CAP_SPLICE_WRITE: ability to use splice() to write to the fuse device
* FUSE_CAP_SPLICE_MOVE: ability to move data to the fuse device with splice()
* FUSE_CAP_SPLICE_READ: ability to use splice() to read from the fuse device
* FUSE_CAP_IOCTL_DIR: ioctl support on directories
*/
#define FUSE_CAP_ASYNC_READ (1 << 0)
#define FUSE_CAP_POSIX_LOCKS (1 << 1)
#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
#define FUSE_CAP_BIG_WRITES (1 << 5)
#define FUSE_CAP_DONT_MASK (1 << 6)
#define FUSE_CAP_SPLICE_WRITE (1 << 7)
#define FUSE_CAP_SPLICE_MOVE (1 << 8)
#define FUSE_CAP_SPLICE_READ (1 << 9)
#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
#define FUSE_CAP_IOCTL_DIR (1 << 11)
/**
* Ioctl flags
*
* FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
* FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
* FUSE_IOCTL_RETRY: retry with new iovecs
* FUSE_IOCTL_DIR: is a directory
*
* FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
*/
#define FUSE_IOCTL_COMPAT (1 << 0)
#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
#define FUSE_IOCTL_RETRY (1 << 2)
#define FUSE_IOCTL_DIR (1 << 4)
#define FUSE_IOCTL_MAX_IOV 256
/**
* Connection information, passed to the ->init() method
*
@ -109,14 +163,35 @@ struct fuse_conn_info {
*/
unsigned max_readahead;
/**
* Capability flags, that the kernel supports
*/
unsigned capable;
/**
* Capability flags, that the filesystem wants to enable
*/
unsigned want;
/**
* Maximum number of backgrounded requests
*/
unsigned max_background;
/**
* Kernel congestion threshold parameter
*/
unsigned congestion_threshold;
/**
* For future use.
*/
unsigned reserved[27];
unsigned reserved[23];
};
struct fuse_session;
struct fuse_chan;
struct fuse_pollhandle;
/**
* Create a FUSE mountpoint
@ -177,6 +252,193 @@ int fuse_daemonize(int foreground);
*/
int fuse_version(void);
/**
* Destroy poll handle
*
* @param ph the poll handle
*/
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
/* ----------------------------------------------------------- *
* Data buffer *
* ----------------------------------------------------------- */
/**
* Buffer flags
*/
enum fuse_buf_flags {
/**
* Buffer contains a file descriptor
*
* If this flag is set, the .fd field is valid, otherwise the
* .mem fields is valid.
*/
FUSE_BUF_IS_FD = (1 << 1),
/**
* Seek on the file descriptor
*
* If this flag is set then the .pos field is valid and is
* used to seek to the given offset before performing
* operation on file descriptor.
*/
FUSE_BUF_FD_SEEK = (1 << 2),
/**
* Retry operation on file descriptor
*
* If this flag is set then retry operation on file descriptor
* until .size bytes have been copied or an error or EOF is
* detected.
*/
FUSE_BUF_FD_RETRY = (1 << 3),
};
/**
* Buffer copy flags
*/
enum fuse_buf_copy_flags {
/**
* Don't use splice(2)
*
* Always fall back to using read and write instead of
* splice(2) to copy data from one file descriptor to another.
*
* If this flag is not set, then only fall back if splice is
* unavailable.
*/
FUSE_BUF_NO_SPLICE = (1 << 1),
/**
* Force splice
*
* Always use splice(2) to copy data from one file descriptor
* to another. If splice is not available, return -EINVAL.
*/
FUSE_BUF_FORCE_SPLICE = (1 << 2),
/**
* Try to move data with splice.
*
* If splice is used, try to move pages from the source to the
* destination instead of copying. See documentation of
* SPLICE_F_MOVE in splice(2) man page.
*/
FUSE_BUF_SPLICE_MOVE = (1 << 3),
/**
* Don't block on the pipe when copying data with splice
*
* Makes the operations on the pipe non-blocking (if the pipe
* is full or empty). See SPLICE_F_NONBLOCK in the splice(2)
* man page.
*/
FUSE_BUF_SPLICE_NONBLOCK= (1 << 4),
};
/**
* Single data buffer
*
* Generic data buffer for I/O, extended attributes, etc... Data may
* be supplied as a memory pointer or as a file descriptor
*/
struct fuse_buf {
/**
* Size of data in bytes
*/
size_t size;
/**
* Buffer flags
*/
enum fuse_buf_flags flags;
/**
* Memory pointer
*
* Used unless FUSE_BUF_IS_FD flag is set.
*/
void *mem;
/**
* File descriptor
*
* Used if FUSE_BUF_IS_FD flag is set.
*/
int fd;
/**
* File position
*
* Used if FUSE_BUF_FD_SEEK flag is set.
*/
off_t pos;
};
/**
* Data buffer vector
*
* An array of data buffers, each containing a memory pointer or a
* file descriptor.
*
* Allocate dynamically to add more than one buffer.
*/
struct fuse_bufvec {
/**
* Number of buffers in the array
*/
size_t count;
/**
* Index of current buffer within the array
*/
size_t idx;
/**
* Current offset within the current buffer
*/
size_t off;
/**
* Array of buffers
*/
struct fuse_buf buf[1];
};
/* Initialize bufvec with a single buffer of given size */
#define FUSE_BUFVEC_INIT(size__) \
((struct fuse_bufvec) { \
/* .count= */ 1, \
/* .idx = */ 0, \
/* .off = */ 0, \
/* .buf = */ { /* [0] = */ { \
/* .size = */ (size__), \
/* .flags = */ (enum fuse_buf_flags) 0, \
/* .mem = */ NULL, \
/* .fd = */ -1, \
/* .pos = */ 0, \
} } \
} )
/**
* Get total size of data in a fuse buffer vector
*
* @param bufv buffer vector
* @return size of data
*/
size_t fuse_buf_size(const struct fuse_bufvec *bufv);
/**
* Copy data from one buffer vector to another
*
* @param dst destination buffer vector
* @param src source buffer vector
* @param flags flags controlling the copy
* @return actual number of bytes copied or -errno on error
*/
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
enum fuse_buf_copy_flags flags);
/* ----------------------------------------------------------- *
* Signal handling *
* ----------------------------------------------------------- */
@ -209,7 +471,7 @@ void fuse_remove_signal_handlers(struct fuse_session *se);
#if FUSE_USE_VERSION < 26
# if defined(__FreeBSD__) || defined(__HAIKU__)
# if FUSE_USE_VERSION < 25
# error On FreeBSD and Haiku API version 25 or greater must be used
# error On FreeBSD API version 25 or greater must be used
# endif
# endif
# include "fuse_common_compat.h"

View File

@ -65,7 +65,7 @@ struct fuse *fuse_setup_compat25(int argc, char *argv[],
void fuse_teardown_compat22(struct fuse *fuse, int fd, char *mountpoint);
#if !defined(__FreeBSD__) && !defined(__HAIKU__)
#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__HAIKU__)
#include <sys/statfs.h>
struct fuse_operations_compat22 {
@ -198,4 +198,4 @@ struct fuse *fuse_new_compat1(int fd, int flags,
void fuse_main_compat1(int argc, char *argv[],
const struct fuse_operations_compat1 *op);
#endif /* !__FreeBSD__ && !__HAIKU__ */
#endif /* __FreeBSD__ || __NetBSD__ */

View File

@ -77,9 +77,16 @@ struct fuse_entry_param {
/** Generation number for this entry.
*
* The ino/generation pair should be unique for the filesystem's
* lifetime. It must be non-zero, otherwise FUSE will treat it as an
* error.
* If the file system will be exported over NFS, the
* ino/generation pairs need to be unique over the file
* system's lifetime (rather than just the mount time). So if
* the file system reuses an inode after it has been deleted,
* it must assign a new, previously unused generation number
* to the inode at the same time.
*
* The generation must be non-zero, otherwise FUSE will treat
* it as an error.
*
*/
unsigned long generation;
@ -99,7 +106,14 @@ struct fuse_entry_param {
double entry_timeout;
};
/** Additional context associated with requests */
/**
* Additional context associated with requests.
*
* Note that the reported client uid, gid and pid may be zero in some
* situations. For example, if the FUSE file system is running in a
* PID or user namespace but then accessed from outside the namespace,
* there is no valid uid/pid/gid that could be reported.
*/
struct fuse_ctx {
/** User ID of the calling process */
uid_t uid;
@ -109,6 +123,14 @@ struct fuse_ctx {
/** Thread ID of the calling process */
pid_t pid;
/** Umask of the calling process (introduced in version 2.8) */
mode_t umask;
};
struct fuse_forget_data {
uint64_t ino;
uint64_t nlookup;
};
/* 'to_set' flags in setattr */
@ -118,6 +140,8 @@ struct fuse_ctx {
#define FUSE_SET_ATTR_SIZE (1 << 3)
#define FUSE_SET_ATTR_ATIME (1 << 4)
#define FUSE_SET_ATTR_MTIME (1 << 5)
#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
/* ----------------------------------------------------------- *
* Request methods and replies *
@ -183,18 +207,31 @@ struct fuse_lowlevel_ops {
/**
* Forget about an inode
*
* The nlookup parameter indicates the number of lookups
* previously performed on this inode.
* This function is called when the kernel removes an inode
* from its internal caches.
*
* If the filesystem implements inode lifetimes, it is recommended
* that inodes acquire a single reference on each lookup, and lose
* nlookup references on each forget.
* The inode's lookup count increases by one for every call to
* fuse_reply_entry and fuse_reply_create. The nlookup parameter
* indicates by how much the lookup count should be decreased.
*
* The filesystem may ignore forget calls, if the inodes don't
* need to have a limited lifetime.
* Inodes with a non-zero lookup count may receive request from
* the kernel even after calls to unlink, rmdir or (when
* overwriting an existing file) rename. Filesystems must handle
* such requests properly and it is recommended to defer removal
* of the inode until the lookup count reaches zero. Calls to
* unlink, remdir or rename will be followed closely by forget
* unless the file or directory is open, in which case the
* kernel issues forget only after the release or releasedir
* calls.
*
* On unmount it is not guaranteed, that all referenced inodes
* will receive a forget message.
* Note that if a file system will be exported over NFS the
* inodes lifetime must extend even beyond forget. See the
* generation field in struct fuse_entry_param above.
*
* On unmount the lookup count for all inodes implicitly drops
* to zero. It is not guaranteed that the file system will
* receive corresponding forget messages for the affected
* inodes.
*
* Valid replies:
* fuse_reply_none
@ -298,6 +335,11 @@ struct fuse_lowlevel_ops {
/**
* Remove a file
*
* If the file's inode's lookup count is non-zero, the file
* system is expected to postpone any removal of the inode
* until the lookup count reaches zero (see description of the
* forget function).
*
* Valid replies:
* fuse_reply_err
*
@ -310,6 +352,11 @@ struct fuse_lowlevel_ops {
/**
* Remove a directory
*
* If the directory's inode's lookup count is non-zero, the
* file system is expected to postpone any removal of the
* inode until the lookup count reaches zero (see description
* of the forget function).
*
* Valid replies:
* fuse_reply_err
*
@ -335,6 +382,12 @@ struct fuse_lowlevel_ops {
const char *name);
/** Rename a file
*
* If the target exists it should be atomically replaced. If
* the target's inode's lookup count is non-zero, the file
* system is expected to postpone any removal of the inode
* until the lookup count reaches zero (see description of the
* forget function).
*
* Valid replies:
* fuse_reply_err
@ -406,6 +459,8 @@ struct fuse_lowlevel_ops {
*
* Valid replies:
* fuse_reply_buf
* fuse_reply_iov
* fuse_reply_data
* fuse_reply_err
*
* @param req request handle
@ -555,6 +610,7 @@ struct fuse_lowlevel_ops {
*
* Valid replies:
* fuse_reply_buf
* fuse_reply_data
* fuse_reply_err
*
* @param req request handle
@ -640,6 +696,7 @@ struct fuse_lowlevel_ops {
*
* Valid replies:
* fuse_reply_buf
* fuse_reply_data
* fuse_reply_xattr
* fuse_reply_err
*
@ -666,6 +723,7 @@ struct fuse_lowlevel_ops {
*
* Valid replies:
* fuse_reply_buf
* fuse_reply_data
* fuse_reply_xattr
* fuse_reply_err
*
@ -781,7 +839,7 @@ struct fuse_lowlevel_ops {
* @param req request handle
* @param ino the inode number
* @param fi file information
* @param lock the region/type to test
* @param lock the region/type to set
* @param sleep locking operation may sleep
*/
void (*setlk) (fuse_req_t req, fuse_ino_t ino,
@ -807,6 +865,169 @@ struct fuse_lowlevel_ops {
*/
void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
uint64_t idx);
/**
* Ioctl
*
* Note: For unrestricted ioctls (not allowed for FUSE
* servers), data in and out areas can be discovered by giving
* iovs and setting FUSE_IOCTL_RETRY in @flags. For
* restricted ioctls, kernel prepares in/out data area
* according to the information encoded in cmd.
*
* Introduced in version 2.8
*
* Valid replies:
* fuse_reply_ioctl_retry
* fuse_reply_ioctl
* fuse_reply_ioctl_iov
* fuse_reply_err
*
* @param req request handle
* @param ino the inode number
* @param cmd ioctl command
* @param arg ioctl argument
* @param fi file information
* @param flags for FUSE_IOCTL_* flags
* @param in_buf data fetched from the caller
* @param in_bufsz number of fetched bytes
* @param out_bufsz maximum size of output data
*/
void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd, void *arg,
struct fuse_file_info *fi, unsigned flags,
const void *in_buf, size_t in_bufsz, size_t out_bufsz);
/**
* Poll for IO readiness
*
* Introduced in version 2.8
*
* Note: If ph is non-NULL, the client should notify
* when IO readiness events occur by calling
* fuse_lowelevel_notify_poll() with the specified ph.
*
* Regardless of the number of times poll with a non-NULL ph
* is received, single notification is enough to clear all.
* Notifying more times incurs overhead but doesn't harm
* correctness.
*
* The callee is responsible for destroying ph with
* fuse_pollhandle_destroy() when no longer in use.
*
* Valid replies:
* fuse_reply_poll
* fuse_reply_err
*
* @param req request handle
* @param ino the inode number
* @param fi file information
* @param ph poll handle to be used for notification
*/
void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
struct fuse_pollhandle *ph);
/**
* Write data made available in a buffer
*
* This is a more generic version of the ->write() method. If
* FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the
* kernel supports splicing from the fuse device, then the
* data will be made available in pipe for supporting zero
* copy data transfer.
*
* buf->count is guaranteed to be one (and thus buf->idx is
* always zero). The write_buf handler must ensure that
* bufv->off is correctly updated (reflecting the number of
* bytes read from bufv->buf[0]).
*
* Introduced in version 2.9
*
* Valid replies:
* fuse_reply_write
* fuse_reply_err
*
* @param req request handle
* @param ino the inode number
* @param bufv buffer containing the data
* @param off offset to write to
* @param fi file information
*/
void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
struct fuse_bufvec *bufv, off_t off,
struct fuse_file_info *fi);
/**
* Callback function for the retrieve request
*
* Introduced in version 2.9
*
* Valid replies:
* fuse_reply_none
*
* @param req request handle
* @param cookie user data supplied to fuse_lowlevel_notify_retrieve()
* @param ino the inode number supplied to fuse_lowlevel_notify_retrieve()
* @param offset the offset supplied to fuse_lowlevel_notify_retrieve()
* @param bufv the buffer containing the returned data
*/
void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
off_t offset, struct fuse_bufvec *bufv);
/**
* Forget about multiple inodes
*
* See description of the forget function for more
* information.
*
* Introduced in version 2.9
*
* Valid replies:
* fuse_reply_none
*
* @param req request handle
*/
void (*forget_multi) (fuse_req_t req, size_t count,
struct fuse_forget_data *forgets);
/**
* Acquire, modify or release a BSD file lock
*
* Note: if the locking methods are not implemented, the kernel
* will still allow file locking to work locally. Hence these are
* only interesting for network filesystems and similar.
*
* Introduced in version 2.9
*
* Valid replies:
* fuse_reply_err
*
* @param req request handle
* @param ino the inode number
* @param fi file information
* @param op the locking operation, see flock(2)
*/
void (*flock) (fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info *fi, int op);
/**
* Allocate requested space. If this function returns success then
* subsequent writes to the specified range shall not fail due to the lack
* of free space on the file system storage media.
*
* Introduced in version 2.9
*
* Valid replies:
* fuse_reply_err
*
* @param req request handle
* @param ino the inode number
* @param offset starting point for allocated region
* @param length size of allocated region
* @param mode determines the operation to be performed on the given range,
* see fallocate(2)
*/
void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
off_t offset, off_t length, struct fuse_file_info *fi);
};
/**
@ -840,6 +1061,9 @@ void fuse_reply_none(fuse_req_t req);
* Possible requests:
* lookup, mknod, mkdir, symlink, link
*
* Side effects:
* increments the lookup count on success
*
* @param req request handle
* @param e the entry parameters
* @return zero for success, -errno for failure to send reply
@ -855,6 +1079,9 @@ int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
* Possible requests:
* create
*
* Side effects:
* increments the lookup count on success
*
* @param req request handle
* @param e the entry parameters
* @param fi file information
@ -870,7 +1097,7 @@ int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
* getattr, setattr
*
* @param req request handle
* @param the attributes
* @param attr the attributes
* @param attr_timeout validity timeout (in seconds) for the attributes
* @return zero for success, -errno for failure to send reply
*/
@ -929,6 +1156,20 @@ int fuse_reply_write(fuse_req_t req, size_t count);
*/
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
/**
* Reply with data copied/moved from buffer(s)
*
* Possible requests:
* read, readdir, getxattr, listxattr
*
* @param req request handle
* @param bufv buffer vector
* @param flags flags controlling the copy
* @return zero for success, -errno for failure to send reply
*/
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
enum fuse_buf_copy_flags flags);
/**
* Reply with data vector
*
@ -976,7 +1217,7 @@ int fuse_reply_xattr(fuse_req_t req, size_t count);
* @param lock the lock information
* @return zero for success, -errno for failure to send reply
*/
int fuse_reply_lock(fuse_req_t req, struct flock *lock);
int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
/**
* Reply with block index
@ -997,7 +1238,7 @@ int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
/**
* Add a directory entry to the buffer
*
* Buffer needs to be large enough to hold the entry. Of it's not,
* Buffer needs to be large enough to hold the entry. If it's not,
* then the entry is not filled in but the size of the entry is still
* returned. The caller can check this by comparing the bufsize
* parameter with the returned entry size. If the entry size is
@ -1013,7 +1254,7 @@ int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
* @param req request handle
* @param buf the point where the new entry will be added to the buffer
* @param bufsize remaining size of the buffer
* @param the name of the entry
* @param name the name of the entry
* @param stbuf the file attributes
* @param off the offset of the next entry
* @return the space needed for the entry
@ -1022,6 +1263,176 @@ size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
const char *name, const struct stat *stbuf,
off_t off);
/**
* Reply to ask for data fetch and output buffer preparation. ioctl
* will be retried with the specified input data fetched and output
* buffer prepared.
*
* Possible requests:
* ioctl
*
* @param req request handle
* @param in_iov iovec specifying data to fetch from the caller
* @param in_count number of entries in in_iov
* @param out_iov iovec specifying addresses to write output to
* @param out_count number of entries in out_iov
* @return zero for success, -errno for failure to send reply
*/
int fuse_reply_ioctl_retry(fuse_req_t req,
const struct iovec *in_iov, size_t in_count,
const struct iovec *out_iov, size_t out_count);
/**
* Reply to finish ioctl
*
* Possible requests:
* ioctl
*
* @param req request handle
* @param result result to be passed to the caller
* @param buf buffer containing output data
* @param size length of output data
*/
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
/**
* Reply to finish ioctl with iov buffer
*
* Possible requests:
* ioctl
*
* @param req request handle
* @param result result to be passed to the caller
* @param iov the vector containing the data
* @param count the size of vector
*/
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
int count);
/**
* Reply with poll result event mask
*
* @param req request handle
* @param revents poll result event mask
*/
int fuse_reply_poll(fuse_req_t req, unsigned revents);
/* ----------------------------------------------------------- *
* Notification *
* ----------------------------------------------------------- */
/**
* Notify IO readiness event
*
* For more information, please read comment for poll operation.
*
* @param ph poll handle to notify IO readiness event for
*/
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
/**
* Notify to invalidate cache for an inode
*
* @param ch the channel through which to send the invalidation
* @param ino the inode number
* @param off the offset in the inode where to start invalidating
* or negative to invalidate attributes only
* @param len the amount of cache to invalidate or 0 for all
* @return zero for success, -errno for failure
*/
int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino,
off_t off, off_t len);
/**
* Notify to invalidate parent attributes and the dentry matching
* parent/name
*
* To avoid a deadlock don't call this function from a filesystem operation and
* don't call it with a lock held that can also be held by a filesystem
* operation.
*
* @param ch the channel through which to send the invalidation
* @param parent inode number
* @param name file name
* @param namelen strlen() of file name
* @return zero for success, -errno for failure
*/
int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent,
const char *name, size_t namelen);
/**
* Notify to invalidate parent attributes and delete the dentry matching
* parent/name if the dentry's inode number matches child (otherwise it
* will invalidate the matching dentry).
*
* To avoid a deadlock don't call this function from a filesystem operation and
* don't call it with a lock held that can also be held by a filesystem
* operation.
*
* @param ch the channel through which to send the notification
* @param parent inode number
* @param child inode number
* @param name file name
* @param namelen strlen() of file name
* @return zero for success, -errno for failure
*/
int fuse_lowlevel_notify_delete(struct fuse_chan *ch,
fuse_ino_t parent, fuse_ino_t child,
const char *name, size_t namelen);
/**
* Store data to the kernel buffers
*
* Synchronously store data in the kernel buffers belonging to the
* given inode. The stored data is marked up-to-date (no read will be
* performed against it, unless it's invalidated or evicted from the
* cache).
*
* If the stored data overflows the current file size, then the size
* is extended, similarly to a write(2) on the filesystem.
*
* If this function returns an error, then the store wasn't fully
* completed, but it may have been partially completed.
*
* @param ch the channel through which to send the invalidation
* @param ino the inode number
* @param offset the starting offset into the file to store to
* @param bufv buffer vector
* @param flags flags controlling the copy
* @return zero for success, -errno for failure
*/
int fuse_lowlevel_notify_store(struct fuse_chan *ch, fuse_ino_t ino,
off_t offset, struct fuse_bufvec *bufv,
enum fuse_buf_copy_flags flags);
/**
* Retrieve data from the kernel buffers
*
* Retrieve data in the kernel buffers belonging to the given inode.
* If successful then the retrieve_reply() method will be called with
* the returned data.
*
* Only present pages are returned in the retrieve reply. Retrieving
* stops when it finds a non-present page and only data prior to that is
* returned.
*
* If this function returns an error, then the retrieve will not be
* completed and no reply will be sent.
*
* This function doesn't change the dirty state of pages in the kernel
* buffer. For dirty pages the write() method will be called
* regardless of having been retrieved previously.
*
* @param ch the channel through which to send the invalidation
* @param ino the inode number
* @param size the number of bytes to retrieve
* @param offset the starting offset into the file to retrieve from
* @param cookie user data to supply to the reply callback
* @return zero for success, -errno for failure
*/
int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, fuse_ino_t ino,
size_t size, off_t offset, void *cookie);
/* ----------------------------------------------------------- *
* Utility functions *
* ----------------------------------------------------------- */
@ -1045,6 +1456,27 @@ void *fuse_req_userdata(fuse_req_t req);
*/
const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
/**
* Get the current supplementary group IDs for the specified request
*
* Similar to the getgroups(2) system call, except the return value is
* always the total number of group IDs, even if it is larger than the
* specified size.
*
* The current fuse kernel module in linux (as of 2.6.30) doesn't pass
* the group list to userspace, hence this function needs to parse
* "/proc/$TID/task/$TID/status" to get the group IDs.
*
* This feature may not be supported on all operating systems. In
* such a case this function will return -ENOSYS.
*
* @param req request handle
* @param size size of given array
* @param list array of group IDs to be filled in
* @return the total number of supplementary group IDs or -errno on failure
*/
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
/**
* Callback function for an interrupt
*
@ -1062,7 +1494,7 @@ typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
*
* @param req request handle
* @param func the callback function or NULL for unregister
* @parm data user data passed to the callback function
* @param data user data passed to the callback function
*/
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func,
void *data);
@ -1196,6 +1628,34 @@ struct fuse_chan *fuse_session_next_chan(struct fuse_session *se,
void fuse_session_process(struct fuse_session *se, const char *buf, size_t len,
struct fuse_chan *ch);
/**
* Process a raw request supplied in a generic buffer
*
* This is a more generic version of fuse_session_process(). The
* fuse_buf may contain a memory buffer or a pipe file descriptor.
*
* @param se the session
* @param buf the fuse_buf containing the request
* @param ch channel on which the request was received
*/
void fuse_session_process_buf(struct fuse_session *se,
const struct fuse_buf *buf, struct fuse_chan *ch);
/**
* Receive a raw request supplied in a generic buffer
*
* This is a more generic version of fuse_chan_recv(). The fuse_buf
* supplied to this function contains a suitably allocated memory
* buffer. This may be overwritten with a file descriptor buffer.
*
* @param se the session
* @param buf the fuse_buf to store the request in
* @param chp pointer to the channel
* @return the actual size of the raw request, or -errno on error
*/
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
struct fuse_chan **chp);
/**
* Destroy a session
*
@ -1225,6 +1685,14 @@ void fuse_session_reset(struct fuse_session *se);
*/
int fuse_session_exited(struct fuse_session *se);
/**
* Get the user data provided to the session
*
* @param se the session
* @return the user data
*/
void *fuse_session_data(struct fuse_session *se);
/**
* Enter a single threaded event loop
*

View File

@ -72,7 +72,7 @@ size_t fuse_dirent_size(size_t namelen);
char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf,
off_t off);
#if !defined(__FreeBSD__) && !defined(__HAIKU__)
#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__HAIKU__)
#include <sys/statfs.h>
@ -139,7 +139,7 @@ struct fuse_session *fuse_lowlevel_new_compat(const char *opts,
const struct fuse_lowlevel_ops_compat *op,
size_t op_size, void *userdata);
#endif /* !__FreeBSD__ && !__HAIKU__ */
#endif /* __FreeBSD__ || __NetBSD__ */
struct fuse_chan_ops_compat24 {
int (*receive)(struct fuse_chan *ch, char *buf, size_t size);

View File

@ -21,7 +21,7 @@ extern "C" {
/**
* Option description
*
* This structure describes a single option, and and action associated
* This structure describes a single option, and action associated
* with it, in case it matches.
*
* More than one such match may occur, in which case the action for
@ -100,7 +100,7 @@ struct fuse_opt {
* Last option. An array of 'struct fuse_opt' must end with a NULL
* template value
*/
#define FUSE_OPT_END { .templ = NULL }
#define FUSE_OPT_END { NULL, 0, 0 }
/**
* Argument list
@ -130,7 +130,7 @@ struct fuse_args {
/**
* Key value passed to the processing function for all non-options
*
* Non-options are the arguments beginning with a charater other than
* Non-options are the arguments beginning with a character other than
* '-' or all arguments after the special '--' option
*/
#define FUSE_OPT_KEY_NONOPT -2
@ -161,7 +161,7 @@ struct fuse_args {
*
* The 'arg' parameter will always contain the whole argument or
* option including the parameter if exists. A two-argument option
* ("-x foo") is always converted to single arguemnt option of the
* ("-x foo") is always converted to single argument option of the
* form "-xfoo" before this function is called.
*
* Options of the form '-ofoo' are passed to this function without the
@ -211,6 +211,15 @@ int fuse_opt_parse(struct fuse_args *args, void *data,
*/
int fuse_opt_add_opt(char **opts, const char *opt);
/**
* Add an option, escaping commas, to a comma separated option list
*
* @param opts is a pointer to an option list, may point to a NULL value
* @param opt is the option to add
* @return -1 on allocation error, 0 on success
*/
int fuse_opt_add_opt_escaped(char **opts, const char *opt);
/**
* Add an argument to a NULL terminated argument vector
*
@ -225,7 +234,7 @@ int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
* argument vector
*
* Adds the argument to the N-th position. This is useful for adding
* options at the beggining of the array which must not come after the
* options at the beginning of the array which must not come after the
* special '--' option.
*
* @param args is the structure containing the current argument list

View File

@ -11,6 +11,7 @@
#include <new>
#include "fuse_fs.h"
#include "FUSELowLevel.h"
#include "FUSEVolume.h"
#include "../RequestThread.h"
@ -127,6 +128,7 @@ FUSEFileSystem::FUSEFileSystem(const char* fsName,
fInitSemaphore(-1),
fExitSemaphore(-1),
fInitParameters(NULL),
fUserData(NULL),
fFS(NULL)
{
fClientFSType = CLIENT_FS_FUSE;
@ -277,6 +279,21 @@ PRINT(("FUSEFileSystem::FinishInitClientFS()\n"));
}
status_t
FUSEFileSystem::FinishInitClientFS(fuse_config* config,
const fuse_lowlevel_ops* ops, size_t opSize, void* userData)
{
PRINT(("FUSEFileSystem::FinishInitClientFS()\n"));
fExitStatus = B_ERROR;
fFUSEConfig = *config;
// do the initialization
fInitStatus = _InitClientFS(ops, opSize, userData);
return fInitStatus;
}
status_t
FUSEFileSystem::MainLoop(bool multithreaded)
{
@ -297,6 +314,8 @@ PRINT((" waiting for unmounting done\n"));
if (fFS != NULL)
fuse_fs_destroy(fFS);
else
fuse_ll_destroy(&fLowLevelOps, fUserData);
return fExitStatus;
}
@ -364,6 +383,32 @@ fNodeCapabilities.Dump();
}
status_t
FUSEFileSystem::_InitClientFS(const fuse_lowlevel_ops* lowLevelOps, size_t lowLevelOpSize,
void* userData)
{
fLowLevelOps = *lowLevelOps;
_InitCapabilities();
PRINT(("volume capabilities:\n"));
fVolumeCapabilities.Dump();
PRINT(("node capabilities:\n"));
fNodeCapabilities.Dump();
// init connection info
fConnectionInfo.proto_major = 0;
fConnectionInfo.proto_minor = 0;
fConnectionInfo.async_read = false;
fConnectionInfo.max_write = 64 * 1024;
fConnectionInfo.max_readahead = 64 * 1024;
fUserData = userData;
fuse_ll_init(&fLowLevelOps, userData, &fConnectionInfo);
return B_OK;
}
void
FUSEFileSystem::_InitCapabilities()
{
@ -372,15 +417,19 @@ FUSEFileSystem::_InitCapabilities()
// Volume operations
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_UNMOUNT, true);
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_FS_INFO, fFS->ops.statfs);
// missing: FS_VOLUME_CAPABILITY_WRITE_FS_INFO
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_SYNC, fFS->ops.fsync);
// emulated via fsync()
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_GET_VNODE, true);
// emulated
// vnode operations
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LOOKUP, true);
// emulated
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_GET_VNODE_NAME, true);
// emulated
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_PUT_VNODE, true);
// emulated
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_VNODE, true);
// emulated
// index directory & index operations
// missing: FS_VOLUME_CAPABILITY_OPEN_INDEX_DIR
// missing: FS_VOLUME_CAPABILITY_CLOSE_INDEX_DIR
@ -399,16 +448,6 @@ FUSEFileSystem::_InitCapabilities()
// missing: FS_VOLUME_CAPABILITY_READ_QUERY
// missing: FS_VOLUME_CAPABILITY_REWIND_QUERY
// vnode operations
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LOOKUP, true);
// emulated
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_GET_VNODE_NAME, true);
// emulated
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_PUT_VNODE, true);
// emulated
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_VNODE, true);
// emulated
// VM file access
// missing: FS_VNODE_CAPABILITY_CAN_PAGE
// missing: FS_VNODE_CAPABILITY_READ_PAGES
@ -423,66 +462,134 @@ FUSEFileSystem::_InitCapabilities()
// emulated
// missing: FS_VNODE_CAPABILITY_SELECT
// missing: FS_VNODE_CAPABILITY_DESELECT
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FSYNC, fFS->ops.fsync);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_SYMLINK, fFS->ops.readlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_SYMLINK, fFS->ops.symlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LINK, fFS->ops.link);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_UNLINK, fFS->ops.unlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME, fFS->ops.rename);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_ACCESS, fFS->ops.access);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_STAT, fFS->ops.getattr);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_STAT,
fFS->ops.chmod != NULL || fFS->ops.chown != NULL
|| fFS->ops.truncate != NULL || fFS->ops.utimens != NULL
|| fFS->ops.utime != NULL);
// file operations
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE, fFS->ops.create);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN, fFS->ops.open);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE, fFS->ops.flush);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_COOKIE, fFS->ops.release);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ, fFS->ops.read);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE, fFS->ops.write);
// directory operations
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_DIR, fFS->ops.mkdir);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_DIR, fFS->ops.rmdir);
bool readDirSupport = fFS->ops.opendir != NULL || fFS->ops.readdir != NULL
|| fFS->ops.getdir;
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_DIR, readDirSupport);
// not needed: FS_VNODE_CAPABILITY_CLOSE_DIR
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, readDirSupport);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, readDirSupport);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, readDirSupport);
// attribute directory operations
bool hasAttributes = fFS->ops.listxattr != NULL;
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR, hasAttributes);
// not needed: FS_VNODE_CAPABILITY_CLOSE_ATTR_DIR
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_DIR_COOKIE,
hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_DIR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_ATTR_DIR, hasAttributes);
// attribute operations
// // we emulate open_attr() and free_attr_dir_cookie() if either read_attr()
// // or write_attr() is present
// bool hasAttributes = (fFS->ops.read_attr || fFS->ops.write_attr);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_ATTR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE_ATTR, false);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_COOKIE, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR, fFS->ops.getxattr);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_ATTR, fFS->ops.write_attr);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_STAT,
if (fFS == NULL) {
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FSYNC, fLowLevelOps.fsync);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_SYMLINK, fLowLevelOps.readlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_SYMLINK, fLowLevelOps.symlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LINK, fLowLevelOps.link);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_UNLINK, fLowLevelOps.unlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME, fLowLevelOps.rename);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_ACCESS, fLowLevelOps.access);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_STAT, fLowLevelOps.getattr);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_STAT, fLowLevelOps.setattr != NULL);
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_FS_INFO, fLowLevelOps.statfs);
// missing: FS_VOLUME_CAPABILITY_WRITE_FS_INFO
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_SYNC, fLowLevelOps.fsync);
// emulated via fsync()
// file operations
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE, fLowLevelOps.create);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN, fLowLevelOps.open);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE, fLowLevelOps.flush);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_COOKIE, fLowLevelOps.release);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ, fLowLevelOps.read);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE, fLowLevelOps.write);
// directory operations
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_DIR, fLowLevelOps.mkdir);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_DIR, fLowLevelOps.rmdir);
bool readDirSupport = fLowLevelOps.opendir != NULL || fLowLevelOps.readdir != NULL;
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_DIR, readDirSupport);
// not needed: FS_VNODE_CAPABILITY_CLOSE_DIR
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, readDirSupport);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, readDirSupport);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, readDirSupport);
// attribute directory operations
bool hasAttributes = fLowLevelOps.listxattr != NULL;
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR, hasAttributes);
// not needed: FS_VNODE_CAPABILITY_CLOSE_ATTR_DIR
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_DIR_COOKIE,
hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_DIR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_ATTR_DIR, hasAttributes);
// attribute operations
// // we emulate open_attr() and free_attr_dir_cookie() if either read_attr()
// // or write_attr() is present
// bool hasAttributes = (fLowLevelOps.read_attr || fLowLevelOps.write_attr);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_ATTR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_COOKIE, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR, fLowLevelOps.getxattr);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_ATTR, fLowLevelOps.write_attr);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_STAT,
fLowLevelOps.getxattr);
// // missing: FS_VNODE_CAPABILITY_WRITE_ATTR_STAT
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME_ATTR, fLowLevelOps.rename_attr);
} else {
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FSYNC, fFS->ops.fsync);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_SYMLINK, fFS->ops.readlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_SYMLINK, fFS->ops.symlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_LINK, fFS->ops.link);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_UNLINK, fFS->ops.unlink);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME, fFS->ops.rename);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_ACCESS, fFS->ops.access);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_STAT, fFS->ops.getattr);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_STAT,
fFS->ops.chmod != NULL || fFS->ops.chown != NULL
|| fFS->ops.truncate != NULL || fFS->ops.utimens != NULL
|| fFS->ops.utime != NULL);
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_READ_FS_INFO, fFS->ops.statfs);
// missing: FS_VOLUME_CAPABILITY_WRITE_FS_INFO
fVolumeCapabilities.Set(FS_VOLUME_CAPABILITY_SYNC, fFS->ops.fsync);
// emulated via fsync()
// file operations
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE, fFS->ops.create);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN, fFS->ops.open);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CLOSE, fFS->ops.flush);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_COOKIE, fFS->ops.release);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ, fFS->ops.read);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE, fFS->ops.write);
// directory operations
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_DIR, fFS->ops.mkdir);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_DIR, fFS->ops.rmdir);
bool readDirSupport = fFS->ops.opendir != NULL || fFS->ops.readdir != NULL
|| fFS->ops.getdir;
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_DIR, readDirSupport);
// not needed: FS_VNODE_CAPABILITY_CLOSE_DIR
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_DIR_COOKIE, readDirSupport);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_DIR, readDirSupport);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_DIR, readDirSupport);
// attribute directory operations
bool hasAttributes = fFS->ops.listxattr != NULL;
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR_DIR, hasAttributes);
// not needed: FS_VNODE_CAPABILITY_CLOSE_ATTR_DIR
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_DIR_COOKIE,
hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_DIR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REWIND_ATTR_DIR, hasAttributes);
// attribute operations
// // we emulate open_attr() and free_attr_dir_cookie() if either read_attr()
// // or write_attr() is present
// bool hasAttributes = (fFS->ops.read_attr || fFS->ops.write_attr);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_CREATE_ATTR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_OPEN_ATTR, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_FREE_ATTR_COOKIE, hasAttributes);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR, fFS->ops.getxattr);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_WRITE_ATTR, fFS->ops.write_attr);
fNodeCapabilities.Set(FS_VNODE_CAPABILITY_READ_ATTR_STAT,
fFS->ops.getxattr);
// // missing: FS_VNODE_CAPABILITY_WRITE_ATTR_STAT
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME_ATTR, fFS->ops.rename_attr);
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_REMOVE_ATTR, fFS->ops.remove_attr);
// // missing: FS_VNODE_CAPABILITY_WRITE_ATTR_STAT
// fNodeCapabilities.Set(FS_VNODE_CAPABILITY_RENAME_ATTR, fFS->ops.rename_attr);
}
}

View File

@ -31,6 +31,12 @@ public:
const FSVNodeCapabilities& GetNodeCapabilities() const
{ return fNodeCapabilities; }
const fuse_lowlevel_ops* GetLowlevelOps() const
{
if (fFS == NULL)
return &fLowLevelOps;
return NULL;
}
fuse_fs* GetFS() const { return fFS; }
const fuse_config& GetFUSEConfig() const { return fFUSEConfig; }
@ -47,6 +53,9 @@ public:
status_t FinishInitClientFS(fuse_config* config,
const fuse_operations* ops, size_t opSize,
void* userData);
status_t FinishInitClientFS(fuse_config* config,
const fuse_lowlevel_ops* ops, size_t opSize,
void* userData);
status_t MainLoop(bool multithreaded);
private:
@ -58,6 +67,8 @@ private:
status_t _InitClientFS(const fuse_operations* ops,
size_t opSize, void* userData);
status_t _InitClientFS(const fuse_lowlevel_ops* ops,
size_t opSize, void* userData);
void _InitCapabilities();
@ -68,8 +79,9 @@ private:
status_t fExitStatus;
sem_id fInitSemaphore;
sem_id fExitSemaphore;
fuse_operations fOps;
const char* fInitParameters;
fuse_lowlevel_ops fLowLevelOps;
void* fUserData;
fuse_fs* fFS;
fuse_conn_info fConnectionInfo;
fuse_config fFUSEConfig;

View File

@ -0,0 +1,552 @@
/*
* Copyright 2022, Adrien Destugues <pulkomandy@pulkomandy.tk>
* Distributed under terms of the MIT license.
*/
#include "FUSELowLevel.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <Errors.h>
// Reimplement fuse_req in our own way. In libfuse, the requests are communicated to the kernel,
// but in our case, they remain entirely inside userlandfs_server. This means we can use a much
// simpler system, by passing pointers directly between userlandfs and the libfuse client code,
// and synchronizing them with a semaphore to wait for the replies.
struct fuse_req {
fuse_req()
: fReplyResult(0),
fReplyBuf(NULL)
{
sem_init(&fSyncSem, 0, 0);
}
~fuse_req() {
sem_destroy(&fSyncSem);
}
void Wait() {
sem_wait(&fSyncSem);
}
void Notify() {
sem_post(&fSyncSem);
}
sem_t fSyncSem;
ssize_t fReplyResult;
fuse_fill_dir_t fRequestFiller; // callback function to fill buffer for directory reads
void* fRequestCookie;
// The reply can contain various things, depending on which function was called
union {
struct stat* fReplyAttr;
struct fuse_entry_param fReplyEntry;
struct fuse_file_info* fReplyOpen;
struct statvfs* fReplyStat;
char* fReplyBuf;
};
};
// #pragma mark - Convenience functions for calling fuse lowlevel filesstems easily
void
fuse_ll_init(const fuse_lowlevel_ops* ops, void* userdata, struct fuse_conn_info* conn)
{
if (ops->init != NULL) {
ops->init(userdata, conn);
}
}
void
fuse_ll_destroy(const fuse_lowlevel_ops* ops, void* userdata)
{
if (ops->destroy != NULL) {
ops->destroy(userdata);
}
}
int
fuse_ll_lookup(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
struct stat* st)
{
if (ops->lookup == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->lookup(&request, parent, name);
request.Wait();
*st = request.fReplyEntry.attr;
st->st_ino = request.fReplyEntry.ino;
return request.fReplyResult;
}
int
fuse_ll_getattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct stat* st)
{
if (ops->getattr == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
request.fReplyAttr = st;
ops->getattr(&request, ino, NULL);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_setattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const struct stat *attr,
int to_set)
{
if (ops->setattr == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
//request.fReplyAttr = attr;
ops->setattr(&request, ino, const_cast<struct stat*>(attr), to_set, NULL);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_readlink(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t size)
{
if (ops->readlink == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
request.fReplyBuf = buffer;
request.fReplyResult = size;
ops->readlink(&request, ino);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_mkdir(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
mode_t mode)
{
if (ops->mkdir == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->mkdir(&request, parent, name, mode);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_unlink(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name)
{
if (ops->unlink == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->unlink(&request, parent, name);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_rmdir(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name)
{
if (ops->rmdir == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->rmdir(&request, parent, name);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_symlink(const fuse_lowlevel_ops* ops, const char* link, fuse_ino_t parent,
const char* name)
{
if (ops->symlink == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->symlink(&request, link, parent, name);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_rename(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
fuse_ino_t newparent, const char *newname)
{
if (ops->rename == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->rename(&request, parent, name, newparent, newname);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_link(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_ino_t newparent,
const char *newname)
{
if (ops->link == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->link(&request, ino, newparent, newname);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_open(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi)
{
if (ops->open == NULL)
return 0;
fuse_req request;
request.fReplyOpen = ffi;
ops->open(&request, ino, ffi);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_read(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t bufferSize,
off_t position, fuse_file_info* ffi)
{
if (ops->read == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
request.fReplyBuf = buffer;
ops->read(&request, ino, bufferSize, position, ffi);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_write(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const char *buf,
size_t size, off_t off, struct fuse_file_info *fi)
{
if (ops->write == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->write(&request, ino, buf, size, off, fi);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_flush(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi)
{
if (ops->flush == NULL)
return 0;
fuse_req request;
ops->flush(&request, ino, ffi);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_release(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi)
{
if (ops->release == NULL)
return 0;
fuse_req request;
ops->release(&request, ino, ffi);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_fsync(const fuse_lowlevel_ops* ops, fuse_ino_t ino, int datasync, fuse_file_info* ffi)
{
if (ops->fsync == NULL)
return 0;
fuse_req request;
ops->fsync(&request, ino, datasync, ffi);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_opendir(const fuse_lowlevel_ops* ops, fuse_ino_t inode, struct fuse_file_info* ffi)
{
// intentioanlly check for readdir here. Some filesystems do not need an opendir, but still
// implement readdir. However if readdir is not implemented, there is no point in trying to
// open a directory.
if (ops->readdir == NULL)
return B_NOT_SUPPORTED;
if (ops->opendir) {
fuse_req request;
ops->opendir(&request, inode, ffi);
request.Wait();
return request.fReplyResult;
}
return 0;
}
int
fuse_ll_readdir(const fuse_lowlevel_ops* ops, fuse_ino_t ino, void* cookie, fuse_fill_dir_t filler,
off_t pos, fuse_file_info* ffi)
{
if (ops->readdir == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
request.fRequestFiller = filler;
request.fRequestCookie = cookie;
request.fReplyBuf = NULL;
do {
request.fReplyResult = 0;
ops->readdir(&request, ino, UINT32_MAX, pos, ffi);
request.Wait();
if (request.fReplyResult > 0)
pos += request.fReplyResult;
} while (request.fReplyResult > 0);
return request.fReplyResult;
}
int
fuse_ll_releasedir(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct fuse_file_info *fi)
{
if (ops->releasedir == NULL)
return 0;
fuse_req request;
ops->releasedir(&request, ino, fi);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_statfs(const fuse_lowlevel_ops* ops, fuse_ino_t inode, struct statvfs* stat)
{
if (ops->statfs == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
request.fReplyStat = stat;
ops->statfs(&request, inode);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_getxattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const char *name,
char* buffer, size_t size)
{
if (ops->getxattr == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
request.fReplyResult = size;
request.fReplyBuf = buffer;
ops->getxattr(&request, ino, name, size);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_listxattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t size)
{
if (ops->listxattr == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
request.fReplyResult = size;
request.fReplyBuf = (char*)buffer;
ops->listxattr(&request, ino, size);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_access(const fuse_lowlevel_ops* ops, fuse_ino_t ino, int mask)
{
if (ops->access == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->access(&request, ino, mask);
request.Wait();
return request.fReplyResult;
}
int
fuse_ll_create(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
mode_t mode, struct fuse_file_info *fi, fuse_ino_t& ino)
{
// TODO if the create op is missing, we could try using mknod + open instead
if (ops->create == NULL)
return B_NOT_SUPPORTED;
fuse_req request;
ops->create(&request, parent, name, mode, fi);
request.Wait();
ino = request.fReplyEntry.ino;
return request.fReplyResult;
}
//#pragma mark - lowlevel replies handling
int
fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
{
*req->fReplyAttr = *attr;
req->Notify();
return 0;
}
int
fuse_reply_create(fuse_req_t req, const struct fuse_entry_param* e, const struct fuse_file_info* fi)
{
req->fReplyEntry = *e;
req->Notify();
return 0;
}
int
fuse_reply_readlink(fuse_req_t req, const char* link)
{
strlcpy(req->fReplyBuf, link, req->fReplyResult);
req->fReplyResult = strlen(link);
req->Notify();
return 0;
}
int
fuse_reply_open(fuse_req_t req, const struct fuse_file_info* f)
{
*req->fReplyOpen = *f;
req->Notify();
return 0;
}
int
fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
{
if (req->fReplyBuf)
memcpy(req->fReplyBuf, buf, size);
req->fReplyResult = size;
req->Notify();
return 0;
}
int
fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
{
req->fReplyEntry = *e;
req->Notify();
return 0;
}
int
fuse_reply_err(fuse_req_t req, int err)
{
assert(err >= 0);
req->fReplyResult = -err;
req->Notify();
return 0;
}
int
fuse_reply_statfs(fuse_req_t req, const struct statvfs* stat)
{
*req->fReplyStat = *stat;
req->Notify();
return 0;
}
int
fuse_reply_write(fuse_req_t req, size_t count)
{
req->fReplyResult = count;
req->Notify();
return 0;
}
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name,
const struct stat *stbuf, off_t off)
{
// Special case where the client wants to know how much space an entry will use (it's always
// 1 slot in our case)
if ((buf == NULL) && (bufsize == 0))
return 1;
int ret = req->fRequestFiller(req->fRequestCookie, name, stbuf, off);
if (ret != 0)
return 0;
return 1;
}
// #pragma mark - Stubs for FUSE functions called by client code, that we don't need
void fuse_session_add_chan(struct fuse_session* se, struct fuse_chan* ch)
{
}
void fuse_session_remove_chan(struct fuse_chan* ch)
{
}
void fuse_session_destroy(struct fuse_session* se)
{
}
void fuse_session_exit(struct fuse_session* se)
{
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2022, Adrien Destugues <pulkomandy@pulkomandy.tk>
* Distributed under terms of the MIT license.
*/
#ifndef FUSELOWLEVEL_H
#define FUSELOWLEVEL_H
#include <semaphore.h>
#include <stdlib.h>
#include "fuse_api.h"
void fuse_ll_init(const fuse_lowlevel_ops* ops, void* userdata, struct fuse_conn_info* conn);
void fuse_ll_destroy(const fuse_lowlevel_ops* ops, void *userdata);
int fuse_ll_lookup(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
struct stat* st);
int fuse_ll_getattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct stat* st);
int fuse_ll_setattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const struct stat *attr,
int to_set);
int fuse_ll_readlink(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t size);
int fuse_ll_mkdir(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
mode_t mode);
int fuse_ll_unlink(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name);
int fuse_ll_rmdir(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name);
int fuse_ll_symlink(const fuse_lowlevel_ops* ops, const char* link, fuse_ino_t parent,
const char* name);
int fuse_ll_rename(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
fuse_ino_t newparent, const char *newname);
int fuse_ll_link(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_ino_t newparent,
const char *newname);
int fuse_ll_open(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct fuse_file_info *fi);
int fuse_ll_read(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t bufferSize,
off_t position, fuse_file_info* ffi);
int fuse_ll_write(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const char *buf,
size_t size, off_t off, struct fuse_file_info *fi);
int fuse_ll_flush(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct fuse_file_info *fi);
int fuse_ll_release(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct fuse_file_info *fi);
int fuse_ll_fsync(const fuse_lowlevel_ops* ops, fuse_ino_t ino, int datasync,
struct fuse_file_info *fi);
int fuse_ll_opendir(const fuse_lowlevel_ops* ops, fuse_ino_t inode, struct fuse_file_info* ffi);
int fuse_ll_readdir(const fuse_lowlevel_ops* ops, fuse_ino_t ino, void* cookie,
fuse_fill_dir_t filler, off_t pos, fuse_file_info* ffi);
int fuse_ll_releasedir(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct fuse_file_info *fi);
int fuse_ll_statfs(const fuse_lowlevel_ops* ops, fuse_ino_t inode, struct statvfs* stat);
int fuse_ll_getxattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const char *name,
char* buffer, size_t size);
int fuse_ll_listxattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t size);
int fuse_ll_access(const fuse_lowlevel_ops* ops, fuse_ino_t ino, int mask);
int fuse_ll_create(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
mode_t mode, struct fuse_file_info *fi, fuse_ino_t& ino);
#endif /* !FUSELOWLEVEL_H */

View File

@ -12,6 +12,7 @@
#include "fuse_fs.h"
#include "FUSEEntry.h"
#include "FUSEFileSystem.h"
#include "../Volume.h"
@ -143,7 +144,8 @@ private:
friend struct MultiNodeLocker;
private:
inline FUSEFileSystem* _FileSystem() const;
inline FUSEFileSystem* _FileSystem() const
{ return static_cast<FUSEFileSystem*>(fFileSystem); }
ino_t _GenerateNodeID();
@ -205,13 +207,13 @@ private:
private:
RWLockManager fLockManager;
Locker fLock;
const fuse_lowlevel_ops* fOps;
fuse_fs* fFS;
FUSEEntryTable fEntries;
FUSENodeTable fNodes;
FUSENode* fRootNode;
ino_t fNextNodeID;
bool fUseNodeIDs; // TODO: Actually read the
// option!
bool fUseNodeIDs;
char fName[B_OS_NAME_LENGTH];
};

View File

@ -20,6 +20,7 @@ DEFINES += USER=1 ;
DEFINES += DEBUG_APP="\\\"libuserlandfs_fuse\\\"" ;
DEFINES += BUILDING_USERLAND_FS_SERVER=1 ;
DEFINES += _FILE_OFFSET_BITS=64 ;
DEFINES += PACKAGE_VERSION=\\\"2.9.9\\\" ;
# the library providing the FUSE interface for add-ons
SharedLibrary libuserlandfs_fuse.so
@ -28,7 +29,10 @@ SharedLibrary libuserlandfs_fuse.so
fuse_fs.cpp
fuse_main.cpp
fuse_opt.c
fuse_signals.c
helper.c
FUSEFileSystem.cpp
FUSELowLevel.cpp
FUSEVolume.cpp
mime_ext_table.c
:

View File

@ -133,6 +133,32 @@ fuse_new(struct fuse_chan* ch, struct fuse_args* args,
}
struct fuse_session *fuse_lowlevel_new(struct fuse_args *args,
const struct fuse_lowlevel_ops *lowLevelOps, size_t lowLevelOpSize, void *userData)
{
// parse args
fuse_config config;
memset(&config, 0, sizeof(config));
config.entry_timeout = 1.0;
config.attr_timeout = 1.0;
config.negative_timeout = 0.0;
config.intr_signal = SIGUSR1;
bool success = fuse_parse_lib_config_args(args, &config);
if (!success) {
PRINT(("fuse_lowlevel_new(): failed to parse arguments!\n"));
return NULL;
}
// run the main loop
status_t error = FUSEFileSystem::GetInstance()->FinishInitClientFS(&config,
lowLevelOps, lowLevelOpSize, userData);
return error == B_OK ? (struct fuse_session*)FUSEFileSystem::GetInstance() : NULL;
}
void
fuse_destroy(struct fuse* f)
{
@ -156,6 +182,22 @@ fuse_loop_mt(struct fuse* f)
}
int
fuse_session_loop(struct fuse_session* f)
{
status_t error = FUSEFileSystem::GetInstance()->MainLoop(false);
return error == B_OK ? 0 : -1;
}
int
fuse_session_loop_mt(struct fuse_session* f)
{
status_t error = FUSEFileSystem::GetInstance()->MainLoop(true);
return error == B_OK ? 0 : -1;
}
void
fuse_exit(struct fuse* f)
{

View File

@ -0,0 +1,73 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB
*/
#include "fuse_api.h"
#include "fuse_lowlevel.h"
#include <stdio.h>
#include <string.h>
#include <signal.h>
static struct fuse_session *fuse_instance;
static void exit_handler(int sig)
{
(void) sig;
if (fuse_instance)
fuse_session_exit(fuse_instance);
}
static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
{
struct sigaction sa;
struct sigaction old_sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = remove ? SIG_DFL : handler;
sigemptyset(&(sa.sa_mask));
sa.sa_flags = 0;
if (sigaction(sig, NULL, &old_sa) == -1) {
perror("fuse: cannot get old signal handler");
return -1;
}
if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
sigaction(sig, &sa, NULL) == -1) {
perror("fuse: cannot set signal handler");
return -1;
}
return 0;
}
int fuse_set_signal_handlers(struct fuse_session *se)
{
if (set_one_signal_handler(SIGHUP, exit_handler, 0) == -1 ||
set_one_signal_handler(SIGINT, exit_handler, 0) == -1 ||
set_one_signal_handler(SIGTERM, exit_handler, 0) == -1 ||
set_one_signal_handler(SIGPIPE, SIG_IGN, 0) == -1)
return -1;
fuse_instance = se;
return 0;
}
void fuse_remove_signal_handlers(struct fuse_session *se)
{
if (fuse_instance != se)
fprintf(stderr,
"fuse: fuse_remove_signal_handlers: unknown session\n");
else
fuse_instance = NULL;
set_one_signal_handler(SIGHUP, exit_handler, 1);
set_one_signal_handler(SIGINT, exit_handler, 1);
set_one_signal_handler(SIGTERM, exit_handler, 1);
set_one_signal_handler(SIGPIPE, SIG_IGN, 1);
}

View File

@ -0,0 +1,186 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB.
*/
#include "config.h"
#include "fuse_api.h"
#include "fuse_misc.h"
#include "fuse_opt.h"
#include "fuse_lowlevel.h"
#include "fuse_common_compat.h"
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <sys/param.h>
enum {
KEY_HELP,
KEY_HELP_NOHEADER,
KEY_VERSION,
};
struct helper_opts {
int singlethread;
int foreground;
int nodefault_subtype;
char *mountpoint;
};
#define FUSE_HELPER_OPT(t, p) { t, offsetof(struct helper_opts, p), 1 }
static const struct fuse_opt fuse_helper_opts[] = {
FUSE_HELPER_OPT("-d", foreground),
FUSE_HELPER_OPT("debug", foreground),
FUSE_HELPER_OPT("-f", foreground),
FUSE_HELPER_OPT("-s", singlethread),
FUSE_HELPER_OPT("fsname=", nodefault_subtype),
FUSE_HELPER_OPT("subtype=", nodefault_subtype),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("-ho", KEY_HELP_NOHEADER),
FUSE_OPT_KEY("-V", KEY_VERSION),
FUSE_OPT_KEY("--version", KEY_VERSION),
FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP),
FUSE_OPT_END
};
static void usage(const char *progname)
{
fprintf(stderr,
"usage: %s mountpoint [options]\n\n", progname);
fprintf(stderr,
"general options:\n"
" -o opt,[opt...] mount options\n"
" -h --help print help\n"
" -V --version print version\n"
"\n");
}
static void helper_help(void)
{
fprintf(stderr,
"FUSE options:\n"
" -d -o debug enable debug output (implies -f)\n"
" -f foreground operation\n"
" -s disable multi-threaded operation\n"
"\n"
);
}
static void helper_version(void)
{
fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION);
}
static int fuse_helper_opt_proc(void *data, const char *arg, int key,
struct fuse_args *outargs)
{
struct helper_opts *hopts = data;
switch (key) {
case KEY_HELP:
usage(outargs->argv[0]);
/* fall through */
case KEY_HELP_NOHEADER:
helper_help();
return fuse_opt_add_arg(outargs, "-h");
case KEY_VERSION:
helper_version();
return 1;
case FUSE_OPT_KEY_NONOPT:
if (!hopts->mountpoint) {
char mountpoint[PATH_MAX];
if (realpath(arg, mountpoint) == NULL) {
fprintf(stderr,
"fuse: bad mount point `%s': %s\n",
arg, strerror(errno));
return -1;
}
return fuse_opt_add_opt(&hopts->mountpoint, mountpoint);
} else {
fprintf(stderr, "fuse: invalid argument `%s'\n", arg);
return -1;
}
default:
return 1;
}
}
static int add_default_subtype(const char *progname, struct fuse_args *args)
{
int res;
char *subtype_opt;
const char *basename = strrchr(progname, '/');
if (basename == NULL)
basename = progname;
else if (basename[1] != '\0')
basename++;
subtype_opt = (char *) malloc(strlen(basename) + 64);
if (subtype_opt == NULL) {
fprintf(stderr, "fuse: memory allocation failed\n");
return -1;
}
sprintf(subtype_opt, "-osubtype=%s", basename);
res = fuse_opt_add_arg(args, subtype_opt);
free(subtype_opt);
return res;
}
int fuse_parse_cmdline(struct fuse_args *args, char **mountpoint,
int *multithreaded, int *foreground)
{
int res;
struct helper_opts hopts;
memset(&hopts, 0, sizeof(hopts));
res = fuse_opt_parse(args, &hopts, fuse_helper_opts,
fuse_helper_opt_proc);
if (res == -1)
return -1;
if (!hopts.nodefault_subtype) {
res = add_default_subtype(args->argv[0], args);
if (res == -1)
goto err;
}
if (mountpoint)
*mountpoint = hopts.mountpoint;
else
free(hopts.mountpoint);
if (multithreaded)
*multithreaded = !hopts.singlethread;
if (foreground)
*foreground = hopts.foreground;
return 0;
err:
free(hopts.mountpoint);
return -1;
}
int fuse_daemonize(int foreground)
{
// On Haiku, always run "foreground", the userlandfs_server already takes care of running
// as a server if needed.
return 0;
}