diff --git a/data/develop/fuse.pc b/data/develop/fuse.pc index eb436792d9..4370cb36c3 100644 --- a/data/develop/fuse.pc +++ b/data/develop/fuse.pc @@ -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 diff --git a/headers/private/userlandfs/fuse/fuse.h b/headers/private/userlandfs/fuse/fuse.h index 0758babcd9..e3a49317c2 100644 --- a/headers/private/userlandfs/fuse/fuse.h +++ b/headers/private/userlandfs/fuse/fuse.h @@ -31,6 +31,7 @@ #include #include #include +#include #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 diff --git a/headers/private/userlandfs/fuse/fuse_common.h b/headers/private/userlandfs/fuse/fuse_common.h index e43b22d0f2..8b9a1ae935 100644 --- a/headers/private/userlandfs/fuse/fuse_common.h +++ b/headers/private/userlandfs/fuse/fuse_common.h @@ -17,12 +17,13 @@ #include "fuse_opt.h" #include +#include /** 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" diff --git a/headers/private/userlandfs/fuse/fuse_compat.h b/headers/private/userlandfs/fuse/fuse_compat.h index ccf1be80c1..25d8cb6371 100644 --- a/headers/private/userlandfs/fuse/fuse_compat.h +++ b/headers/private/userlandfs/fuse/fuse_compat.h @@ -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 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__ */ diff --git a/headers/private/userlandfs/fuse/fuse_lowlevel.h b/headers/private/userlandfs/fuse/fuse_lowlevel.h index 9330548c80..54bb84bc54 100644 --- a/headers/private/userlandfs/fuse/fuse_lowlevel.h +++ b/headers/private/userlandfs/fuse/fuse_lowlevel.h @@ -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 * diff --git a/headers/private/userlandfs/fuse/fuse_lowlevel_compat.h b/headers/private/userlandfs/fuse/fuse_lowlevel_compat.h index d808899fe4..886d3fc05e 100644 --- a/headers/private/userlandfs/fuse/fuse_lowlevel_compat.h +++ b/headers/private/userlandfs/fuse/fuse_lowlevel_compat.h @@ -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 @@ -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); diff --git a/headers/private/userlandfs/fuse/fuse_opt.h b/headers/private/userlandfs/fuse/fuse_opt.h index 7ae08af647..add0a30894 100644 --- a/headers/private/userlandfs/fuse/fuse_opt.h +++ b/headers/private/userlandfs/fuse/fuse_opt.h @@ -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 diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEFileSystem.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEFileSystem.cpp index fa5e825386..86fa03d680 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEFileSystem.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEFileSystem.cpp @@ -11,6 +11,7 @@ #include #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); + } } diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEFileSystem.h b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEFileSystem.h index 945f7ebba0..ad4dd84ae9 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEFileSystem.h +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEFileSystem.h @@ -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; diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSELowLevel.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSELowLevel.cpp new file mode 100644 index 0000000000..5a7e6754d8 --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSELowLevel.cpp @@ -0,0 +1,552 @@ +/* + * Copyright 2022, Adrien Destugues + * Distributed under terms of the MIT license. + */ + +#include "FUSELowLevel.h" + +#include +#include +#include + +#include + + +// 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(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) +{ +} diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSELowLevel.h b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSELowLevel.h new file mode 100644 index 0000000000..6a884c5127 --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSELowLevel.h @@ -0,0 +1,56 @@ +/* + * Copyright 2022, Adrien Destugues + * Distributed under terms of the MIT license. + */ + +#ifndef FUSELOWLEVEL_H +#define FUSELOWLEVEL_H + +#include +#include + +#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 */ diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp index c7787d4358..50bbddf436 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.cpp @@ -15,7 +15,7 @@ #include -#include "FUSEFileSystem.h" +#include "FUSELowLevel.h" #include "../kernel_emu.h" #include "../RequestThread.h" @@ -690,13 +690,6 @@ private: // #pragma mark - -inline FUSEFileSystem* -FUSEVolume::_FileSystem() const -{ - return static_cast(fFileSystem); -} - - FUSEVolume::FUSEVolume(FUSEFileSystem* fileSystem, dev_t id) : Volume(fileSystem, id), @@ -751,20 +744,31 @@ printf("FUSEVolume::Mount()\n"); if (error != B_OK) RETURN_ERROR(error); - fFS = _FileSystem()->GetFS(); + fOps = _FileSystem()->GetLowlevelOps(); + if (fOps == NULL) + fFS = _FileSystem()->GetFS(); _FileSystem()->GetVolumeCapabilities(fCapabilities); const fuse_config& config = _FileSystem()->GetFUSEConfig(); fUseNodeIDs = config.use_ino; // update the fuse_context::private_data field before calling into the FS - fuse_context* context = (fuse_context*)RequestThread::GetCurrentThread() - ->GetContext()->GetFSData(); - context->private_data = fFS->userData; + if (fFS != NULL) { + fuse_context* context = (fuse_context*)RequestThread::GetCurrentThread() + ->GetContext()->GetFSData(); + + context->private_data = fFS->userData; + } // get the root node struct stat st; - int fuseError = fuse_fs_getattr(fFS, "/", &st); + + int fuseError; + if (fOps != NULL) + fuseError = fuse_ll_getattr(fOps, FUSE_ROOT_ID, &st); + else + fuseError = fuse_fs_getattr(fFS, "/", &st); + if (fuseError != 0) RETURN_ERROR(fuseError); @@ -836,21 +840,33 @@ FUSEVolume::Sync() // node is dirty -- we have to sync it - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - continue; + int fuseError; + if (fOps != NULL) { + fuse_file_info cookie; + fuseError = fuse_ll_open(fOps, node->id, &cookie); + if (fuseError == 0) { + fuse_ll_fsync(fOps, node->id, 0, &cookie); + // full sync, not only data + fuse_ll_flush(fOps, node->id, &cookie); + fuse_ll_release(fOps, node->id, &cookie); + } + } else { + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + continue; - // open, sync, and close the node - FileCookie cookie(O_RDONLY); - int fuseError = fuse_fs_open(fFS, path, &cookie); - if (fuseError == 0) { - fuseError = fuse_fs_fsync(fFS, path, 0, &cookie); - // full sync, not only data - fuse_fs_flush(fFS, path, &cookie); - fuse_fs_release(fFS, path, &cookie); + // open, sync, and close the node + FileCookie cookie(O_RDONLY); + fuseError = fuse_fs_open(fFS, path, &cookie); + if (fuseError == 0) { + fuseError = fuse_fs_fsync(fFS, path, 0, &cookie); + // full sync, not only data + fuse_fs_flush(fFS, path, &cookie); + fuse_fs_release(fFS, path, &cookie); + } } if (fuseError == 0) { @@ -875,11 +891,18 @@ FUSEVolume::ReadFSInfo(fs_info* info) // No Haiku FUSE extensions, so our knowledge is limited: use some values // from statfs and make reasonable guesses for the rest of them. - if (fFS->ops.statfs == NULL) - return B_UNSUPPORTED; - struct statvfs st; - int fuseError = fuse_fs_statfs(fFS, "/", &st); + int fuseError; + + if (fOps != NULL) { + fuseError = fuse_ll_statfs(fOps, FUSE_ROOT_ID, &st); + } else { + if (fFS->ops.statfs == NULL) + return B_UNSUPPORTED; + + fuseError = fuse_fs_statfs(fFS, "/", &st); + } + if (fuseError != 0) return fuseError; @@ -1013,29 +1036,47 @@ FUSEVolume::FSync(void* _node) if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); + int fuseError; + bool dirty; AutoLocker locker(fLock); + if (fOps != NULL) { + // mark the node not dirty + dirty = node->dirty; + node->dirty = false; - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + locker.Unlock(); - // mark the node not dirty - bool dirty = node->dirty; - node->dirty = false; + fuse_file_info cookie; + fuseError = fuse_ll_open(fOps, node->id, &cookie); + if (fuseError == 0) { + fuseError = fuse_ll_fsync(fOps, node->id, 0, &cookie); + // full sync, not only data + fuse_ll_flush(fOps, node->id, &cookie); + fuse_ll_release(fOps, node->id, &cookie); + } + } else { + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + // mark the node not dirty + dirty = node->dirty; + node->dirty = false; - // open, sync, and close the node - FileCookie cookie(O_RDONLY); - int fuseError = fuse_fs_open(fFS, path, &cookie); - if (fuseError == 0) { - fuseError = fuse_fs_fsync(fFS, path, 0, &cookie); - // full sync, not only data - fuse_fs_flush(fFS, path, &cookie); - fuse_fs_release(fFS, path, &cookie); + locker.Unlock(); + + // open, sync, and close the node + FileCookie cookie(O_RDONLY); + fuseError = fuse_fs_open(fFS, path, &cookie); + if (fuseError == 0) { + fuseError = fuse_fs_fsync(fFS, path, 0, &cookie); + // full sync, not only data + fuse_fs_flush(fFS, path, &cookie); + fuse_fs_release(fFS, path, &cookie); + } } if (fuseError != 0) { @@ -1060,29 +1101,40 @@ FUSEVolume::ReadSymlink(void* _node, char* buffer, size_t bufferSize, if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_readlink(fOps, node->id, buffer, bufferSize); + if (fuseError != 0) { + *_bytesRead = 0; + return fuseError; + } + // fuse_ll_readlink returns the actual size (even if the data didn't fit the buffer) + *_bytesRead = fuseError; + } else { + AutoLocker locker(fLock); - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); - // read the symlink - int fuseError = fuse_fs_readlink(fFS, path, buffer, bufferSize); - if (fuseError != 0) { - *_bytesRead = 0; - return fuseError; + // read the symlink + int fuseError = fuse_fs_readlink(fFS, path, buffer, bufferSize); + if (fuseError != 0) { + *_bytesRead = 0; + return fuseError; + } + + // fuse_fs_readlink() is supposed to return a NULL-terminated string, which + // the Haiku interface doesn't require. We have to return the string length, + // though. + *_bytesRead = strnlen(buffer, bufferSize); } - // fuse_fs_readlink() is supposed to return a NULL-terminated string, which - // the Haiku interface doesn't require. We have to return the string length, - // though. - *_bytesRead = strnlen(buffer, bufferSize); - return B_OK; } @@ -1100,26 +1152,32 @@ FUSEVolume::CreateSymlink(void* _dir, const char* name, const char* target, if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_symlink(fOps, target, dir->id, name); + } else { + AutoLocker locker(fLock); - // get a path for the entry - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(dir, name, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the entry + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(dir, name, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); + + // create the symlink + fuseError = fuse_fs_symlink(fFS, target, path); + } - // create the symlink - int fuseError = fuse_fs_symlink(fFS, target, path); if (fuseError != 0) RETURN_ERROR(fuseError); // TODO: Set the mode?! // mark the dir dirty - locker.Lock(); + AutoLocker locker(fLock); dir->dirty = true; locker.Unlock(); @@ -1148,30 +1206,35 @@ FUSEVolume::Link(void* _dir, const char* name, void* _node) if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_link(fOps, node->id, dir->id, name); + } else { + AutoLocker locker(fLock); - // get a path for the entries - char oldPath[B_PATH_NAME_LENGTH]; - size_t oldPathLen; - status_t error = _BuildPath(node, oldPath, oldPathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the entries + char oldPath[B_PATH_NAME_LENGTH]; + size_t oldPathLen; + status_t error = _BuildPath(node, oldPath, oldPathLen); + if (error != B_OK) + RETURN_ERROR(error); - char newPath[B_PATH_NAME_LENGTH]; - size_t newPathLen; - error = _BuildPath(dir, name, newPath, newPathLen); - if (error != B_OK) - RETURN_ERROR(error); + char newPath[B_PATH_NAME_LENGTH]; + size_t newPathLen; + error = _BuildPath(dir, name, newPath, newPathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); - // link - int fuseError = fuse_fs_link(fFS, oldPath, newPath); + // link + fuseError = fuse_fs_link(fFS, oldPath, newPath); + } if (fuseError != 0) RETURN_ERROR(fuseError); // mark the dir and the node dirty - locker.Lock(); + AutoLocker locker(fLock); dir->dirty = true; node->dirty = true; locker.Unlock(); @@ -1200,24 +1263,29 @@ FUSEVolume::Unlink(void* _dir, const char* name) ino_t nodeID; bool doNodeMonitoring = _GetNodeID(dir, name, &nodeID); - AutoLocker locker(fLock); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_unlink(fOps, dir->id, name); + } else { + AutoLocker locker(fLock); - // get a path for the entry - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(dir, name, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the entry + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(dir, name, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); - // unlink - int fuseError = fuse_fs_unlink(fFS, path); + // unlink + fuseError = fuse_fs_unlink(fFS, path); + } if (fuseError != 0) RETURN_ERROR(fuseError); // remove the entry - locker.Lock(); + AutoLocker locker(fLock); _RemoveEntry(dir, name); // mark the dir dirty @@ -1249,30 +1317,35 @@ FUSEVolume::Rename(void* _oldDir, const char* oldName, void* _newDir, if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_rename(fOps, oldDir->id, oldName, newDir->id, newName); + } else { + AutoLocker locker(fLock); - // get a path for the entries - char oldPath[B_PATH_NAME_LENGTH]; - size_t oldPathLen; - status_t error = _BuildPath(oldDir, oldName, oldPath, oldPathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the entries + char oldPath[B_PATH_NAME_LENGTH]; + size_t oldPathLen; + status_t error = _BuildPath(oldDir, oldName, oldPath, oldPathLen); + if (error != B_OK) + RETURN_ERROR(error); - char newPath[B_PATH_NAME_LENGTH]; - size_t newPathLen; - error = _BuildPath(newDir, newName, newPath, newPathLen); - if (error != B_OK) - RETURN_ERROR(error); + char newPath[B_PATH_NAME_LENGTH]; + size_t newPathLen; + error = _BuildPath(newDir, newName, newPath, newPathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); - // rename - int fuseError = fuse_fs_rename(fFS, oldPath, newPath); + // rename + fuseError = fuse_fs_rename(fFS, oldPath, newPath); + } if (fuseError != 0) RETURN_ERROR(fuseError); // rename the entry - locker.Lock(); + AutoLocker locker(fLock); _RenameEntry(oldDir, oldName, newDir, newName); // mark the dirs dirty @@ -1300,19 +1373,25 @@ FUSEVolume::Access(void* _node, int mode) if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_access(fOps, node->id, mode); + } else { + AutoLocker locker(fLock); - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); + + // call the access hook on the path + fuseError = fuse_fs_access(fFS, path, mode); + } - // call the access hook on the path - int fuseError = fuse_fs_access(fFS, path, mode); if (fuseError != 0) return fuseError; @@ -1332,24 +1411,29 @@ FUSEVolume::ReadStat(void* _node, struct stat* st) if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); - - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); - - locker.Unlock(); - st->st_dev = GetID(); st->st_ino = node->id; st->st_blksize = 2048; st->st_type = 0; - // stat the path - int fuseError = fuse_fs_getattr(fFS, path, st); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_getattr(fOps, node->id, st); + } else { + AutoLocker locker(fLock); + + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); + + locker.Unlock(); + + // stat the path + fuseError = fuse_fs_getattr(fFS, path, st); + } if (fuseError != 0) return fuseError; @@ -1369,70 +1453,76 @@ FUSEVolume::WriteStat(void* _node, const struct stat* st, uint32 mask) if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); - - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); - - locker.Unlock(); - - // permissions - if ((mask & B_STAT_MODE) != 0) { - int fuseError = fuse_fs_chmod(fFS, path, st->st_mode); + if (fOps != NULL) { + int fuseError = fuse_ll_setattr(fOps, node->id, st, mask); if (fuseError != 0) RETURN_ERROR(fuseError); - } + } else { + AutoLocker locker(fLock); - // owner - if ((mask & (B_STAT_UID | B_STAT_GID)) != 0) { - uid_t uid = (mask & B_STAT_UID) != 0 ? st->st_uid : (uid_t)-1; - gid_t gid = (mask & B_STAT_GID) != 0 ? st->st_gid : (gid_t)-1; - int fuseError = fuse_fs_chown(fFS, path, uid, gid); - if (fuseError != 0) - RETURN_ERROR(fuseError); - } + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - // size - if ((mask & B_STAT_SIZE) != 0) { - // truncate - int fuseError = fuse_fs_truncate(fFS, path, st->st_size); - if (fuseError != 0) - RETURN_ERROR(fuseError); - } + locker.Unlock(); - // access/modification time - if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) != 0) { - timespec tv[2] = { - {st->st_atime, 0}, - {st->st_mtime, 0} - }; - - // If either time is not specified, we need to stat the file to get the - // current value. - if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) - != (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) { - struct stat currentStat; - int fuseError = fuse_fs_getattr(fFS, path, ¤tStat); + // permissions + if ((mask & B_STAT_MODE) != 0) { + int fuseError = fuse_fs_chmod(fFS, path, st->st_mode); if (fuseError != 0) RETURN_ERROR(fuseError); - - if ((mask & B_STAT_ACCESS_TIME) == 0) - tv[0].tv_sec = currentStat.st_atime; - else - tv[1].tv_sec = currentStat.st_mtime; } - int fuseError = fuse_fs_utimens(fFS, path, tv); - if (fuseError != 0) - RETURN_ERROR(fuseError); + // owner + if ((mask & (B_STAT_UID | B_STAT_GID)) != 0) { + uid_t uid = (mask & B_STAT_UID) != 0 ? st->st_uid : (uid_t)-1; + gid_t gid = (mask & B_STAT_GID) != 0 ? st->st_gid : (gid_t)-1; + int fuseError = fuse_fs_chown(fFS, path, uid, gid); + if (fuseError != 0) + RETURN_ERROR(fuseError); + } + + // size + if ((mask & B_STAT_SIZE) != 0) { + // truncate + int fuseError = fuse_fs_truncate(fFS, path, st->st_size); + if (fuseError != 0) + RETURN_ERROR(fuseError); + } + + // access/modification time + if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) != 0) { + timespec tv[2] = { + {st->st_atime, 0}, + {st->st_mtime, 0} + }; + + // If either time is not specified, we need to stat the file to get the + // current value. + if ((mask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) + != (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) { + struct stat currentStat; + int fuseError = fuse_fs_getattr(fFS, path, ¤tStat); + if (fuseError != 0) + RETURN_ERROR(fuseError); + + if ((mask & B_STAT_ACCESS_TIME) == 0) + tv[0].tv_sec = currentStat.st_atime; + else + tv[1].tv_sec = currentStat.st_mtime; + } + + int fuseError = fuse_fs_utimens(fFS, path, tv); + if (fuseError != 0) + RETURN_ERROR(fuseError); + } } // mark the node dirty - locker.Lock(); + AutoLocker locker(fLock); node->dirty = true; // send node monitoring message @@ -1471,36 +1561,58 @@ FUSEVolume::Create(void* _dir, const char* name, int openMode, int mode, RETURN_ERROR(B_NO_MEMORY); ObjectDeleter cookieDeleter(cookie); - AutoLocker locker(fLock); - - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(dir, name, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); - - locker.Unlock(); - - // create the file - int fuseError = fuse_fs_create(fFS, path, mode, cookie); - if (fuseError != 0) - RETURN_ERROR(fuseError); - - // get the node FUSENode* node; - error = _GetNode(dir, name, &node); - if (error != B_OK) { - // This is bad. We've create the file successfully, but couldn't get - // the node. Close the file and delete the entry. - fuse_fs_flush(fFS, path, cookie); - fuse_fs_release(fFS, path, cookie); - fuse_fs_unlink(fFS, path); - RETURN_ERROR(error); + int fuseError; + if (fOps) { + fuse_file_info cookie; + fuse_ino_t ino; + fuseError = fuse_ll_create(fOps, dir->id, name, mode, &cookie, ino); + if (fuseError != 0) + RETURN_ERROR(fuseError); + + // get the node + // TODO do we really need it? + status_t error = _GetNode(dir, name, &node); + if (error != B_OK) { + // This is bad. We've create the file successfully, but couldn't get + // the node. Delete the entry. + // We can't close the file because we don't know its inode. + fuse_ll_flush(fOps, ino, &cookie); + fuse_ll_release(fOps, ino, &cookie); + fuse_ll_unlink(fOps, dir->id, name); + RETURN_ERROR(error); + } + } else { + AutoLocker locker(fLock); + + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(dir, name, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); + + locker.Unlock(); + + // create the file + fuseError = fuse_fs_create(fFS, path, mode, cookie); + if (fuseError != 0) + RETURN_ERROR(fuseError); + + // get the node + error = _GetNode(dir, name, &node); + if (error != B_OK) { + // This is bad. We've create the file successfully, but couldn't get + // the node. Close the file and delete the entry. + fuse_fs_flush(fFS, path, cookie); + fuse_fs_release(fFS, path, cookie); + fuse_fs_unlink(fFS, path); + RETURN_ERROR(error); + } } // mark the dir and the node dirty - locker.Lock(); + AutoLocker locker(fLock); dir->dirty = true; node->dirty = true; locker.Unlock(); @@ -1538,37 +1650,60 @@ FUSEVolume::Open(void* _node, int openMode, void** _cookie) RETURN_ERROR(B_NO_MEMORY); ObjectDeleter cookieDeleter(cookie); - AutoLocker locker(fLock); - - // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); - locker.Unlock(); + int fuseError; + struct fuse_file_info llCookie; + // FIXME store this in the FileCookie for lowlevel streams, we'll need it in read, write... + if (fOps != NULL) { + llCookie.flags = openMode; + // TODO do we need to perform a conversion here? + fuseError = fuse_ll_open(fOps, node->id, &llCookie); + } else { + AutoLocker locker(fLock); + + // get a path for the node + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); + + locker.Unlock(); + + // open the file + fuseError = fuse_fs_open(fFS, path, cookie); + } - // open the file - int fuseError = fuse_fs_open(fFS, path, cookie); if (fuseError != 0) RETURN_ERROR(fuseError); // truncate the file, if requested if (truncate) { - fuseError = fuse_fs_ftruncate(fFS, path, 0, cookie); - if (fuseError == ENOSYS) { - // Fallback to truncate if ftruncate is not implemented - fuseError = fuse_fs_truncate(fFS, path, 0); - } - if (fuseError != 0) { - fuse_fs_flush(fFS, path, cookie); - fuse_fs_release(fFS, path, cookie); - RETURN_ERROR(fuseError); + if (fOps != NULL) { + struct stat st; + st.st_size = 0; + fuseError = fuse_ll_setattr(fOps, node->id, &st, FUSE_SET_ATTR_SIZE); + + if (fuseError != 0) { + fuse_ll_flush(fOps, node->id, &llCookie); + fuse_ll_release(fOps, node->id, &llCookie); + RETURN_ERROR(fuseError); + } + } else { + fuseError = fuse_fs_ftruncate(fFS, path, 0, cookie); + if (fuseError == ENOSYS) { + // Fallback to truncate if ftruncate is not implemented + fuseError = fuse_fs_truncate(fFS, path, 0); + } + if (fuseError != 0) { + fuse_fs_flush(fFS, path, cookie); + fuse_fs_release(fFS, path, cookie); + RETURN_ERROR(fuseError); + } } // mark the node dirty - locker.Lock(); + AutoLocker locker(fLock); node->dirty = true; // send node monitoring message @@ -1597,19 +1732,25 @@ FUSEVolume::Close(void* _node, void* _cookie) if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int fuseError; - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + if (fOps != NULL) { + fuseError = fuse_ll_flush(fOps, node->id, cookie); + } else { + AutoLocker locker(fLock); - locker.Unlock(); + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - // flush the file - int fuseError = fuse_fs_flush(fFS, path, cookie); + locker.Unlock(); + + // flush the file + fuseError = fuse_fs_flush(fFS, path, cookie); + } if (fuseError != 0) return fuseError; @@ -1632,19 +1773,25 @@ FUSEVolume::FreeCookie(void* _node, void* _cookie) ObjectDeleter cookieDeleter(cookie); - AutoLocker locker(fLock); + int fuseError; + if (fOps) { + fuseError = fuse_ll_release(fOps, node->id, cookie); + } else { + AutoLocker locker(fLock); - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); + + // release the file + fuseError = fuse_fs_release(fFS, path, cookie); + } - // release the file - int fuseError = fuse_fs_release(fFS, path, cookie); if (fuseError != 0) return fuseError; @@ -1668,20 +1815,25 @@ FUSEVolume::Read(void* _node, void* _cookie, off_t pos, void* buffer, if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int bytesRead; - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + if (fOps != NULL) { + bytesRead = fuse_ll_read(fOps, node->id, (char*)buffer, bufferSize, pos, cookie); + } else { + AutoLocker locker(fLock); - locker.Unlock(); + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - // read the file - int bytesRead = fuse_fs_read(fFS, path, (char*)buffer, bufferSize, pos, - cookie); + locker.Unlock(); + + // read the file + bytesRead = fuse_fs_read(fFS, path, (char*)buffer, bufferSize, pos, cookie); + } if (bytesRead < 0) return bytesRead; @@ -1706,25 +1858,30 @@ FUSEVolume::Write(void* _node, void* _cookie, off_t pos, const void* buffer, if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int bytesWritten; + if (fOps != NULL) { + bytesWritten = fuse_ll_write(fOps, node->id, (const char*)buffer, bufferSize, pos, cookie); + } else { + AutoLocker locker(fLock); - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); - // write the file - int bytesWritten = fuse_fs_write(fFS, path, (const char*)buffer, bufferSize, - pos, cookie); + // write the file + bytesWritten = fuse_fs_write(fFS, path, (const char*)buffer, bufferSize, + pos, cookie); + } if (bytesWritten < 0) return bytesWritten; // mark the node dirty - locker.Lock(); + AutoLocker locker(fLock); node->dirty = true; // send node monitoring message @@ -1755,24 +1912,29 @@ FUSEVolume::CreateDir(void* _dir, const char* name, int mode) if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_mkdir(fOps, dir->id, name, mode); + } else { + AutoLocker locker(fLock); - // get a path for the entry - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(dir, name, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the entry + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(dir, name, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); - // create the dir - int fuseError = fuse_fs_mkdir(fFS, path, mode); + // create the dir + fuseError = fuse_fs_mkdir(fFS, path, mode); + } if (fuseError != 0) RETURN_ERROR(fuseError); // mark the dir dirty - locker.Lock(); + AutoLocker locker(fLock); dir->dirty = true; // send node monitoring message @@ -1802,24 +1964,29 @@ FUSEVolume::RemoveDir(void* _dir, const char* name) ino_t nodeID; bool doNodeMonitoring = _GetNodeID(dir, name, &nodeID); - AutoLocker locker(fLock); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_rmdir(fOps, dir->id, name); + } else { + AutoLocker locker(fLock); - // get a path for the entry - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(dir, name, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the entry + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(dir, name, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); - // remove the dir - int fuseError = fuse_fs_rmdir(fFS, path); + // remove the dir + fuseError = fuse_fs_rmdir(fFS, path); + } if (fuseError != 0) RETURN_ERROR(fuseError); // remove the entry - locker.Lock(); + AutoLocker locker(fLock); _RemoveEntry(dir, name); // mark the parent dir dirty @@ -1853,26 +2020,32 @@ FUSEVolume::OpenDir(void* _node, void** _cookie) RETURN_ERROR(B_NO_MEMORY); ObjectDeleter cookieDeleter(cookie); - AutoLocker locker(fLock); - - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); - - locker.Unlock(); - - if (fFS->ops.readdir == NULL && fFS->ops.getdir != NULL) { - // no open call -- the FS only supports the deprecated getdir() - // interface - cookie->getdirInterface = true; - } else { - // open the dir - int fuseError = fuse_fs_opendir(fFS, path, cookie); + if (fOps) { + int fuseError = fuse_ll_opendir(fOps, node->id, cookie); if (fuseError != 0) return fuseError; + } else { + AutoLocker locker(fLock); + + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); + + locker.Unlock(); + + if (fFS->ops.readdir == NULL && fFS->ops.getdir != NULL) { + // no open call -- the FS only supports the deprecated getdir() + // interface + cookie->getdirInterface = true; + } else { + // open the dir + int fuseError = fuse_fs_opendir(fFS, path, cookie); + if (fuseError != 0) + return fuseError; + } } cookieDeleter.Detach(); @@ -1902,22 +2075,28 @@ FUSEVolume::FreeDirCookie(void* _node, void* _cookie) ObjectDeleter cookieDeleter(cookie); - if (cookie->getdirInterface) - return B_OK; + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_releasedir(fOps, node->id, cookie); + } else { + if (cookie->getdirInterface) + return B_OK; - AutoLocker locker(fLock); + AutoLocker locker(fLock); - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); - locker.Unlock(); + locker.Unlock(); + + // release the dir + fuseError = fuse_fs_releasedir(fFS, path, cookie); + } - // release the dir - int fuseError = fuse_fs_releasedir(fFS, path, cookie); if (fuseError != 0) return fuseError; @@ -1958,29 +2137,37 @@ FUSEVolume::ReadDir(void* _node, void* _cookie, void* buffer, size_t bufferSize, ReadDirBuffer readDirBuffer(this, node, cookie, buffer, bufferSize, count); - - // get a path for the node - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); - off_t offset = cookie->currentEntryOffset; - locker.Unlock(); - // read the dir int fuseError; - if (cookie->getdirInterface) { -PRINT((" using getdir() interface\n")); - fuseError = fFS->ops.getdir(path, (fuse_dirh_t)&readDirBuffer, - &_AddReadDirEntryGetDir); + if (fOps != NULL) { + locker.Unlock(); + + // TODO pass the cookie from opendir here instead of NULL + fuseError = fuse_ll_readdir(fOps, node->id, &readDirBuffer, &_AddReadDirEntry, + offset, NULL); } else { + // get a path for the node + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); + + locker.Unlock(); + + if (cookie->getdirInterface) { +PRINT((" using getdir() interface\n")); + fuseError = fFS->ops.getdir(path, (fuse_dirh_t)&readDirBuffer, + &_AddReadDirEntryGetDir); + } else { PRINT((" using readdir() interface\n")); - fuseError = fuse_fs_readdir(fFS, path, &readDirBuffer, - &_AddReadDirEntry, offset, cookie); + fuseError = fuse_fs_readdir(fFS, path, &readDirBuffer, + &_AddReadDirEntry, offset, cookie); + } } + if (fuseError != 0) return fuseError; @@ -2081,32 +2268,43 @@ FUSEVolume::ReadAttrDir(void* _node, void* _cookie, void* buffer, if (nodeLocker.Status() != B_OK) RETURN_ERROR(nodeLocker.Status()); - AutoLocker locker(fLock); - - // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + if (fOps == NULL) { + AutoLocker locker(fLock); - locker.Unlock(); + // get a path for the node + status_t error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); + + locker.Unlock(); + } if (!cookie->IsValid()) { // cookie not yet valid -- get the length of the list - int listSize = fuse_fs_listxattr(fFS, path, NULL, 0); + int listSize; + if (fOps != NULL) + listSize = fuse_ll_listxattr(fOps, node->id, NULL, 0); + else + listSize = fuse_fs_listxattr(fFS, path, NULL, 0); + if (listSize < 0) RETURN_ERROR(listSize); while (true) { // allocate space for the listing - error = cookie->Allocate(listSize); + status_t error = cookie->Allocate(listSize); if (error != B_OK) RETURN_ERROR(error); // read the listing - int bytesRead = fuse_fs_listxattr(fFS, path, - cookie->AttributesBuffer(), listSize); + int bytesRead; + if (fOps != NULL) { + bytesRead = fuse_ll_listxattr(fOps, node->id, cookie->AttributesBuffer(), + listSize); + } else + bytesRead = fuse_fs_listxattr(fFS, path, cookie->AttributesBuffer(), listSize); if (bytesRead < 0) RETURN_ERROR(bytesRead); @@ -2169,18 +2367,25 @@ FUSEVolume::OpenAttr(void* _node, const char* name, int openMode, RETURN_ERROR(B_UNSUPPORTED); } - AutoLocker locker(fLock); - - // get a path for the node char path[B_PATH_NAME_LENGTH]; size_t pathLen; - status_t error = _BuildPath(node, path, pathLen); - if (error != B_OK) - RETURN_ERROR(error); + status_t error; + int attrSize; + if (fOps != NULL) { + attrSize = fuse_ll_getxattr(fOps, node->id, name, NULL, 0); + } else { + AutoLocker locker(fLock); - locker.Unlock(); + // get a path for the node + error = _BuildPath(node, path, pathLen); + if (error != B_OK) + RETURN_ERROR(error); + + locker.Unlock(); + + attrSize = fuse_fs_getxattr(fFS, path, name, NULL, 0); + } - int attrSize = fuse_fs_getxattr(fFS, path, name, NULL, 0); if (attrSize < 0) { if (strcmp(name, kAttrMimeTypeName) == 0) { // Return a fake MIME type attribute based on the file extension @@ -2205,8 +2410,12 @@ FUSEVolume::OpenAttr(void* _node, const char* name, int openMode, RETURN_ERROR(error); } - int bytesRead = fuse_fs_getxattr(fFS, path, name, cookie->Buffer(), - attrSize); + int bytesRead; + if (fOps != NULL) + bytesRead = fuse_ll_getxattr(fOps, node->id, name, cookie->Buffer(), attrSize); + else + bytesRead = fuse_fs_getxattr(fFS, path, name, cookie->Buffer(), attrSize); + if (bytesRead < 0) { delete cookie; return bytesRead; @@ -2395,23 +2604,26 @@ FUSEVolume::_InternalGetNode(FUSENode* dir, const char* entryName, return B_OK; } - // construct a path for the entry - char path[B_PATH_NAME_LENGTH]; - size_t pathLen = 0; - status_t error = _BuildPath(dir, entryName, path, pathLen); - if (error != B_OK) - return error; - - locker.Unlock(); - - // stat the path + int fuseError; struct stat st; - int fuseError = fuse_fs_getattr(fFS, path, &st); + if (fOps != NULL) { + fuseError = fuse_ll_lookup(fOps, dir->id, entryName, &st); + } else { + // construct a path for the entry + char path[B_PATH_NAME_LENGTH]; + size_t pathLen = 0; + status_t error = _BuildPath(dir, entryName, path, pathLen); + if (error != B_OK) + return error; + + locker.Unlock(); + + // stat the path + fuseError = fuse_fs_getattr(fFS, path, &st); + } if (fuseError != 0) return fuseError; - locker.Lock(); - // lookup the entry in the table again entry = fEntries.Lookup(FUSEEntryRef(dir->id, entryName)); if (entry != NULL) { @@ -3026,20 +3238,25 @@ FUSEVolume::_AddReadDirEntry(ReadDirBuffer* buffer, const char* name, int type, // If we don't have a valid type, we need to stat the node first. if (type == 0) { - char path[B_PATH_NAME_LENGTH]; - size_t pathLen; - status_t error = _BuildPath(buffer->directory, name, path, - pathLen); - if (error != B_OK) { - buffer->error = error; - return 0; - } - - locker.Unlock(); - - // stat the path struct stat st; - int fuseError = fuse_fs_getattr(fFS, path, &st); + int fuseError; + if (fOps != NULL) { + fuseError = fuse_ll_getattr(fOps, node->id, &st); + } else { + char path[B_PATH_NAME_LENGTH]; + size_t pathLen; + status_t error = _BuildPath(buffer->directory, name, path, + pathLen); + if (error != B_OK) { + buffer->error = error; + return 0; + } + + locker.Unlock(); + + // stat the path + fuseError = fuse_fs_getattr(fFS, path, &st); + } locker.Lock(); diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.h b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.h index 89abd73128..3c87434e24 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.h +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/FUSEVolume.h @@ -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(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]; }; diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/Jamfile b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/Jamfile index ddee99853b..1a1b058577 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/Jamfile +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/Jamfile @@ -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 : diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/fuse_main.cpp b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/fuse_main.cpp index 2633970169..6ce9a52f65 100644 --- a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/fuse_main.cpp +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/fuse_main.cpp @@ -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) { diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/fuse_signals.c b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/fuse_signals.c new file mode 100644 index 0000000000..6eac961d73 --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/fuse_signals.c @@ -0,0 +1,73 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + 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 +#include +#include + +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); +} + diff --git a/src/add-ons/kernel/file_systems/userlandfs/server/fuse/helper.c b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/helper.c new file mode 100644 index 0000000000..b876a7df1a --- /dev/null +++ b/src/add-ons/kernel/file_systems/userlandfs/server/fuse/helper.c @@ -0,0 +1,186 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + 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 +#include +#include +#include +#include +#include +#include +#include + +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; +} +