diff options
Diffstat (limited to '')
48 files changed, 25212 insertions, 31 deletions
@@ -0,0 +1,574 @@ +/**************************************************************************** + **************************************************************************** + *** + *** This header was automatically generated from a Linux kernel header + *** of the same name, to make information necessary for userspace to + *** call into the kernel available to libc. It contains only constants, + *** structures, and macros generated from the original header, and thus, + *** contains no copyrightable information. + *** + *** To edit the content of this header, modify the corresponding + *** source file (e.g. under external/kernel-headers/original/) then + *** run bionic/libc/kernel/tools/update_all.py + *** + *** Any manual change here will be lost the next time this script will + *** be run. You've been warned! + *** + **************************************************************************** + ****************************************************************************/ +#ifndef _LINUX_FUSE_H +#define _LINUX_FUSE_H +#include <stdint.h> +#define FUSE_KERNEL_VERSION 7 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_KERNEL_MINOR_VERSION 22 +#define FUSE_ROOT_ID 1 +struct fuse_attr { + uint64_t ino; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t size; + uint64_t blocks; + uint64_t atime; + uint64_t mtime; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t mode; + uint32_t nlink; + uint32_t uid; + uint32_t gid; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t rdev; + uint32_t blksize; + uint32_t padding; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_kstatfs { + uint64_t blocks; + uint64_t bfree; + uint64_t bavail; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t files; + uint64_t ffree; + uint32_t bsize; + uint32_t namelen; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t frsize; + uint32_t padding; + uint32_t spare[6]; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_file_lock { + uint64_t start; + uint64_t end; + uint32_t type; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t pid; +}; +#define FATTR_MODE (1 << 0) +#define FATTR_UID (1 << 1) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FATTR_GID (1 << 2) +#define FATTR_SIZE (1 << 3) +#define FATTR_ATIME (1 << 4) +#define FATTR_MTIME (1 << 5) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FATTR_FH (1 << 6) +#define FATTR_ATIME_NOW (1 << 7) +#define FATTR_MTIME_NOW (1 << 8) +#define FATTR_LOCKOWNER (1 << 9) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FOPEN_DIRECT_IO (1 << 0) +#define FOPEN_KEEP_CACHE (1 << 1) +#define FOPEN_NONSEEKABLE (1 << 2) +#define FUSE_ASYNC_READ (1 << 0) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) +#define FUSE_SPLICE_WRITE (1 << 7) +#define FUSE_SPLICE_MOVE (1 << 8) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_SPLICE_READ (1 << 9) +#define FUSE_FLOCK_LOCKS (1 << 10) +#define FUSE_HAS_IOCTL_DIR (1 << 11) +#define FUSE_AUTO_INVAL_DATA (1 << 12) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_DO_READDIRPLUS (1 << 13) +#define FUSE_READDIRPLUS_AUTO (1 << 14) +#define FUSE_ASYNC_DIO (1 << 15) +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_RELEASE_FLUSH (1 << 0) +#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1) +#define FUSE_GETATTR_FH (1 << 0) +#define FUSE_LK_FLOCK (1 << 0) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_WRITE_CACHE (1 << 0) +#define FUSE_WRITE_LOCKOWNER (1 << 1) +#define FUSE_READ_LOCKOWNER (1 << 1) +#define FUSE_IOCTL_COMPAT (1 << 0) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) +#define FUSE_IOCTL_32BIT (1 << 3) +#define FUSE_IOCTL_DIR (1 << 4) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_IOCTL_MAX_IOV 256 +#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0) +enum fuse_opcode { + FUSE_LOOKUP = 1, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_FORGET = 2, + FUSE_GETATTR = 3, + FUSE_SETATTR = 4, + FUSE_READLINK = 5, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_SYMLINK = 6, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, + FUSE_OPEN = 14, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_READ = 15, + FUSE_WRITE = 16, + FUSE_STATFS = 17, + FUSE_RELEASE = 18, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_FSYNC = 20, + FUSE_SETXATTR = 21, + FUSE_GETXATTR = 22, + FUSE_LISTXATTR = 23, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_REMOVEXATTR = 24, + FUSE_FLUSH = 25, + FUSE_INIT = 26, + FUSE_OPENDIR = 27, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_READDIR = 28, + FUSE_RELEASEDIR = 29, + FUSE_FSYNCDIR = 30, + FUSE_GETLK = 31, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_SETLK = 32, + FUSE_SETLKW = 33, + FUSE_ACCESS = 34, + FUSE_CREATE = 35, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_INTERRUPT = 36, + FUSE_BMAP = 37, + FUSE_DESTROY = 38, + FUSE_IOCTL = 39, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_POLL = 40, + FUSE_NOTIFY_REPLY = 41, + FUSE_BATCH_FORGET = 42, + FUSE_FALLOCATE = 43, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_READDIRPLUS = 44, + CUSE_INIT = 4096, +}; +enum fuse_notify_code { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_NOTIFY_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_STORE = 4, +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + FUSE_NOTIFY_RETRIEVE = 5, + FUSE_NOTIFY_DELETE = 6, + FUSE_NOTIFY_CODE_MAX, +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_MIN_READ_BUFFER 8192 +#define FUSE_COMPAT_ENTRY_OUT_SIZE 120 +struct fuse_entry_out { + uint64_t nodeid; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t generation; + uint64_t entry_valid; + uint64_t attr_valid; + uint32_t entry_valid_nsec; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t attr_valid_nsec; + struct fuse_attr attr; +}; +struct fuse_forget_in { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t nlookup; +}; +struct fuse_forget_one { + uint64_t nodeid; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t nlookup; +}; +struct fuse_batch_forget_in { + uint32_t count; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t dummy; +}; +struct fuse_getattr_in { + uint32_t getattr_flags; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t dummy; + uint64_t fh; +}; +#define FUSE_COMPAT_ATTR_OUT_SIZE 96 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_attr_out { + uint64_t attr_valid; + uint32_t attr_valid_nsec; + uint32_t dummy; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + struct fuse_attr attr; +}; +#define FUSE_COMPAT_MKNOD_IN_SIZE 8 +struct fuse_mknod_in { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t mode; + uint32_t rdev; + uint32_t umask; + uint32_t padding; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +struct fuse_mkdir_in { + uint32_t mode; + uint32_t umask; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +struct fuse_rename_in { + uint64_t newdir; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_link_in { + uint64_t oldnodeid; +}; +struct fuse_setattr_in { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t valid; + uint32_t padding; + uint64_t fh; + uint64_t size; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t lock_owner; + uint64_t atime; + uint64_t mtime; + uint64_t unused2; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t atimensec; + uint32_t mtimensec; + uint32_t unused3; + uint32_t mode; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t unused4; + uint32_t uid; + uint32_t gid; + uint32_t unused5; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +struct fuse_open_in { + uint32_t flags; + uint32_t unused; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +struct fuse_create_in { + uint32_t flags; + uint32_t mode; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t umask; + uint32_t padding; +}; +struct fuse_open_out { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t fh; + uint32_t open_flags; + uint32_t padding; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_release_in { + uint64_t fh; + uint32_t flags; + uint32_t release_flags; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t lock_owner; +}; +struct fuse_flush_in { + uint64_t fh; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t unused; + uint32_t padding; + uint64_t lock_owner; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_read_in { + uint64_t fh; + uint64_t offset; + uint32_t size; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t read_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +#define FUSE_COMPAT_WRITE_IN_SIZE 24 +struct fuse_write_in { + uint64_t fh; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t offset; + uint32_t size; + uint32_t write_flags; + uint64_t lock_owner; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t flags; + uint32_t padding; +}; +struct fuse_write_out { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t size; + uint32_t padding; +}; +#define FUSE_COMPAT_STATFS_SIZE 48 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; +struct fuse_fsync_in { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t fh; + uint32_t fsync_flags; + uint32_t padding; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_setxattr_in { + uint32_t size; + uint32_t flags; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_getxattr_in { + uint32_t size; + uint32_t padding; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_getxattr_out { + uint32_t size; + uint32_t padding; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_lk_in { + uint64_t fh; + uint64_t owner; + struct fuse_file_lock lk; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t lk_flags; + uint32_t padding; +}; +struct fuse_lk_out { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + struct fuse_file_lock lk; +}; +struct fuse_access_in { + uint32_t mask; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t padding; +}; +struct fuse_init_in { + uint32_t major; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t flags; + uint16_t max_background; + uint16_t congestion_threshold; + uint32_t max_write; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +#define CUSE_INIT_INFO_MAX 4096 +struct cuse_init_in { + uint32_t major; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t minor; + uint32_t unused; + uint32_t flags; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct cuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t unused; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t flags; + uint32_t max_read; + uint32_t max_write; + uint32_t dev_major; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t dev_minor; + uint32_t spare[10]; +}; +struct fuse_interrupt_in { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t unique; +}; +struct fuse_bmap_in { + uint64_t block; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t blocksize; + uint32_t padding; +}; +struct fuse_bmap_out { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t block; +}; +struct fuse_ioctl_in { + uint64_t fh; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t flags; + uint32_t cmd; + uint64_t arg; + uint32_t in_size; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t out_size; +}; +struct fuse_ioctl_iovec { + uint64_t base; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t len; +}; +struct fuse_ioctl_out { + int32_t result; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t flags; + uint32_t in_iovs; + uint32_t out_iovs; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_poll_in { + uint64_t fh; + uint64_t kh; + uint32_t flags; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t events; +}; +struct fuse_poll_out { + uint32_t revents; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t padding; +}; +struct fuse_notify_poll_wakeup_out { + uint64_t kh; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +struct fuse_fallocate_in { + uint64_t fh; + uint64_t offset; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t length; + uint32_t mode; + uint32_t padding; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_in_header { + uint32_t len; + uint32_t opcode; + uint64_t unique; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t nodeid; + uint32_t uid; + uint32_t gid; + uint32_t pid; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t padding; +}; +struct fuse_out_header { + uint32_t len; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + int32_t error; + uint64_t unique; +}; +struct fuse_dirent { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t ino; + uint64_t off; + uint32_t namelen; + uint32_t type; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + char name[]; +}; +#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) +#define FUSE_DIRENT_ALIGN(x) (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define FUSE_DIRENT_SIZE(d) FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) +struct fuse_direntplus { + struct fuse_entry_out entry_out; + struct fuse_dirent dirent; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +#define FUSE_NAME_OFFSET_DIRENTPLUS offsetof(struct fuse_direntplus, dirent.name) +#define FUSE_DIRENTPLUS_SIZE(d) FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen) +struct fuse_notify_inval_inode_out { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t ino; + int64_t off; + int64_t len; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_notify_inval_entry_out { + uint64_t parent; + uint32_t namelen; + uint32_t padding; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +struct fuse_notify_delete_out { + uint64_t parent; + uint64_t child; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t namelen; + uint32_t padding; +}; +struct fuse_notify_store_out { +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +}; +struct fuse_notify_retrieve_out { + uint64_t notify_unique; + uint64_t nodeid; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint64_t offset; + uint32_t size; + uint32_t padding; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +struct fuse_notify_retrieve_in { + uint64_t dummy1; + uint64_t offset; + uint32_t size; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ + uint32_t dummy2; + uint64_t dummy3; + uint64_t dummy4; +}; +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#endif diff --git a/fuse/AUTHORS b/fuse/AUTHORS new file mode 100644 index 000000000..8c1e88f2c --- /dev/null +++ b/fuse/AUTHORS @@ -0,0 +1,9 @@ +FUSE +---- + +Miklos Szeredi <miklos@szeredi.hu> + +CUSE +---- + +Tejun Heo <teheo@suse.de> diff --git a/fuse/Android.mk b/fuse/Android.mk new file mode 100644 index 000000000..00763ea7e --- /dev/null +++ b/fuse/Android.mk @@ -0,0 +1,53 @@ +# Copyright (C) 2009 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + android/statvfs.c \ + buffer.c \ + cuse_lowlevel.c \ + fuse.c \ + fuse_kern_chan.c \ + fuse_loop.c \ + fuse_loop_mt.c \ + fuse_lowlevel.c \ + fuse_mt.c fuse_opt.c \ + fuse_session.c \ + fuse_signals.c \ + helper.c \ + mount.c \ + mount_util.c \ + ulockmgr.c + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/android \ + $(LOCAL_PATH)/include + +LOCAL_SHARED_LIBRARIES := \ + libutils + +LOCAL_CFLAGS := \ + -D_FILE_OFFSET_BITS=64 \ + -DFUSE_USE_VERSION=26 \ + -fno-strict-aliasing + +LOCAL_CFLAGS += -Wno-pointer-arith -Wno-sign-compare -Wno-unused-parameter + +LOCAL_MODULE := libfusetwrp +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_LIBRARY) diff --git a/fuse/COPYING b/fuse/COPYING new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/fuse/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/fuse/COPYING.LIB b/fuse/COPYING.LIB new file mode 100644 index 000000000..4362b4915 --- /dev/null +++ b/fuse/COPYING.LIB @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/fuse/ChangeLog b/fuse/ChangeLog new file mode 100644 index 000000000..f2e5d024f --- /dev/null +++ b/fuse/ChangeLog @@ -0,0 +1,3535 @@ +2015-05-22 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.9.4 + +2015-05-22 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: fix exec environment for mount and umount. Found by + Tavis Ormandy (CVE-2015-3202). + +2015-02-26 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: fix fuse_remove_signal_handlers() to properly restore + the default signal handler. Reported by: Chris Johnson + +2014-07-21 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: highlevel API: fix directory file handle passed to + ioctl() method. Reported by Eric Biggers + +2014-07-15 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: document deadlock avoidance for + fuse_notify_inval_entry() and fuse_notify_delete() + + * fusermount, libfuse: send value as unsigned in "user_id=" and + "group_id=" options. Uids/gids larger than 2147483647 would + result in EINVAL when mounting the filesystem. This also needs a + fix in the kernel. + +2014-03-26 Miklos Szeredi <miklos@szeredi.hu> + + * Initilaize stat buffer passed to ->getattr() and ->fgetattr() to + zero in all cases. Reported by Daniel Iwan + +2013-08-26 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: Add missing includes. This allows compiling fuse with + musl. Patch by Daniel Thau + +2013-07-01 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.9.3 + +2013-06-20 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: fix multiple close of device fd. Reported by Dan + Greenfield + +2013-03-19 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: fix thread cancel race. Exiting a worker my race with + cancelling that same worker. This caused a segmenation + fault. Reported and tested by Anatol Pomozov + +2013-02-04 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: fix crash in unlock_path(). Patch by Ratna Manoj + + * libfuse: fix the 'remember' option. The lru list was not + initialized for the "/" path. This resulted in remove_node_lru() + crashing on LOOKUP-DOTDOT. Patch by Madan Valluri + + * libfuse: configure: detect new util-linux + + * libfuse: Use AC_CONFIG_HEADERS instead of AM_CONFIG_HEADER. + Patch by Anatol Pomozov + + * libfuse: rename ./configure.in to ./configure.ac. Patch by + Anatol Pomozov + +2012-10-01 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.9.2 + +2012-10-01 Miklos Szeredi <miklos@szeredi.hu> + + * Fix deadlock in libfuse. Running "svn update" on a fuse + filesystem could deadlock because of a bug in the way the paths + are locked. Reported by Kazuaki Anami + +2012-08-23 Miklos Szeredi <miklos@szeredi.hu> + + * Fix missing config.h in buffer.c. Reported by Matthew Gabeler-Lee + +2012-08-14 Miklos Szeredi <miklos@szeredi.hu> + + * Not unhashing the name in forget (commit on 2011-12-09) broke + the forget logic in a subtle way, resulting in "fuse internal + error: node NNN not found" and causing the filesystem daemon to + abort. Fix by incrementing the node refcount if nlookup goes from + zero to one. Reported by Kyle Lippincott + +2012-08-13 Miklos Szeredi <miklos@szeredi.hu> + + * Fix linking against GNU libiconv. Patch by Natanael Copa + +2012-07-19 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.9.1 + +2012-07-19 Miklos Szeredi <miklos@szeredi.hu> + + * Fix crash caused by freeing a stack address. Reported by Itay + Perl + +2012-07-04 Miklos Szeredi <miklos@szeredi.hu> + + * Fix install of mount.fuse from out-of-tree build. Patch by + Olivier Blin + + * Fix build with automake >= 1.12.1. Patch by Olivier Blin + +2012-04-24 Miklos Szeredi <miklos@szeredi.hu> + + * Add fallocate operation. Only works on linux kernels 3.5 or + later. Patch by Anatol Pomozov + +2012-05-16 Miklos Szeredi <miklos@szeredi.hu> + + * Linking to a library that uses threads requires the application + to be linked with -pthreads otherwise some pthread functions will + be linked to stubs in glibc. So move -pthread from Libs.private + to Libs in fuse.pc. Reported by Werner Fink + + * Fix the compile command in the examples. Reported by Luciano + Dalle Ore + +2012-04-20 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.9.0 + +2012-04-20 Miklos Szeredi <miklos@szeredi.hu> + + * Add missing fuse_fs_flock to fuse_versionscript + +2012-04-10 Miklos Szeredi <miklos@szeredi.hu> + + * Check protocol version before sending notifications and return + -ENOSYS if a particular notification is not supported. + + * Add 'flag_utime_omit_ok' flag to fuse_operations. If the + filesystem sets this flag then ->utimens() will receive UTIME_OMIT + and UTIME_NOW values as specified in utimensat(2). + +2012-01-27 Miklos Szeredi <miklos@szeredi.hu> + + * Interpret octal escape codes in options. Requested by Jan + Engelhardt + +2012-01-26 Miklos Szeredi <miklos@szeredi.hu> + + * Add man pages for fusermount, mount.fuse and ulockmgr_server. + Lifted from the Debian package. The man pages were written by + Daniel Baumann and Bastien Roucaries + +2012-01-13 Miklos Szeredi <miklos@szeredi.hu> + + * Disable symbol versions on MacOSX. Patch by Anatol Pomozov + +2012-01-02 Miklos Szeredi <miklos@szeredi.hu> + + * Remove unnecessary mutex unlock at the end of multithreaded + event loop. + +2011-12-09 Miklos Szeredi <miklos@szeredi.hu> + + * Fix hang in wait_on_path(). Reported by Ville Silventoinen + + * Don't unhash name in FORGET. This resulted in ENOENT being + returned for unlinked but still open files if the kernel sent a + FORGET request for the parent directory. + + * Free request in fuse_reply_data(). + +2011-12-08 Miklos Szeredi <miklos@szeredi.hu> + + * Fix build if FUSE_NODE_SLAB is not defined. Patch by Emmanuel + Dreyfus + + * Check for availability of utimensat() function. Patch by + Emmanuel Dreyfus + +2011-12-07 Miklos Szeredi <miklos@szeredi.hu> + + * Add fuse_lowlevel_notify_delete() which tells the kernel that a + file or directory is deleted. Patch by John Muir + +2011-12-06 Miklos Szeredi <miklos@szeredi.hu> + + * Update retrieve_reply() method + +2011-12-05 Miklos Szeredi <miklos@szeredi.hu> + + * Low level API: lock argument of fuse_reply_lock should have a + 'const' qualifier. Reported by Shachar Sharon + + * Add support for ioctl on directories. Reported by Antonio SJ + Musumeci + +2011-10-13 Miklos Szeredi <miklos@szeredi.hu> + + * Reply to request with ENOMEM in case of failure to allocate + request structure. Otherwise the task issuing the request will + just freeze up until the filesystem daemon is killed. Reported by + Stephan Kulow + +2011-09-23 Miklos Szeredi <miklos@szeredi.hu> + + * Replace daemon() function with fork(). Patch by Anatol Pomozov + +2011-08-26 Miklos Szeredi <miklos@szeredi.hu> + + * If configured with --disable-mtab then don't call mount(8) from + libfuse to update the mtab. Reported by: James Sierp + +2011-08-24 Miklos Szeredi <miklos@szeredi.hu> + + * Use LRU list for cleaning up the cache if the "remember=T" + option was given. Patch by therealneworld@gmail.com + +2011-07-06 Miklos Szeredi <miklos@szeredi.hu> + + * Add ->flock() operation to low and high level interfaces. This + fixes problems with emulating flock() with POSIX locking. + Reported by Sebastian Pipping. As with lock/setlk/getlk most + filesystems don't need to implement this, as the kernel takes care + of file locking. The only reason to implement locking operations + is for network filesystems which want file locking to work between + clients. + +2011-07-02 Sebastian Pipping <sebastian@pipping.org> + + * Make xmp_utimens of examples "fusexmp" and "fusexmp_fh" + not follow symlinks as other layers do that already. + +2011-06-02 Miklos Szeredi <miklos@szeredi.hu> + + * Add "remember" option. This works similar to "noforget" except + that eventually the node will be allowed to expire from the cache. + Patch by therealneworld@gmail.com + +2011-05-27 Miklos Szeredi <miklos@szeredi.hu> + + * Check if splice/vmsplice are supported + +2011-05-26 Miklos Szeredi <miklos@szeredi.hu> + + * Remove -lrt -ldl from fuse.pc for dynamic linking since + libfuse.so is already linked with these libraries. Reported by: + Nikolaus Rath + +2011-05-20 Miklos Szeredi <miklos@szeredi.hu> + + * Cleaner build output. Patch by Reuben Hawkins + +2011-05-19 Miklos Szeredi <miklos@szeredi.hu> + + * Disable splice by default, add "splice_read", "splice_write" and + "splice_move" options. Keep the "no_splice_*" variants, which can + disable splice even if the filesystem explicitly enables it. + +2011-04-15 Max Krasnyansky <maxk@kernel.org> + * Added support for "auto_unmount" option which unmounts the + filesystem automatically on process exit (or crash). + +2011-03-30 Miklos Szeredi <miklos@szeredi.hu> + + * Patches by Laszlo Papp fixing various issues found by the + Coverity checker + +2011-03-11 Miklos Szeredi <miklos@szeredi.hu> + + * In case of failure to add to /etc/mtab don't umount. Reported + by Marc Deslauriers + +2011-02-02 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: In fuse_session_loop_mt() don't pause when exiting the + worker threads. The pause() was added in 2.2.1 to prevent + segfault on pthread_cancel() on an exited, detached thread. Now + worker threads are not detached and pthread_cancel() should work + fine even after the thread exited. Reported by Boris Protopopov + +2011-01-31 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: chdir to / before performing mount/umount + + * fusermount: only allow mount and umount if util-linux supports + --no-canonicalize + +2010-12-16 Miklos Szeredi <miklos@szeredi.hu> + + * Highlevel lib: allow hash tables to shrink + + * Highlevel lib: add slab allocation for node cache. This will + allow the memory used by the filesystem to grow and shrink + depending on how many inodes are currently cached. + +2010-12-13 Miklos Szeredi <miklos@szeredi.hu> + + * Highlevel lib: use dynamically resized hash table for looking up + by name and node ID. + +2010-12-07 Miklos Szeredi <miklos@szeredi.hu> + + * Allow batching of forget requests. This allows forget requests + to be processed faster and doesn't require a modification to fuse + filesystems. Reported by Terje Malmedal + + * Add ->forget_multi() operation to the lowlevel API. The + filesystem may implement this to process multiple forget requests + in one call + + * Fix the ambiguity of ioctl ABI on the kernel/userspace boundary + for 32bit vs. 64bit userspace + +2010-11-10 Miklos Szeredi <miklos@szeredi.hu> + + * Add new write_buf() method to the highlevel API. Similarly to + the lowlevel write_buf() method, this allows implementing zero + copy writes. + + * Add a new read_buf() method to the highlevel API. This allows + returning a generic buffer from the read method, which in turn + allows zero copy reads. + + * In fusexmp_fh implement the ->read_buf() and ->write_buf() + methods. Leave the ->read() and ->write() implementations for + reference, even though they are not necessary. + +2010-11-08 Miklos Szeredi <miklos@szeredi.hu> + + * Fix check for read-only fs in mtab update + + * Open /dev/null for write instead of read for redirecting stdout + and stderr + + * If umount(8) supports --fake and --no-canonicalize (util-linux-ng + version 2.18 or later), and umount(2) supports the + UMOUNT_NOFOLLOW flag (linux kernel version 2.6.35 or later) then, + "fusermount -u" will call the umount(2) system call and use + "umount --fake ..." to update /etc/mtab + + * Added --disable-legacy-umount option to configure. This + disables the runtime checking of umount(8) version. When built + with this option then "fusermount -u" will fail if umount(8) + doesn't support the --fake and --no-canonicalize options. + + * Fix fuse_buf_copy() if already at the end of the buffers + + * Add new ->write_buf() method to low level interface. This + allows passig a generic buffer, either containing a memory buffer + or a file descriptor. This allows implementing zero copy writes. + + * Add fuse_session_receive_buf() and fuse_session_process_buf() + which may be used in event loop implementations to replace + fuse_chan_recv() and fuse_session_process() respectively. + + * Remove unnecessary restoring of current working directory in + "fusermount -u" + + * Add ctx->pid to debug output + + * Fix st_nlink value in high level lib if file is unlinked but + still open + + * libfuse: add store request. Request data to be stored in the + kernel buffers for a given inode. + + * libfuse: add retrieve request. Retrieve data stored in the + kernel buffers for a given inode. + +2010-10-14 Miklos Szeredi <miklos@szeredi.hu> + + * Use LTLIBICONV when linking libfuse. This fixes building against + uclibc + libiconv. Patch by Natanael Copa + +2010-10-05 Miklos Szeredi <miklos@szeredi.hu> + + * Add missing argument check in ulockmgr.c to prevent calling + ulockmgr_server with illegal arguments. This would cause an ever + growing list of ulockmgr_server processes with an endless list of + open files which finally exceeds the open file handle limit. + Patch by Markus Ammer + +2010-09-28 Miklos Szeredi <miklos@szeredi.hu> + + * Fix ambiguous symbol version for fuse_chan_new. + fuse_versionscript included fuse_chan_new in both FUSE_2.4 and + FUSE_2.6. Remove the FUSE_2.4, which is invalid. + +2010-09-28 Miklos Szeredi <miklos@szeredi.hu> + + * Fix option escaping for fusermount. If the "fsname=" option + contained a comma then the option parser in fusermount was + confused (Novell bugzilla #641480). Fix by escaping commas when + passing them over to fusermount. Reported by Jan Engelhardt + +2010-08-27 Miklos Szeredi <miklos@szeredi.hu> + + * Add NetBSD support. Patch from Emmanuel Dreyfus + +2010-07-12 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: add buffer interface. Add a generic buffer interface + for use with I/O. Buffer vectors are supplied and each buffer in + the vector may be a memory pointer or a file descriptor. + + * The fuse_reply_fd() interface is converted to using buffers. + +2010-06-23 Miklos Szeredi <miklos@szeredi.hu> + + * Make the number of max background requests and congestion + threshold tunable. New options are "max_background" and + "congestion_threshold". Only effective on linux kernel versions + 2.6.32 or greater. Patch by Csaba Henk + +2010-06-17 Miklos Szeredi <miklos@szeredi.hu> + + * Add fuse_reply_fd() reply function to the low level interface. + On linux version 2.6.35 or greater this will use splice() to move + data directly from a file descriptor to the fuse device without + needing to go though a userspace buffer. With the + FUSE_REPLY_FD_MOVE flag the kernel will attempt to move the data + directly into the filesystem's cache. On earlier kernels it will + fall back to an intermediate buffer. The options + "no_splice_write" and "no_splice_move" can be used to disable + splicing and moving respectively. + +2010-06-15 Miklos Szeredi <miklos@szeredi.hu> + + * Fix out-of-source build. Patch by Jörg Faschingbauer + + * Add a "nopath" option and flag, indicating that path argument + need not be calculated for the following operations: read, write, + flush, release, fsync, readdir, releasedir, fsyncdir, ftruncate, + fgetattr, lock, ioctl and poll. + +2010-05-10 Miklos Szeredi <miklos@szeredi.hu> + + * Remove "chmod root" from install of fusermount. Reported by + Lucas C. Villa Real + +2010-04-26 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.8.4 + +2010-04-26 Miklos Szeredi <miklos@szeredi.hu> + + * Fix checking for symlinks in umount from /tmp. Reported by Al + Viro + + * Fix umounting if /tmp is a symlink. Reported by Franco Broi + +2010-02-18 Miklos Szeredi <miklos@szeredi.hu> + + * Fix definition of FUSE_OPT_END for C++. Reported by Tim + Bruylants + +2010-02-03 Miklos Szeredi <miklos@szeredi.hu> + + * Fix stack alignment for clone() + +2010-02-01 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.8.3 + +2010-02-01 Miklos Szeredi <miklos@szeredi.hu> + + * Using "--no-canonicalize" with umount(8) conflicts with the race + fix, sinceit assumes the supplied path is absolute, while the race + fix relies on the path being relative to the current directory. + Reported by Tom Rindborg + +2010-01-26 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.8.2 + +2010-01-21 Miklos Szeredi <miklos@szeredi.hu> + + * Fix race if two "fusermount -u" instances are run in parallel. + Reported by Dan Rosenberg + + * Make sure that the path to be unmounted doesn't refer to a + symlink + +2010-01-14 Miklos Szeredi <miklos@szeredi.hu> + + * Fix compile error on FreeBSD. Patch by Jay Sullivan + +2009-12-17 Miklos Szeredi <miklos@szeredi.hu> + + * Use '--no-canonicalize' option of mount(8) (available in + util-linux-ng version 2.17 or greater) to avoid calling + readling(2) on the newly mounted filesystem before the mount + procedure is finished. This has caused a deadlock if "audit" was + enabled in the kernel. Also use '--no-canonicalize' for umount to + avoid touching the mounted filesystem. + +2009-09-11 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.8.1 + +2009-08-25 Miklos Szeredi <miklos@szeredi.hu> + + * Fix missing versioned symbol fuse_get_context@FUSE_2.2 + +2009-08-18 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.8.0 + +2009-08-18 Miklos Szeredi <miklos@szeredi.hu> + + * Add missing fuse_session_data to versionscript + + * Make sure all global symbols are prefixed with "fuse_" or "cuse_" + +2009-07-16 Miklos Szeredi <miklos@szeredi.hu> + + * Clarify how the protocol version should be negotiated between + kernel and userspace. Notably libfuse didn't correctly handle the + case when the supported major versions didn't match + + * Add missing pthread link for libulockmgr. Patch by Petr Salinger + +2009-07-02 Miklos Szeredi <miklos@szeredi.hu> + + * The context is extended with a 'umask' field. The umask is sent + for mknod, mkdir and create requests by linux kernel version + 2.6.31 or later, otherwise the umask is set to zero. Also + introduce a new feature flag: FUSE_CAP_DONT_MASK. If the kernel + supports this feature, then this flag will be set in conn->capable + in the ->init() method. If the filesystem sets this flag in in + conn->want, then the create modes will not be masked. + + * Add low level interfaces for lookup cache and attribute + invalidation. This feature is available in linux kernels 2.6.31 + or later. Patch by John Muir + + * Kernel interface version is now 7.12 + + * fusermount: Do not silently ignore command line arguments. + Patch by Sebastian Harl + +2009-06-19 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.8.0-pre3 + +2009-06-19 Miklos Szeredi <miklos@szeredi.hu> + + * Add fuse_getgroups (high level lib) and fuse_req_getgroups (low + level lib) functions to query the supplementary group IDs for the + current request. Currently this is implemented on Linux by + reading from the /proc filesystem. + +2009-06-18 Miklos Szeredi <miklos@szeredi.hu> + + * Add "noforget" option to high level lib to prevent ESTALE errors + on NFS exported filesystems. This result in paths being cached + forever, resulting in ever growing memory usage. Use with care. + + * Add "no_remote_lock" option to disable remote file locking even + if the filesystem implements it. With this option locking + primitives (flock, lockf, fcntl(F_SETLK)) will still work, but + will ignore remotely locked files. + + * CUSE patches from Tejun Heo: + + * Unrestricted ioctl support left some debris. Clean them up: + o No reason to pass around pointer to flags. Pass flags directly. + o Clean up comment and prototype parameter names. + o fuse_lib_ioctl() didn't reply when get_path() failed. Fix it. + o Remove unused variables {in|out}_iov from fuse_lib_ioctl(). + + * Add fuse_reply_ioctl_iov() + + * Move fuse_session, fuse_req and fuse_ll definitions to fuse_i.h + and make send_reply_iov() and fuse_setup_common() global (also in + fuse_i.h). These will be used by CUSE support. + + * Restructure fuse_ll_process() + + * Implement libfuse side of CUSE support. CUSE uses subset of FUSE + operations as dir operations don't make sense for CUSE where one + instance implements single character device. + + CUSE support comes with its own cuse_lowevel_ops and related + initialization and helper functions. Except for initialization, it + usage is basically identical to FUSE. + + This patch also adds example/cusexmp.c which can create a character + device with name and device number specified on command line. The + created device itself is pretty boring. It's a bit bucket supporting + read, write and access via ioctl. + +2009-06-16 Miklos Szeredi <miklos@szeredi.hu> + + * Add missing fuse_reply_bmap to versionscript. Debian + Bug#531329. Reported by Goswin Brederlow + +2009-05-27 Miklos Szeredi <miklos@szeredi.hu> + + * Don't call forget_node() if the lookup was negative and write() + for the reply returned ENOENT. Reported by John Haxby + +2009-05-25 Miklos Szeredi <miklos@szeredi.hu> + + * Add FUSE_CAP_EXPORT_SUPPORT to fuse_common.h + +2009-05-08 Miklos Szeredi <miklos@szeredi.hu> + + * Fix missing newlines in some printfs + + * Fix 'make install-strip'. Reported by Dominick Layfield + +2009-01-05 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.8.0-pre2 + +2008-12-08 Miklos Szeredi <miklos@szeredi.hu> + + * Implement poll support. Patch by Tejun Heo + + * Add missing setattr flags to <fuse_lowlevel.h>. + + * Only pass valid flags to ->setattr(). + +2008-12-05 Miklos Szeredi <miklos@szeredi.hu> + + * Implement ioctl support. On high level interface only + "restricted" ioctls are supported (which are defined with the + _IO(), _IOR(), _IOW() or _IOWR() macros). Unrestricted ioctls + will only be allwed to CUSE (Character Device in Userspace) + servers. Patch by Tejun Heo + +2008-11-28 Miklos Szeredi <miklos@szeredi.hu> + + * If open sets fi->nonseekable, libfuse will tell the kernel that + the file is not seekable. Patch by Tejun Heo + +2008-11-19 Miklos Szeredi <miklos@szeredi.hu> + + * lowlevel lib: fix deadlock if fuse_reply_* is called from the + interrupt handling function. Reported by Tero Marttila + +2008-10-16 Miklos Szeredi <miklos@szeredi.hu> + + * Allow commas in options to be escaped with a backslash + + * Add new function: fuse_opt_add_opt_escaped() + + * Add missing fuse_reply_bmap() to the version script + +2008-10-14 Miklos Szeredi <miklos@szeredi.hu> + + * Pass current file flags to read and write operations + +2008-07-24 Miklos Szeredi <miklos@szeredi.hu> + + * Clean up debug output in highlevel lib + +2008-07-10 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.8.0-pre1 + +2008-06-27 Miklos Szeredi <miklos@szeredi.hu> + + * Fix handling of (no)suid and (no)dev options if filesystem is + mounted from /etc/fstab or via mount(8). Reported by Jan Ondrej. + + * Skip calling mount(8) if /etc/mtab doesn't exist or if it's on a + read-only filesystem. This works around issues with certain mount + implementations. Reported by Szabolcs Szakacsits. + +2008-06-16 Miklos Szeredi <miklos@szeredi.hu> + + * Remove fuse kernel module sources. Linux 2.6.27 will support + NFS exporting. + +2008-06-10 Miklos Szeredi <miklos@szeredi.hu> + + * Fix theoretical infinite loops in libfuse. Reported by Szabolcs + Szakacsits + + * Fix missing <sys/param.h> include for PATH_MAX. Reported by + Szabolcs Szakacsits + +2008-05-23 Miklos Szeredi <miklos@szeredi.hu> + + * Fix mounting over symlink. Reported by Szabolcs Szakacsits + +2008-05-09 Miklos Szeredi <miklos@szeredi.hu> + + * Don't allow bigger than 4kB writes by default on 2.6.26 and + later kernels, so that filesystems not expecting this are not + broken on a kernel upgrade. Provide a 'big_writes' mount option + to enable this feature. In future API revisions this may become + the default. + +2008-04-09 Miklos Szeredi <miklos@szeredi.hu> + + * Update warning message for missing newline at end of fuse.conf + + * Update debug message for successful operation to not include the + string "error:" + +2008-04-08 Miklos Szeredi <miklos@szeredi.hu> + + * Update error message for missing mountpoint parameter. Reported + by Allen Pulsifer + +2008-04-04 Miklos Szeredi <miklos@szeredi.hu> + + * Print library version information to debug output + + * Highlevel lib: don't limit paths to 4095 characters + +2008-03-25 Miklos Szeredi <miklos@szeredi.hu> + + * Fix memory leaks on mount. Patch by Szabolcs Szakacsits + +2008-03-19 Miklos Szeredi <miklos@szeredi.hu> + + * Fix missing pthread_mutex_destroy in error path of + fuse_lib_opendir(). Patch by Szabolcs Szakacsits + +2008-03-07 Miklos Szeredi <miklos@szeredi.hu> + + * Add queuing on contention to per-node lock algorithm, to avoid + starvation. + + * Only enable cancelation when reading a request, otherwise + cancellation could happen with a mutex held, which could hang the + process on umount + +2008-02-08 Miklos Szeredi <miklos@szeredi.hu> + + * Block SIGCHLD when executing mount and umount + + * fusexmp_fh: avoid unnecessary seeking in readdir + + * Update kernel interface to 7.9: + + * Support receiving file handle from kernel in GETATTR request + + * Allow operations with a NULL path argument, if the filesystem + supports it + + * Add support atomic open(O_TRUNC) + + * Support the st_blksize field in struct stat + + * If the "FUSE_THREAD_STACK" environment is set, initialize the + stack size of threads by this value. Patch by Florin Malita + + * Add per-node locking, instead of a global tree lock to protect + the path from changing during operations. Original patch by + Rodrigo Castro + +2008-02-03 Csaba Henk <csaba.henk@creo.hu> + + * lib/mount_bsd.c: + - string formatting fixes + - exit if mounting has failed + (in FreeBSD a mount failure is not critical per se, as the daemon + still could be mounted externally, but waiting for such an event + is more confusing than fruitful) + - ditch the kvm(8) stuff and simply use forced unmount which just + won't block + - prettify option specifications + - add "-onosync_unmount" kernel option + +2008-01-07 Csaba Henk <csaba.henk@creo.hu> + + * lib/mount_bsd.c: + - refine device closing in a race-free way + - add support for "-osubtype" on FreeBSD + + * makeconf.sh: make it work under FreeBSD + +2008-01-03 Csaba Henk <csaba.henk@creo.hu> + + * lib/mount_bsd.c: close device before unmount + (cf. lib/mount.c rev. 1.43) and fix some warnings + +2007-12-23 Miklos Szeredi <miklos@szeredi.hu> + + * Fix './configure --disable-static'. Patch from Ismail Dönmez + +2007-12-17 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.7.2 + +2007-12-12 Miklos Szeredi <miklos@szeredi.hu> + + * Fix kernel module compile for 2.6.24 + + * Invalidate attributes of parent directory after create(), since + the modification time changes. Invalidate attributes on rename, + since some filesystems may update st_ctime. Reported by Szabolcs + Szakacsits + + * Fix NFS exporting to handle 64bit node IDs + + * Disable old symbol versions if __UCLIBC__ is defined. If a + symbol in a library has multiple versions, the runtime linker in + uClibc seems to randomly choose between them. + + * Remove erroneous 'fuse_opt_insert_arg@FUSE_2_5' from + fuse_version_script. fuse_opt_free_args() was added in fuse-2.6. + + * Close fuse device file descriptor before calling umount(), + preventing a deadlock when umount is synchronous. Reported by + Szabolcs Szakacsits + +2007-11-12 Miklos Szeredi <miklos@szeredi.hu> + + * 'fusermount -u' did not umount the filesystem if /etc/mtab was a + symlink. This bug was introduced in 2.7.1 by "Don't call + /bin/[u]mount if /etc/mtab is a symlink". Found by robertsong. + +2007-10-16 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.7.1 + +2007-10-16 Miklos Szeredi <miklos@szeredi.hu> + + * Clarify licence version to be "LGPLv2" for the library + + * kernel fixes: + + * After mount set nlink attribute for the root inode to 1 + + * Fix wake up of task waiting for a reserved request + + * Fix allowing setattr, listxattr and statfs for other users + +2007-09-18 Miklos Szeredi <miklos@szeredi.hu> + + * Add missing context initialization in fuse_fs_chmod(). Bug + found by "iohead" + + * Fix kernel module compilation for 2.6.23. Based on patch by + Marian Marinov + +2007-09-04 Philippe Elie <phil.el@wanadoo.fr> + + * lib/fuse_lowlevel.c: fix a fuse_req leak in do_forget() + +2007-07-31 Miklos Szeredi <miklos@szeredi.hu> + + * Work around hotplug issue, that it calls filesystem with file + descriptors 0, 1 and 2 not open. Tracked down by Leif Johnson + +2007-07-25 Miklos Szeredi <miklos@szeredi.hu> + + * Don't call /bin/[u]mount if /etc/mtab is a symlink. Reported by + Tomas M + + * Also don't touch /etc/mtab if it is within the mounted + filesystem. Suggested by Jeffrey Law + +2007-07-12 Miklos Szeredi <miklos@szeredi.hu> + + * Reset args->argc in fuse_opt_free_args(). Patch by Lucas + C. Villa Real + +2007-07-02 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.7.0 + +2007-07-02 Miklos Szeredi <miklos@szeredi.hu> + + * Accept a NULL "op" for fuse_main(), etc. This is useful if + filesystem is only invoking fuse to print a help message, or + version. Fixes RedHat bugzilla #217343 + +2007-06-22 Miklos Szeredi <miklos@szeredi.hu> + + * lib: fix locking when loading a filesystem module + +2007-06-21 Miklos Szeredi <miklos@szeredi.hu> + + * Add fs subtype support to mount.fuse + +2007-06-20 Miklos Szeredi <miklos@szeredi.hu> + + * Add fs subtype support to libfuse and fusermount + +2007-06-19 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: sync with mainline (2.6.22) + +2007-06-18 Miklos Szeredi <miklos@szeredi.hu> + + * Send debug output to stderr instead of stdout. Patch by Jan + Engelhardt + +2007-06-03 Miklos Szeredi <miklos@szeredi.hu> + + * libulockmgr: Work around a kernel bug in recv(), causing it to + sometimes return zero even if data was available on the socket. + +2007-05-29 Miklos Szeredi <miklos@szeredi.hu> + + * lib: optimization: store parent pointer in node instead of + parent id + +2007-05-25 Miklos Szeredi <miklos@szeredi.hu> + + * lib: don't create new thread for each FORGET request. FORGET + messages sometimes caused so many threads to be created, that + process virtual memory space ran out. Reported by Chris AtLee + +2007-05-24 Miklos Szeredi <miklos@szeredi.hu> + + * lib: fix memory leak on thread creation failure in multithreaded + event loop. Found by Chris AtLee + +2007-05-23 Miklos Szeredi <miklos@szeredi.hu> + + * lowlevel lib: add fuse_reply_iov function, which is similar to + fuse_reply_buf, but accepts a vector of buffers. Patch by Roger + Willcocks + +2007-05-21 Miklos Szeredi <miklos@szeredi.hu> + + * Fix Oops or error if a regular file is created with mknod(2) on + a fuse filesystem. Kernels 2.6.18 onward are affected. Thanks to + J. Cameijo Cerdeira for the report + +2007-05-11 Csaba Henk <csaba.henk@creo.hu> + + * libfuse: fix return value of fuse_loop()/fuse_loop_mt(). + Error reported by Csaba Henk, fix by Miklos Szeredi + + * libfuse: fix unlock in flush + + * libfuse: do unlocking on RELEASE+FLUSH + +2007-05-03 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.7.0-rc1 + +2007-05-02 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: sync with mainline: + + * Use invalidate_mapping_pages() if available + + * Fix BUG when invalid file type is supplied in mount. Patch by + Timo Savola + +2007-04-27 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: call umount(8) directly instead of fusermount if + possible + + * Clean up init script, make it LSB compliant + +2007-04-26 Miklos Szeredi <miklos@szeredi.hu> + + * In multithreaded loop, use a semaphore instead of SIGHUP to wake + up the main thread on umount. This is more elegant, and works + even if signals are blocked. + +2007-04-25 Miklos Szeredi <miklos@szeredi.hu> + + * Improve mounting support in libfuse: + - check non-empty mountpoint + - only fall back to fusermount when necessary + +2007-04-23 Miklos Szeredi <miklos@szeredi.hu> + + * Don't chdir to "/" in foreground mode, it causes more trouble + than it's worth + +2007-04-18 Miklos Szeredi <miklos@szeredi.hu> + + * Replace utils/mount.fuse "sh" script with a "C" program + +2007-04-15 Miklos Szeredi <miklos@szeredi.hu> + + * Add -lulockmgr to compilation comment in fusexmp_fh.c + +2007-04-05 Miklos Szeredi <miklos@szeredi.hu> + + * Check for iconv. Patch by Csaba Henk + + * Add direct umounting + + * Use "fusectl" as the device for the fusectl filesystem. Debian + Bug#417945. Reported by Laurent Bonnaud + +2007-04-01 Csaba Henk <csaba.henk@creo.hu> + + * Fix some FreeBSD related macros. + +2007-03-30 Miklos Szeredi <miklos@szeredi.hu> + + * Add support for direct mounting by libfuse. Fall back on + calling fusermount if it doesn't work + +2007-03-14 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.7.0-pre1 + +2007-03-05 Miklos Szeredi <miklos@szeredi.hu> + + * Correctly handle O_APPEND in direct IO mode. Reported by Greg + Bruno + + * mount.fuse should use /bin/bash. Debian Bug#413403. Reported + by Thomas Weinbrenner + +2007-02-26 Miklos Szeredi <miklos@szeredi.hu> + + * Fix detection of installed fuse in init script. Reported and + fix suggested by Davide Canova + +2007-02-05 Miklos Szeredi <miklos@szeredi.hu> + + * Fix 2.6.9 RHEL kernels, which have compatibility mutex.h, but + don't define mutex_destroy(), bummer. Patch from Phil Schwan + +2007-02-04 Miklos Szeredi <miklos@szeredi.hu> + + * Compile fuseblk for kernels which don't have an option to turn + off the block layer (CONFIG_BLOCK). Reported by Szakacsits + Szabolcs + +2007-02-03 Miklos Szeredi <miklos@szeredi.hu> + + * Add filesystem stacking support to high level API. Filesystem + modules can be built into libfuse or loaded from shared object + (.so) files + + * Add 'subdir' and 'iconv' built in modules + + * lib/fuse.c: Fix locking for the reply code in create and open + +2007-02-02 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: make it compile on "strange" kernels which have emulated + mutexes via <linux/mutex.h> but no i_mutex. Reported by Tomasz + Mateja + +2007-01-28 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: fix BUG in control filesystem if it is umounted and + mounted again, while some fuse filesystems are present. + Bugreport from Florent Mertens + + * kernel: sync with mainline, support 2.6.20 + +2007-01-22 Miklos Szeredi <miklos@szeredi.hu> + + * lib/Makefile.am: actually link libfuse against libfuse_libs + +2007-01-19 Miklos Szeredi <miklos@szeredi.hu> + + * Build fix for 2.6.16 vanila and 2.6.15 FC5 kernels. Patch from + Ian Abbott + +2007-01-18 Miklos Szeredi <miklos@szeredi.hu> + + * Fix abort in fuse_new() compatibility API for opts == NULL case. + Novell bugzilla #233870. Patch from Takashi Iwai. + +2007-01-13 Miklos Szeredi <miklos@szeredi.hu> + + * Fix option parsing in mount.fuse. Patch from Jens M. Noedler + +2007-01-02 Miklos Szeredi <miklos@szeredi.hu> + + * Fix unaligned access in file desctriptor passing in libfuse, + fusermount and ulockmgr. Debian bug ID: 404904. Reported and + tested by Sebastian Fontius + +2006-12-16 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: don't keep unreferenced inodes in the icache. + +2006-12-15 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: Fix detection of fuseblk. Reported by Szakacsits + Szabolcs + + * lib: Fix use after free in fuse_flush(). Reported by Ron + Lindman + +2006-12-10 Miklos Szeredi <miklos@szeredi.hu> + + * mount.fuse: add "setuid=USER" option which does a "su - USER" + for the filesystem + + * fusermount: use "/bin/mount -f" to add entry to /etc/mtab, and + "/bin/umount" to remove entry from /etc/mtab. This gets rid of + the ugly code dealing with mtab, as well as a possible race + between fusermount and mount trying to modify /etc/mtab at the + same time + + * Fix "buffer size too small: 4" warning for users of the + fuse_loop_mt_proc() function. + +2006-12-04 Miklos Szeredi <miklos@szeredi.hu> + + * Fix warnings with gcc-4.1 on 64bit archs. Report from + Harshavardhana + + * Add extra warning options, and fix resulting warnings + + * Really fix fuse_teardown problem + +2006-12-02 Miklos Szeredi <miklos@szeredi.hu> + + * Add -lrt to fuse.pc (if needed) to fix static linking against + libfuse. Reported by Szakacsits Szabolcs + +2006-12-01 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.1 + +2006-11-30 Miklos Szeredi <miklos@szeredi.hu> + + * Fix API version 21 and 22 compatibility for fuse_teardown. + Reported by Bgs + +2006-11-29 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: Print a more helpful message in case the kernel + doesn't support the 'fuseblk' filesystem type. This has been + biting ntfs-3g users. Reported by Yura Pakhuchiy + + * kernel: fix build problem for "make -C ...". Reported by + Stephen Bryant + +2006-11-19 Miklos Szeredi <miklos@szeredi.hu> + + * Fix bug in certain error paths of lookup routines. The request + object was reused for sending FORGET, which is illegal. This bug + could cause an Oops in linux-2.6.18 or in fuse-2.6.0, and might + silently corrupt memory in earlier versions. Report and test + program by Russ Cox + +2006-11-11 Miklos Szeredi <miklos@szeredi.hu> + + * Print an error if an incompatible kernel interface version is + detected in INIT. This will only show if filesystem is started + with -d or -f + + * Fix order of fuse_destroy()/fuse_unmount() in error cleanup of + fuse_setup_common(). Reported by Szakacsits Szabolcs + +2006-11-06 Miklos Szeredi <miklos@szeredi.hu> + + * Fix recursive locking in fuse_create(). Thanks to Takuya + Ishibashi for the bug report + +2006-10-28 Miklos Szeredi <miklos@szeredi.hu> + + * Fix automake problem. Patch from Nix + +2006-10-26 Miklos Szeredi <miklos@szeredi.hu> + + * Fix mount.fuse to use /bin/sh instead of /bin/bash, which is not + always available on embedded systems. Patch from Paul Smith + + * Fix util/Makefile.am, so that failure to run update-rc.d or + device creation doesn't cause make to fail. Reported by Paul + Smith + +2006-10-21 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.0 + +2006-10-18 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: don't try to create a lock file if /etc/mtab is a + symlink. Report and patch from Alexei Sheplyakov (debian bug + #393693) + +2006-10-17 Miklos Szeredi <miklos@szeredi.hu> + + * Minor changes, sync with mainline tree + +2006-10-16 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.0-rc3 + +2006-10-15 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: cleanups + +2006-10-13 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: Fix compilation on patched 2.6.18 (fc6) and 2.6.19. + Report from David Shaw + + * lib: Fix lost error on renaming a file. Report from David Shaw + + * lib: Fix lost error on hiding open files (renaming to + .fuse_hiddenXXXX) + + * kernel: Fix a rare hang on SMP/32bit on heavy filesystem + activity. The cause of the bug was that some calls to + i_size_write() were not protected by a lock, and hence + i_size_seqcount could become corrupted. This caused subsequent + calls to i_size_read() to spin forever. This is a long standing + bug was probably introduced in version 2.2, and thought to be + related to NFS exporting (it's not). It was reported by various + people, but Dana Henriksen has finally helped me to track it down, + so big thanks to him + + * kernel: Protect against truncation of a swapfile + +2006-10-10 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: Check for signature of super_operations->umount_begin(). + Ubuntu kernel 2.6.17 seems to use the new signature found in + 2.6.18. Thanks to Florent Mertens for the report + +2006-10-08 Miklos Szeredi <miklos@szeredi.hu> + + * Make sure inode numers wrap around at 2^32. This is needed on + dual 64bit/32bit architectures, because 32bit applications using + the non-largefile interface would otherwise break (EOVERFLOW error + would be returned by the stat() system call family) + + * ulockmgr: handle the case, when a locking operation fails + because no more file desctriptors are available in + ulockmgr_server. Also work around a Linux kernel bug (known to + exist for all Linux kernel versions <= 2.6.18) which may cause + sent file descriptors to be lost in the above case + + * ulockmgr: optimize file descriptor use + + * restore needed cpp flags to util/Makefile.am + + * Install udev rules as 99-fuse.rules instead of 60-fuse.rules + + * Minor clean up of udev rules + + * Add a synchronous DESTROY message to kernel interface. This is + invoked from umount, when the final instance of the filesystem is + released. It is only sent for filesystems mounted with the + 'blkdev' option for security reasons. + + * If the DESTROY message is received, call the filesystem's + ->destroy() method. In this case it's not called from session + destruction as it would be otherwise. + +2006-10-01 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.0-rc2 + +2006-10-01 Miklos Szeredi <miklos@szeredi.hu> + + * Add support for FLUSH+RELEASE operation for FreeBSD. Original + patch by Csaba Henk + + * Add init script to insert fuse module and mount the control + filesystem. The script is installed as /etc/init.d/fuse and on + debian based systems (where update-rc.d is available) symlinks + from /etc/rc*.d/ are also installed. + + * Include '#define FUSE_USE_VERSION=XX' into examples so they + become more self contained. + +2006-09-30 Miklos Szeredi <miklos@szeredi.hu> + + * API changes: + + * Move lock_owner from a separate argument into fuse_file_info + + * Add a flag to fuse_file_info indicating (1) a highlevel lock + operation (unlock all) was initiated by a flush, (2) a lowlevel + release operation should perform a flush as well. + + * fusermount: revert modprobe change (2006-08-18) since it + doesn't work reliably with udev + + * Add support for block device backed filesystems. This mode is + selected with the 'blkdev' option, which is privileged. + + * Add support for the bmap (FIBMAP ioctl) operation on block + device backed filesystems. This allows swapon and lilo to work on + such filesystems. + + * kernel changes: + + * Drop support for kernels earlier than 2.6.9. Kernel module from + previous (2.5.x) release can be used with library from this + release + + * In fuse_dentry_revalidate() use dget_parent() instead of + dereferencing d_parent, since there's no protection against parent + changing and going away + + * Protect nlookup from concurrent updates + + * In lookup if a directory alias exists but is unused, + then get rid of it, otherwise return -EBUSY. + + * In mkdir if a directory alias exists, return success, but leave + dentry negative. In reality this could happen if a remote rename + immediately followed the mkdir. + + * Don't BUG in fuse_iget() if multiple retries are needed to get a + good inode. This could happen if several lookups are racing for + the same inode. + +2006-09-29 Miklos Szeredi <miklos@szeredi.hu> + + * Fix compilation on 2.6.9. Report from Troy Ayers + +2006-09-27 Miklos Szeredi <miklos@szeredi.hu> + + * Fix Oops in fuse_readpages(). Reported by David Shaw + +2006-09-24 Csaba Henk <csaba.henk@creo.hu> + + * Add support for nanosec times on FreeBSD + + * Fix FreeBSD compatibility issues + +2006-09-23 Miklos Szeredi <miklos@szeredi.hu> + + * Fix one more compatibility bug. Thanks to Ricardo Correia + + * Fix utimens compilation with uClibc. Patch from Jamie Guinan + +2006-09-22 Miklos Szeredi <miklos@szeredi.hu> + + * Fixed several compatibility bugs in low level interface. + Reported by Ricardo Correia + + * Add workaround for ARM caching bug + +2006-09-16 Miklos Szeredi <miklos@szeredi.hu> + + * Rename new utimes() method to more logical utimens() + +2006-09-14 Miklos Szeredi <miklos@szeredi.hu> + + * Fuse tried to unlink already unlinked hidden files. Bug + reported by Milan Svoboda + +2006-09-10 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.0-rc1 + +2006-09-10 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: Fix unlock on close for kernels < 2.6.18 + + * Add ulockmgr library & server. This can be used for handling + file locking requests either directly from libfuse or over a + network, etc. This first version is not optimized and the number + of file descriptors it uses may get out of hand + +2006-09-07 Miklos Szeredi <miklos@szeredi.hu> + + * lib: Add interrupt support to high level library, which may be + enabled with the 'intr' mount option. + + * When an operation is interrupted the thread handling that + operation will receive SIGUSR1 (or other signal specified with the + 'intr_signal=N' option). The library installs a no-op signal + handler for this signal, unless there's already a handler + installed. + + * The filesystem may query interrupt status (regardless of 'intr') + with the fuse_interrupted() function. + + * mount.fuse: initialize $HOME if not set. Report from Sven Goldt + +2006-09-03 Miklos Szeredi <miklos@szeredi.hu> + + * lib: Multithreaded loop now allows unlimited number of threads. + This is needed for locking operations which may block + indefinitely. Also the kernel now doesn't limit the number of + outstanding requests so the library shouldn't do so either. + +2006-09-01 Miklos Szeredi <miklos@szeredi.hu> + + * Fix recursive lock bug in interrupt handling + + * Add utimes() method to highlevel interface, which supports + setting times with nanosecond resolution + +2006-08-18 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: fix page leak if fuse_readpages() failed in it's + initialization. Bug found and original patch from Alexander + Zarochentsev + + * For linux kernels >=2.6.18 (2.6.19 if using the fuse module from + the kernel tree) the statfs method will receive the path within + the filesystem on which the stat(v)fs syscall was called + + * fusermount: try to modprobe fuse module if invoked by root and + unable to open device. This is needed with udev, since the device + node will be created only when the module is inserted, hence + module autoloading won't work. Reported by Szakacsits Szabolcs + +2006-07-30 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: if selinux is active, restore the original file's + security context in unmount_rename(). Redhat bugzilla id 188561. + Patch from Yves Perrenoud + + * Add POSIX file locking operation to high level library + + * Initialize context for unlink of hidden files on umount. Bug + reported by Tim Stoakes + +2006-07-14 Miklos Szeredi <miklos@szeredi.hu> + + * Multiple release() calls can race with each other, resulting in + the hidden file being deleted before the last release finishes. + Bug found and patch tested by Mark Huijgen + +2006-07-05 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: if /dev/fuse doesn't exist, suggest modprobing fuse; + this makes sense on systems using udev. Reported by Szakacsits + Szabolcs + +2006-06-29 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.0-pre3 + +2006-06-29 Miklos Szeredi <miklos@szeredi.hu> + + * Support in kernel module for file locking and interruption. The + same functionality is available in official kernels >= 2.6.18 + +2006-06-28 Miklos Szeredi <miklos@szeredi.hu> + + * Add POSIX file locking support + + * Add request interruption + +2006-06-06 Miklos Szeredi <miklos@szeredi.hu> + + * Add missing pthread_rwlock_destroy(). Patch from Remy Blank + +2006-06-05 Remy Blank <remy.blank@pobox.com> + + * lib: canonicalize mount point in fuse_helper_opt_proc() so that + unmounting succeeds even if mount point was relative. + +2006-06-04 Csaba Henk <csaba.henk@creo.hu> + + * lib: fix emergency umount in helper.c when malloc fails. + (The way it was done would end up in a segfault.) + +2006-06-01 Csaba Henk <csaba.henk@creo.hu> + + * lib: adjust threading related compiler flags. + Switch to "-pthread" from "-lpthread" as that's the preferred + one on several platforms. Consulted with Terrence Cole and + Miklos Szeredi + +2006-05-08 Miklos Szeredi <miklos@szeredi.hu> + + * lib: search fusermount in installation directory (bindir) as + well as in PATH. + +2006-05-03 Miklos Szeredi <miklos@szeredi.hu> + + * lib: fix compilation if CLOCK_MONOTONIC is not defined. + Reported by Christian Magnusson + +2006-04-23 Csaba Henk <csaba.henk@creo.hu> + + * lib: make FreeBSD mount routine recognize if kernel features + backgrounded init and if it does, run the mount util in foreground + (similarly to Linux) + +2006-04-21 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: fix fput deadlock fix, the lockless solution could lead + to "VFS: busy inodes after umount..." + + * kernel: fix race between checking and setting file->private_data + for the device. Found by Al Viro + +2006-04-11 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: remove request pool, instead allocate requests on + demand. Account the number of background requests, and if they go + over a limit, block the allocation of new requests. + + * kernel: fix deadlock if backgrounded request holds the last + reference to the super block + + * kernel: don't use fuse_reset_request() during direct I/O + +2006-04-06 Csaba Henk <csaba.henk@creo.hu> + + * lib: Let FreeBSD mount option parsing routine recognize "no" + prefixes for FUSE specific options as well + +2006-04-01 Miklos Szeredi <miklos@szeredi.hu> + + * lib: Add missing rwlock initialization. Patch by Ryan Bradetich + +2006-03-17 Miklos Szeredi <miklos@szeredi.hu> + + * API changes: + + * fuse_main(), fuse_setup() and fuse_new() have an additionl + user_data parameter + + * fuse_mount() returns a 'struct fuse_chan' pointer instead of a + file descriptor + + * fuse_unmount() receives a 'struct fuse_chan' pointer. It + destroys the given channel + + * fuse_teardown() no longer has a file descriptor parameter + + * new exported functions: fuse_session_remove_chan(), + fuse_get_session(), fuse_daemonize() + + * fuse_chan_recv() may now return a new channel which will be used + to send the reply + +2006-03-16 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.0-pre2 + +2006-03-16 Miklos Szeredi <miklos@szeredi.hu> + + * Don't unmount if already unmounted. This fixes a problem seen + in the following situation: Lazy unmount a busy filesystem; Mount + a new one in top; When the first finally unmounts, the second also + unmounts. Reported by Franco Broi + +2006-03-15 Miklos Szeredi <miklos@szeredi.hu> + + * lowlevel lib: use indirect function calls instead of a + switch/case construct. Besides increased efficiency it helps + maintainability & readability too. Patch from Florin Malita + +2006-03-13 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: replace global spinlock with a per-connection spinlock + +2006-03-10 Miklos Szeredi <miklos@szeredi.hu> + + * Fix source compatibility breakage for fuse_unmount(). Report + from Yura Pakhuchiy + +2006-03-02 Miklos Szeredi <miklos@szeredi.hu> + + * Fix O_ASYNC handling in fuse_dev_release(). From Jeff Dike + +2006-03-01 Miklos Szeredi <miklos@szeredi.hu> + + * Add O_ASYNC and O_NONBLOCK support to FUSE device. Patch by + Jeff Dike + + * Renamed fuse_chan_receive() to fuse_chan_recv() and changed + interface to return -errno in case of error. + +2006-03-01 Csaba Henk <csaba.henk@creo.hu> + + * libfuse: pass device file descriptor to fuse_unmount(), rewrite + FreeBSD implementation so that it uses libc (sysctl backed) instead + of an embdedded script (kmem backed). Adjust the control flow of + hello_ll so that device doesn't get closed before unmount attempt. + +2006-02-25 Miklos Szeredi <miklos@szeredi.hu> + + * Lowlevel lib: return all-zero statvfs data if filesystem doesn't + implement method. This is needed on FreeBSD, and nicer on Linux + too. Highlevel lib already did this. Reported by Csaba Henk + + * Fix negative entry handling. There was a bug, that negative + lookups with timeouts (nodeid == 0) returned -EIO. + +2006-02-23 Miklos Szeredi <miklos@szeredi.hu> + + * Fix race between RELEASE and UNLINK, which might leave + .fuse_hidden* files around + +2006-02-21 Miklos Szeredi <miklos@szeredi.hu> + + * fusexmp_fh: implement flush() method and call close() on the + open file descriptor. This is needed if used on an NFS + filesystem, which buffers data until file is closed. Franco Broi + spotted the situation when 'cp -p' failed to set the modification + time because of this. + +2006-02-20 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.6.0-pre1 + +2006-02-19 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: fix use-after-free bug in interruptred reply_entry(). + Patch from John Muir + + * libfuse: fix wrong symbol versioning for fuse_mount. Debian bug + ID: 352631. Found by Stéphane Rosi + +2006-02-17 Miklos Szeredi <miklos@szeredi.hu> + + * Lowlevel lib: Unify fuse_dirent_size() and fuse_add_dirent() + into a single function fuse_add_direntry(). This cleans up the + interface and makes it possible to do stacking. + +2006-02-16 Miklos Szeredi <miklos@szeredi.hu> + + * Fix rare race betweeen abort and release caused by failed iget() + in fuse_create_open(). + + * Add 'ac_attr_timeout' option e.g. for filesystems which do their + own attribute caching. + +2006-02-15 Miklos Szeredi <miklos@szeredi.hu> + + * Work around FreeBSD runtime linker "feature" which binds an old + version of a symbol to internal references if the symbol has more + than one version. This resulted in infinite recursion in + fuse_lowlevel_new_compat25(). + +2006-02-10 Csaba Henk <csaba.henk@creo.hu> + + * Refine clock_gettime() querying so that linker options + shall be set as it's appropriate for the target platform. + +2006-02-09 Miklos Szeredi <miklos@szeredi.hu> + + * Fix udev rule syntax. Reported by Nix + +2006-02-08 Miklos Szeredi <miklos@szeredi.hu> + + * In some cases udev rule seems to be ineffective when installed + as 40-fuse.rules but work as 60-fuse.rules. Reported by John Hunt + +2006-02-03 Miklos Szeredi <miklos@szeredi.hu> + + * Fix compilation when build directory is different from source + directory. Reported by Frédéric L. W. Meunier + +2006-02-02 Miklos Szeredi <miklos@szeredi.hu> + + * Fix even bigger bug introduced in fix for request_end() on + 2006-01-14. Reported by Gal Rosen + +2006-01-30 Miklos Szeredi <miklos@szeredi.hu> + + * highlevel-lib: add 'auto_cache' option. This caches file data + based on modification time and size + +2006-01-20 Miklos Szeredi <miklos@szeredi.hu> + + * Sanitize storage type and help message in mount_bsd.c. Patch + from Csaba Henk + + * fuse_opt: add new helper constants FUSE_OPT_KEY_KEEP and + FUSE_OPT_KEY_DISCARD + + * Add options 'max_readahead', 'sync_read' and 'async_read' + + * Kernel ABI version 7.6: + + * Negotiate the 'max_readahead' value and 'async_read' flags with + userspace in the INIT method + + * Add connection info to ->init() methods to both lowlevel and + highlevel API + + * Fall back to synchronous read() behavior if either library or + userspace filesystem is using the old interface version. This is + needed so non-updated filesystems won't be confused by the + different read() behavior + +2006-01-19 Miklos Szeredi <miklos@szeredi.hu> + + * lib: if "fsname=" option was given, pass it to fusermount + + * fuse_opt: add new fuse_opt_insert_arg() function, which is + needed by filesystems to implement some argument manipulations + correctly + + * fuse_opt: fix memory leak in handling "--" option + +2006-01-18 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: fix detection of case when fuse is not configured into + the kernel either as module or built-in + + * fuse_opt.h: fix incompatibility with C++ compilers by renaming + 'template' structure member to 'templ'. Reported by Takashi Iwai + + * fuse.h: fix compatibility bugs. Patch by Yura Pakhuchiy + + * kernel: support version 2.6.16 (i_sem -> i_mutex) + +2006-01-16 Miklos Szeredi <miklos@szeredi.hu> + + * Added (again) asynchronous readpages support + + * Each connection now shows up under /sys/fs/fuse/connections + + * Connection attributes exported to sysfs: 'waiting' number of + waiting requests; 'abort' abort the connection + + * Connection may be aborted through either the sysfs interface or + with 'umount -f mountpoint' + +2006-01-14 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.5.0 + +2006-01-14 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: fix a couple of bugs + + * Order of request_end() and fuse_copy_finish() was wrong. + Posthumous note: Franco Broi managed to exploit this, though it + seemed quite impossible + + * request_end() used request pointer after decrementing refcount + + * Clearing ->connected or ->mounted connection flags could race + with setting other bitfields not protected with a lock + +2006-01-10 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: add necessary compile flags for 2.4.X/x86_64. + Report from Sean Ziegeler + +2006-01-09 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.5.0-pre2 + +2006-01-09 Miklos Szeredi <miklos@szeredi.hu> + + * Applied patch from Csaba Henk, to update mount_bsd to new + fuse_mount() semantics + + * Ignore auto,noauto,... options in mount.fuse. Reported by Frank + Steiner and Don Taber + + * fusermount: add 'dirsync' mount option + +2006-01-07 Miklos Szeredi <miklos@szeredi.hu> + + * Improved help reporting and added version reporting to library + +2006-01-06 Miklos Szeredi <miklos@szeredi.hu> + + * Change working directory to "/" even if running in the + foreground. Patch from Jonathan Brandmeyer + + * Changed lots of functions to use 'struct fuse_args' instead of + separate argc and argv + + * Added fuse_parse_cmdline(), fuse_set_signal_handlers() and + fuse_remove_signal_handlers() functions, so that it's now pretty + easy to get all the functionality of fuse_main() with a filesystem + using the lowlevel API. + +2006-01-02 Miklos Szeredi <miklos@szeredi.hu> + + * mount.fuse: the 'user' option should be ignored. Report and + solution from Mattd. + + * mount.fuse: export PATH in the right place. Report and patch + from Hannes Schweizer + +2005-12-16 Miklos Szeredi <miklos@szeredi.hu> + + * Clean up the option parsing interface slightly, by creating an + "argument list" structure, that contains the argument vector and + count + +2005-12-15 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: check if /mnt/mtab is a symlink and don't modify it + in that case + + * kernel: simplify request size limiting. INIT only contains + maximum write size, maximum path component size remains fixed at + 1024 bytes, and maximum xattr size depends on read buffer. + +2005-12-14 Miklos Szeredi <miklos@szeredi.hu> + + * Fix readdir() failure on x86_64, of 32bit programs compiled + without largefile support. Bug report and help from Anthony + Kolasny + + * If lookup returns invalid mode, return -EIO instead of creating + a regular file + + * Add current output argument vector to option processing + function + +2005-12-12 Miklos Szeredi <miklos@szeredi.hu> + + * Fix stale code in ifdef FreeBSD. Patch from Csaba Henk + +2005-12-09 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.5.0-pre1 + +2005-12-09 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: added option parsing interface, defined in + <fuse_opt.h>. + +2005-12-07 Miklos Szeredi <miklos@szeredi.hu> + + * Return EIO for file operations (read, write, fsync, flush) on + open files whose inode has become "bad". Inodes will be marked + "bad" if their type changes. Bug report by Csaba Henk + +2005-12-06 Miklos Szeredi <miklos@szeredi.hu> + + * Use bigger request buffer size. write() did not work on archs + with > 4k page size, Bug report by Mark Haney + + * ABI version 7.5: + + * Extend INIT reply with data size limits + +2005-12-02 Miklos Szeredi <miklos@szeredi.hu> + + * Fix memory leak in fuse_read_cmd()/fuse_process_cmd(). Bug + reported by Vincenzo Ciancia + + * Handle exit-by-umount in fuse_read_cmd() + +2005-11-29 Miklos Szeredi <miklos@szeredi.hu> + + * Check if '-msoft-float' option is supported by compiler when + configuring for a 2.4.x kernel. Bug report by Mark Haney + + * In multithreaded loop send a TERM signal to the main thread if + one of the other threads exit. Needed on FreeBSD for a clean exit + on umount. Should not cause any harm on Linux either + +2005-11-28 Miklos Szeredi <miklos@szeredi.hu> + + * Fix bug in 32-bit file handle compatibility + +2005-11-27 Miklos Szeredi <miklos@szeredi.hu> + + * Block TERM, INT, HUP and QUIT signals in all but the main + thread. According to POSIX it's not specified which thread will + receive these signals. + + * Kernel changes: + + * Check for directory aliasing on mkdir, not just on lookup + + * Check for special node ID values in create+open operation + + * Sync with -mm: readv, writev, aio_read and aio_write methods + added to file operations + + * Cleanups: lookup code, page offset calculation + + * ABI stepped to 7.4, changes: + + * frsize member added to fuse_kstatfs structure + + * added support for negative entry caching: on lowlevel API if + fuse_entry_param::ino is set to zero in reply to a lookup request, + the kernel will cache the dentry for the specified amount of time. + + * libfuse: added 'negative_timeout' option: specifies how much + negative entries should be cached. Default is zero, to be + compatible with prior versions + +2005-11-22 Miklos Szeredi <miklos@szeredi.hu> + + * Add detection of mainline FUSE code in running kernel + +2005-11-21 Miklos Szeredi <miklos@szeredi.hu> + + * Don't use async cancelation in multithreaded loop. This makes + it more portable to systems where read() is not async cancel safe. + Report from Andriy Gapon + +2005-11-20 Miklos Szeredi <miklos@szeredi.hu> + + * Warn if API version 11 compatibility is requested + +2005-11-17 Miklos Szeredi <miklos@szeredi.hu> + + * More FreeBSD merge + + * fusermount: don't allow mountpoints with '\n', '\t', or '\\' in + them, because it corrupts /etc/mtab. Found by Thomas Biege + CVE-2005-3531 + + * libfuse: don't use system() to invoke 'fusermount -u ...' + because it breaks mountpoints with spaces in them into multiple + arguments + +2005-11-16 Miklos Szeredi <miklos@szeredi.hu> + + * Merge library part of FreeBSD port. Patch by Csaba Henk + +2005-11-11 Miklos Szeredi <miklos@szeredi.hu> + + * Use 64bit type for file handle, so the full range supported by + the kernel interface is available to applications + +2005-11-10 Miklos Szeredi <miklos@szeredi.hu> + + * Moved mountpoint argument checking from fuse_parse_cmdline() to + fuse_mount() in preparation to FreeBSD merge. + +2005-11-08 Miklos Szeredi <miklos@szeredi.hu> + + * Remove unneeded close() from fuse_teardown(). Spotted by Csaba + Henk. + +2005-11-07 Miklos Szeredi <miklos@szeredi.hu> + + * Make the statfs change backwards compatible. + +2005-11-06 Miklos Szeredi <miklos@szeredi.hu> + + * Change ->statfs() method to use 'struct statvfs' instead of + 'struct statfs'. This makes the API more portable since statvfs() + is defined by POSIX. + +2005-10-28 Miklos Szeredi <miklos@szeredi.hu> + + * Add fgetattr() method, which currently will only be called after + a successful call to a create() method. + +2005-10-26 Miklos Szeredi <miklos@szeredi.hu> + + * Change kernel ABI version to 7.3 + + * Add ACCESS operation. This is called from the access() system + call if 'default_permissions' mount option is not given, and is + not called on kernels 2.4.* + + * Add atomic CREATE+OPEN operation. This will only work with + 2.6.15 (presumably) or later Linux kernels. + + * Add ftruncate() method. This will only work with 2.6.15 + (presumably) or later Linux kernels. + + * Fix kernel module compile if kernel source and build directories + differ. Report and initial patch by John Eastman + +2005-10-18 Miklos Szeredi <miklos@szeredi.hu> + + * lib: optimize buffer reallocation in fill_dir. + +2005-10-17 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.4.1 + +2005-10-14 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: add debug for write result (by Shaun Jackman) and + warnings for too large read/write result + +2005-10-11 Miklos Szeredi <miklos@szeredi.hu> + + * Spelling fixes, thanks to Ioannis Barkas + +2005-10-10 Miklos Szeredi <miklos@szeredi.hu> + + * fuse_common.h: use extern "C". Thanks to Valient Gough for the + patch + +2005-10-07 Miklos Szeredi <miklos@szeredi.hu> + + * highlevel-lib: init() and destroy() methods didn't have an + initialized fuse_context. Bug reported by Tim Stoakes + +2005-10-04 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.4.0 + +2005-10-03 Miklos Szeredi <miklos@szeredi.hu> + + * Add documentation to fuse_lowlevel.h + + * API cleanups: + + * Remove definitions of unused FATTR_CTIME / FUSE_SET_ATTR_CTIME + + * Move fuse_mount() and fuse_unmount() to fuse_common.h + + * Change the return type of fuse_reply_none() from int to void. + +2005-09-30 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: NFS exporting leaked dentries. Bug found and fixed by + Akshat Aranya. + +2005-09-29 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: fix error message, when unable to open /dev/fuse. + Report by Balázs Pozsár + +2005-09-28 Miklos Szeredi <miklos@szeredi.hu> + + * UClibc fixes from Christian Magnusson + +2005-09-27 Miklos Szeredi <miklos@szeredi.hu> + + * Added NAME="%k" to util/udev.rules. Fix by Mattias Wadman. + +2005-09-26 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.4.0-rc1 + +2005-09-26 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: allow user umount in the case when /etc/mtab is a + symlink to /proc/mounts. Reported by Balázs Pozsár. + +2005-09-23 Miklos Szeredi <miklos@szeredi.hu> + + * Check for special node ID values in lookup and creation + +2005-09-22 Miklos Szeredi <miklos@szeredi.hu> + + * Slight optimization in returning EINVAL error in case of an open + with O_DIRECT flag. + +2005-09-20 Miklos Szeredi <miklos@szeredi.hu> + + * Remove '--enable-auto-modprobe' configure flag. Module + auto-loading is now handled by the kernel. + +2005-09-15 Miklos Szeredi <miklos@szeredi.hu> + + * Install UDEV rule file, so /dev/fuse is created with mode 0666. + Help from Jens M. Noedler. + +2005-09-14 Miklos Szeredi <miklos@szeredi.hu> + + * Add memory cleanup on thread exit + +2005-09-13 Miklos Szeredi <miklos@szeredi.hu> + + * Set umask to zero in fusexmp and fusexmp_fh, so that + files/directories are created with the requested mode. + +2005-09-12 Miklos Szeredi <miklos@szeredi.hu> + + * Don't ignore read error in multithreaded loop + +2005-09-08 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.4.0-pre2 + +2005-09-08 Miklos Szeredi <miklos@szeredi.hu> + + * Revert lock and access operations. Postpone these until 2.5. + +2005-09-02 Miklos Szeredi <miklos@szeredi.hu> + + * Fix compile warning on 2.6.13 and later + + * Fix compilation on old kernels + +2005-08-19 Miklos Szeredi <miklos@szeredi.hu> + + * lib: always refresh directory contents after rewinddir() to + conform to SUS. Bug found by John Muir. + +2005-08-15 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.4.0-pre1 + +2005-08-14 Miklos Szeredi <miklos@szeredi.hu> + + * lib: cleaned up (or messed up, depending on your POV) the low + level library API. Hopefully this is close to the final form. + +2005-08-05 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: don't allow empty mountpoint argument, which defeats + automatic umounting in fuse_main(). Bugreport by Václav Jůza + +2005-08-03 Miklos Szeredi <miklos@szeredi.hu> + + * fix warnings in fuse.h and fuse_lowlevel.h if -Wshadow compiler + option is used (Paul Alfille). + +2005-08-02 Miklos Szeredi <miklos@szeredi.hu> + + * highlevel-lib: added mount options "attr_timeout" and + "entry_timeout". These options control the length of time file + attributes and entries (names) are cached. Both default to 1.0 + second. + + * kernel: correctly handle zero timeout for attributes and entries + +2005-08-01 Miklos Szeredi <miklos@szeredi.hu> + + * Added missing symbols to versionscript (Joshua J. Berry) + + * kernel: implement two flags, open can set: 'direct_io' and + 'keep_cache'. These correspond exactly to mount options + 'direct_io' and 'kernel_cache', but allow a per-open setting. + + * Move 'direct_io' and 'kernel_cache' mount option handling to + userspace. For both mount options, if the option is given, then + the respective open flag is set, otherwise the open flag is left + unmodified (so the filesystem can set it). + + * lib (highlevel): make open method optional + +2005-07-28 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: invalidate attributes for read/readdir/readlink + operations + + * kernel: detect newer UML kernels + +2005-07-26 Miklos Szeredi <miklos@szeredi.hu> + + * Make the installation path of fuse.ko and mount.fuse + configurable through INSTALL_MOD_PATH and MOUNT_FUSE_PATH + environment variables. Requirement and help from Csaba Henk. + +2005-07-22 Miklos Szeredi <miklos@szeredi.hu> + + * Fix bug, that causes filesystem requests to hang when unique + request counter becomes negative. This happens after + 2,147,483,648 operations, so most people won't care. Thanks to + Franco Broi for the report and testing. + +2005-07-21 Miklos Szeredi <miklos@szeredi.hu> + + * Don't change mtime/ctime/atime to local time on read/write. + Bug reported by Ben Grimm + + * Install fuse_common.h and fuse_lowlevel.h. Report by Christian + Magnusson + + * fusermount: use getopt_long() for option parsing. It allows the + use of '--' to stop argument scanning, so fusermount can now + operate on directories whose names begin with a '-'. Patch by + Adam Connell + +2005-07-15 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: add '-v', '--version' and '--help' options + + * add inode based API + +2005-07-12 Miklos Szeredi <miklos@szeredi.hu> + + * lib: don't block signals in worker threads. Problem noticed by + Usarin Heininga + +2005-07-07 Miklos Szeredi <miklos@szeredi.hu> + + * lib: don't allow both 'allow_other' and 'allow_root' options to + be given + +2005-07-06 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: check if mountpoint is empty (only '.' and '..' for + directories, and size = 0 for regular files). If "nonempty" + option is given, omit this check. This is useful, so users don't + accidentally hide data (e.g. from backup programs). Thanks to + Frank van Maarseveen for pointing this out. + + * kernel: check if mandatory mount options ('fd', 'rootmode', + 'user_id', 'group_id') are all given + + * lib: simplify 'readdir_ino' handling + + * lib: add mount options 'umask=M', 'uid=N', 'gid=N' + +2005-07-03 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: clean up 'direct_io' code + +2005-06-28 Miklos Szeredi <miklos@szeredi.hu> + + * Add 'mount.fuse' written by Petr Klima + + * '/dev/fuse' is created by 'make install' if does not yet exist + +2005-06-20 Miklos Szeredi <miklos@szeredi.hu> + + * Fix UCLIBC compile error. Patch by Christian Magnusson + +2005-06-08 Miklos Szeredi <miklos@szeredi.hu> + + * Enable the auto-loading of the module via access to the + corresponding device file. Patch by Takashi Iwai. + + * Allow mounting a regular file (over a regular file) for + unprivleged users. + + * Do not create temporary device file. Require "/dev/fuse" to + exist, and be readable/writable by the mounting user. + +2005-06-02 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3.0 + +2005-06-02 Miklos Szeredi <miklos@szeredi.hu> + + * Fix serious information leak: if the filesystem returns a short + byte count to a read request, and there are non-zero number of + pages which are not filled at all, these pages will not be zeroed. + Hence the user can read out previous memory contents. Found by + Sven Tantau. + +2005-05-27 Miklos Szeredi <miklos@szeredi.hu> + + * Add "readdir_ino" mount option, which tries to fill in the d_ino + field in struct dirent. This mount option is ignored if "use_ino" + is used. It helps some programs (e.g. 'pwd' used over NFS from a + non-Linux OS). Patch by David Shaw. + +2005-05-12 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3-rc1 + +2005-05-12 Miklos Szeredi <miklos@szeredi.hu> + + * File save in krusader and other editors doesn't work with sshfs, + because open() is interrupted by a periodic signal, and open() + restarts forever, without any progress. This could just be fixed + in open(), but the problem is more generic: if signals are + received more often than the filesystem can get the request to + userspace, it will never finish. This is probably only a + theoretical problem, nevertheless I'm removing the possibility to + interrupt requests with anything other than SIGKILL, even before + being sent to userspace. Bugreport by Eduard Czimbalmos. + +2005-05-09 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: add "tree_lock" rwlock, that is locked for write in + rename, unlink and rmdir, and locked for read in all other + operations. This should fix the rename/release race reported by + Valient Gough and others. The solution is very coarse, a finer + grained locking scheme could be implemented, but it would be much + more complex. Let's see whether this is good enough. + +2005-05-09 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3-pre7 + +2005-05-08 Miklos Szeredi <miklos@szeredi.hu> + + * Better fix for out of order FORGET messages. Now the + LOOKUP/FORGET messages are balanced exactly (one FORGET can + balance many lookups), so the order no longer matters. This + changes the kernel ABI slightly, but the library remains backward + compatible. + +2005-05-06 Miklos Szeredi <miklos@szeredi.hu> + + * Fix abort for out of order FORGET messages. Again. Spotted by + Franco Broi again. Sorry :) + +2005-04-29 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3-pre6 + +2005-04-29 Miklos Szeredi <miklos@szeredi.hu> + + * Make fusermount work with fuse kernel modules not yet supporting + the "group_id" option (added for the purpose of stricter + permission checking). + +2005-04-28 Miklos Szeredi <miklos@szeredi.hu> + + * Check for hard-linked directories in lookup. This could cause + problems in the VFS, which assumes that such objects never exist. + + * Make checking of permission for other users more strict. Now + the same privilege is required for the mount owner as for ptrace + on the process performing the filesystem operation. + +2005-04-23 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3-pre5 + +2005-04-22 Miklos Szeredi <miklos@szeredi.hu> + + * Add -msoft-float to kernel module compile flags for 2.4.X. This + is needed on certain architectures. Report from Chris Kirby + + * Fix buggy behavior of open(..., O_CREAT|O_EXCL) if interrupted. + Reported by David Shaw + + * Remove "allow_root" option from kernel module, and implement + it's functionality in the library + + * Fix Oops caused by premature release of fuse_conn. Clean up + related code, to be more readable + + * Sendfile should not use page cache if "direct_io" mount option + is given + +2005-04-08 Miklos Szeredi <miklos@szeredi.hu> + + * Fix Oops in case of nfs export. Spotted by David Shaw + + * Fix another Oops in case of write over nfs with direct_io turned + on. Again spotted by David Shaw + +2005-04-07 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3-pre4 + +2005-04-07 Miklos Szeredi <miklos@szeredi.hu> + + * lib: finalized new readdir() interface, which now supersedes the + getdir() method. + +2005-04-03 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3-pre3 + +2005-04-03 Miklos Szeredi <miklos@szeredi.hu> + + * Implement backward compatibility with version 5 kernel ABI + +2005-04-01 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3-pre2 + +2005-04-01 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: fix dirent offset handling + + * lib: add readdir and releasedir methods + + * lib: use fh field of fuse_file_info in opendir, readdir, + releasedir and fsyncdir methods + + * lib: check kernel API version and bail out of it's old. This + will be properly fixed in the next release + +2005-03-31 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.3-pre1 + +2005-03-31 Miklos Szeredi <miklos@szeredi.hu> + + * kernel API: add padding to structures, so 64bit and 32bit + compiler will return the same size + + * kernel API: add offset field to fuse_dirent. This will allow + more sophisticated readdir interface for userspace + + * kernel API: change major number to 6 + + * kernel: fix warnings on 64bit archs + + * kernel: in case of API version mismatch, return ECONNREFUSED + +2005-03-24 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: trivial cleanups + +2005-03-21 Miklos Szeredi <miklos@szeredi.hu> + + * Add fsyncdir() operation + +2005-03-19 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: add locking to background list (fixes previous fix) + +2005-03-18 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: fix bug which could cause leave busy inodes after + unmount, and Oops. + +2005-03-08 Miklos Szeredi <miklos@szeredi.hu> + + * examples: add -lpthread to link flags to work around valgrind + quirk + + * lib: don't exit threads, so cancelation doesn't cause segfault + +2005-03-04 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: fix nasty bug which could cause an Oops under certain + situations. Found by Magnus Johansson + +2005-02-28 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: added opendir() method. This can be used in case + permission checking in getdir() is too late. Thanks to Usarin + Heininga for pointing out this deficiency + + * libfuse: added init() and destroy() methods to fuse_operations + + * kernel: llseek() method for files and directories made explicit + + * kernel: fixed inode leak in NFS export in case of nodeid + wrapping + +2005-02-15 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: clean up some unitialized memory found with valgrind + + * Add -lpthread to Libs in fuse.pc. Valgrind seems to need an + explicitly linked libpthread for applications + +2005-02-10 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: set umask, otherwise /etc/mtab will have + unpredictable permission. Spotted by Jindrich Kolorenc + + * fusermount: set owner and group of /etc/mtab to original values + on unmount + + * libfuse: add 'use_ino' option to help. Patch by Valient Gough + +2005-02-07 Miklos Szeredi <miklos@szeredi.hu> + + * Cleaned up directory reading (temporary file is not used) + +2005-02-02 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.2 + +2005-02-02 Miklos Szeredi <miklos@szeredi.hu> + + * Fix possible race when operation is interrupted + +2005-01-28 Miklos Szeredi <miklos@szeredi.hu> + + * Fix compilation on 2.6.7 + +2005-01-26 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.2-pre6 + +2005-01-26 Miklos Szeredi <miklos@szeredi.hu> + + * Fix bug in link() operation which caused the wrong path to be + passed as the first argument. Found by Anton Altaparmakov + +2005-01-21 Miklos Szeredi <miklos@szeredi.hu> + + * LIB: fix double reply in readdir operation + + * fusermount: fix uid checking bug. Patch by Adam Connell + + * KERNEL: fix compile on various RedHat patched 2.4 kernels. + Patch by Keshava Gowda + +2005-01-20 Miklos Szeredi <miklos@szeredi.hu> + + * KERNEL: provide correct llseek semantics for fuse device (fixes + a bug on Progeny 2.4.20 kernel). Reported by Valient Gough + +2005-01-20 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.2-pre5 (matches kernel 2.6.11-rc1-mm2) + +2005-01-18 Miklos Szeredi <miklos@szeredi.hu> + + * KERNEL ABI: remove GETDIR operation, and add OPENDIR, READDIR + and RELEASEDIR. This ends the ugly hack of passing a file + descriptor to the kernel, and actually makes the code simpler. + +2005-01-17 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.2-pre4 + +2005-01-17 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: remove capability setting, which was the cause of + problems for some users. It seems that FS related capabilities + are removed by setfsuid(), so this isn't even needed. + +2005-01-15 Miklos Szeredi <miklos@szeredi.hu> + + * fix compilation on 2.4 kernels (reported by Valient Gough) + + * fix failure to unmount bug (found by David Shaw) + + * fusermount: improve parsing of /etc/fuse.conf + +2005-01-13 Miklos Szeredi <miklos@szeredi.hu> + + * Remove 'mount_max' and 'user_allow_other' module options. These + are now checked by fusermount, and can be set in /etc/fuse.conf + + * KERNEL: change check for fsid == 0 to capable(CAP_DAC_OVERRIDE) + +2005-01-11 Miklos Szeredi <miklos@szeredi.hu> + + * KERNEL: fix possible inode allocation problem, where + sizeof(struct inode) is not aligned (found by Mike Waychison) + + * KERNEL: use new follow_link/put_link methods + + * KERNEL: cosmetic fixes + +2005-01-10 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.2-pre3 + +2005-01-10 Miklos Szeredi <miklos@szeredi.hu> + + * Add missing code that was accidently left out + +2005-01-09 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.2-pre2 + +2005-01-09 Miklos Szeredi <miklos@szeredi.hu> + + * Change "uid" mount option to "user_id" to avoid confusion with a + mount option "uid" commonly used by many filesystems + +2005-01-09 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.2-pre1 + +2005-01-09 Miklos Szeredi <miklos@szeredi.hu> + + * If FUSE is configured in the kernel, don't build it by default + +2005-01-07 Miklos Szeredi <miklos@szeredi.hu> + + * Compile fix by Christian Magnusson + +2005-01-05 Miklos Szeredi <miklos@szeredi.hu> + + * Fix compilation for 2.6.{0-5} kernels + +2005-01-04 Miklos Szeredi <miklos@szeredi.hu> + + * KERNEL: if request is interrupted, still keep reference to used + inode(s) and file, so that FORGET and RELEASE are not sent until + userspace finishes the request. + + * remove /{sys,proc}/fs/fuse/version, and instead add an INIT + request with the same information, which is more flexible, + simpler, works on embedded systems. + +2004-12-16 Miklos Szeredi <miklos@szeredi.hu> + + * KERNEL ABI: update interface to make it independent of type + sizes. This will help on 64 bit architectures which can run + legacy 32 bit applications. + + * KERNEL ABI: add "len" field to request headers. This will allow + sending/receiving requests in multiple chunks. + + * KERNEL: handle file type change more intelligently + + * LIB: "-o debug" option should disable backgrounding (fix by + Fabien Reygrobellet) + +2004-12-13 Miklos Szeredi <miklos@szeredi.hu> + + * KERNEL: invalidate dentry/attributes if interrupted request + could leave filesystem in an unknown state. + +2004-12-12 Miklos Szeredi <miklos@szeredi.hu> + + * KERNEL: lots of cleanups related to avoiding possible deadlocks. + These will cause some regressions, but stability is considered + more important. If any of these features turns out to be + important, it can be readded with the deadlock problems addressed. + + * Make all requests interruptible (only with SIGKILL currently). + This can be used to break any deadlock produced by the userspace + filesystem accessing it's own exported files. The RELEASE request + is special, because if it's interrupted before sending it to + userspace it is still sent, but the reply is not awaited. + + * If request is interrupted before being sent to userspace, and if + it hasn't yet got any side effects, it is always restarted, + regardless of the SA_RESTART flag. This makes these interruptions + transparent to the process. + + * Remove shared-writable mmap support, which was prone to an + out-of-memory deadlock situation + + * Remove INVALIDATE userspace initiated request + + * Make readpages() synchronous. Asynchronous requests are + deadlock prone, since they cannot be interrupted. + + * Add readv/writev support to fuse device operations + + * Remove some printks, which userspace FS can use for a DoS + against syslog + + * Remove 'large_read' mount option from 2.6 in kernel, check it in + fusermount instead + + * LIB: improve compatibility with a fuse.h header installed in + ${prefix}/include which in turn includes the real header. + + * LIB: improve compatibility by defining fuse_main() (which is now + not used), so old configure scripts find it. + +2004-12-10 Miklos Szeredi <miklos@szeredi.hu> + + * When mounting on a subdirectory of / don't duplicate slashes at + the beggining of path (spotted by David Shaw) + +2004-12-09 Miklos Szeredi <miklos@szeredi.hu> + + * Fix bug causing garbage in mount options (spotted by David Shaw) + +2004-12-07 Miklos Szeredi <miklos@szeredi.hu> + + * Add 'writepage' flag to 'fuse_file_info'. + + * More comments in fuse.h + + * Get rid of double underscores + +2004-12-04 Miklos Szeredi <miklos@szeredi.hu> + + * Add -D_FILE_OFFSET_BITS=64 to cflags provided by pkg-config + + * helper.c: add -ho option, which only displays the options not + the usage header. This can be used by filesystems which have + their own options. + +2004-12-03 Miklos Szeredi <miklos@szeredi.hu> + + * Add source compatibility to 2.1 and 1.1 APIs. To select betwen + versions simply define FUSE_USE_VERSION to 22, 21 or 11 before + including the fuse header + + * Add binary compatibility to 2.1 version of library with symbol + versioning + +2004-12-03 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.1 + +2004-12-01 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: clean up writing functions + + * kernel: no allocation on write in direct_io mode + + * move linux/fuse.h to fuse_kernel.h + +2004-11-30 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: clean up reading functions + +2004-11-29 Miklos Szeredi <miklos@szeredi.hu> + + * kernel: make readpage() uninterruptible + + * kernel: check readonly filesystem flag in fuse_permission + + * lib: don't die if version file not found and new style device + exists + + * lib: add '-r' option, which is short for '-o ro' + + * fusermount: simplify device opening + + * kernel: when direct_io is turend on, copy data directly to + destination without itermediate buffer. More efficient and safer, + since no allocation is done. + + * fusermount: fix warning if fuse module is not loaded + + * kernel: use /dev/fuse on 2.4 too + +2004-11-26 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse API change: open, read, write, flush, fsync and release + are passed a 'struct fuse_file_info' pointer containing the open + flags (open and release), and the file handle. Verion changed to + 3.0. + +2004-11-23 Miklos Szeredi <miklos@szeredi.hu> + + * More cleanups in the kernel + + * The 10,229 charater device number has been assigned for FUSE + + * Version file checking fix (reported by Christian Magnusson) + + * fusermount: opening the fuse device now doesn't need /sys. + + * Optimize reading by controlling the maximum readahead based on + the 'max_read' mount option + + * fixes for UCLIBC (Christian Magnusson) + +2004-11-19 Miklos Szeredi <miklos@szeredi.hu> + + * Cleaned up kernel in preparation for merge into mainline: + + * Use /sys/fs/fuse/version instead of /proc/fs/fuse/version + + * Use real device (/dev/fuse) instead of /proc/fs/fuse/dev + + * __user annotations for sparse + + * allocate individual pages instead of kmalloc in fuse_readdir, + fuse_read and fuse_write. + + * Fix NFS export in case "use_ino" mount option is given + + * Make libfuse and fusermount compatible with future versions + + * fusermount: properly add mount options to /etc/mtab + +2004-11-15 Miklos Szeredi <miklos@szeredi.hu> + + * fusermount: do not resolve last component of mountpoint on if it + is '.' or '..'. This new path resolvation is now done on mount as + well as unmount. This enables relative paths to work on unmount. + + * fusermount: parse common mount options like "ro", "rw", etc... + + * Allow module params to be changed through sysfs + +2004-11-14 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.1-pre1 + +2004-11-14 Miklos Szeredi <miklos@szeredi.hu> + + * Fix bug in fuse_readpages() causing Oops in certain situations. + Bug found by Vincenzo Ciancia. + + * Fix compilation with kernels versions > 2.6.9. + +2004-11-11 Miklos Szeredi <miklos@szeredi.hu> + + * Check kernel interface version in fusermount to prevent + strangeness in case of mismatch. + + * No need to allocate fuse_conn until actual mount happens + + * Fix potential race between umount and fuse_invalidate + + * Check superblock of proc file in addition to inode number + + * Fix race between request_send_noreply() and fuse_dev_release() + +2004-11-10 Miklos Szeredi <miklos@szeredi.hu> + + * Separate configure for the kernel directory + + * Don't allow write to return more than 'count' + + * Extend kernel interface for future use + +2004-11-09 Miklos Szeredi <miklos@szeredi.hu> + + * Fix 'makeconf.sh' to use autoreconf if available + +2004-11-08 Miklos Szeredi <miklos@szeredi.hu> + + * Add ino argument to 'fuse_dirfil_t'. NOTE: This breaks source + compatibility with earlier versions. To compile earier versions + just add '-DFUSE_DIRFIL_COMPAT' compile flag or fix the source. + Do not use the "use_ino" mount flag with filesystems compiled with + FUSE_DIRFIL_COMPAT. + + * Add pkg-config support. To compile a FUSE based filesystem you + can do "gcc -Wall `pkg-config --cflags --libs fuse` myfs.c -o myfs" + or similar. Note, that the PKG_CONFIG_PATH environment variable + usually needs to be set to "/usr/local/lib/pkgconfig". + + * fuse.h is now installed in ${prefix}/include/fuse/ + +2004-11-02 Miklos Szeredi <miklos@szeredi.hu> + + * Added "use_ino" mount option. This enables the filesystems to + set the st_ino field on files + +2004-11-01 Miklos Szeredi <miklos@szeredi.hu> + + * Fix compile problems with ancient (<=2.4.18) kernels (reported + by Jeremy Smith) + + * Add "allow_root" mount option. Patch by Yaroslav Rastrigin + + * Clear the 'exited' flag when mail loop is finished + +2004-10-28 Miklos Szeredi <miklos@szeredi.hu> + + * Make xattr functions work under 2.6 (bug found by Vincenzo + Ciancia) + +2004-10-26 Miklos Szeredi <miklos@szeredi.hu> + + * Reset request in fuse_flush() (bugreport by David Shaw) + +2004-10-21 Miklos Szeredi <miklos@szeredi.hu> + + * fuse_main() now does not exit on error, rather it returns an + error code + + * Exported __fuse_setup() and __fuse_teardown() functions, which + make it easier to implement a custom event loop. + + * Use daemon() call to background the filesystem after mounting. + This function closes the standard input, output and error and + changes the current working directory to "/". + +2004-10-14 Miklos Szeredi <miklos@szeredi.hu> + + * Released 1.9 + +2004-10-09 Miklos Szeredi <miklos@szeredi.hu> + + * Don't allow fuse_flush() to be interrupted (bug found by David + Shaw) + +2004-09-27 Miklos Szeredi <miklos@szeredi.hu> + + * Add PID to fuse_context. Patch by Steven James + + * Change file handle type to 'unsigned long' in kernel interface + +2004-09-22 Miklos Szeredi <miklos@szeredi.hu> + + * A slight API change: fuse_get_context() doesn't need the "fuse" + pointer, but the returned context contains it instead. The + fuse_get() function is not needed anymore, so it's removed. + + * Fix mounting and umounting FUSE filesystem under another FUSE + filesystem by non-root (bug spotted by Valient Gough) + +2004-09-21 Miklos Szeredi <miklos@szeredi.hu> + + * Fix deadlock in case of memory allocation failure. Patch by + Christian Magnusson + +2004-09-16 Miklos Szeredi <miklos@szeredi.hu> + + * Check memory allocation failures in libfuse + +2004-09-14 Miklos Szeredi <miklos@szeredi.hu> + + * Check temporary file creation failure in do_getdir(). Bug + spotted by Terje Oseberg + +2004-09-13 Miklos Szeredi <miklos@szeredi.hu> + + * Allow "large_read" option for 2.6 kernels but warn of deprecation + + * Make requests non-interruptible so race with FORGET is avoided. + This is only a temporary solution + + * Support compiling FUSE kernel module on 2.4.x UML kernels + +2004-09-09 Miklos Szeredi <miklos@szeredi.hu> + + * Fix bug in case two FORGETs for the same node are executed in + the wrong order. Bug spotted and endured for months by Franco + Broi, and logfile for solution provided by Terje Oseberg + +2004-09-01 Miklos Szeredi <miklos@szeredi.hu> + + * Add -D_REENTRANT to the compile flags + + * Add documentation of fuse internals by Terje Oseberg + +2004-08-16 Miklos Szeredi <miklos@szeredi.hu> + + * Change release method to be non-interruptible. Fixes bug + causing missing release() call when program which has opened files + is killed (reported by Franco Broi and David Shaw) + +2004-07-29 Miklos Szeredi <miklos@szeredi.hu> + + * Add fuse_invalidate() to library API + +2004-07-26 Miklos Szeredi <miklos@szeredi.hu> + + * Check permissions in setattr if 'default_permissions' flag is + set. Bug spotted by Damjan Lango + +2004-07-24 Miklos Szeredi <miklos@szeredi.hu> + + * 'large_read' mount option removed for 2.6 kernels, since the + default (dynamic read size) is better + + * Extend kernel API with file handles. A file handle is returned + by open, and passed to read, write, flush, fsync and release. + This is currently only used for debug output in the library. + + * Security changes: + + * Change the current directory to the mountpoint before checking + the permissions and mount filesystem on "." + + * By default don't modprobe the fuse module for non-root. The old + behavior can be restored with the '--enable-auto-modprobe' flag of + ./configure + + * By default don't allow shared writable mappings for non-root. + The old behavior can be restored with the 'user_mmap=1' module + parameter + +2004-07-23 Miklos Szeredi <miklos@szeredi.hu> + + * Clean up mount option passing to fusermount and to fuse_new() + BEWARE: this changes the userspace API slightly, and the command + line usage of programs using fuse_main() + +2004-07-20 Miklos Szeredi <miklos@szeredi.hu> + + * Optimize reading under 2.6 kernels by issuing multiple page + asynchronous read requests + +2004-07-18 Miklos Szeredi <miklos@szeredi.hu> + + * Only use redirty_page_for_writepage() for kernels >= 2.6.6 + +2004-07-16 Miklos Szeredi <miklos@szeredi.hu> + + * Separate directory entry and inode attribute validity timer + + * New write semaphore to stop page writeback during truncate + + * Fsync now waits for all writes to complete before sending the + request + + * Optimization: if a page is completely written by + fuse_commit_write(), clear the dirty flag and set the uptodate + flag for that page + + * Some memory cleanup at exit + +2004-07-13 Miklos Szeredi <miklos@szeredi.hu> + + * Add FUSE_HARD_REMOVE flag, and '-i' option to fuse main, which + disable the "hide if open" behavior of unlink/rename. + + * If temporary buffer allocation fails in raw read, fall back to a + smaller buffer + +2004-07-12 Miklos Szeredi <miklos@szeredi.hu> + + * Fix bug in do_open() in libfuse: open count was incremented + after the reply is sent so it could race with unlink/forget and + cause an abort. + +2004-07-08 Miklos Szeredi <miklos@szeredi.hu> + + * When performing create or remove operation, refresh the parent's + attributes on next revalidate, as i_nlink (and maybe size/time) + could be inacurate. + + * Use redirty_page_for_writepage() in fuse_writepage() for skipped + pages (2.6 only) + + * Set set_page_dirty address space operation (2.6 only) + +2004-07-06 Miklos Szeredi <miklos@szeredi.hu> + + * Minor fix in read: print debug info even if read size is zero + +2004-07-04 Miklos Szeredi <miklos@szeredi.hu> + + * Fix race between truncate and writepage (fsx-linux now runs + without error) + +2004-07-02 Miklos Szeredi <miklos@szeredi.hu> + + * Fix kernel hang on mkfifo under 2.4 kernels (spotted and patch + by Mattias Wadman) + + * Added option for direct read/write (-r) + + * Fix revalidate time setting for newly created inodes + + * Remove uid==0 check for '-x' option in fusermount (kernel checks + this) + + * fuse_main() only installs handlers for signals (out of INT, HUP, + TERM, PIPE), for which no handler has yet been installed + + * Add module option 'user_allow_other' which if set to non-zero + will allow non root user to specify the 'allow_other' mount option + ('-x' option of fusermount) + + * Fix deadlock between page writeback completion and truncate + (bug found by Valient Gough with the fsx-linux utility) + +2004-07-01 Miklos Szeredi <miklos@szeredi.hu> + + * Change passing fuse include dir to 2.6 kernel make system more + robust (fixes compile problems seen on SuSE 9.1 with updated 2.6 + kernel) + +2004-06-30 Miklos Szeredi <miklos@szeredi.hu> + + * Acquire inode->i_sem before open and release methods to prevent + concurrent rename or unlink operations. + + * Make __fuse_read_cmd() read only one command. This allows + multiplexing the fuse file descriptor with other event sources + using select() or poll() (patch by Jeff Harris) + + * Export 'exited' flag with __fuse_exited() (patch by Jeff Harris) + +2004-06-27 Miklos Szeredi <miklos@szeredi.hu> + + * Fix file offset wrap around at 4G when doing large reads + +2004-06-24 Miklos Szeredi <miklos@szeredi.hu> + + * Fix memory leak in open (Valient Gough) + +2004-06-24 Miklos Szeredi <miklos@szeredi.hu> + + * Add "close after delete" support to libfuse (patch by Valient + Gough) + + * Cancel all worker threads before exit in multithreaded mode + +2004-06-23 Miklos Szeredi <miklos@szeredi.hu> + + * Fix locking bugs + + * Don't send reply to RELEASE + + * Work with newer libtool (1.5a) + + * Check for st_atim member of struct stat + +2004-06-22 Miklos Szeredi <miklos@szeredi.hu> + + * No request allocation needed on inode and file release + +2004-06-21 Miklos Szeredi <miklos@szeredi.hu> + + * Fix possible inode leak in userspace in case of unfinished + lookup/mknod/mkdir/symlink/link operation. + +2004-06-20 Miklos Szeredi <miklos@szeredi.hu> + + * Fix some races and cleanups in fuse_read_super() + +2004-06-19 Miklos Szeredi <miklos@szeredi.hu> + + * Requests are allocated at open time + +2004-06-03 Miklos Szeredi <miklos@szeredi.hu> + + * Build shared library as well as static (using libtool) + + * Change FUSE_MINOR_VERSION from 1 to 0. I know it's illegal but + there has not been a release with the previous minor number, and I + hope nobody is using it for anything. + + * Change fuse_main(), so that default behavior is to go into + background if mount is successful. '-f' and '-d' options disable + backgrounding. This fixes the "Why does my FUSE daemon hang?" + newbie complaint. + + * Cache ENOSYS (function not implemented) errors on *xattr, flush + and fsync + + * Don't call getdir method from open() only from first readdir(). + Open is sometimes just used to store the current directory + (e.g. find) + +2004-05-18 Miklos Szeredi <miklos@szeredi.hu> + + * Added flush() call + +2004-05-04 Miklos Szeredi <miklos@szeredi.hu> + + * Extended attributes support for 2.4 (patch by Cody Pisto) + +2004-04-20 Miklos Szeredi <miklos@szeredi.hu> + + * Fixed parser with modversions (Mattias Wadman) + +2004-04-19 Miklos Szeredi <miklos@szeredi.hu> + + * Added mount option parser to 2.4 build + +2004-04-13 Miklos Szeredi <miklos@szeredi.hu> + + * Replaced binary mount data with text options + + * Show FUSE specific mount options in /proc/mounts + + * Check in fuse.h whether _FILE_OFFSET_BITS is set to 64 + +2004-04-09 Miklos Szeredi <miklos@szeredi.hu> + + * Check some limits so userspace won't get too big requests + +2004-04-05 Miklos Szeredi <miklos@szeredi.hu> + + * Kill compile warning + + * Upgraded user-mount patch for 2.6.5 + +2004-04-02 Miklos Szeredi <miklos@szeredi.hu> + + * Add detection of user-mode-linux to configure + +2004-03-31 Miklos Szeredi <miklos@szeredi.hu> + + * fixed zero size case for getxattr and listxattr + +2004-03-30 Miklos Szeredi <miklos@szeredi.hu> + + * new fusermount flag '-z': lazy unmount, default is not lazy + + * Extended attributes operations added (getxattr, setxattr, + listxattr, removexattr) + +2004-03-25 Miklos Szeredi <miklos@szeredi.hu> + + * If filesystem doesn't define a statfs operation, then an + all-zero default statfs is returned instead of ENOSYS + +2004-03-24 Miklos Szeredi <miklos@szeredi.hu> + + * Add FS_BINARY_MOUNTDATA filesystem flag for kernels > 2.6.4 + +2004-03-09 Miklos Szeredi <miklos@szeredi.hu> + + * Fix for uClinux (Christian Magnusson) + +2004-03-02 Miklos Szeredi <miklos@szeredi.hu> + + * fuse_main() adds "-n progname" to the fusermount command line + + * More kernel interface changes: + + * Lookup/getattr return cache timeout values + +2004-02-25 Miklos Szeredi <miklos@szeredi.hu> + + * Clean up option parsing in fuse_main() + + * Added fuse_get() function which returns the fuse object created + by fuse_main() + +2004-02-20 Miklos Szeredi <miklos@szeredi.hu> + + * removed old way of mounting (fusermount mountpoint program) + + * more kernel interface changes: + + * added nanosecond precision to file times + + * removed interface version from mount data + + * added /proc/fs/fuse/version which contains MAJOR.MINOR + +2004-02-19 Miklos Szeredi <miklos@szeredi.hu> + + * statfs library API changed to match other methods. Since this + is not backward compatible FUSE_MAJOR_VERSION is changed to 2 + + * kernel interface changes follow: + + * statfs changed to 64 bits, added 'bavail' field + + * add generation number to lookup result + + * optimized mknod/mkdir/symlink/link (no separate lookup is + needed) + + * rdev size increased to 32 bits for mknod + + * kernel interface version changed to 3.1 + +2004-02-18 Miklos Szeredi <miklos@szeredi.hu> + + * user-mount upgraded for 2.6.3 kernel + +2004-02-17 Miklos Szeredi <miklos@szeredi.hu> + + * Added user-mount.2.6.2-rc3.patch + + * Add FS_SAFE flag to fuse filesystem + + * fusermount should allow (un)mounting for non-root even if not + suid-root + +2004-02-12 Miklos Szeredi <miklos@szeredi.hu> + + * Remove MS_PERMISSION mount flag (that means something else now) + +2004-02-10 Miklos Szeredi <miklos@szeredi.hu> + + * Added check for i_size_read/write functions to configure.in + (patch by Valient Gough) + +2004-02-06 Miklos Szeredi <miklos@szeredi.hu> + + * Fixed writing >= 2G files + + * Check file size on open (with generic_file_open()) + + * Readpage calls flush_dcache_page() after storing data + + * Use i_size_read/write for accessing inode->i_size + + * Make loopback mount of a fuse file work + +2004-02-04 Miklos Szeredi <miklos@szeredi.hu> + + * Released 1.1 + +2004-01-29 Miklos Szeredi <miklos@szeredi.hu> + + * Properly check if the inode exists in fuse_invalidate + +2004-01-27 Miklos Szeredi <miklos@szeredi.hu> + + * Added -q option for fusermount + + * fuse_unmount() now uses -q option of fusermount, so no error is + printed if the cause of the program exit is that the filesystem + has already been unmounted + + * Fix i_nlink correctness after rmdir/unlink + +2004-01-26 Miklos Szeredi <miklos@szeredi.hu> + + * Released 1.1-pre2 + +2004-01-26 Miklos Szeredi <miklos@szeredi.hu> + + * Fix typo (thanks Marcos Dione) + + * Compile fixes for 2.4 kernels + +2004-01-23 Miklos Szeredi <miklos@szeredi.hu> + + * Fix CONFIG_MODVERSIONS compile on 2.6 + +2004-01-22 Miklos Szeredi <miklos@szeredi.hu> + + * Write all pending data before a RELEASE operation + + * Suppress 'Bad file descriptor' warning on exit + + * Replaced fusermount option '-d xxx' with '-n xxx' so it doesn't + get confused with '-d' of fuse_main() (sorry about this change) + + * New fusermount option '-l' which enables big reads. Big reads + are now disabled by default. + + * fuse_main() can accept fusermount arguments after a '--' + +2004-01-19 Miklos Szeredi <miklos@szeredi.hu> + + * Support for exporting filesystem over NFS (see README.NFS) + +2004-01-14 Miklos Szeredi <miklos@szeredi.hu> + + * Support non-blocking writepage on 2.6. This makes FUSE behave + much more nicely in low-memory situations + + * Fix 32-bit dev handling in getattr and mknod for 2.6 kernels. + (Note: the mknod method does not yet use 32bit device number) + +2004-01-13 Miklos Szeredi <miklos@szeredi.hu> + + * Code cleanups + +2004-01-07 Miklos Szeredi <miklos@szeredi.hu> + + * Released 1.1-pre1 + +2004-01-06 Miklos Szeredi <miklos@szeredi.hu> + + * Integrated 2.6 kernel support patch by Michael Grigoriev + + * Improvements and cleanups for 2.6 kernels + +2004-01-05 Miklos Szeredi <miklos@szeredi.hu> + + * Added -d option to fusermount + +2003-12-15 Miklos Szeredi <miklos@szeredi.hu> + + * Added major+minor version to library API, and minor version to + kernel API + +2003-12-13 David McNab <david@rebirthing.co.nz> + + * Implemented fsync support in examples/example.py + + * Implemented 'fsync' and 'statfs' methods in python + interface + +2003-12-12 Miklos Szeredi <miklos@szeredi.hu> + + * Make it compile on 2.4.19. + + * Add fsync operation (write file failed on xemacs & vi) + +2003-12-12 David McNab <david@rebirthing.co.nz> + + * Added distutils support to the python module, as per standard + python development practice + +2003-12-11 Miklos Szeredi <miklos@szeredi.hu> + + * Add file locking for mount/unmount (based on patch by Valient + Gough) + +2003-12-11 David McNab <david@rebirthing.co.nz> + + * Python filesystem - was broken with python2.3, now fixed: + - changed PyTuple_* calls to PySequence_*, because os.lstat + is no longer returning a pure tuple + - changed PyInt_Check() calls to also call PyLong_Check, + to cover for cases (eg os.lstat) where longs are returned + - Added support for file 'release' handling, which IMO is + essential since this signals to a FS that writes to a file + are complete (and therefore the file can now be disposed of + meaningfully at the python filesystem's discretion) + - Added '__init__' handler to base Fuse class, which allows + your Python class to know the mountpoint and mount args, + as attributes myfs.mountpoint, myfs.optlist, myfs.optdict + + * General: + - added 'mount.fuse' script (in util/ dir), which is meant to be + symlinked from /sbin, and which allows FUSE filesystems to + be mounted with the 'mount' command, and listed in fstab; + also, mount arguments get passed to your filesystem + + +2003-11-04 Miklos Szeredi <miklos@szeredi.hu> + + * Fix kernel version detection (again). Bugreport by Peter Levart + +2003-11-03 Miklos Szeredi <miklos@szeredi.hu> + + * Applied read combining patch by Michael Grigoriev (tested by + Valient Gough and Vincent Wagelaar) + +2003-10-22 Miklos Szeredi <miklos@szeredi.hu> + + * Mtab handling fix in fusermount by "Valient Gough" (SF patch + #766443) + +2003-10-13 Miklos Szeredi <miklos@szeredi.hu> + + * Error code fixes in kernel module + +2003-10-04 Miklos Szeredi <miklos@szeredi.hu> + + * kernel version detection fix + + * fusermount now uses "lazy" umount option + + * fusermount can use modprobe with module-init-tools + +2003-09-08 Miklos Szeredi <miklos@szeredi.hu> + + * Integrated caching patch by Michael Grigoriev + + * Added "Filesystems" file with descriptions of projects using + FUSE + + * Added patch by Michael Grigoriev to allow compliation of FUSE + kernel module for 2.6 kernels + +2003-06-02 Miklos Szeredi <miklos@szeredi.hu> + + * And another spec-file fix by Achim Settelmeier + +2003-05-26 Miklos Szeredi <miklos@szeredi.hu> + + * Spec-file fix by Achim Settelmeier + +2003-03-10 Miklos Szeredi <miklos@szeredi.hu> + + * Fix umount oops (found by Samuli Kärkkäinen) + +2003-03-05 Miklos Szeredi <miklos@szeredi.hu> + + * Merge of fuse_redhat.spec and fuse.spec by Achim Settelmeier + +2003-03-04 Miklos Szeredi <miklos@szeredi.hu> + + * Updated fuse.spec file (Achim Settelmeier) + +2003-02-19 Miklos Szeredi <miklos@szeredi.hu> + + * Version 1.0 released + +2003-02-12 Miklos Szeredi <miklos@szeredi.hu> + + * SuSE compilation fix by Juan-Mariano de Goyeneche + +2002-12-10 Miklos Szeredi <miklos@szeredi.hu> + + * The release() VFS call is now exported to the FUSE interface + +2002-12-05 Miklos Szeredi <miklos@szeredi.hu> + + * 64 bit file offset fixes in the fuse kernel module + + * Added function 'fuse_exit()' which can be used to exit the main + loop + +2002-12-03 Miklos Szeredi <miklos@szeredi.hu> + + * Added _FILE_OFFSET_BITS=64 define to fuse.h. Note, that this is + an incompatible interface change. + +2002-10-28 Miklos Szeredi <miklos@szeredi.hu> + + * Portablility fix (bug reported by C. Chris Erway) + +2002-10-25 Miklos Szeredi <miklos@szeredi.hu> + + * Use Mark Glines' fd passing method for default operation instead + of old reexec + +2002-10-22 Miklos Szeredi <miklos@szeredi.hu> + + * fix "Stale NFS file handle" bug caused by changes in 2.4.19 + +2002-10-22 Miklos Szeredi <miklos@szeredi.hu> + + * fix incompatiblity with Red Hat kernels, with help from Nathan + Thompson-Amato. + +2002-04-18 Mark Glines <mark@glines.org> + + * added an alternative to fuse_mount(), called + fuse_mount_ioslave(), which does not need to reexec the + FUSE program. + * added a small helper util needed by fuse_mount_ioslave(). + +2002-03-16 Mark Glines <mark@glines.org> + + * use struct fuse_statfs everywhere possible to avoid problems + with the headerfiles changing struct statfs member sizes + +2002-03-01 Miklos Szeredi <miklos@szeredi.hu> + + * Another RPM spec file for RedHat >= 7 by Ian Pilcher + +2002-01-14 Miklos Szeredi <miklos@szeredi.hu> + + * RPM support by Achim Settelmeier + +2002-01-09 Miklos Szeredi <miklos@szeredi.hu> + + * Version 0.95 released + +2002-01-09 Miklos Szeredi <miklos@szeredi.hu> + + * Revaidate all path components not just the last, this means a + very small performance penalty for being more up-to-date. + +2002-01-08 Miklos Szeredi <miklos@szeredi.hu> + + * Update and fix python interface + +2002-01-07 Mark Glines <mark@glines.org> + + * Added statfs() support to kernel, lib, examples, and perl! + +2001-12-26 Miklos Szeredi <miklos@szeredi.hu> + + * Better cross compilation support + + * Ported to Compaq IPAQ + +2001-12-20 Miklos Szeredi <miklos@szeredi.hu> + + * Added function fuse_get_context() to library API (inspired by + patch from Matt Ryan) + + * Added flags to fusermount and to kernel interface to control + permission checking + + * Integrated fuse_set_operations() into fuse_new() + +2001-12-08 Miklos Szeredi <miklos@szeredi.hu> + + * Applied header protection + extern "C" patch by Roland + Bauerschmidt + +2001-12-02 Miklos Szeredi <miklos@szeredi.hu> + + * Added perl bindings by Mark Glines + +2001-11-21 Miklos Szeredi <miklos@szeredi.hu> + + * Cleaned up way of mounting simple filesystems. + + * fuse_main() helper function added + +2001-11-18 Miklos Szeredi <miklos@szeredi.hu> + + * Optimized read/write operations, so that minimal copying of data + is done + +2001-11-14 Miklos Szeredi <miklos@szeredi.hu> + + * Python bindings by Jeff Epler added + +2001-11-13 Miklos Szeredi <miklos@szeredi.hu> + + * Fixed vfsmount reference leak in fuse_follow_link + + * FS blocksize is set to PAGE_CACHE_SIZE, blksize attribute from + userspace is ignored + +2001-11-09 Miklos Szeredi <miklos@szeredi.hu> + + * Started ChangeLog diff --git a/fuse/README b/fuse/README new file mode 100644 index 000000000..398dd6557 --- /dev/null +++ b/fuse/README @@ -0,0 +1,380 @@ +General Information +=================== + +FUSE (Filesystem in Userspace) is a simple interface for userspace +programs to export a virtual filesystem to the Linux kernel. FUSE +also aims to provide a secure method for non privileged users to +create and mount their own filesystem implementations. + +You can download the source code releases from + + http://sourceforge.net/projects/fuse + +or alternatively you can use CVS to get the very latest development +version: + + cvs -d :pserver:anonymous@fuse.cvs.sourceforge.net:/cvsroot/fuse co fuse + + +Dependencies +============ + +Linux kernel version 2.6.X where X >= 9. + +Alternatively a kernel module from FUSE release 2.5.* can be used with +this release, which supports kernels >= 2.4.21. + +Installation +============ + +./configure +make +make install +modprobe fuse + +You may also need to add '/usr/local/lib' to '/etc/ld.so.conf' and/or +run ldconfig. + +You'll also need a fuse kernel module, Linux kernels 2.6.14 or later +contain FUSE support. + +For more details see the file 'INSTALL' + +How To Use +========== + +FUSE is made up of three main parts: + + - A kernel filesystem module + + - A userspace library + + - A mount/unmount program + + +Here's how to create your very own virtual filesystem in five easy +steps (after installing FUSE): + + 1) Edit the file example/fusexmp.c to do whatever you want... + + 2) Build the fusexmp program + + 3) run 'example/fusexmp /mnt/fuse -d' + + 4) ls -al /mnt/fuse + + 5) Be glad + +If it doesn't work out, please ask! Also see the file 'include/fuse.h' for +detailed documentation of the library interface. + +Security +======== + +If you run 'make install', the fusermount program is installed +set-user-id to root. This is done to allow normal users to mount +their own filesystem implementations. + +There must however be some limitations, in order to prevent Bad User from +doing nasty things. Currently those limitations are: + + - The user can only mount on a mountpoint, for which it has write + permission + + - The mountpoint is not a sticky directory which isn't owned by the + user (like /tmp usually is) + + - No other user (including root) can access the contents of the mounted + filesystem. + +Configuration +============= + +Some options regarding mount policy can be set in the file +'/etc/fuse.conf' + +Currently these options are: + +mount_max = NNN + + Set the maximum number of FUSE mounts allowed to non-root users. + The default is 1000. + +user_allow_other + + Allow non-root users to specify the 'allow_other' or 'allow_root' + mount options. + + +Mount options +============= + +Most of the generic mount options described in 'man mount' are +supported (ro, rw, suid, nosuid, dev, nodev, exec, noexec, atime, +noatime, sync async, dirsync). Filesystems are mounted with +'-onodev,nosuid' by default, which can only be overridden by a +privileged user. + +These are FUSE specific mount options that can be specified for all +filesystems: + +default_permissions + + By default FUSE doesn't check file access permissions, the + filesystem is free to implement it's access policy or leave it to + the underlying file access mechanism (e.g. in case of network + filesystems). This option enables permission checking, restricting + access based on file mode. This is option is usually useful + together with the 'allow_other' mount option. + +allow_other + + This option overrides the security measure restricting file access + to the user mounting the filesystem. So all users (including root) + can access the files. This option is by default only allowed to + root, but this restriction can be removed with a configuration + option described in the previous section. + +allow_root + + This option is similar to 'allow_other' but file access is limited + to the user mounting the filesystem and root. This option and + 'allow_other' are mutually exclusive. + +kernel_cache + + This option disables flushing the cache of the file contents on + every open(). This should only be enabled on filesystems, where the + file data is never changed externally (not through the mounted FUSE + filesystem). Thus it is not suitable for network filesystems and + other "intermediate" filesystems. + + NOTE: if this option is not specified (and neither 'direct_io') data + is still cached after the open(), so a read() system call will not + always initiate a read operation. + +auto_cache + + This option enables automatic flushing of the data cache on open(). + The cache will only be flushed if the modification time or the size + of the file has changed. + +large_read + + Issue large read requests. This can improve performance for some + filesystems, but can also degrade performance. This option is only + useful on 2.4.X kernels, as on 2.6 kernels requests size is + automatically determined for optimum performance. + +direct_io + + This option disables the use of page cache (file content cache) in + the kernel for this filesystem. This has several affects: + + - Each read() or write() system call will initiate one or more + read or write operations, data will not be cached in the + kernel. + + - The return value of the read() and write() system calls will + correspond to the return values of the read and write + operations. This is useful for example if the file size is not + known in advance (before reading it). + +max_read=N + + With this option the maximum size of read operations can be set. + The default is infinite. Note that the size of read requests is + limited anyway to 32 pages (which is 128kbyte on i386). + +max_readahead=N + + Set the maximum number of bytes to read-ahead. The default is + determined by the kernel. On linux-2.6.22 or earlier it's 131072 + (128kbytes) + +max_write=N + + Set the maximum number of bytes in a single write operation. The + default is 128kbytes. Note, that due to various limitations, the + size of write requests can be much smaller (4kbytes). This + limitation will be removed in the future. + +async_read + + Perform reads asynchronously. This is the default + +sync_read + + Perform all reads (even read-ahead) synchronously. + +hard_remove + + The default behavior is that if an open file is deleted, the file is + renamed to a hidden file (.fuse_hiddenXXX), and only removed when + the file is finally released. This relieves the filesystem + implementation of having to deal with this problem. This option + disables the hiding behavior, and files are removed immediately in + an unlink operation (or in a rename operation which overwrites an + existing file). + + It is recommended that you not use the hard_remove option. When + hard_remove is set, the following libc functions fail on unlinked + files (returning errno of ENOENT): + - read() + - write() + - fsync() + - close() + - f*xattr() + - ftruncate() + - fstat() + - fchmod() + - fchown() + +debug + + Turns on debug information printing by the library. + +fsname=NAME + + Sets the filesystem source (first field in /etc/mtab). The default + is the program name. + +subtype=TYPE + + Sets the filesystem type (third field in /etc/mtab). The default is + the program name. + + If the kernel suppports it, /etc/mtab and /proc/mounts will show the + filesystem type as "fuse.TYPE" + + If the kernel doesn't support subtypes, the source filed will be + "TYPE#NAME", or if fsname option is not specified, just "TYPE". + +use_ino + + Honor the 'st_ino' field in getattr() and fill_dir(). This value is + used to fill in the 'st_ino' field in the stat()/lstat()/fstat() + functions and the 'd_ino' field in the readdir() function. The + filesystem does not have to guarantee uniqueness, however some + applications rely on this value being unique for the whole + filesystem. + +readdir_ino + + If 'use_ino' option is not given, still try to fill in the 'd_ino' + field in readdir(). If the name was previously looked up, and is + still in the cache, the inode number found there will be used. + Otherwise it will be set to '-1'. If 'use_ino' option is given, + this option is ignored. + +nonempty + + Allows mounts over a non-empty file or directory. By default these + mounts are rejected (from version 2.3.1) to prevent accidental + covering up of data, which could for example prevent automatic + backup. + +umask=M + + Override the permission bits in 'st_mode' set by the filesystem. + The resulting permission bits are the ones missing from the given + umask value. The value is given in octal representation. + +uid=N + + Override the 'st_uid' field set by the filesystem. + +gid=N + + Override the 'st_gid' field set by the filesystem. + +blkdev + + Mount a filesystem backed by a block device. This is a privileged + option. The device must be specified with the 'fsname=NAME' option. + +entry_timeout=T + + The timeout in seconds for which name lookups will be cached. The + default is 1.0 second. For all the timeout options, it is possible + to give fractions of a second as well (e.g. "-oentry_timeout=2.8") + +negative_timeout=T + + The timeout in seconds for which a negative lookup will be cached. + This means, that if file did not exist (lookup retuned ENOENT), the + lookup will only be redone after the timeout, and the file/directory + will be assumed to not exist until then. The default is 0.0 second, + meaning that caching negative lookups are disabled. + +attr_timeout=T + + The timeout in seconds for which file/directory attributes are + cached. The default is 1.0 second. + +ac_attr_timeout=T + + The timeout in seconds for which file attributes are cached for the + purpose of checking if "auto_cache" should flush the file data on + open. The default is the value of 'attr_timeout' + +intr + + Allow requests to be interrupted. Turning on this option may result + in unexpected behavior, if the filesystem does not support request + interruption. + +intr_signal=NUM + + Specify which signal number to send to the filesystem when a request + is interrupted. The default is 10 (USR1). + +modules=M1[:M2...] + + Add modules to the filesystem stack. Modules are pushed in the + order they are specified, with the original filesystem being on the + bottom of the stack. + + +Modules distributed with fuse +----------------------------- + +iconv +````` +Perform file name character set conversion. Options are: + +from_code=CHARSET + + Character set to convert from (see iconv -l for a list of possible + values). Default is UTF-8. + +to_code=CHARSET + + Character set to convert to. Default is determined by the current + locale. + + +subdir +`````` +Prepend a given directory to each path. Options are: + +subdir=DIR + + Directory to prepend to all paths. This option is mandatory. + +rellinks + + Transform absolute symlinks into relative + +norellinks + + Do not transform absolute symlinks into relative. This is the default. + + +Reporting bugs +============== + +Please send bug reports to the <fuse-devel@lists.sourceforge.net> +mailing list. + +The list is open, you need not be subscribed to post. diff --git a/fuse/android/config.h b/fuse/android/config.h new file mode 100644 index 000000000..dac8c1671 --- /dev/null +++ b/fuse/android/config.h @@ -0,0 +1,87 @@ +/* include/config.h. Generated from config.h.in by configure. */ +/* include/config.h.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the `fdatasync' function. */ +#define HAVE_FDATASYNC 1 + +/* Define to 1 if you have the `fork' function. */ +#define HAVE_FORK 1 + +/* Define if you have the iconv() function and it works. */ +#define HAVE_ICONV 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `setxattr' function. */ +#define HAVE_SETXATTR 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if `st_atim' is member of `struct stat'. */ +//#define HAVE_STRUCT_STAT_ST_ATIM 0 + +/* Define to 1 if `st_atimespec' is member of `struct stat'. */ +/* #undef HAVE_STRUCT_STAT_ST_ATIMESPEC */ + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Define as const if the declaration of iconv() needs const. */ +#define ICONV_CONST + +/* Don't update /etc/mtab */ +/* #undef IGNORE_MTAB */ + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +/* #undef NO_MINUS_C_MINUS_O */ + +/* Name of package */ +#define PACKAGE "fuse" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "fuse" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "fuse 2.9.3" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "fuse" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "2.9.3" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "2.9.3" diff --git a/fuse/android/statvfs.c b/fuse/android/statvfs.c new file mode 100644 index 000000000..7cec574da --- /dev/null +++ b/fuse/android/statvfs.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "sys/statvfs.h" +#include <sys/statfs.h> + +#define MAP(to,from) vfs->to = sfs.from + +int statvfs(const char *path, struct statvfs *vfs) { + struct statfs sfs; + int ret; + int *fsid; + if ((ret = statfs(path, &sfs)) != 0) + return ret; + + MAP(f_bsize, f_bsize); + MAP(f_frsize, f_frsize); + MAP(f_blocks, f_blocks); + MAP(f_bfree, f_bfree); + MAP(f_bavail, f_bavail); + MAP(f_files, f_files); + MAP(f_ffree, f_ffree); + MAP(f_namemax, f_namelen); + + vfs->f_favail = 0; + vfs->f_flag = 0; + + fsid = (int *)&sfs.f_fsid; + vfs->f_fsid = (fsid[0] << sizeof(fsid[0])) | fsid[1]; + + return ret; +} diff --git a/fuse/android/sys/statvfs.h b/fuse/android/sys/statvfs.h new file mode 100644 index 000000000..0d770dd72 --- /dev/null +++ b/fuse/android/sys/statvfs.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _SYS_STATVFS_H_ +#define _SYS_STATVFS_H_ +#include <sys/types.h> + +struct statvfs { + unsigned long f_bsize; /* file system block size */ + unsigned long f_frsize; /* fragment size */ + fsblkcnt_t f_blocks; /* size of fs in f_frsize units */ + fsblkcnt_t f_bfree; /* # free blocks */ + fsblkcnt_t f_bavail; /* # free blocks for non-root */ + fsfilcnt_t f_files; /* # inodes */ + fsfilcnt_t f_ffree; /* # free inodes */ + fsfilcnt_t f_favail; /* # free inodes for non-root */ + unsigned long f_fsid; /* file system ID */ + unsigned long f_flag; /* mount flags */ + unsigned long f_namemax; /* maximum filename length */ +}; + +int statvfs(const char *, struct statvfs *); +#endif diff --git a/fuse/buffer.c b/fuse/buffer.c new file mode 100644 index 000000000..6fa55c979 --- /dev/null +++ b/fuse/buffer.c @@ -0,0 +1,318 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#define _GNU_SOURCE + +#include "config.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <assert.h> + +size_t fuse_buf_size(const struct fuse_bufvec *bufv) +{ + size_t i; + size_t size = 0; + + for (i = 0; i < bufv->count; i++) { + if (bufv->buf[i].size == SIZE_MAX) + size = SIZE_MAX; + else + size += bufv->buf[i].size; + } + + return size; +} + +static size_t min_size(size_t s1, size_t s2) +{ + return s1 < s2 ? s1 : s2; +} + +static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + ssize_t res = 0; + size_t copied = 0; + + while (len) { + if (dst->flags & FUSE_BUF_FD_SEEK) { + res = pwrite64(dst->fd, src->mem + src_off, len, + dst->pos + dst_off); + } else { + res = write(dst->fd, src->mem + src_off, len); + } + if (res == -1) { + if (!copied) + return -errno; + break; + } + if (res == 0) + break; + + copied += res; + if (!(dst->flags & FUSE_BUF_FD_RETRY)) + break; + + src_off += res; + dst_off += res; + len -= res; + } + + return copied; +} + +static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + ssize_t res = 0; + size_t copied = 0; + + while (len) { + if (src->flags & FUSE_BUF_FD_SEEK) { + res = pread(src->fd, dst->mem + dst_off, len, + src->pos + src_off); + } else { + res = read(src->fd, dst->mem + dst_off, len); + } + if (res == -1) { + if (!copied) + return -errno; + break; + } + if (res == 0) + break; + + copied += res; + if (!(src->flags & FUSE_BUF_FD_RETRY)) + break; + + dst_off += res; + src_off += res; + len -= res; + } + + return copied; +} + +static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + char buf[4096]; + struct fuse_buf tmp = { + .size = sizeof(buf), + .flags = 0, + }; + ssize_t res; + size_t copied = 0; + + tmp.mem = buf; + + while (len) { + size_t this_len = min_size(tmp.size, len); + size_t read_len; + + res = fuse_buf_read(&tmp, 0, src, src_off, this_len); + if (res < 0) { + if (!copied) + return res; + break; + } + if (res == 0) + break; + + read_len = res; + res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len); + if (res < 0) { + if (!copied) + return res; + break; + } + if (res == 0) + break; + + copied += res; + + if (res < this_len) + break; + + dst_off += res; + src_off += res; + len -= res; + } + + return copied; +} + +#ifdef HAVE_SPLICE +static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + int splice_flags = 0; + loff_t *srcpos = NULL; + loff_t *dstpos = NULL; + loff_t srcpos_val; + loff_t dstpos_val; + ssize_t res; + size_t copied = 0; + + if (flags & FUSE_BUF_SPLICE_MOVE) + splice_flags |= SPLICE_F_MOVE; + if (flags & FUSE_BUF_SPLICE_NONBLOCK) + splice_flags |= SPLICE_F_NONBLOCK; + + if (src->flags & FUSE_BUF_FD_SEEK) { + srcpos_val = src->pos + src_off; + srcpos = &srcpos_val; + } + if (dst->flags & FUSE_BUF_FD_SEEK) { + dstpos_val = dst->pos + dst_off; + dstpos = &dstpos_val; + } + + while (len) { + res = splice(src->fd, srcpos, dst->fd, dstpos, len, + splice_flags); + if (res == -1) { + if (copied) + break; + + if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE)) + return -errno; + + /* Maybe splice is not supported for this combination */ + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, + len); + } + if (res == 0) + break; + + copied += res; + if (!(src->flags & FUSE_BUF_FD_RETRY) && + !(dst->flags & FUSE_BUF_FD_RETRY)) { + break; + } + + len -= res; + } + + return copied; +} +#else +static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + (void) flags; + + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len); +} +#endif + + +static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + int src_is_fd = src->flags & FUSE_BUF_IS_FD; + int dst_is_fd = dst->flags & FUSE_BUF_IS_FD; + + if (!src_is_fd && !dst_is_fd) { + void *dstmem = dst->mem + dst_off; + void *srcmem = src->mem + src_off; + + if (dstmem != srcmem) { + if (dstmem + len <= srcmem || srcmem + len <= dstmem) + memcpy(dstmem, srcmem, len); + else + memmove(dstmem, srcmem, len); + } + + return len; + } else if (!src_is_fd) { + return fuse_buf_write(dst, dst_off, src, src_off, len); + } else if (!dst_is_fd) { + return fuse_buf_read(dst, dst_off, src, src_off, len); + } else if (flags & FUSE_BUF_NO_SPLICE) { + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len); + } else { + return fuse_buf_splice(dst, dst_off, src, src_off, len, flags); + } +} + +static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv) +{ + if (bufv->idx < bufv->count) + return &bufv->buf[bufv->idx]; + else + return NULL; +} + +static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len) +{ + const struct fuse_buf *buf = fuse_bufvec_current(bufv); + + bufv->off += len; + assert(bufv->off <= buf->size); + if (bufv->off == buf->size) { + assert(bufv->idx < bufv->count); + bufv->idx++; + if (bufv->idx == bufv->count) + return 0; + bufv->off = 0; + } + return 1; +} + +ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv, + enum fuse_buf_copy_flags flags) +{ + size_t copied = 0; + + if (dstv == srcv) + return fuse_buf_size(dstv); + + for (;;) { + const struct fuse_buf *src = fuse_bufvec_current(srcv); + const struct fuse_buf *dst = fuse_bufvec_current(dstv); + size_t src_len; + size_t dst_len; + size_t len; + ssize_t res; + + if (src == NULL || dst == NULL) + break; + + src_len = src->size - srcv->off; + dst_len = dst->size - dstv->off; + len = min_size(src_len, dst_len); + + res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags); + if (res < 0) { + if (!copied) + return res; + break; + } + copied += res; + + if (!fuse_bufvec_advance(srcv, res) || + !fuse_bufvec_advance(dstv, res)) + break; + + if (res < len) + break; + } + + return copied; +} diff --git a/fuse/cuse_lowlevel.c b/fuse/cuse_lowlevel.c new file mode 100644 index 000000000..ae08ed485 --- /dev/null +++ b/fuse/cuse_lowlevel.c @@ -0,0 +1,371 @@ +/* + CUSE: Character device in Userspace + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo <teheo@suse.de> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "cuse_lowlevel.h" +#include "fuse_kernel.h" +#include "fuse_i.h" +#include "fuse_opt.h" +#include "fuse_misc.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stddef.h> +#include <errno.h> +#include <unistd.h> + +struct cuse_data { + struct cuse_lowlevel_ops clop; + unsigned max_read; + unsigned dev_major; + unsigned dev_minor; + unsigned flags; + unsigned dev_info_len; + char dev_info[]; +}; + +static struct cuse_lowlevel_ops *req_clop(fuse_req_t req) +{ + return &req->f->cuse_data->clop; +} + +static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->open(req, fi); +} + +static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + loff_t off, struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->read(req, size, off, fi); +} + +static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, loff_t off, struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->write(req, buf, size, off, fi); +} + +static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->flush(req, fi); +} + +static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->release(req, fi); +} + +static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->fsync(req, datasync, fi); +} + +static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + (void)ino; + req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz, + out_bufsz); +} + +static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + (void)ino; + req_clop(req)->poll(req, fi, ph); +} + +static size_t cuse_pack_info(int argc, const char **argv, char *buf) +{ + size_t size = 0; + int i; + + for (i = 0; i < argc; i++) { + size_t len; + + len = strlen(argv[i]) + 1; + size += len; + if (buf) { + memcpy(buf, argv[i], len); + buf += len; + } + } + + return size; +} + +static struct cuse_data *cuse_prep_data(const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop) +{ + struct cuse_data *cd; + size_t dev_info_len; + + dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, + NULL); + + if (dev_info_len > CUSE_INIT_INFO_MAX) { + fprintf(stderr, "cuse: dev_info (%zu) too large, limit=%u\n", + dev_info_len, CUSE_INIT_INFO_MAX); + return NULL; + } + + cd = calloc(1, sizeof(*cd) + dev_info_len); + if (!cd) { + fprintf(stderr, "cuse: failed to allocate cuse_data\n"); + return NULL; + } + + memcpy(&cd->clop, clop, sizeof(cd->clop)); + cd->max_read = 131072; + cd->dev_major = ci->dev_major; + cd->dev_minor = ci->dev_minor; + cd->dev_info_len = dev_info_len; + cd->flags = ci->flags; + cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info); + + return cd; +} + +struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + void *userdata) +{ + struct fuse_lowlevel_ops lop; + struct cuse_data *cd; + struct fuse_session *se; + struct fuse_ll *ll; + + cd = cuse_prep_data(ci, clop); + if (!cd) + return NULL; + + memset(&lop, 0, sizeof(lop)); + lop.init = clop->init; + lop.destroy = clop->destroy; + lop.open = clop->open ? cuse_fll_open : NULL; + lop.read = clop->read ? cuse_fll_read : NULL; + lop.write = clop->write ? cuse_fll_write : NULL; + lop.flush = clop->flush ? cuse_fll_flush : NULL; + lop.release = clop->release ? cuse_fll_release : NULL; + lop.fsync = clop->fsync ? cuse_fll_fsync : NULL; + lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL; + lop.poll = clop->poll ? cuse_fll_poll : NULL; + + se = fuse_lowlevel_new_common(args, &lop, sizeof(lop), userdata); + if (!se) { + free(cd); + return NULL; + } + ll = se->data; + ll->cuse_data = cd; + + return se; +} + +static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg, + char *dev_info, unsigned dev_info_len) +{ + struct iovec iov[3]; + + iov[1].iov_base = arg; + iov[1].iov_len = sizeof(struct cuse_init_out); + iov[2].iov_base = dev_info; + iov[2].iov_len = dev_info_len; + + return fuse_send_reply_iov_nofree(req, 0, iov, 3); +} + +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_init_in *arg = (struct fuse_init_in *) inarg; + struct cuse_init_out outarg; + struct fuse_ll *f = req->f; + struct cuse_data *cd = f->cuse_data; + size_t bufsize = fuse_chan_bufsize(req->ch); + struct cuse_lowlevel_ops *clop = req_clop(req); + + (void) nodeid; + if (f->debug) { + fprintf(stderr, "CUSE_INIT: %u.%u\n", arg->major, arg->minor); + fprintf(stderr, "flags=0x%08x\n", arg->flags); + } + f->conn.proto_major = arg->major; + f->conn.proto_minor = arg->minor; + f->conn.capable = 0; + f->conn.want = 0; + + if (arg->major < 7) { + fprintf(stderr, "cuse: unsupported protocol version: %u.%u\n", + arg->major, arg->minor); + fuse_reply_err(req, EPROTO); + return; + } + + if (bufsize < FUSE_MIN_READ_BUFFER) { + fprintf(stderr, "cuse: warning: buffer size too small: %zu\n", + bufsize); + bufsize = FUSE_MIN_READ_BUFFER; + } + + bufsize -= 4096; + if (bufsize < f->conn.max_write) + f->conn.max_write = bufsize; + + f->got_init = 1; + if (f->op.init) + f->op.init(f->userdata, &f->conn); + + memset(&outarg, 0, sizeof(outarg)); + outarg.major = FUSE_KERNEL_VERSION; + outarg.minor = FUSE_KERNEL_MINOR_VERSION; + outarg.flags = cd->flags; + outarg.max_read = cd->max_read; + outarg.max_write = f->conn.max_write; + outarg.dev_major = cd->dev_major; + outarg.dev_minor = cd->dev_minor; + + if (f->debug) { + fprintf(stderr, " CUSE_INIT: %u.%u\n", + outarg.major, outarg.minor); + fprintf(stderr, " flags=0x%08x\n", outarg.flags); + fprintf(stderr, " max_read=0x%08x\n", outarg.max_read); + fprintf(stderr, " max_write=0x%08x\n", outarg.max_write); + fprintf(stderr, " dev_major=%u\n", outarg.dev_major); + fprintf(stderr, " dev_minor=%u\n", outarg.dev_minor); + fprintf(stderr, " dev_info: %.*s\n", cd->dev_info_len, + cd->dev_info); + } + + cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len); + + if (clop->init_done) + clop->init_done(f->userdata); + + fuse_free_req(req); +} + +struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + int *multithreaded, void *userdata) +{ + const char *devname = "/dev/cuse"; + static const struct fuse_opt kill_subtype_opts[] = { + FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_DISCARD), + FUSE_OPT_END + }; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_chan *ch; + int fd; + int foreground; + int res; + + res = fuse_parse_cmdline(&args, NULL, multithreaded, &foreground); + if (res == -1) + goto err_args; + + res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL); + if (res == -1) + goto err_args; + + /* + * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos + * would ensue. + */ + do { + fd = open("/dev/null", O_RDWR); + if (fd > 2) + close(fd); + } while (fd >= 0 && fd <= 2); + + se = cuse_lowlevel_new(&args, ci, clop, userdata); + fuse_opt_free_args(&args); + if (se == NULL) + goto err_args; + + fd = open(devname, O_RDWR); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT) + fprintf(stderr, "cuse: device not found, try 'modprobe cuse' first\n"); + else + fprintf(stderr, "cuse: failed to open %s: %s\n", + devname, strerror(errno)); + goto err_se; + } + + ch = fuse_kern_chan_new(fd); + if (!ch) { + close(fd); + goto err_se; + } + + fuse_session_add_chan(se, ch); + + res = fuse_set_signal_handlers(se); + if (res == -1) + goto err_se; + + res = fuse_daemonize(foreground); + if (res == -1) + goto err_sig; + + return se; + +err_sig: + fuse_remove_signal_handlers(se); +err_se: + fuse_session_destroy(se); +err_args: + fuse_opt_free_args(&args); + return NULL; +} + +void cuse_lowlevel_teardown(struct fuse_session *se) +{ + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); +} + +int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, void *userdata) +{ + struct fuse_session *se; + int multithreaded; + int res; + + se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded, + userdata); + if (se == NULL) + return 1; + + if (multithreaded) + res = fuse_session_loop_mt(se); + else + res = fuse_session_loop(se); + + cuse_lowlevel_teardown(se); + if (res == -1) + return 1; + + return 0; +} diff --git a/fuse/fuse.c b/fuse/fuse.c new file mode 100644 index 000000000..2c1aa17d8 --- /dev/null +++ b/fuse/fuse.c @@ -0,0 +1,4942 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + + +/* For pthread_rwlock_t */ +#define _GNU_SOURCE + +#include "config.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include "fuse_opt.h" +#include "fuse_misc.h" +#include "fuse_common_compat.h" +#include "fuse_compat.h" +#include "fuse_kernel.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdbool.h> +#include <unistd.h> +#include <time.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> +#include <signal.h> +#include <assert.h> +#include <poll.h> +#include <sys/param.h> +#include <sys/uio.h> +#include <sys/time.h> +#include <sys/mman.h> +#include <sys/file.h> + +#ifdef USE_MODULES +#include <dlfcn.h> +#endif + +#define FUSE_NODE_SLAB 1 + +#ifndef MAP_ANONYMOUS +#undef FUSE_NODE_SLAB +#endif + +#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1 + +#define FUSE_UNKNOWN_INO 0xffffffff +#define OFFSET_MAX 0x7fffffffffffffffLL + +#define NODE_TABLE_MIN_SIZE 8192 + +struct fuse_config { + unsigned int uid; + unsigned int gid; + unsigned int umask; + double entry_timeout; + double negative_timeout; + double attr_timeout; + double ac_attr_timeout; + int ac_attr_timeout_set; + int remember; + int nopath; + int debug; + int hard_remove; + int use_ino; + int readdir_ino; + int set_mode; + int set_uid; + int set_gid; + int direct_io; + int kernel_cache; + int auto_cache; + int intr; + int intr_signal; + int help; + char *modules; +}; + +struct fuse_fs { + struct fuse_operations op; + struct fuse_module *m; + void *user_data; + int compat; + int debug; +}; + +struct fusemod_so { + void *handle; + int ctr; +}; + +struct lock_queue_element { + struct lock_queue_element *next; + pthread_cond_t cond; + fuse_ino_t nodeid1; + const char *name1; + char **path1; + struct node **wnode1; + fuse_ino_t nodeid2; + const char *name2; + char **path2; + struct node **wnode2; + int err; + bool first_locked : 1; + bool second_locked : 1; + bool done : 1; +}; + +struct node_table { + struct node **array; + size_t use; + size_t size; + size_t split; +}; + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +struct node_slab { + struct list_head list; /* must be the first member */ + struct list_head freelist; + int used; +}; + +struct fuse { + struct fuse_session *se; + struct node_table name_table; + struct node_table id_table; + struct list_head lru_table; + fuse_ino_t ctr; + unsigned int generation; + unsigned int hidectr; + pthread_mutex_t lock; + struct fuse_config conf; + int intr_installed; + struct fuse_fs *fs; + int nullpath_ok; + int utime_omit_ok; + struct lock_queue_element *lockq; + int pagesize; + struct list_head partial_slabs; + struct list_head full_slabs; + pthread_t prune_thread; +}; + +struct lock { + int type; + loff_t start; + loff_t end; + pid_t pid; + uint64_t owner; + struct lock *next; +}; + +struct node { + struct node *name_next; + struct node *id_next; + fuse_ino_t nodeid; + unsigned int generation; + int refctr; + struct node *parent; + char *name; + uint64_t nlookup; + int open_count; + struct timespec stat_updated; + struct timespec mtime; + loff_t size; + struct lock *locks; + unsigned int is_hidden : 1; + unsigned int cache_valid : 1; + int treelock; + char inline_name[32]; +}; + +#define TREELOCK_WRITE -1 +#define TREELOCK_WAIT_OFFSET INT_MIN + +struct node_lru { + struct node node; + struct list_head lru; + struct timespec forget_time; +}; + +struct fuse_dh { + pthread_mutex_t lock; + struct fuse *fuse; + fuse_req_t req; + char *contents; + int allocated; + unsigned len; + unsigned size; + unsigned needlen; + int filled; + uint64_t fh; + int error; + fuse_ino_t nodeid; +}; + +/* old dir handle */ +struct fuse_dirhandle { + fuse_fill_dir_t filler; + void *buf; +}; + +struct fuse_context_i { + struct fuse_context ctx; + fuse_req_t req; +}; + +static pthread_key_t fuse_context_key; +static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER; +static int fuse_context_ref; + +#ifdef USE_MODULES +static struct fusemod_so *fuse_current_so; +static struct fuse_module *fuse_modules; + +static int fuse_load_so_name(const char *soname) +{ + struct fusemod_so *so; + + so = calloc(1, sizeof(struct fusemod_so)); + if (!so) { + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; + } + + fuse_current_so = so; + so->handle = dlopen(soname, RTLD_NOW); + fuse_current_so = NULL; + if (!so->handle) { + fprintf(stderr, "fuse: %s\n", dlerror()); + goto err; + } + if (!so->ctr) { + fprintf(stderr, "fuse: %s did not register any modules\n", + soname); + goto err; + } + return 0; + +err: + if (so->handle) + dlclose(so->handle); + free(so); + return -1; +} + +static int fuse_load_so_module(const char *module) +{ + int res; + char *soname = malloc(strlen(module) + 64); + if (!soname) { + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; + } + sprintf(soname, "libfusemod_%s.so", module); + res = fuse_load_so_name(soname); + free(soname); + return res; +} + +static struct fuse_module *fuse_find_module(const char *module) +{ + struct fuse_module *m; + for (m = fuse_modules; m; m = m->next) { + if (strcmp(module, m->name) == 0) { + m->ctr++; + break; + } + } + return m; +} + +static struct fuse_module *fuse_get_module(const char *module) +{ + struct fuse_module *m; + + pthread_mutex_lock(&fuse_context_lock); + m = fuse_find_module(module); + if (!m) { + int err = fuse_load_so_module(module); + if (!err) + m = fuse_find_module(module); + } + pthread_mutex_unlock(&fuse_context_lock); + return m; +} + +static void fuse_put_module(struct fuse_module *m) +{ + pthread_mutex_lock(&fuse_context_lock); + assert(m->ctr > 0); + m->ctr--; + if (!m->ctr && m->so) { + struct fusemod_so *so = m->so; + assert(so->ctr > 0); + so->ctr--; + if (!so->ctr) { + struct fuse_module **mp; + for (mp = &fuse_modules; *mp;) { + if ((*mp)->so == so) + *mp = (*mp)->next; + else + mp = &(*mp)->next; + } + dlclose(so->handle); + free(so); + } + } + pthread_mutex_unlock(&fuse_context_lock); +} +#endif + +static void init_list_head(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static void list_add(struct list_head *new, struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add_head(struct list_head *new, struct list_head *head) +{ + list_add(new, head, head->next); +} + +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + list_add(new, head->prev, head); +} + +static inline void list_del(struct list_head *entry) +{ + struct list_head *prev = entry->prev; + struct list_head *next = entry->next; + + next->prev = prev; + prev->next = next; +} + +static inline int lru_enabled(struct fuse *f) +{ + return f->conf.remember > 0; +} + +static struct node_lru *node_lru(struct node *node) +{ + return (struct node_lru *) node; +} + +static size_t get_node_size(struct fuse *f) +{ + if (lru_enabled(f)) + return sizeof(struct node_lru); + else + return sizeof(struct node); +} + +#ifdef FUSE_NODE_SLAB +static struct node_slab *list_to_slab(struct list_head *head) +{ + return (struct node_slab *) head; +} + +static struct node_slab *node_to_slab(struct fuse *f, struct node *node) +{ + return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1)); +} + +static int alloc_slab(struct fuse *f) +{ + void *mem; + struct node_slab *slab; + char *start; + size_t num; + size_t i; + size_t node_size = get_node_size(f); + + mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (mem == MAP_FAILED) + return -1; + + slab = mem; + init_list_head(&slab->freelist); + slab->used = 0; + num = (f->pagesize - sizeof(struct node_slab)) / node_size; + + start = (char *) mem + f->pagesize - num * node_size; + for (i = 0; i < num; i++) { + struct list_head *n; + + n = (struct list_head *) (start + i * node_size); + list_add_tail(n, &slab->freelist); + } + list_add_tail(&slab->list, &f->partial_slabs); + + return 0; +} + +static struct node *alloc_node(struct fuse *f) +{ + struct node_slab *slab; + struct list_head *node; + + if (list_empty(&f->partial_slabs)) { + int res = alloc_slab(f); + if (res != 0) + return NULL; + } + slab = list_to_slab(f->partial_slabs.next); + slab->used++; + node = slab->freelist.next; + list_del(node); + if (list_empty(&slab->freelist)) { + list_del(&slab->list); + list_add_tail(&slab->list, &f->full_slabs); + } + memset(node, 0, sizeof(struct node)); + + return (struct node *) node; +} + +static void free_slab(struct fuse *f, struct node_slab *slab) +{ + int res; + + list_del(&slab->list); + res = munmap(slab, f->pagesize); + if (res == -1) + fprintf(stderr, "fuse warning: munmap(%p) failed\n", slab); +} + +static void free_node_mem(struct fuse *f, struct node *node) +{ + struct node_slab *slab = node_to_slab(f, node); + struct list_head *n = (struct list_head *) node; + + slab->used--; + if (slab->used) { + if (list_empty(&slab->freelist)) { + list_del(&slab->list); + list_add_tail(&slab->list, &f->partial_slabs); + } + list_add_head(n, &slab->freelist); + } else { + free_slab(f, slab); + } +} +#else +static struct node *alloc_node(struct fuse *f) +{ + return (struct node *) calloc(1, get_node_size(f)); +} + +static void free_node_mem(struct fuse *f, struct node *node) +{ + (void) f; + free(node); +} +#endif + +static size_t id_hash(struct fuse *f, fuse_ino_t ino) +{ + uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size; + uint64_t oldhash = hash % (f->id_table.size / 2); + + if (oldhash >= f->id_table.split) + return oldhash; + else + return hash; +} + +static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid) +{ + size_t hash = id_hash(f, nodeid); + struct node *node; + + for (node = f->id_table.array[hash]; node != NULL; node = node->id_next) + if (node->nodeid == nodeid) + return node; + + return NULL; +} + +static struct node *get_node(struct fuse *f, fuse_ino_t nodeid) +{ + struct node *node = get_node_nocheck(f, nodeid); + if (!node) { + fprintf(stderr, "fuse internal error: node %llu not found\n", + (unsigned long long) nodeid); + abort(); + } + return node; +} + +static void curr_time(struct timespec *now); +static double diff_timespec(const struct timespec *t1, + const struct timespec *t2); + +static void remove_node_lru(struct node *node) +{ + struct node_lru *lnode = node_lru(node); + list_del(&lnode->lru); + init_list_head(&lnode->lru); +} + +static void set_forget_time(struct fuse *f, struct node *node) +{ + struct node_lru *lnode = node_lru(node); + + list_del(&lnode->lru); + list_add_tail(&lnode->lru, &f->lru_table); + curr_time(&lnode->forget_time); +} + +static void free_node(struct fuse *f, struct node *node) +{ + if (node->name != node->inline_name) + free(node->name); + free_node_mem(f, node); +} + +static void node_table_reduce(struct node_table *t) +{ + size_t newsize = t->size / 2; + void *newarray; + + if (newsize < NODE_TABLE_MIN_SIZE) + return; + + newarray = realloc(t->array, sizeof(struct node *) * newsize); + if (newarray != NULL) + t->array = newarray; + + t->size = newsize; + t->split = t->size / 2; +} + +static void remerge_id(struct fuse *f) +{ + struct node_table *t = &f->id_table; + int iter; + + if (t->split == 0) + node_table_reduce(t); + + for (iter = 8; t->split > 0 && iter; iter--) { + struct node **upper; + + t->split--; + upper = &t->array[t->split + t->size / 2]; + if (*upper) { + struct node **nodep; + + for (nodep = &t->array[t->split]; *nodep; + nodep = &(*nodep)->id_next); + + *nodep = *upper; + *upper = NULL; + break; + } + } +} + +static void unhash_id(struct fuse *f, struct node *node) +{ + struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)]; + + for (; *nodep != NULL; nodep = &(*nodep)->id_next) + if (*nodep == node) { + *nodep = node->id_next; + f->id_table.use--; + + if(f->id_table.use < f->id_table.size / 4) + remerge_id(f); + return; + } +} + +static int node_table_resize(struct node_table *t) +{ + size_t newsize = t->size * 2; + void *newarray; + + newarray = realloc(t->array, sizeof(struct node *) * newsize); + if (newarray == NULL) + return -1; + + t->array = newarray; + memset(t->array + t->size, 0, t->size * sizeof(struct node *)); + t->size = newsize; + t->split = 0; + + return 0; +} + +static void rehash_id(struct fuse *f) +{ + struct node_table *t = &f->id_table; + struct node **nodep; + struct node **next; + size_t hash; + + if (t->split == t->size / 2) + return; + + hash = t->split; + t->split++; + for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) { + struct node *node = *nodep; + size_t newhash = id_hash(f, node->nodeid); + + if (newhash != hash) { + next = nodep; + *nodep = node->id_next; + node->id_next = t->array[newhash]; + t->array[newhash] = node; + } else { + next = &node->id_next; + } + } + if (t->split == t->size / 2) + node_table_resize(t); +} + +static void hash_id(struct fuse *f, struct node *node) +{ + size_t hash = id_hash(f, node->nodeid); + node->id_next = f->id_table.array[hash]; + f->id_table.array[hash] = node; + f->id_table.use++; + + if (f->id_table.use >= f->id_table.size / 2) + rehash_id(f); +} + +static size_t name_hash(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + uint64_t hash = parent; + uint64_t oldhash; + + for (; *name; name++) + hash = hash * 31 + (unsigned char) *name; + + hash %= f->name_table.size; + oldhash = hash % (f->name_table.size / 2); + if (oldhash >= f->name_table.split) + return oldhash; + else + return hash; +} + +static void unref_node(struct fuse *f, struct node *node); + +static void remerge_name(struct fuse *f) +{ + struct node_table *t = &f->name_table; + int iter; + + if (t->split == 0) + node_table_reduce(t); + + for (iter = 8; t->split > 0 && iter; iter--) { + struct node **upper; + + t->split--; + upper = &t->array[t->split + t->size / 2]; + if (*upper) { + struct node **nodep; + + for (nodep = &t->array[t->split]; *nodep; + nodep = &(*nodep)->name_next); + + *nodep = *upper; + *upper = NULL; + break; + } + } +} + +static void unhash_name(struct fuse *f, struct node *node) +{ + if (node->name) { + size_t hash = name_hash(f, node->parent->nodeid, node->name); + struct node **nodep = &f->name_table.array[hash]; + + for (; *nodep != NULL; nodep = &(*nodep)->name_next) + if (*nodep == node) { + *nodep = node->name_next; + node->name_next = NULL; + unref_node(f, node->parent); + if (node->name != node->inline_name) + free(node->name); + node->name = NULL; + node->parent = NULL; + f->name_table.use--; + + if (f->name_table.use < f->name_table.size / 4) + remerge_name(f); + return; + } + fprintf(stderr, + "fuse internal error: unable to unhash node: %llu\n", + (unsigned long long) node->nodeid); + abort(); + } +} + +static void rehash_name(struct fuse *f) +{ + struct node_table *t = &f->name_table; + struct node **nodep; + struct node **next; + size_t hash; + + if (t->split == t->size / 2) + return; + + hash = t->split; + t->split++; + for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) { + struct node *node = *nodep; + size_t newhash = name_hash(f, node->parent->nodeid, node->name); + + if (newhash != hash) { + next = nodep; + *nodep = node->name_next; + node->name_next = t->array[newhash]; + t->array[newhash] = node; + } else { + next = &node->name_next; + } + } + if (t->split == t->size / 2) + node_table_resize(t); +} + +static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid, + const char *name) +{ + size_t hash = name_hash(f, parentid, name); + struct node *parent = get_node(f, parentid); + if (strlen(name) < sizeof(node->inline_name)) { + strcpy(node->inline_name, name); + node->name = node->inline_name; + } else { + node->name = strdup(name); + if (node->name == NULL) + return -1; + } + + parent->refctr ++; + node->parent = parent; + node->name_next = f->name_table.array[hash]; + f->name_table.array[hash] = node; + f->name_table.use++; + + if (f->name_table.use >= f->name_table.size / 2) + rehash_name(f); + + return 0; +} + +static void delete_node(struct fuse *f, struct node *node) +{ + if (f->conf.debug) + fprintf(stderr, "DELETE: %llu\n", + (unsigned long long) node->nodeid); + + assert(node->treelock == 0); + unhash_name(f, node); + if (lru_enabled(f)) + remove_node_lru(node); + unhash_id(f, node); + free_node(f, node); +} + +static void unref_node(struct fuse *f, struct node *node) +{ + assert(node->refctr > 0); + node->refctr --; + if (!node->refctr) + delete_node(f, node); +} + +static fuse_ino_t next_id(struct fuse *f) +{ + do { + f->ctr = (f->ctr + 1) & 0xffffffff; + if (!f->ctr) + f->generation ++; + } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO || + get_node_nocheck(f, f->ctr) != NULL); + return f->ctr; +} + +static struct node *lookup_node(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + size_t hash = name_hash(f, parent, name); + struct node *node; + + for (node = f->name_table.array[hash]; node != NULL; node = node->name_next) + if (node->parent->nodeid == parent && + strcmp(node->name, name) == 0) + return node; + + return NULL; +} + +static void inc_nlookup(struct node *node) +{ + if (!node->nlookup) + node->refctr++; + node->nlookup++; +} + +static struct node *find_node(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + if (!name) + node = get_node(f, parent); + else + node = lookup_node(f, parent, name); + if (node == NULL) { + node = alloc_node(f); + if (node == NULL) + goto out_err; + + node->nodeid = next_id(f); + node->generation = f->generation; + if (f->conf.remember) + inc_nlookup(node); + + if (hash_name(f, node, parent, name) == -1) { + free_node(f, node); + node = NULL; + goto out_err; + } + hash_id(f, node); + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(node); + init_list_head(&lnode->lru); + } + } else if (lru_enabled(f) && node->nlookup == 1) { + remove_node_lru(node); + } + inc_nlookup(node); +out_err: + pthread_mutex_unlock(&f->lock); + return node; +} + +static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name) +{ + size_t len = strlen(name); + + if (s - len <= *buf) { + unsigned pathlen = *bufsize - (s - *buf); + unsigned newbufsize = *bufsize; + char *newbuf; + + while (newbufsize < pathlen + len + 1) { + if (newbufsize >= 0x80000000) + newbufsize = 0xffffffff; + else + newbufsize *= 2; + } + + newbuf = realloc(*buf, newbufsize); + if (newbuf == NULL) + return NULL; + + *buf = newbuf; + s = newbuf + newbufsize - pathlen; + memmove(s, newbuf + *bufsize - pathlen, pathlen); + *bufsize = newbufsize; + } + s -= len; + strncpy(s, name, len); + s--; + *s = '/'; + + return s; +} + +static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode, + struct node *end) +{ + struct node *node; + + if (wnode) { + assert(wnode->treelock == TREELOCK_WRITE); + wnode->treelock = 0; + } + + for (node = get_node(f, nodeid); + node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) { + assert(node->treelock != 0); + assert(node->treelock != TREELOCK_WAIT_OFFSET); + assert(node->treelock != TREELOCK_WRITE); + node->treelock--; + if (node->treelock == TREELOCK_WAIT_OFFSET) + node->treelock = 0; + } +} + +static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnodep, bool need_lock) +{ + unsigned bufsize = 256; + char *buf; + char *s; + struct node *node; + struct node *wnode = NULL; + int err; + + *path = NULL; + + err = -ENOMEM; + buf = malloc(bufsize); + if (buf == NULL) + goto out_err; + + s = buf + bufsize - 1; + *s = '\0'; + + if (name != NULL) { + s = add_name(&buf, &bufsize, s, name); + err = -ENOMEM; + if (s == NULL) + goto out_free; + } + + if (wnodep) { + assert(need_lock); + wnode = lookup_node(f, nodeid, name); + if (wnode) { + if (wnode->treelock != 0) { + if (wnode->treelock > 0) + wnode->treelock += TREELOCK_WAIT_OFFSET; + err = -EAGAIN; + goto out_free; + } + wnode->treelock = TREELOCK_WRITE; + } + } + + for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + err = -ENOENT; + if (node->name == NULL || node->parent == NULL) + goto out_unlock; + + err = -ENOMEM; + s = add_name(&buf, &bufsize, s, node->name); + if (s == NULL) + goto out_unlock; + + if (need_lock) { + err = -EAGAIN; + if (node->treelock < 0) + goto out_unlock; + + node->treelock++; + } + } + + if (s[0]) + memmove(buf, s, bufsize - (s - buf)); + else + strcpy(buf, "/"); + + *path = buf; + if (wnodep) + *wnodep = wnode; + + return 0; + + out_unlock: + if (need_lock) + unlock_path(f, nodeid, wnode, node); + out_free: + free(buf); + + out_err: + return err; +} + +static void queue_element_unlock(struct fuse *f, struct lock_queue_element *qe) +{ + struct node *wnode; + + if (qe->first_locked) { + wnode = qe->wnode1 ? *qe->wnode1 : NULL; + unlock_path(f, qe->nodeid1, wnode, NULL); + qe->first_locked = false; + } + if (qe->second_locked) { + wnode = qe->wnode2 ? *qe->wnode2 : NULL; + unlock_path(f, qe->nodeid2, wnode, NULL); + qe->second_locked = false; + } +} + +static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe) +{ + int err; + bool first = (qe == f->lockq); + + if (!qe->path1) { + /* Just waiting for it to be unlocked */ + if (get_node(f, qe->nodeid1)->treelock == 0) + pthread_cond_signal(&qe->cond); + + return; + } + + if (!qe->first_locked) { + err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1, + qe->wnode1, true); + if (!err) + qe->first_locked = true; + else if (err != -EAGAIN) + goto err_unlock; + } + if (!qe->second_locked && qe->path2) { + err = try_get_path(f, qe->nodeid2, qe->name2, qe->path2, + qe->wnode2, true); + if (!err) + qe->second_locked = true; + else if (err != -EAGAIN) + goto err_unlock; + } + + if (qe->first_locked && (qe->second_locked || !qe->path2)) { + err = 0; + goto done; + } + + /* + * Only let the first element be partially locked otherwise there could + * be a deadlock. + * + * But do allow the first element to be partially locked to prevent + * starvation. + */ + if (!first) + queue_element_unlock(f, qe); + + /* keep trying */ + return; + +err_unlock: + queue_element_unlock(f, qe); +done: + qe->err = err; + qe->done = true; + pthread_cond_signal(&qe->cond); +} + +static void wake_up_queued(struct fuse *f) +{ + struct lock_queue_element *qe; + + for (qe = f->lockq; qe != NULL; qe = qe->next) + queue_element_wakeup(f, qe); +} + +static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid, + const char *name, bool wr) +{ + if (f->conf.debug) { + struct node *wnode = NULL; + + if (wr) + wnode = lookup_node(f, nodeid, name); + + if (wnode) + fprintf(stderr, "%s %li (w)\n", msg, wnode->nodeid); + else + fprintf(stderr, "%s %li\n", msg, nodeid); + } +} + +static void queue_path(struct fuse *f, struct lock_queue_element *qe) +{ + struct lock_queue_element **qp; + + qe->done = false; + qe->first_locked = false; + qe->second_locked = false; + pthread_cond_init(&qe->cond, NULL); + qe->next = NULL; + for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next); + *qp = qe; +} + +static void dequeue_path(struct fuse *f, struct lock_queue_element *qe) +{ + struct lock_queue_element **qp; + + pthread_cond_destroy(&qe->cond); + for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next); + *qp = qe->next; +} + +static int wait_path(struct fuse *f, struct lock_queue_element *qe) +{ + queue_path(f, qe); + + do { + pthread_cond_wait(&qe->cond, &f->lock); + } while (!qe->done); + + dequeue_path(f, qe); + + return qe->err; +} + +static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnode) +{ + int err; + + pthread_mutex_lock(&f->lock); + err = try_get_path(f, nodeid, name, path, wnode, true); + if (err == -EAGAIN) { + struct lock_queue_element qe = { + .nodeid1 = nodeid, + .name1 = name, + .path1 = path, + .wnode1 = wnode, + }; + debug_path(f, "QUEUE PATH", nodeid, name, !!wnode); + err = wait_path(f, &qe); + debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode); + } + pthread_mutex_unlock(&f->lock); + + return err; +} + +static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path) +{ + return get_path_common(f, nodeid, NULL, path, NULL); +} + +static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path) +{ + int err = 0; + + if (f->conf.nopath) { + *path = NULL; + } else { + err = get_path_common(f, nodeid, NULL, path, NULL); + if (err == -ENOENT && f->nullpath_ok) + err = 0; + } + + return err; +} + +static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path) +{ + return get_path_common(f, nodeid, name, path, NULL); +} + +static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnode) +{ + return get_path_common(f, nodeid, name, path, wnode); +} + +static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2, + char **path1, char **path2, + struct node **wnode1, struct node **wnode2) +{ + int err; + + /* FIXME: locking two paths needs deadlock checking */ + err = try_get_path(f, nodeid1, name1, path1, wnode1, true); + if (!err) { + err = try_get_path(f, nodeid2, name2, path2, wnode2, true); + if (err) { + struct node *wn1 = wnode1 ? *wnode1 : NULL; + + unlock_path(f, nodeid1, wn1, NULL); + free(*path1); + } + } + return err; +} + +static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2, + char **path1, char **path2, + struct node **wnode1, struct node **wnode2) +{ + int err; + + pthread_mutex_lock(&f->lock); + err = try_get_path2(f, nodeid1, name1, nodeid2, name2, + path1, path2, wnode1, wnode2); + if (err == -EAGAIN) { + struct lock_queue_element qe = { + .nodeid1 = nodeid1, + .name1 = name1, + .path1 = path1, + .wnode1 = wnode1, + .nodeid2 = nodeid2, + .name2 = name2, + .path2 = path2, + .wnode2 = wnode2, + }; + + debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1); + debug_path(f, " PATH2", nodeid2, name2, !!wnode2); + err = wait_path(f, &qe); + debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1); + debug_path(f, " PATH2", nodeid2, name2, !!wnode2); + } + pthread_mutex_unlock(&f->lock); + + return err; +} + +static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid, + struct node *wnode, char *path) +{ + pthread_mutex_lock(&f->lock); + unlock_path(f, nodeid, wnode, NULL); + if (f->lockq) + wake_up_queued(f); + pthread_mutex_unlock(&f->lock); + free(path); +} + +static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path) +{ + if (path) + free_path_wrlock(f, nodeid, NULL, path); +} + +static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2, + struct node *wnode1, struct node *wnode2, + char *path1, char *path2) +{ + pthread_mutex_lock(&f->lock); + unlock_path(f, nodeid1, wnode1, NULL); + unlock_path(f, nodeid2, wnode2, NULL); + wake_up_queued(f); + pthread_mutex_unlock(&f->lock); + free(path1); + free(path2); +} + +static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup) +{ + struct node *node; + if (nodeid == FUSE_ROOT_ID) + return; + pthread_mutex_lock(&f->lock); + node = get_node(f, nodeid); + + /* + * Node may still be locked due to interrupt idiocy in open, + * create and opendir + */ + while (node->nlookup == nlookup && node->treelock) { + struct lock_queue_element qe = { + .nodeid1 = nodeid, + }; + + debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false); + queue_path(f, &qe); + + do { + pthread_cond_wait(&qe.cond, &f->lock); + } while (node->nlookup == nlookup && node->treelock); + + dequeue_path(f, &qe); + debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false); + } + + assert(node->nlookup >= nlookup); + node->nlookup -= nlookup; + if (!node->nlookup) { + unref_node(f, node); + } else if (lru_enabled(f) && node->nlookup == 1) { + set_forget_time(f, node); + } + pthread_mutex_unlock(&f->lock); +} + +static void unlink_node(struct fuse *f, struct node *node) +{ + if (f->conf.remember) { + assert(node->nlookup > 1); + node->nlookup--; + } + unhash_name(f, node); +} + +static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, name); + if (node != NULL) + unlink_node(f, node); + pthread_mutex_unlock(&f->lock); +} + +static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname, + fuse_ino_t newdir, const char *newname, int hide) +{ + struct node *node; + struct node *newnode; + int err = 0; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, olddir, oldname); + newnode = lookup_node(f, newdir, newname); + if (node == NULL) + goto out; + + if (newnode != NULL) { + if (hide) { + fprintf(stderr, "fuse: hidden file got created during hiding\n"); + err = -EBUSY; + goto out; + } + unlink_node(f, newnode); + } + + unhash_name(f, node); + if (hash_name(f, node, newdir, newname) == -1) { + err = -ENOMEM; + goto out; + } + + if (hide) + node->is_hidden = 1; + +out: + pthread_mutex_unlock(&f->lock); + return err; +} + +static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf) +{ + if (!f->conf.use_ino) + stbuf->st_ino = nodeid; + if (f->conf.set_mode) + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.umask); + if (f->conf.set_uid) + stbuf->st_uid = f->conf.uid; + if (f->conf.set_gid) + stbuf->st_gid = f->conf.gid; +} + +static struct fuse *req_fuse(fuse_req_t req) +{ + return (struct fuse *) fuse_req_userdata(req); +} + +static void fuse_intr_sighandler(int sig) +{ + (void) sig; + /* Nothing to do */ +} + +struct fuse_intr_data { + pthread_t id; + pthread_cond_t cond; + int finished; +}; + +static void fuse_interrupt(fuse_req_t req, void *d_) +{ + struct fuse_intr_data *d = d_; + struct fuse *f = req_fuse(req); + + if (d->id == pthread_self()) + return; + + pthread_mutex_lock(&f->lock); + while (!d->finished) { + struct timeval now; + struct timespec timeout; + + pthread_kill(d->id, f->conf.intr_signal); + gettimeofday(&now, NULL); + timeout.tv_sec = now.tv_sec + 1; + timeout.tv_nsec = now.tv_usec * 1000; + pthread_cond_timedwait(&d->cond, &f->lock, &timeout); + } + pthread_mutex_unlock(&f->lock); +} + +static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + pthread_mutex_lock(&f->lock); + d->finished = 1; + pthread_cond_broadcast(&d->cond); + pthread_mutex_unlock(&f->lock); + fuse_req_interrupt_func(req, NULL, NULL); + pthread_cond_destroy(&d->cond); +} + +static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d) +{ + d->id = pthread_self(); + pthread_cond_init(&d->cond, NULL); + d->finished = 0; + fuse_req_interrupt_func(req, fuse_interrupt, d); +} + +static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + if (f->conf.intr) + fuse_do_finish_interrupt(f, req, d); +} + +static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + if (f->conf.intr) + fuse_do_prepare_interrupt(req, d); +} + +#if !defined(__FreeBSD__) && !defined(__NetBSD__) + +static int fuse_compat_open(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + int err; + if (!fs->compat || fs->compat >= 25) + err = fs->op.open(path, fi); + else if (fs->compat == 22) { + struct fuse_file_info_compat tmp; + memcpy(&tmp, fi, sizeof(tmp)); + err = ((struct fuse_operations_compat22 *) &fs->op)->open(path, + &tmp); + memcpy(fi, &tmp, sizeof(tmp)); + fi->fh = tmp.fh; + } else + err = ((struct fuse_operations_compat2 *) &fs->op) + ->open(path, fi->flags); + return err; +} + +static int fuse_compat_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + if (!fs->compat || fs->compat >= 22) + return fs->op.release(path, fi); + else + return ((struct fuse_operations_compat2 *) &fs->op) + ->release(path, fi->flags); +} + +static int fuse_compat_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + if (!fs->compat || fs->compat >= 25) + return fs->op.opendir(path, fi); + else { + int err; + struct fuse_file_info_compat tmp; + memcpy(&tmp, fi, sizeof(tmp)); + err = ((struct fuse_operations_compat22 *) &fs->op) + ->opendir(path, &tmp); + memcpy(fi, &tmp, sizeof(tmp)); + fi->fh = tmp.fh; + return err; + } +} + +static void convert_statfs_compat(struct fuse_statfs_compat1 *compatbuf, + struct statvfs *stbuf) +{ + stbuf->f_bsize = compatbuf->block_size; + stbuf->f_blocks = compatbuf->blocks; + stbuf->f_bfree = compatbuf->blocks_free; + stbuf->f_bavail = compatbuf->blocks_free; + stbuf->f_files = compatbuf->files; + stbuf->f_ffree = compatbuf->files_free; + stbuf->f_namemax = compatbuf->namelen; +} + +static void convert_statfs_old(struct statfs *oldbuf, struct statvfs *stbuf) +{ + stbuf->f_bsize = oldbuf->f_bsize; + stbuf->f_blocks = oldbuf->f_blocks; + stbuf->f_bfree = oldbuf->f_bfree; + stbuf->f_bavail = oldbuf->f_bavail; + stbuf->f_files = oldbuf->f_files; + stbuf->f_ffree = oldbuf->f_ffree; + stbuf->f_namemax = oldbuf->f_namelen; +} + +static int fuse_compat_statfs(struct fuse_fs *fs, const char *path, + struct statvfs *buf) +{ + int err; + + if (!fs->compat || fs->compat >= 25) { + err = fs->op.statfs(fs->compat == 25 ? "/" : path, buf); + } else if (fs->compat > 11) { + struct statfs oldbuf; + err = ((struct fuse_operations_compat22 *) &fs->op) + ->statfs("/", &oldbuf); + if (!err) + convert_statfs_old(&oldbuf, buf); + } else { + struct fuse_statfs_compat1 compatbuf; + memset(&compatbuf, 0, sizeof(struct fuse_statfs_compat1)); + err = ((struct fuse_operations_compat1 *) &fs->op) + ->statfs(&compatbuf); + if (!err) + convert_statfs_compat(&compatbuf, buf); + } + return err; +} + +#else /* __FreeBSD__ || __NetBSD__ */ + +static inline int fuse_compat_open(struct fuse_fs *fs, char *path, + struct fuse_file_info *fi) +{ + return fs->op.open(path, fi); +} + +static inline int fuse_compat_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + return fs->op.release(path, fi); +} + +static inline int fuse_compat_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + return fs->op.opendir(path, fi); +} + +static inline int fuse_compat_statfs(struct fuse_fs *fs, const char *path, + struct statvfs *buf) +{ + return fs->op.statfs(fs->compat == 25 ? "/" : path, buf); +} + +#endif /* __FreeBSD__ || __NetBSD__ */ + +int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.getattr) { + if (fs->debug) + fprintf(stderr, "getattr %s\n", path); + + return fs->op.getattr(path, buf); + } else { + return -ENOSYS; + } +} + +int fuse_fs_fgetattr(struct fuse_fs *fs, const char *path, struct stat *buf, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.fgetattr) { + if (fs->debug) + fprintf(stderr, "fgetattr[%llu] %s\n", + (unsigned long long) fi->fh, path); + + return fs->op.fgetattr(path, buf, fi); + } else if (path && fs->op.getattr) { + if (fs->debug) + fprintf(stderr, "getattr %s\n", path); + + return fs->op.getattr(path, buf); + } else { + return -ENOSYS; + } +} + +int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, + const char *newpath) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.rename) { + if (fs->debug) + fprintf(stderr, "rename %s %s\n", oldpath, newpath); + + return fs->op.rename(oldpath, newpath); + } else { + return -ENOSYS; + } +} + +int fuse_fs_unlink(struct fuse_fs *fs, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.unlink) { + if (fs->debug) + fprintf(stderr, "unlink %s\n", path); + + return fs->op.unlink(path); + } else { + return -ENOSYS; + } +} + +int fuse_fs_rmdir(struct fuse_fs *fs, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.rmdir) { + if (fs->debug) + fprintf(stderr, "rmdir %s\n", path); + + return fs->op.rmdir(path); + } else { + return -ENOSYS; + } +} + +int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.symlink) { + if (fs->debug) + fprintf(stderr, "symlink %s %s\n", linkname, path); + + return fs->op.symlink(linkname, path); + } else { + return -ENOSYS; + } +} + +int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.link) { + if (fs->debug) + fprintf(stderr, "link %s %s\n", oldpath, newpath); + + return fs->op.link(oldpath, newpath); + } else { + return -ENOSYS; + } +} + +int fuse_fs_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.release) { + if (fs->debug) + fprintf(stderr, "release%s[%llu] flags: 0x%x\n", + fi->flush ? "+flush" : "", + (unsigned long long) fi->fh, fi->flags); + + return fuse_compat_release(fs, path, fi); + } else { + return 0; + } +} + +int fuse_fs_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.opendir) { + int err; + + if (fs->debug) + fprintf(stderr, "opendir flags: 0x%x %s\n", fi->flags, + path); + + err = fuse_compat_opendir(fs, path, fi); + + if (fs->debug && !err) + fprintf(stderr, " opendir[%lli] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; + } else { + return 0; + } +} + +int fuse_fs_open(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.open) { + int err; + + if (fs->debug) + fprintf(stderr, "open flags: 0x%x %s\n", fi->flags, + path); + + err = fuse_compat_open(fs, path, fi); + + if (fs->debug && !err) + fprintf(stderr, " open[%lli] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; + } else { + return 0; + } +} + +static void fuse_free_buf(struct fuse_bufvec *buf) +{ + if (buf != NULL) { + size_t i; + + for (i = 0; i < buf->count; i++) + free(buf->buf[i].mem); + free(buf); + } +} + +int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec **bufp, size_t size, loff_t off, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.read || fs->op.read_buf) { + int res; + + if (fs->debug) + fprintf(stderr, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); + + if (fs->op.read_buf) { + res = fs->op.read_buf(path, bufp, size, off, fi); + } else { + struct fuse_bufvec *buf; + void *mem; + + buf = malloc(sizeof(struct fuse_bufvec)); + if (buf == NULL) + return -ENOMEM; + + mem = malloc(size); + if (mem == NULL) { + free(buf); + return -ENOMEM; + } + *buf = FUSE_BUFVEC_INIT(size); + buf->buf[0].mem = mem; + *bufp = buf; + + res = fs->op.read(path, mem, size, off, fi); + if (res >= 0) + buf->buf[0].size = res; + } + + if (fs->debug && res >= 0) + fprintf(stderr, " read[%llu] %zu bytes from %llu\n", + (unsigned long long) fi->fh, + fuse_buf_size(*bufp), + (unsigned long long) off); + if (res >= 0 && fuse_buf_size(*bufp) > (int) size) + fprintf(stderr, "fuse: read too many bytes\n"); + + if (res < 0) + return res; + + return 0; + } else { + return -ENOSYS; + } +} + +int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size, + loff_t off, struct fuse_file_info *fi) +{ + int res; + struct fuse_bufvec *buf = NULL; + + res = fuse_fs_read_buf(fs, path, &buf, size, off, fi); + if (res == 0) { + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size); + + dst.buf[0].mem = mem; + res = fuse_buf_copy(&dst, buf, 0); + } + fuse_free_buf(buf); + + return res; +} + +int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec *buf, loff_t off, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.write_buf || fs->op.write) { + int res; + size_t size = fuse_buf_size(buf); + + assert(buf->idx == 0 && buf->off == 0); + if (fs->debug) + fprintf(stderr, + "write%s[%llu] %zu bytes to %llu flags: 0x%x\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, + size, + (unsigned long long) off, + fi->flags); + + if (fs->op.write_buf) { + res = fs->op.write_buf(path, buf, off, fi); + } else { + void *mem = NULL; + struct fuse_buf *flatbuf; + struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size); + + if (buf->count == 1 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + flatbuf = &buf->buf[0]; + } else { + res = -ENOMEM; + mem = malloc(size); + if (mem == NULL) + goto out; + + tmp.buf[0].mem = mem; + res = fuse_buf_copy(&tmp, buf, 0); + if (res <= 0) + goto out_free; + + tmp.buf[0].size = res; + flatbuf = &tmp.buf[0]; + } + + res = fs->op.write(path, flatbuf->mem, flatbuf->size, + off, fi); +out_free: + free(mem); + } +out: + if (fs->debug && res >= 0) + fprintf(stderr, " write%s[%llu] %u bytes to %llu\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, res, + (unsigned long long) off); + if (res > (int) size) + fprintf(stderr, "fuse: wrote too many bytes\n"); + + return res; + } else { + return -ENOSYS; + } +} + +int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem, + size_t size, loff_t off, struct fuse_file_info *fi) +{ + struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size); + + bufv.buf[0].mem = (void *) mem; + + return fuse_fs_write_buf(fs, path, &bufv, off, fi); +} + +int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.fsync) { + if (fs->debug) + fprintf(stderr, "fsync[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsync(path, datasync, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.fsyncdir) { + if (fs->debug) + fprintf(stderr, "fsyncdir[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsyncdir(path, datasync, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_flush(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.flush) { + if (fs->debug) + fprintf(stderr, "flush[%llu]\n", + (unsigned long long) fi->fh); + + return fs->op.flush(path, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.statfs) { + if (fs->debug) + fprintf(stderr, "statfs %s\n", path); + + return fuse_compat_statfs(fs, path, buf); + } else { + buf->f_namemax = 255; + buf->f_bsize = 512; + return 0; + } +} + +int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.releasedir) { + if (fs->debug) + fprintf(stderr, "releasedir[%llu] flags: 0x%x\n", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.releasedir(path, fi); + } else { + return 0; + } +} + +static int fill_dir_old(struct fuse_dirhandle *dh, const char *name, int type, + ino_t ino) +{ + int res; + struct stat stbuf; + + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_mode = type << 12; + stbuf.st_ino = ino; + + res = dh->filler(dh->buf, name, &stbuf, 0); + return res ? -ENOMEM : 0; +} + +int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, + fuse_fill_dir_t filler, loff_t off, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.readdir) { + if (fs->debug) + fprintf(stderr, "readdir[%llu] from %llu\n", + (unsigned long long) fi->fh, + (unsigned long long) off); + + return fs->op.readdir(path, buf, filler, off, fi); + } else if (fs->op.getdir) { + struct fuse_dirhandle dh; + + if (fs->debug) + fprintf(stderr, "getdir[%llu]\n", + (unsigned long long) fi->fh); + + dh.filler = filler; + dh.buf = buf; + return fs->op.getdir(path, &dh, fill_dir_old); + } else { + return -ENOSYS; + } +} + +int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.create) { + int err; + + if (fs->debug) + fprintf(stderr, + "create flags: 0x%x %s 0%o umask=0%03o\n", + fi->flags, path, mode, + fuse_get_context()->umask); + + err = fs->op.create(path, mode, fi); + + if (fs->debug && !err) + fprintf(stderr, " create[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; + } else { + return -ENOSYS; + } +} + +int fuse_fs_lock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int cmd, struct flock *lock) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.lock) { + if (fs->debug) + fprintf(stderr, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n", + (unsigned long long) fi->fh, + (cmd == F_GETLK ? "F_GETLK" : + (cmd == F_SETLK ? "F_SETLK" : + (cmd == F_SETLKW ? "F_SETLKW" : "???"))), + (lock->l_type == F_RDLCK ? "F_RDLCK" : + (lock->l_type == F_WRLCK ? "F_WRLCK" : + (lock->l_type == F_UNLCK ? "F_UNLCK" : + "???"))), + (unsigned long long) lock->l_start, + (unsigned long long) lock->l_len, + (unsigned long long) lock->l_pid); + + return fs->op.lock(path, fi, cmd, lock); + } else { + return -ENOSYS; + } +} + +int fuse_fs_flock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int op) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.flock) { + if (fs->debug) { + int xop = op & ~LOCK_NB; + + fprintf(stderr, "lock[%llu] %s%s\n", + (unsigned long long) fi->fh, + xop == LOCK_SH ? "LOCK_SH" : + (xop == LOCK_EX ? "LOCK_EX" : + (xop == LOCK_UN ? "LOCK_UN" : "???")), + (op & LOCK_NB) ? "|LOCK_NB" : ""); + } + return fs->op.flock(path, fi, op); + } else { + return -ENOSYS; + } +} + +int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.chown) { + if (fs->debug) + fprintf(stderr, "chown %s %lu %lu\n", path, + (unsigned long) uid, (unsigned long) gid); + + return fs->op.chown(path, uid, gid); + } else { + return -ENOSYS; + } +} + +int fuse_fs_truncate(struct fuse_fs *fs, const char *path, loff_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.truncate) { + if (fs->debug) + fprintf(stderr, "truncate %s %llu\n", path, + (unsigned long long) size); + + return fs->op.truncate(path, size); + } else { + return -ENOSYS; + } +} + +int fuse_fs_ftruncate(struct fuse_fs *fs, const char *path, loff_t size, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.ftruncate) { + if (fs->debug) + fprintf(stderr, "ftruncate[%llu] %llu\n", + (unsigned long long) fi->fh, + (unsigned long long) size); + + return fs->op.ftruncate(path, size, fi); + } else if (path && fs->op.truncate) { + if (fs->debug) + fprintf(stderr, "truncate %s %llu\n", path, + (unsigned long long) size); + + return fs->op.truncate(path, size); + } else { + return -ENOSYS; + } +} + +int fuse_fs_utimens(struct fuse_fs *fs, const char *path, + const struct timespec tv[2]) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.utimens) { + if (fs->debug) + fprintf(stderr, "utimens %s %li.%09lu %li.%09lu\n", + path, tv[0].tv_sec, tv[0].tv_nsec, + tv[1].tv_sec, tv[1].tv_nsec); + + return fs->op.utimens(path, tv); + } else if(fs->op.utime) { + struct utimbuf buf; + + if (fs->debug) + fprintf(stderr, "utime %s %li %li\n", path, + tv[0].tv_sec, tv[1].tv_sec); + + buf.actime = tv[0].tv_sec; + buf.modtime = tv[1].tv_sec; + return fs->op.utime(path, &buf); + } else { + return -ENOSYS; + } +} + +int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.access) { + if (fs->debug) + fprintf(stderr, "access %s 0%o\n", path, mask); + + return fs->op.access(path, mask); + } else { + return -ENOSYS; + } +} + +int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, + size_t len) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.readlink) { + if (fs->debug) + fprintf(stderr, "readlink %s %lu\n", path, + (unsigned long) len); + + return fs->op.readlink(path, buf, len); + } else { + return -ENOSYS; + } +} + +int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, + dev_t rdev) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.mknod) { + if (fs->debug) + fprintf(stderr, "mknod %s 0%o 0x%llx umask=0%03o\n", + path, mode, (unsigned long long) rdev, + fuse_get_context()->umask); + + return fs->op.mknod(path, mode, rdev); + } else { + return -ENOSYS; + } +} + +int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.mkdir) { + if (fs->debug) + fprintf(stderr, "mkdir %s 0%o umask=0%03o\n", + path, mode, fuse_get_context()->umask); + + return fs->op.mkdir(path, mode); + } else { + return -ENOSYS; + } +} + +int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, + const char *value, size_t size, int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.setxattr) { + if (fs->debug) + fprintf(stderr, "setxattr %s %s %lu 0x%x\n", + path, name, (unsigned long) size, flags); + + return fs->op.setxattr(path, name, value, size, flags); + } else { + return -ENOSYS; + } +} + +int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, + char *value, size_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.getxattr) { + if (fs->debug) + fprintf(stderr, "getxattr %s %s %lu\n", + path, name, (unsigned long) size); + + return fs->op.getxattr(path, name, value, size); + } else { + return -ENOSYS; + } +} + +int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, + size_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.listxattr) { + if (fs->debug) + fprintf(stderr, "listxattr %s %lu\n", + path, (unsigned long) size); + + return fs->op.listxattr(path, list, size); + } else { + return -ENOSYS; + } +} + +int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, + uint64_t *idx) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.bmap) { + if (fs->debug) + fprintf(stderr, "bmap %s blocksize: %lu index: %llu\n", + path, (unsigned long) blocksize, + (unsigned long long) *idx); + + return fs->op.bmap(path, blocksize, idx); + } else { + return -ENOSYS; + } +} + +int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.removexattr) { + if (fs->debug) + fprintf(stderr, "removexattr %s %s\n", path, name); + + return fs->op.removexattr(path, name); + } else { + return -ENOSYS; + } +} + +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) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.ioctl) { + if (fs->debug) + fprintf(stderr, "ioctl[%llu] 0x%x flags: 0x%x\n", + (unsigned long long) fi->fh, cmd, flags); + + return fs->op.ioctl(path, cmd, arg, fi, flags, data); + } else + return -ENOSYS; +} + +int fuse_fs_poll(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, struct fuse_pollhandle *ph, + unsigned *reventsp) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.poll) { + int res; + + if (fs->debug) + fprintf(stderr, "poll[%llu] ph: %p\n", + (unsigned long long) fi->fh, ph); + + res = fs->op.poll(path, fi, ph, reventsp); + + if (fs->debug && !res) + fprintf(stderr, " poll[%llu] revents: 0x%x\n", + (unsigned long long) fi->fh, *reventsp); + + return res; + } else + return -ENOSYS; +} + +int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, + loff_t offset, loff_t length, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.fallocate) { + if (fs->debug) + fprintf(stderr, "fallocate %s mode %x, offset: %llu, length: %llu\n", + path, + mode, + (unsigned long long) offset, + (unsigned long long) length); + + return fs->op.fallocate(path, mode, offset, length, fi); + } else + return -ENOSYS; +} + +static int is_open(struct fuse *f, fuse_ino_t dir, const char *name) +{ + struct node *node; + int isopen = 0; + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, name); + if (node && node->open_count > 0) + isopen = 1; + pthread_mutex_unlock(&f->lock); + return isopen; +} + +static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname, + char *newname, size_t bufsize) +{ + struct stat buf; + struct node *node; + struct node *newnode; + char *newpath; + int res; + int failctr = 10; + + do { + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, oldname); + if (node == NULL) { + pthread_mutex_unlock(&f->lock); + return NULL; + } + do { + f->hidectr ++; + snprintf(newname, bufsize, ".fuse_hidden%08x%08x", + (unsigned int) node->nodeid, f->hidectr); + newnode = lookup_node(f, dir, newname); + } while(newnode); + + res = try_get_path(f, dir, newname, &newpath, NULL, false); + pthread_mutex_unlock(&f->lock); + if (res) + break; + + memset(&buf, 0, sizeof(buf)); + res = fuse_fs_getattr(f->fs, newpath, &buf); + if (res == -ENOENT) + break; + free(newpath); + newpath = NULL; + } while(res == 0 && --failctr); + + return newpath; +} + +static int hide_node(struct fuse *f, const char *oldpath, + fuse_ino_t dir, const char *oldname) +{ + char newname[64]; + char *newpath; + int err = -EBUSY; + + newpath = hidden_name(f, dir, oldname, newname, sizeof(newname)); + if (newpath) { + err = fuse_fs_rename(f->fs, oldpath, newpath); + if (!err) + err = rename_node(f, dir, oldname, dir, newname, 1); + free(newpath); + } + return err; +} + +static int mtime_eq(const struct stat *stbuf, const struct timespec *ts) +{ + return stbuf->st_mtime == ts->tv_sec && + ST_MTIM_NSEC(stbuf) == ts->tv_nsec; +} + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC CLOCK_REALTIME +#endif + +static void curr_time(struct timespec *now) +{ + static clockid_t clockid = CLOCK_MONOTONIC; + int res = clock_gettime(clockid, now); + if (res == -1 && errno == EINVAL) { + clockid = CLOCK_REALTIME; + res = clock_gettime(clockid, now); + } + if (res == -1) { + perror("fuse: clock_gettime"); + abort(); + } +} + +static void update_stat(struct node *node, const struct stat *stbuf) +{ + if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) || + stbuf->st_size != node->size)) + node->cache_valid = 0; + node->mtime.tv_sec = stbuf->st_mtime; + node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf); + node->size = stbuf->st_size; + curr_time(&node->stat_updated); +} + +static int lookup_path(struct fuse *f, fuse_ino_t nodeid, + const char *name, const char *path, + struct fuse_entry_param *e, struct fuse_file_info *fi) +{ + int res; + + memset(e, 0, sizeof(struct fuse_entry_param)); + if (fi) + res = fuse_fs_fgetattr(f->fs, path, &e->attr, fi); + else + res = fuse_fs_getattr(f->fs, path, &e->attr); + if (res == 0) { + struct node *node; + + node = find_node(f, nodeid, name); + if (node == NULL) + res = -ENOMEM; + else { + e->ino = node->nodeid; + e->generation = node->generation; + e->entry_timeout = f->conf.entry_timeout; + e->attr_timeout = f->conf.attr_timeout; + if (f->conf.auto_cache) { + pthread_mutex_lock(&f->lock); + update_stat(node, &e->attr); + pthread_mutex_unlock(&f->lock); + } + set_stat(f, e->ino, &e->attr); + if (f->conf.debug) + fprintf(stderr, " NODEID: %lu\n", + (unsigned long) e->ino); + } + } + return res; +} + +static struct fuse_context_i *fuse_get_context_internal(void) +{ + struct fuse_context_i *c; + + c = (struct fuse_context_i *) pthread_getspecific(fuse_context_key); + if (c == NULL) { + c = (struct fuse_context_i *) + calloc(1, sizeof(struct fuse_context_i)); + if (c == NULL) { + /* This is hard to deal with properly, so just + abort. If memory is so low that the + context cannot be allocated, there's not + much hope for the filesystem anyway */ + fprintf(stderr, "fuse: failed to allocate thread specific data\n"); + abort(); + } + pthread_setspecific(fuse_context_key, c); + } + return c; +} + +static void fuse_freecontext(void *data) +{ + free(data); +} + +static int fuse_create_context_key(void) +{ + int err = 0; + pthread_mutex_lock(&fuse_context_lock); + if (!fuse_context_ref) { + err = pthread_key_create(&fuse_context_key, fuse_freecontext); + if (err) { + fprintf(stderr, "fuse: failed to create thread specific key: %s\n", + strerror(err)); + pthread_mutex_unlock(&fuse_context_lock); + return -1; + } + } + fuse_context_ref++; + pthread_mutex_unlock(&fuse_context_lock); + return 0; +} + +static void fuse_delete_context_key(void) +{ + pthread_mutex_lock(&fuse_context_lock); + fuse_context_ref--; + if (!fuse_context_ref) { + free(pthread_getspecific(fuse_context_key)); + pthread_key_delete(fuse_context_key); + } + pthread_mutex_unlock(&fuse_context_lock); +} + +static struct fuse *req_fuse_prepare(fuse_req_t req) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + const struct fuse_ctx *ctx = fuse_req_ctx(req); + c->req = req; + c->ctx.fuse = req_fuse(req); + c->ctx.uid = ctx->uid; + c->ctx.gid = ctx->gid; + c->ctx.pid = ctx->pid; + c->ctx.umask = ctx->umask; + return c->ctx.fuse; +} + +static inline void reply_err(fuse_req_t req, int err) +{ + /* fuse_reply_err() uses non-negated errno values */ + fuse_reply_err(req, -err); +} + +static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e, + int err) +{ + if (!err) { + struct fuse *f = req_fuse(req); + if (fuse_reply_entry(req, e) == -ENOENT) { + /* Skip forget for negative result */ + if (e->ino != 0) + forget_node(f, e->ino, 1); + } + } else + reply_err(req, err); +} + +void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.write_buf) + conn->want &= ~FUSE_CAP_SPLICE_READ; + if (!fs->op.lock) + conn->want &= ~FUSE_CAP_POSIX_LOCKS; + if (!fs->op.flock) + conn->want &= ~FUSE_CAP_FLOCK_LOCKS; + if (fs->op.init) + fs->user_data = fs->op.init(conn); +} + +static void fuse_lib_init(void *data, struct fuse_conn_info *conn) +{ + struct fuse *f = (struct fuse *) data; + struct fuse_context_i *c = fuse_get_context_internal(); + + memset(c, 0, sizeof(*c)); + c->ctx.fuse = f; + conn->want |= FUSE_CAP_EXPORT_SUPPORT; + fuse_fs_init(f->fs, conn); +} + +void fuse_fs_destroy(struct fuse_fs *fs) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.destroy) + fs->op.destroy(fs->user_data); +#ifdef USE_MODULES + if (fs->m) + fuse_put_module(fs->m); +#endif + free(fs); +} + +static void fuse_lib_destroy(void *data) +{ + struct fuse *f = (struct fuse *) data; + struct fuse_context_i *c = fuse_get_context_internal(); + + memset(c, 0, sizeof(*c)); + c->ctx.fuse = f; + fuse_fs_destroy(f->fs); + f->fs = NULL; +} + +static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + struct node *dot = NULL; + + if (name[0] == '.') { + int len = strlen(name); + + if (len == 1 || (name[1] == '.' && len == 2)) { + pthread_mutex_lock(&f->lock); + if (len == 1) { + if (f->conf.debug) + fprintf(stderr, "LOOKUP-DOT\n"); + dot = get_node_nocheck(f, parent); + if (dot == NULL) { + pthread_mutex_unlock(&f->lock); + reply_entry(req, &e, -ESTALE); + return; + } + dot->refctr++; + } else { + if (f->conf.debug) + fprintf(stderr, "LOOKUP-DOTDOT\n"); + parent = get_node(f, parent)->parent->nodeid; + } + pthread_mutex_unlock(&f->lock); + name = NULL; + } + } + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + if (f->conf.debug) + fprintf(stderr, "LOOKUP %s\n", path); + fuse_prepare_interrupt(f, req, &d); + err = lookup_path(f, parent, name, path, &e, NULL); + if (err == -ENOENT && f->conf.negative_timeout != 0.0) { + e.ino = 0; + e.entry_timeout = f->conf.negative_timeout; + err = 0; + } + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + if (dot) { + pthread_mutex_lock(&f->lock); + unref_node(f, dot); + pthread_mutex_unlock(&f->lock); + } + reply_entry(req, &e, err); +} + +static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup) +{ + if (f->conf.debug) + fprintf(stderr, "FORGET %llu/%llu\n", (unsigned long long)ino, + (unsigned long long) nlookup); + forget_node(f, ino, nlookup); +} + +static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, + unsigned long nlookup) +{ + do_forget(req_fuse(req), ino, nlookup); + fuse_reply_none(req); +} + +static void fuse_lib_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + struct fuse *f = req_fuse(req); + size_t i; + + for (i = 0; i < count; i++) + do_forget(f, forgets[i].ino, forgets[i].nlookup); + + fuse_reply_none(req); +} + + +static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct stat buf; + char *path; + int err; + + memset(&buf, 0, sizeof(buf)); + + if (fi != NULL && f->fs->op.fgetattr) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + if (fi) + err = fuse_fs_fgetattr(f->fs, path, &buf, fi); + else + err = fuse_fs_getattr(f->fs, path, &buf); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->is_hidden && buf.st_nlink > 0) + buf.st_nlink--; + if (f->conf.auto_cache) + update_stat(node, &buf); + pthread_mutex_unlock(&f->lock); + set_stat(f, ino, &buf); + fuse_reply_attr(req, &buf, f->conf.attr_timeout); + } else + reply_err(req, err); +} + +int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.chmod) + return fs->op.chmod(path, mode); + else + return -ENOSYS; +} + +static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct stat buf; + char *path; + int err; + + memset(&buf, 0, sizeof(buf)); + if (valid == FUSE_SET_ATTR_SIZE && fi != NULL && + f->fs->op.ftruncate && f->fs->op.fgetattr) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = 0; + if (!err && (valid & FUSE_SET_ATTR_MODE)) + err = fuse_fs_chmod(f->fs, path, attr->st_mode); + if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : (uid_t) -1; + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : (gid_t) -1; + err = fuse_fs_chown(f->fs, path, uid, gid); + } + if (!err && (valid & FUSE_SET_ATTR_SIZE)) { + if (fi) + err = fuse_fs_ftruncate(f->fs, path, + attr->st_size, fi); + else + err = fuse_fs_truncate(f->fs, path, + attr->st_size); + } +#ifdef HAVE_UTIMENSAT + if (!err && f->utime_omit_ok && + (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + err = fuse_fs_utimens(f->fs, path, tv); + } else +#endif + if (!err && + (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) == + (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + tv[0].tv_sec = attr->st_atime; + tv[0].tv_nsec = ST_ATIM_NSEC(attr); + tv[1].tv_sec = attr->st_mtime; + tv[1].tv_nsec = ST_MTIM_NSEC(attr); + err = fuse_fs_utimens(f->fs, path, tv); + } + if (!err) { + if (fi) + err = fuse_fs_fgetattr(f->fs, path, &buf, fi); + else + err = fuse_fs_getattr(f->fs, path, &buf); + } + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + if (f->conf.auto_cache) { + pthread_mutex_lock(&f->lock); + update_stat(get_node(f, ino), &buf); + pthread_mutex_unlock(&f->lock); + } + set_stat(f, ino, &buf); + fuse_reply_attr(req, &buf, f->conf.attr_timeout); + } else + reply_err(req, err); +} + +static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_access(f->fs, path, mask); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino) +{ + struct fuse *f = req_fuse_prepare(req); + char linkname[PATH_MAX + 1]; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname)); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + linkname[PATH_MAX] = '\0'; + fuse_reply_readlink(req, linkname); + } else + reply_err(req, err); +} + +static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = -ENOSYS; + if (S_ISREG(mode)) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = O_CREAT | O_EXCL | O_WRONLY; + err = fuse_fs_create(f->fs, path, mode, &fi); + if (!err) { + err = lookup_path(f, parent, name, path, &e, + &fi); + fuse_fs_release(f->fs, path, &fi); + } + } + if (err == -ENOSYS) { + err = fuse_fs_mknod(f->fs, path, mode, rdev); + if (!err) + err = lookup_path(f, parent, name, path, &e, + NULL); + } + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_mkdir(f->fs, path, mode); + if (!err) + err = lookup_path(f, parent, name, path, &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct node *wnode; + char *path; + int err; + + err = get_path_wrlock(f, parent, name, &path, &wnode); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + if (!f->conf.hard_remove && is_open(f, parent, name)) { + err = hide_node(f, path, parent, name); + } else { + err = fuse_fs_unlink(f->fs, path); + if (!err) + remove_node(f, parent, name); + } + fuse_finish_interrupt(f, req, &d); + free_path_wrlock(f, parent, wnode, path); + } + reply_err(req, err); +} + +static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct node *wnode; + char *path; + int err; + + err = get_path_wrlock(f, parent, name, &path, &wnode); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_rmdir(f->fs, path); + fuse_finish_interrupt(f, req, &d); + if (!err) + remove_node(f, parent, name); + free_path_wrlock(f, parent, wnode, path); + } + reply_err(req, err); +} + +static void fuse_lib_symlink(fuse_req_t req, const char *linkname, + fuse_ino_t parent, const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_symlink(f->fs, linkname, path); + if (!err) + err = lookup_path(f, parent, name, path, &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir, + const char *oldname, fuse_ino_t newdir, + const char *newname) +{ + struct fuse *f = req_fuse_prepare(req); + char *oldpath; + char *newpath; + struct node *wnode1; + struct node *wnode2; + int err; + + err = get_path2(f, olddir, oldname, newdir, newname, + &oldpath, &newpath, &wnode1, &wnode2); + if (!err) { + struct fuse_intr_data d; + err = 0; + fuse_prepare_interrupt(f, req, &d); + if (!f->conf.hard_remove && is_open(f, newdir, newname)) + err = hide_node(f, newpath, newdir, newname); + if (!err) { + err = fuse_fs_rename(f->fs, oldpath, newpath); + if (!err) + err = rename_node(f, olddir, oldname, newdir, + newname, 0); + } + fuse_finish_interrupt(f, req, &d); + free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath); + } + reply_err(req, err); +} + +static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *oldpath; + char *newpath; + int err; + + err = get_path2(f, ino, NULL, newparent, newname, + &oldpath, &newpath, NULL, NULL); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_link(f->fs, oldpath, newpath); + if (!err) + err = lookup_path(f, newparent, newname, newpath, + &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath); + } + reply_entry(req, &e, err); +} + +static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path, + struct fuse_file_info *fi) +{ + struct node *node; + int unlink_hidden = 0; + const char *compatpath; + + if (path != NULL || f->nullpath_ok || f->conf.nopath) + compatpath = path; + else + compatpath = "-"; + + fuse_fs_release(f->fs, compatpath, fi); + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + assert(node->open_count > 0); + --node->open_count; + if (node->is_hidden && !node->open_count) { + unlink_hidden = 1; + node->is_hidden = 0; + } + pthread_mutex_unlock(&f->lock); + + if(unlink_hidden) { + if (path) { + fuse_fs_unlink(f->fs, path); + } else if (f->conf.nopath) { + char *unlinkpath; + + if (get_path(f, ino, &unlinkpath) == 0) + fuse_fs_unlink(f->fs, unlinkpath); + + free_path(f, ino, unlinkpath); + } + } +} + +static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_create(f->fs, path, mode, fi); + if (!err) { + err = lookup_path(f, parent, name, path, &e, fi); + if (err) + fuse_fs_release(f->fs, path, fi); + else if (!S_ISREG(e.attr.st_mode)) { + err = -EIO; + fuse_fs_release(f->fs, path, fi); + forget_node(f, e.ino, 1); + } else { + if (f->conf.direct_io) + fi->direct_io = 1; + if (f->conf.kernel_cache) + fi->keep_cache = 1; + + } + } + fuse_finish_interrupt(f, req, &d); + } + if (!err) { + pthread_mutex_lock(&f->lock); + get_node(f, e.ino)->open_count++; + pthread_mutex_unlock(&f->lock); + if (fuse_reply_create(req, &e, fi) == -ENOENT) { + /* The open syscall was interrupted, so it + must be cancelled */ + fuse_do_release(f, e.ino, path, fi); + forget_node(f, e.ino, 1); + } + } else { + reply_err(req, err); + } + + free_path(f, parent, path); +} + +static double diff_timespec(const struct timespec *t1, + const struct timespec *t2) +{ + return (t1->tv_sec - t2->tv_sec) + + ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0; +} + +static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path, + struct fuse_file_info *fi) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->cache_valid) { + struct timespec now; + + curr_time(&now); + if (diff_timespec(&now, &node->stat_updated) > + f->conf.ac_attr_timeout) { + struct stat stbuf; + int err; + pthread_mutex_unlock(&f->lock); + err = fuse_fs_fgetattr(f->fs, path, &stbuf, fi); + pthread_mutex_lock(&f->lock); + if (!err) + update_stat(node, &stbuf); + else + node->cache_valid = 0; + } + } + if (node->cache_valid) + fi->keep_cache = 1; + + node->cache_valid = 1; + pthread_mutex_unlock(&f->lock); +} + +static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_open(f->fs, path, fi); + if (!err) { + if (f->conf.direct_io) + fi->direct_io = 1; + if (f->conf.kernel_cache) + fi->keep_cache = 1; + + if (f->conf.auto_cache) + open_auto_cache(f, ino, path, fi); + } + fuse_finish_interrupt(f, req, &d); + } + if (!err) { + pthread_mutex_lock(&f->lock); + get_node(f, ino)->open_count++; + pthread_mutex_unlock(&f->lock); + if (fuse_reply_open(req, fi) == -ENOENT) { + /* The open syscall was interrupted, so it + must be cancelled */ + fuse_do_release(f, ino, path, fi); + } + } else + reply_err(req, err); + + free_path(f, ino, path); +} + +static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size, + loff_t off, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_bufvec *buf = NULL; + char *path; + int res; + + res = get_path_nullok(f, ino, &path); + if (res == 0) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (res == 0) + fuse_reply_data(req, buf, FUSE_BUF_SPLICE_MOVE); + else + reply_err(req, res); + + fuse_free_buf(buf); +} + +static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *buf, loff_t off, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int res; + + res = get_path_nullok(f, ino, &path); + if (res == 0) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_write_buf(f->fs, path, buf, off, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (res >= 0) + fuse_reply_write(req, res); + else + reply_err(req, res); +} + +static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fsync(f->fs, path, datasync, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi, + struct fuse_file_info *fi) +{ + struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh; + memset(fi, 0, sizeof(struct fuse_file_info)); + fi->fh = dh->fh; + fi->fh_old = dh->fh; + return dh; +} + +static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_dh *dh; + struct fuse_file_info fi; + char *path; + int err; + + dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh)); + if (dh == NULL) { + reply_err(req, -ENOMEM); + return; + } + memset(dh, 0, sizeof(struct fuse_dh)); + dh->fuse = f; + dh->contents = NULL; + dh->len = 0; + dh->filled = 0; + dh->nodeid = ino; + fuse_mutex_init(&dh->lock); + + llfi->fh = (uintptr_t) dh; + + memset(&fi, 0, sizeof(fi)); + fi.flags = llfi->flags; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_opendir(f->fs, path, &fi); + fuse_finish_interrupt(f, req, &d); + dh->fh = fi.fh; + } + if (!err) { + if (fuse_reply_open(req, llfi) == -ENOENT) { + /* The opendir syscall was interrupted, so it + must be cancelled */ + fuse_fs_releasedir(f->fs, path, &fi); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + } else { + reply_err(req, err); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + free_path(f, ino, path); +} + +static int extend_contents(struct fuse_dh *dh, unsigned minsize) +{ + if (minsize > dh->size) { + char *newptr; + unsigned newsize = dh->size; + if (!newsize) + newsize = 1024; + while (newsize < minsize) { + if (newsize >= 0x80000000) + newsize = 0xffffffff; + else + newsize *= 2; + } + + newptr = (char *) realloc(dh->contents, newsize); + if (!newptr) { + dh->error = -ENOMEM; + return -1; + } + dh->contents = newptr; + dh->size = newsize; + } + return 0; +} + +static int fill_dir(void *dh_, const char *name, const struct stat *statp, + loff_t off) +{ + struct fuse_dh *dh = (struct fuse_dh *) dh_; + struct stat stbuf; + size_t newlen; + + if (statp) + stbuf = *statp; + else { + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = FUSE_UNKNOWN_INO; + } + + if (!dh->fuse->conf.use_ino) { + stbuf.st_ino = FUSE_UNKNOWN_INO; + if (dh->fuse->conf.readdir_ino) { + struct node *node; + pthread_mutex_lock(&dh->fuse->lock); + node = lookup_node(dh->fuse, dh->nodeid, name); + if (node) + stbuf.st_ino = (ino_t) node->nodeid; + pthread_mutex_unlock(&dh->fuse->lock); + } + } + + if (off) { + if (extend_contents(dh, dh->needlen) == -1) + return 1; + + dh->filled = 0; + newlen = dh->len + + fuse_add_direntry(dh->req, dh->contents + dh->len, + dh->needlen - dh->len, name, + &stbuf, off); + if (newlen > dh->needlen) + return 1; + } else { + newlen = dh->len + + fuse_add_direntry(dh->req, NULL, 0, name, NULL, 0); + if (extend_contents(dh, newlen) == -1) + return 1; + + fuse_add_direntry(dh->req, dh->contents + dh->len, + dh->size - dh->len, name, &stbuf, newlen); + } + dh->len = newlen; + return 0; +} + +static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + size_t size, loff_t off, struct fuse_dh *dh, + struct fuse_file_info *fi) +{ + char *path; + int err; + + if (f->fs->op.readdir) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + dh->len = 0; + dh->error = 0; + dh->needlen = size; + dh->filled = 1; + dh->req = req; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_readdir(f->fs, path, dh, fill_dir, off, fi); + fuse_finish_interrupt(f, req, &d); + dh->req = NULL; + if (!err) + err = dh->error; + if (err) + dh->filled = 0; + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + loff_t off, struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_file_info fi; + struct fuse_dh *dh = get_dirhandle(llfi, &fi); + + pthread_mutex_lock(&dh->lock); + /* According to SUS, directory contents need to be refreshed on + rewinddir() */ + if (!off) + dh->filled = 0; + + if (!dh->filled) { + int err = readdir_fill(f, req, ino, size, off, dh, &fi); + if (err) { + reply_err(req, err); + goto out; + } + } + if (dh->filled) { + if (off < dh->len) { + if (off + size > dh->len) + size = dh->len - off; + } else + size = 0; + } else { + size = dh->len; + off = 0; + } + fuse_reply_buf(req, dh->contents + off, size); +out: + pthread_mutex_unlock(&dh->lock); +} + +static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_file_info fi; + struct fuse_dh *dh = get_dirhandle(llfi, &fi); + char *path; + const char *compatpath; + + get_path_nullok(f, ino, &path); + if (path != NULL || f->nullpath_ok || f->conf.nopath) + compatpath = path; + else + compatpath = "-"; + + fuse_prepare_interrupt(f, req, &d); + fuse_fs_releasedir(f->fs, compatpath, &fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + pthread_mutex_lock(&dh->lock); + pthread_mutex_unlock(&dh->lock); + pthread_mutex_destroy(&dh->lock); + free(dh->contents); + free(dh); + reply_err(req, 0); +} + +static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_file_info fi; + char *path; + int err; + + get_dirhandle(llfi, &fi); + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino) +{ + struct fuse *f = req_fuse_prepare(req); + struct statvfs buf; + char *path = NULL; + int err = 0; + + memset(&buf, 0, sizeof(buf)); + if (ino) + err = get_path(f, ino, &path); + + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_statfs(f->fs, path ? path : "/", &buf); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (!err) + fuse_reply_statfs(req, &buf); + else + reply_err(req, err); +} + +static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_setxattr(f->fs, path, name, value, size, flags); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + const char *name, char *value, size_t size) +{ + int err; + char *path; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_getxattr(f->fs, path, name, value, size); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + struct fuse *f = req_fuse_prepare(req); + int res; + + if (size) { + char *value = (char *) malloc(size); + if (value == NULL) { + reply_err(req, -ENOMEM); + return; + } + res = common_getxattr(f, req, ino, name, value, size); + if (res > 0) + fuse_reply_buf(req, value, res); + else + reply_err(req, res); + free(value); + } else { + res = common_getxattr(f, req, ino, name, NULL, 0); + if (res >= 0) + fuse_reply_xattr(req, res); + else + reply_err(req, res); + } +} + +static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + char *list, size_t size) +{ + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_listxattr(f->fs, path, list, size); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + struct fuse *f = req_fuse_prepare(req); + int res; + + if (size) { + char *list = (char *) malloc(size); + if (list == NULL) { + reply_err(req, -ENOMEM); + return; + } + res = common_listxattr(f, req, ino, list, size); + if (res > 0) + fuse_reply_buf(req, list, res); + else + reply_err(req, res); + free(list); + } else { + res = common_listxattr(f, req, ino, NULL, 0); + if (res >= 0) + fuse_reply_xattr(req, res); + else + reply_err(req, res); + } +} + +static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_removexattr(f->fs, path, name); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static struct lock *locks_conflict(struct node *node, const struct lock *lock) +{ + struct lock *l; + + for (l = node->locks; l; l = l->next) + if (l->owner != lock->owner && + lock->start <= l->end && l->start <= lock->end && + (l->type == F_WRLCK || lock->type == F_WRLCK)) + break; + + return l; +} + +static void delete_lock(struct lock **lockp) +{ + struct lock *l = *lockp; + *lockp = l->next; + free(l); +} + +static void insert_lock(struct lock **pos, struct lock *lock) +{ + lock->next = *pos; + *pos = lock; +} + +static int locks_insert(struct node *node, struct lock *lock) +{ + struct lock **lp; + struct lock *newl1 = NULL; + struct lock *newl2 = NULL; + + if (lock->type != F_UNLCK || lock->start != 0 || + lock->end != OFFSET_MAX) { + newl1 = malloc(sizeof(struct lock)); + newl2 = malloc(sizeof(struct lock)); + + if (!newl1 || !newl2) { + free(newl1); + free(newl2); + return -ENOLCK; + } + } + + for (lp = &node->locks; *lp;) { + struct lock *l = *lp; + if (l->owner != lock->owner) + goto skip; + + if (lock->type == l->type) { + if (l->end < lock->start - 1) + goto skip; + if (lock->end < l->start - 1) + break; + if (l->start <= lock->start && lock->end <= l->end) + goto out; + if (l->start < lock->start) + lock->start = l->start; + if (lock->end < l->end) + lock->end = l->end; + goto delete; + } else { + if (l->end < lock->start) + goto skip; + if (lock->end < l->start) + break; + if (lock->start <= l->start && l->end <= lock->end) + goto delete; + if (l->end <= lock->end) { + l->end = lock->start - 1; + goto skip; + } + if (lock->start <= l->start) { + l->start = lock->end + 1; + break; + } + *newl2 = *l; + newl2->start = lock->end + 1; + l->end = lock->start - 1; + insert_lock(&l->next, newl2); + newl2 = NULL; + } + skip: + lp = &l->next; + continue; + + delete: + delete_lock(lp); + } + if (lock->type != F_UNLCK) { + *newl1 = *lock; + insert_lock(lp, newl1); + newl1 = NULL; + } +out: + free(newl1); + free(newl2); + return 0; +} + +static void flock_to_lock(struct flock *flock, struct lock *lock) +{ + memset(lock, 0, sizeof(struct lock)); + lock->type = flock->l_type; + lock->start = flock->l_start; + lock->end = + flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX; + lock->pid = flock->l_pid; +} + +static void lock_to_flock(struct lock *lock, struct flock *flock) +{ + flock->l_type = lock->type; + flock->l_start = lock->start; + flock->l_len = + (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1; + flock->l_pid = lock->pid; +} + +static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + const char *path, struct fuse_file_info *fi) +{ + struct fuse_intr_data d; + struct flock lock; + struct lock l; + int err; + int errlock; + + fuse_prepare_interrupt(f, req, &d); + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + err = fuse_fs_flush(f->fs, path, fi); + errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock); + fuse_finish_interrupt(f, req, &d); + + if (errlock != -ENOSYS) { + flock_to_lock(&lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + + /* if op.lock() is defined FLUSH is needed regardless + of op.flush() */ + if (err == -ENOSYS) + err = 0; + } + return err; +} + +static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err = 0; + + get_path_nullok(f, ino, &path); + if (fi->flush) { + err = fuse_flush_common(f, req, ino, path, fi); + if (err == -ENOSYS) + err = 0; + } + + fuse_prepare_interrupt(f, req, &d); + fuse_do_release(f, ino, path, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + reply_err(req, err); +} + +static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + get_path_nullok(f, ino, &path); + err = fuse_flush_common(f, req, ino, path, fi); + free_path(f, ino, path); + + reply_err(req, err); +} + +static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock, + int cmd) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_lock(f->fs, path, fi, cmd, lock); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock) +{ + int err; + struct lock l; + struct lock *conflict; + struct fuse *f = req_fuse(req); + + flock_to_lock(lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + conflict = locks_conflict(get_node(f, ino), &l); + if (conflict) + lock_to_flock(conflict, lock); + pthread_mutex_unlock(&f->lock); + if (!conflict) + err = fuse_lock_common(req, ino, fi, lock, F_GETLK); + else + err = 0; + + if (!err) + fuse_reply_lock(req, lock); + else + reply_err(req, err); +} + +static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock, + int sleep) +{ + int err = fuse_lock_common(req, ino, fi, lock, + sleep ? F_SETLKW : F_SETLK); + if (!err) { + struct fuse *f = req_fuse(req); + struct lock l; + flock_to_lock(lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + } + reply_err(req, err); +} + +static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, int op) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (err == 0) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_flock(f->fs, path, fi, op); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize, + uint64_t idx) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_bmap(f->fs, path, blocksize, &idx); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) + fuse_reply_bmap(req, idx); + else + reply_err(req, err); +} + +static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, + struct fuse_file_info *llfi, unsigned int flags, + const void *in_buf, size_t in_bufsz, + size_t out_bufsz) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_file_info fi; + char *path, *out_buf = NULL; + int err; + + err = -EPERM; + if (flags & FUSE_IOCTL_UNRESTRICTED) + goto err; + + if (flags & FUSE_IOCTL_DIR) + get_dirhandle(llfi, &fi); + else + fi = *llfi; + + if (out_bufsz) { + err = -ENOMEM; + out_buf = malloc(out_bufsz); + if (!out_buf) + goto err; + } + + assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz); + if (out_buf) + memcpy(out_buf, in_buf, in_bufsz); + + err = get_path_nullok(f, ino, &path); + if (err) + goto err; + + fuse_prepare_interrupt(f, req, &d); + + err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags, + out_buf ?: (void *)in_buf); + + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + fuse_reply_ioctl(req, err, out_buf, out_bufsz); + goto out; +err: + reply_err(req, err); +out: + free(out_buf); +} + +static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + unsigned revents = 0; + + err = get_path_nullok(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_poll(f->fs, path, fi, ph, &revents); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) + fuse_reply_poll(req, revents); + else + reply_err(req, err); +} + +static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + loff_t offset, loff_t length, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static int clean_delay(struct fuse *f) +{ + /* + * This is calculating the delay between clean runs. To + * reduce the number of cleans we are doing them 10 times + * within the remember window. + */ + int min_sleep = 60; + int max_sleep = 3600; + int sleep_time = f->conf.remember / 10; + + if (sleep_time > max_sleep) + return max_sleep; + if (sleep_time < min_sleep) + return min_sleep; + return sleep_time; +} + +int fuse_clean_cache(struct fuse *f) +{ + struct node_lru *lnode; + struct list_head *curr, *next; + struct node *node; + struct timespec now; + + pthread_mutex_lock(&f->lock); + + curr_time(&now); + + for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) { + double age; + + next = curr->next; + lnode = list_entry(curr, struct node_lru, lru); + node = &lnode->node; + + age = diff_timespec(&now, &lnode->forget_time); + if (age <= f->conf.remember) + break; + + assert(node->nlookup == 1); + + /* Don't forget active directories */ + if (node->refctr > 1) + continue; + + node->nlookup = 0; + unhash_name(f, node); + unref_node(f, node); + } + pthread_mutex_unlock(&f->lock); + + return clean_delay(f); +} + +static struct fuse_lowlevel_ops fuse_path_ops = { + .init = fuse_lib_init, + .destroy = fuse_lib_destroy, + .lookup = fuse_lib_lookup, + .forget = fuse_lib_forget, + .forget_multi = fuse_lib_forget_multi, + .getattr = fuse_lib_getattr, + .setattr = fuse_lib_setattr, + .access = fuse_lib_access, + .readlink = fuse_lib_readlink, + .mknod = fuse_lib_mknod, + .mkdir = fuse_lib_mkdir, + .unlink = fuse_lib_unlink, + .rmdir = fuse_lib_rmdir, + .symlink = fuse_lib_symlink, + .rename = fuse_lib_rename, + .link = fuse_lib_link, + .create = fuse_lib_create, + .open = fuse_lib_open, + .read = fuse_lib_read, + .write_buf = fuse_lib_write_buf, + .flush = fuse_lib_flush, + .release = fuse_lib_release, + .fsync = fuse_lib_fsync, + .opendir = fuse_lib_opendir, + .readdir = fuse_lib_readdir, + .releasedir = fuse_lib_releasedir, + .fsyncdir = fuse_lib_fsyncdir, + .statfs = fuse_lib_statfs, + .setxattr = fuse_lib_setxattr, + .getxattr = fuse_lib_getxattr, + .listxattr = fuse_lib_listxattr, + .removexattr = fuse_lib_removexattr, + .getlk = fuse_lib_getlk, + .setlk = fuse_lib_setlk, + .flock = fuse_lib_flock, + .bmap = fuse_lib_bmap, + .ioctl = fuse_lib_ioctl, + .poll = fuse_lib_poll, + .fallocate = fuse_lib_fallocate, +}; + +int fuse_notify_poll(struct fuse_pollhandle *ph) +{ + return fuse_lowlevel_notify_poll(ph); +} + +static void free_cmd(struct fuse_cmd *cmd) +{ + free(cmd->buf); + free(cmd); +} + +void fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd) +{ + fuse_session_process(f->se, cmd->buf, cmd->buflen, cmd->ch); + free_cmd(cmd); +} + +int fuse_exited(struct fuse *f) +{ + return fuse_session_exited(f->se); +} + +struct fuse_session *fuse_get_session(struct fuse *f) +{ + return f->se; +} + +static struct fuse_cmd *fuse_alloc_cmd(size_t bufsize) +{ + struct fuse_cmd *cmd = (struct fuse_cmd *) malloc(sizeof(*cmd)); + if (cmd == NULL) { + fprintf(stderr, "fuse: failed to allocate cmd\n"); + return NULL; + } + cmd->buf = (char *) malloc(bufsize); + if (cmd->buf == NULL) { + fprintf(stderr, "fuse: failed to allocate read buffer\n"); + free(cmd); + return NULL; + } + return cmd; +} + +struct fuse_cmd *fuse_read_cmd(struct fuse *f) +{ + struct fuse_chan *ch = fuse_session_next_chan(f->se, NULL); + size_t bufsize = fuse_chan_bufsize(ch); + struct fuse_cmd *cmd = fuse_alloc_cmd(bufsize); + if (cmd != NULL) { + int res = fuse_chan_recv(&ch, cmd->buf, bufsize); + if (res <= 0) { + free_cmd(cmd); + if (res < 0 && res != -EINTR && res != -EAGAIN) + fuse_exit(f); + return NULL; + } + cmd->buflen = res; + cmd->ch = ch; + } + return cmd; +} + +static int fuse_session_loop_remember(struct fuse *f) +{ + struct fuse_session *se = f->se; + int res = 0; + struct timespec now; + time_t next_clean; + struct fuse_chan *ch = fuse_session_next_chan(se, NULL); + size_t bufsize = fuse_chan_bufsize(ch); + char *buf = (char *) malloc(bufsize); + struct pollfd fds = { + .fd = fuse_chan_fd(ch), + .events = POLLIN + }; + + if (!buf) { + fprintf(stderr, "fuse: failed to allocate read buffer\n"); + return -1; + } + + curr_time(&now); + next_clean = now.tv_sec; + while (!fuse_session_exited(se)) { + struct fuse_chan *tmpch = ch; + struct fuse_buf fbuf = { + .mem = buf, + .size = bufsize, + }; + unsigned timeout; + + curr_time(&now); + if (now.tv_sec < next_clean) + timeout = next_clean - now.tv_sec; + else + timeout = 0; + + res = poll(&fds, 1, timeout * 1000); + if (res == -1) { + if (errno == -EINTR) + continue; + else + break; + } else if (res > 0) { + res = fuse_session_receive_buf(se, &fbuf, &tmpch); + + if (res == -EINTR) + continue; + if (res <= 0) + break; + + fuse_session_process_buf(se, &fbuf, tmpch); + } else { + timeout = fuse_clean_cache(f); + curr_time(&now); + next_clean = now.tv_sec + timeout; + } + } + + free(buf); + fuse_session_reset(se); + return res < 0 ? -1 : 0; +} + +int fuse_loop(struct fuse *f) +{ + if (!f) + return -1; + + if (lru_enabled(f)) + return fuse_session_loop_remember(f); + + return fuse_session_loop(f->se); +} + +int fuse_invalidate(struct fuse *f, const char *path) +{ + (void) f; + (void) path; + return -EINVAL; +} + +void fuse_exit(struct fuse *f) +{ + fuse_session_exit(f->se); +} + +struct fuse_context *fuse_get_context(void) +{ + return &fuse_get_context_internal()->ctx; +} + +/* + * The size of fuse_context got extended, so need to be careful about + * incompatibility (i.e. a new binary cannot work with an old + * library). + */ +struct fuse_context *fuse_get_context_compat22(void); +struct fuse_context *fuse_get_context_compat22(void) +{ + return &fuse_get_context_internal()->ctx; +} +FUSE_SYMVER(".symver fuse_get_context_compat22,fuse_get_context@FUSE_2.2"); + +int fuse_getgroups(int size, gid_t list[]) +{ + fuse_req_t req = fuse_get_context_internal()->req; + return fuse_req_getgroups(req, size, list); +} + +int fuse_interrupted(void) +{ + return fuse_req_interrupted(fuse_get_context_internal()->req); +} + +void fuse_set_getcontext_func(struct fuse_context *(*func)(void)) +{ + (void) func; + /* no-op */ +} + +enum { + KEY_HELP, +}; + +#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v } + +static const struct fuse_opt fuse_lib_opts[] = { + FUSE_OPT_KEY("-h", KEY_HELP), + FUSE_OPT_KEY("--help", KEY_HELP), + FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), + FUSE_LIB_OPT("debug", debug, 1), + FUSE_LIB_OPT("-d", debug, 1), + FUSE_LIB_OPT("hard_remove", hard_remove, 1), + FUSE_LIB_OPT("use_ino", use_ino, 1), + FUSE_LIB_OPT("readdir_ino", readdir_ino, 1), + FUSE_LIB_OPT("direct_io", direct_io, 1), + FUSE_LIB_OPT("kernel_cache", kernel_cache, 1), + FUSE_LIB_OPT("auto_cache", auto_cache, 1), + FUSE_LIB_OPT("noauto_cache", auto_cache, 0), + FUSE_LIB_OPT("umask=", set_mode, 1), + FUSE_LIB_OPT("umask=%o", umask, 0), + FUSE_LIB_OPT("uid=", set_uid, 1), + FUSE_LIB_OPT("uid=%d", uid, 0), + FUSE_LIB_OPT("gid=", set_gid, 1), + FUSE_LIB_OPT("gid=%d", gid, 0), + FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0), + FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0), + FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0), + FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1), + FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0), + FUSE_LIB_OPT("noforget", remember, -1), + FUSE_LIB_OPT("remember=%u", remember, 0), + FUSE_LIB_OPT("nopath", nopath, 1), + FUSE_LIB_OPT("intr", intr, 1), + FUSE_LIB_OPT("intr_signal=%d", intr_signal, 0), + FUSE_LIB_OPT("modules=%s", modules, 0), + FUSE_OPT_END +}; + +static void fuse_lib_help(void) +{ + fprintf(stderr, +" -o hard_remove immediate removal (don't hide files)\n" +" -o use_ino let filesystem set inode numbers\n" +" -o readdir_ino try to fill in d_ino in readdir\n" +" -o direct_io use direct I/O\n" +" -o kernel_cache cache files in kernel\n" +" -o [no]auto_cache enable caching based on modification times (off)\n" +" -o umask=M set file permissions (octal)\n" +" -o uid=N set file owner\n" +" -o gid=N set file group\n" +" -o entry_timeout=T cache timeout for names (1.0s)\n" +" -o negative_timeout=T cache timeout for deleted names (0.0s)\n" +" -o attr_timeout=T cache timeout for attributes (1.0s)\n" +" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n" +" -o noforget never forget cached inodes\n" +" -o remember=T remember cached inodes for T seconds (0s)\n" +" -o nopath don't supply path if not necessary\n" +" -o intr allow requests to be interrupted\n" +" -o intr_signal=NUM signal to send on interrupt (%i)\n" +" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n" +"\n", FUSE_DEFAULT_INTR_SIGNAL); +} + +#ifdef USE_MODULES +static void fuse_lib_help_modules(void) +{ + struct fuse_module *m; + fprintf(stderr, "\nModule options:\n"); + pthread_mutex_lock(&fuse_context_lock); + for (m = fuse_modules; m; m = m->next) { + struct fuse_fs *fs = NULL; + struct fuse_fs *newfs; + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + if (fuse_opt_add_arg(&args, "") != -1 && + fuse_opt_add_arg(&args, "-h") != -1) { + fprintf(stderr, "\n[%s]\n", m->name); + newfs = m->factory(&args, &fs); + assert(newfs == NULL); + } + fuse_opt_free_args(&args); + } + pthread_mutex_unlock(&fuse_context_lock); +} +#endif + +static int fuse_lib_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) arg; (void) outargs; + + if (key == KEY_HELP) { + struct fuse_config *conf = (struct fuse_config *) data; + fuse_lib_help(); + conf->help = 1; + } + + return 1; +} + +int fuse_is_lib_option(const char *opt) +{ + return fuse_lowlevel_is_lib_option(opt) || + fuse_opt_match(fuse_lib_opts, opt); +} + +static int fuse_init_intr_signal(int signum, int *installed) +{ + struct sigaction old_sa; + + if (sigaction(signum, NULL, &old_sa) == -1) { + perror("fuse: cannot get old signal handler"); + return -1; + } + + if (old_sa.sa_handler == SIG_DFL) { + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = fuse_intr_sighandler; + sigemptyset(&sa.sa_mask); + + if (sigaction(signum, &sa, NULL) == -1) { + perror("fuse: cannot set interrupt signal handler"); + return -1; + } + *installed = 1; + } + return 0; +} + +static void fuse_restore_intr_signal(int signum) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = SIG_DFL; + sigaction(signum, &sa, NULL); +} + +#ifdef USE_MODULES +static int fuse_push_module(struct fuse *f, const char *module, + struct fuse_args *args) +{ + struct fuse_fs *fs[2] = { f->fs, NULL }; + struct fuse_fs *newfs; + struct fuse_module *m = fuse_get_module(module); + + if (!m) + return -1; + + newfs = m->factory(args, fs); + if (!newfs) { + fuse_put_module(m); + return -1; + } + newfs->m = m; + f->fs = newfs; + f->nullpath_ok = newfs->op.flag_nullpath_ok && f->nullpath_ok; + f->conf.nopath = newfs->op.flag_nopath && f->conf.nopath; + f->utime_omit_ok = newfs->op.flag_utime_omit_ok && f->utime_omit_ok; + return 0; +} +#endif + +struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, + void *user_data) +{ + struct fuse_fs *fs; + + if (sizeof(struct fuse_operations) < op_size) { + fprintf(stderr, "fuse: warning: library too old, some operations may not not work\n"); + op_size = sizeof(struct fuse_operations); + } + + fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs)); + if (!fs) { + fprintf(stderr, "fuse: failed to allocate fuse_fs object\n"); + return NULL; + } + + fs->user_data = user_data; + if (op) + memcpy(&fs->op, op, op_size); + return fs; +} + +static int node_table_init(struct node_table *t) +{ + t->size = NODE_TABLE_MIN_SIZE; + t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size); + if (t->array == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; + } + t->use = 0; + t->split = 0; + + return 0; +} + +static void thread_exit_handler(int sig) +{ + pthread_exit(0); +} + +static void *fuse_prune_nodes(void *fuse) +{ + struct fuse *f = fuse; + int sleep_time; + +#if defined(__ANDROID__) + struct sigaction actions; + memset(&actions, 0, sizeof(actions)); + sigemptyset(&actions.sa_mask); + actions.sa_flags = 0; + actions.sa_handler = thread_exit_handler; + sigaction(SIGUSR1, &actions, NULL); + + sigset_t setusr1; + sigemptyset(&setusr1); + sigaddset(&setusr1, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &setusr1, NULL); +#endif + + while(1) { + sleep_time = fuse_clean_cache(f); + sleep(sleep_time); + } + return NULL; +} + +int fuse_start_cleanup_thread(struct fuse *f) +{ + if (lru_enabled(f)) + return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f); + + return 0; +} + +void fuse_stop_cleanup_thread(struct fuse *f) +{ + if (lru_enabled(f)) { + pthread_mutex_lock(&f->lock); +#if defined(__ANDROID__) + pthread_kill(f->prune_thread, SIGUSR1); +#else + pthread_cancel(f->prune_thread); +#endif + pthread_mutex_unlock(&f->lock); + pthread_join(f->prune_thread, NULL); + } +} + +struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data, int compat) +{ + struct fuse *f; + struct node *root; + struct fuse_fs *fs; + struct fuse_lowlevel_ops llop = fuse_path_ops; + + if (fuse_create_context_key() == -1) + goto out; + + f = (struct fuse *) calloc(1, sizeof(struct fuse)); + if (f == NULL) { + fprintf(stderr, "fuse: failed to allocate fuse object\n"); + goto out_delete_context_key; + } + + fs = fuse_fs_new(op, op_size, user_data); + if (!fs) + goto out_free; + + fs->compat = compat; + f->fs = fs; + f->nullpath_ok = fs->op.flag_nullpath_ok; + f->conf.nopath = fs->op.flag_nopath; + f->utime_omit_ok = fs->op.flag_utime_omit_ok; + + /* Oh f**k, this is ugly! */ + if (!fs->op.lock) { + llop.getlk = NULL; + llop.setlk = NULL; + } + + f->conf.entry_timeout = 1.0; + f->conf.attr_timeout = 1.0; + f->conf.negative_timeout = 0.0; + f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL; + + f->pagesize = getpagesize(); + init_list_head(&f->partial_slabs); + init_list_head(&f->full_slabs); + init_list_head(&f->lru_table); + + if (fuse_opt_parse(args, &f->conf, fuse_lib_opts, + fuse_lib_opt_proc) == -1) + goto out_free_fs; + +#ifdef USE_MODULES + if (f->conf.modules) { + char *module; + char *next; + + for (module = f->conf.modules; module; module = next) { + char *p; + for (p = module; *p && *p != ':'; p++); + next = *p ? p + 1 : NULL; + *p = '\0'; + if (module[0] && + fuse_push_module(f, module, args) == -1) + goto out_free_fs; + } + } +#endif + + if (!f->conf.ac_attr_timeout_set) + f->conf.ac_attr_timeout = f->conf.attr_timeout; + +#if defined(__FreeBSD__) || defined(__NetBSD__) + /* + * In FreeBSD, we always use these settings as inode numbers + * are needed to make getcwd(3) work. + */ + f->conf.readdir_ino = 1; +#endif + + if (compat && compat <= 25) { + if (fuse_sync_compat_args(args) == -1) + goto out_free_fs; + } + + f->se = fuse_lowlevel_new_common(args, &llop, sizeof(llop), f); + if (f->se == NULL) { +#ifdef USE_MODULES + if (f->conf.help) + fuse_lib_help_modules(); +#endif + goto out_free_fs; + } + + fuse_session_add_chan(f->se, ch); + + if (f->conf.debug) { + fprintf(stderr, "nullpath_ok: %i\n", f->nullpath_ok); + fprintf(stderr, "nopath: %i\n", f->conf.nopath); + fprintf(stderr, "utime_omit_ok: %i\n", f->utime_omit_ok); + } + + /* Trace topmost layer by default */ + f->fs->debug = f->conf.debug; + f->ctr = 0; + f->generation = 0; + if (node_table_init(&f->name_table) == -1) + goto out_free_session; + + if (node_table_init(&f->id_table) == -1) + goto out_free_name_table; + + fuse_mutex_init(&f->lock); + + root = alloc_node(f); + if (root == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); + goto out_free_id_table; + } + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(root); + init_list_head(&lnode->lru); + } + + strcpy(root->inline_name, "/"); + root->name = root->inline_name; + + if (f->conf.intr && + fuse_init_intr_signal(f->conf.intr_signal, + &f->intr_installed) == -1) + goto out_free_root; + + root->parent = NULL; + root->nodeid = FUSE_ROOT_ID; + inc_nlookup(root); + hash_id(f, root); + + return f; + +out_free_root: + free(root); +out_free_id_table: + free(f->id_table.array); +out_free_name_table: + free(f->name_table.array); +out_free_session: + fuse_session_destroy(f->se); +out_free_fs: + /* Horrible compatibility hack to stop the destructor from being + called on the filesystem without init being called first */ + fs->op.destroy = NULL; + fuse_fs_destroy(f->fs); + free(f->conf.modules); +out_free: + free(f); +out_delete_context_key: + fuse_delete_context_key(); +out: + return NULL; +} + +struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + void *user_data) +{ + return fuse_new_common(ch, args, op, op_size, user_data, 0); +} + +void fuse_destroy(struct fuse *f) +{ + size_t i; + + if (f->conf.intr && f->intr_installed) + fuse_restore_intr_signal(f->conf.intr_signal); + + if (f->fs) { + struct fuse_context_i *c = fuse_get_context_internal(); + + memset(c, 0, sizeof(*c)); + c->ctx.fuse = f; + + for (i = 0; i < f->id_table.size; i++) { + struct node *node; + + for (node = f->id_table.array[i]; node != NULL; + node = node->id_next) { + if (node->is_hidden) { + char *path; + if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) { + fuse_fs_unlink(f->fs, path); + free(path); + } + } + } + } + } + for (i = 0; i < f->id_table.size; i++) { + struct node *node; + struct node *next; + + for (node = f->id_table.array[i]; node != NULL; node = next) { + next = node->id_next; + free_node(f, node); + f->id_table.use--; + } + } + assert(list_empty(&f->partial_slabs)); + assert(list_empty(&f->full_slabs)); + + free(f->id_table.array); + free(f->name_table.array); + pthread_mutex_destroy(&f->lock); + fuse_session_destroy(f->se); + free(f->conf.modules); + free(f); + fuse_delete_context_key(); +} + +static struct fuse *fuse_new_common_compat25(int fd, struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, int compat) +{ + struct fuse *f = NULL; + struct fuse_chan *ch = fuse_kern_chan_new(fd); + + if (ch) + f = fuse_new_common(ch, args, op, op_size, NULL, compat); + + return f; +} + +#ifdef USE_MODULES +/* called with fuse_context_lock held or during initialization (before + main() has been called) */ +void fuse_register_module(struct fuse_module *mod) +{ + mod->ctr = 0; + mod->so = fuse_current_so; + if (mod->so) + mod->so->ctr++; + mod->next = fuse_modules; + fuse_modules = mod; +} +#endif + +#if !defined(__FreeBSD__) && !defined(__NetBSD__) + +static struct fuse *fuse_new_common_compat(int fd, const char *opts, + const struct fuse_operations *op, + size_t op_size, int compat) +{ + struct fuse *f; + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + + if (fuse_opt_add_arg(&args, "") == -1) + return NULL; + if (opts && + (fuse_opt_add_arg(&args, "-o") == -1 || + fuse_opt_add_arg(&args, opts) == -1)) { + fuse_opt_free_args(&args); + return NULL; + } + f = fuse_new_common_compat25(fd, &args, op, op_size, compat); + fuse_opt_free_args(&args); + + return f; +} + +struct fuse *fuse_new_compat22(int fd, const char *opts, + const struct fuse_operations_compat22 *op, + size_t op_size) +{ + return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op, + op_size, 22); +} + +struct fuse *fuse_new_compat2(int fd, const char *opts, + const struct fuse_operations_compat2 *op) +{ + return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op, + sizeof(struct fuse_operations_compat2), + 21); +} + +struct fuse *fuse_new_compat1(int fd, int flags, + const struct fuse_operations_compat1 *op) +{ + const char *opts = NULL; + if (flags & FUSE_DEBUG_COMPAT1) + opts = "debug"; + return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op, + sizeof(struct fuse_operations_compat1), + 11); +} + +FUSE_SYMVER(".symver fuse_exited,__fuse_exited@FUSE_UNVERSIONED"); +FUSE_SYMVER(".symver fuse_process_cmd,__fuse_process_cmd@FUSE_UNVERSIONED"); +FUSE_SYMVER(".symver fuse_read_cmd,__fuse_read_cmd@FUSE_UNVERSIONED"); +FUSE_SYMVER(".symver fuse_set_getcontext_func,__fuse_set_getcontext_func@FUSE_UNVERSIONED"); +FUSE_SYMVER(".symver fuse_new_compat2,fuse_new@FUSE_UNVERSIONED"); +FUSE_SYMVER(".symver fuse_new_compat22,fuse_new@FUSE_2.2"); + +#endif /* __FreeBSD__ || __NetBSD__ */ + +struct fuse *fuse_new_compat25(int fd, struct fuse_args *args, + const struct fuse_operations_compat25 *op, + size_t op_size) +{ + return fuse_new_common_compat25(fd, args, (struct fuse_operations *) op, + op_size, 25); +} + +FUSE_SYMVER(".symver fuse_new_compat25,fuse_new@FUSE_2.5"); diff --git a/fuse/fuse_i.h b/fuse/fuse_i.h new file mode 100644 index 000000000..fa3715606 --- /dev/null +++ b/fuse/fuse_i.h @@ -0,0 +1,130 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse.h" +#include "fuse_lowlevel.h" + +struct fuse_chan; +struct fuse_ll; + +struct fuse_session { + struct fuse_session_ops op; + + int (*receive_buf)(struct fuse_session *se, struct fuse_buf *buf, + struct fuse_chan **chp); + + void (*process_buf)(void *data, const struct fuse_buf *buf, + struct fuse_chan *ch); + + void *data; + + volatile int exited; + + struct fuse_chan *ch; +}; + +struct fuse_req { + struct fuse_ll *f; + uint64_t unique; + int ctr; + pthread_mutex_t lock; + struct fuse_ctx ctx; + struct fuse_chan *ch; + int interrupted; + unsigned int ioctl_64bit : 1; + union { + struct { + uint64_t unique; + } i; + struct { + fuse_interrupt_func_t func; + void *data; + } ni; + } u; + struct fuse_req *next; + struct fuse_req *prev; +}; + +struct fuse_notify_req { + uint64_t unique; + void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t, + const void *, const struct fuse_buf *); + struct fuse_notify_req *next; + struct fuse_notify_req *prev; +}; + +struct fuse_ll { + int debug; + int allow_root; + int atomic_o_trunc; + int no_remote_posix_lock; + int no_remote_flock; + int big_writes; + int splice_write; + int splice_move; + int splice_read; + int no_splice_write; + int no_splice_move; + int no_splice_read; + struct fuse_lowlevel_ops op; + int got_init; + struct cuse_data *cuse_data; + void *userdata; + uid_t owner; + struct fuse_conn_info conn; + struct fuse_req list; + struct fuse_req interrupts; + pthread_mutex_t lock; + int got_destroy; + pthread_key_t pipe_key; + int broken_splice_nonblock; + uint64_t notify_ctr; + struct fuse_notify_req notify_list; +}; + +struct fuse_cmd { + char *buf; + size_t buflen; + struct fuse_chan *ch; +}; + +struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data, int compat); + +int fuse_sync_compat_args(struct fuse_args *args); + +struct fuse_chan *fuse_kern_chan_new(int fd); + +struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); + +void fuse_kern_unmount_compat22(const char *mountpoint); +int fuse_chan_clearfd(struct fuse_chan *ch); + +void fuse_kern_unmount(const char *mountpoint, int fd); +int fuse_kern_mount(const char *mountpoint, struct fuse_args *args); + +int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, + int count); +void fuse_free_req(fuse_req_t req); + + +struct fuse *fuse_setup_common(int argc, char *argv[], + const struct fuse_operations *op, + size_t op_size, + char **mountpoint, + int *multithreaded, + int *fd, + void *user_data, + int compat); + +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg); + +int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg); diff --git a/fuse/fuse_kern_chan.c b/fuse/fuse_kern_chan.c new file mode 100644 index 000000000..4a9beb8f8 --- /dev/null +++ b/fuse/fuse_kern_chan.c @@ -0,0 +1,98 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_lowlevel.h" +#include "fuse_kernel.h" +#include "fuse_i.h" + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> + +static int fuse_kern_chan_receive(struct fuse_chan **chp, char *buf, + size_t size) +{ + struct fuse_chan *ch = *chp; + int err; + ssize_t res; + struct fuse_session *se = fuse_chan_session(ch); + assert(se != NULL); + +restart: + res = read(fuse_chan_fd(ch), buf, size); + err = errno; + + if (fuse_session_exited(se)) + return 0; + if (res == -1) { + /* ENOENT means the operation was interrupted, it's safe + to restart */ + if (err == ENOENT) + goto restart; + + if (err == ENODEV) { + fuse_session_exit(se); + return 0; + } + /* Errors occurring during normal operation: EINTR (read + interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem + umounted) */ + if (err != EINTR && err != EAGAIN) + perror("fuse: reading device"); + return -err; + } + if ((size_t) res < sizeof(struct fuse_in_header)) { + fprintf(stderr, "short read on fuse device\n"); + return -EIO; + } + return res; +} + +static int fuse_kern_chan_send(struct fuse_chan *ch, const struct iovec iov[], + size_t count) +{ + if (iov) { + ssize_t res = writev(fuse_chan_fd(ch), iov, count); + int err = errno; + + if (res == -1) { + struct fuse_session *se = fuse_chan_session(ch); + + assert(se != NULL); + + /* ENOENT means the operation was interrupted */ + if (!fuse_session_exited(se) && err != ENOENT) + perror("fuse: writing device"); + return -err; + } + } + return 0; +} + +static void fuse_kern_chan_destroy(struct fuse_chan *ch) +{ + int fd = fuse_chan_fd(ch); + + if (fd != -1) + close(fd); +} + +#define MIN_BUFSIZE 0x21000 + +struct fuse_chan *fuse_kern_chan_new(int fd) +{ + struct fuse_chan_ops op = { + .receive = fuse_kern_chan_receive, + .send = fuse_kern_chan_send, + .destroy = fuse_kern_chan_destroy, + }; + size_t bufsize = getpagesize() + 0x1000; + bufsize = bufsize < MIN_BUFSIZE ? MIN_BUFSIZE : bufsize; + return fuse_chan_new(&op, fd, bufsize, NULL); +} diff --git a/fuse/fuse_loop.c b/fuse/fuse_loop.c new file mode 100644 index 000000000..b7b4ca4ee --- /dev/null +++ b/fuse/fuse_loop.c @@ -0,0 +1,46 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_lowlevel.h" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +int fuse_session_loop(struct fuse_session *se) +{ + int res = 0; + struct fuse_chan *ch = fuse_session_next_chan(se, NULL); + size_t bufsize = fuse_chan_bufsize(ch); + char *buf = (char *) malloc(bufsize); + if (!buf) { + fprintf(stderr, "fuse: failed to allocate read buffer\n"); + return -1; + } + + while (!fuse_session_exited(se)) { + struct fuse_chan *tmpch = ch; + struct fuse_buf fbuf = { + .mem = buf, + .size = bufsize, + }; + + res = fuse_session_receive_buf(se, &fbuf, &tmpch); + + if (res == -EINTR) + continue; + if (res <= 0) + break; + + fuse_session_process_buf(se, &fbuf, tmpch); + } + + free(buf); + fuse_session_reset(se); + return res < 0 ? -1 : 0; +} diff --git a/fuse/fuse_loop_mt.c b/fuse/fuse_loop_mt.c new file mode 100644 index 000000000..90fc1e694 --- /dev/null +++ b/fuse/fuse_loop_mt.c @@ -0,0 +1,291 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "fuse_lowlevel.h" +#include "fuse_misc.h" +#include "fuse_kernel.h" +#include "fuse_i.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <semaphore.h> +#include <errno.h> +#include <sys/time.h> + +/* Environment var controlling the thread stack size */ +#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK" + +struct fuse_worker { + struct fuse_worker *prev; + struct fuse_worker *next; + pthread_t thread_id; + size_t bufsize; + char *buf; + struct fuse_mt *mt; +}; + +struct fuse_mt { + pthread_mutex_t lock; + int numworker; + int numavail; + struct fuse_session *se; + struct fuse_chan *prevch; + struct fuse_worker main; + sem_t finish; + int exit; + int error; +}; + +static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next) +{ + struct fuse_worker *prev = next->prev; + w->next = next; + w->prev = prev; + prev->next = w; + next->prev = w; +} + +static void list_del_worker(struct fuse_worker *w) +{ + struct fuse_worker *prev = w->prev; + struct fuse_worker *next = w->next; + prev->next = next; + next->prev = prev; +} + +static int fuse_loop_start_thread(struct fuse_mt *mt); + +static void thread_exit_handler(int sig) +{ + pthread_exit(0); +} + +static void *fuse_do_work(void *data) +{ + struct fuse_worker *w = (struct fuse_worker *) data; + struct fuse_mt *mt = w->mt; + +#if defined(__ANDROID__) + struct sigaction actions; + memset(&actions, 0, sizeof(actions)); + sigemptyset(&actions.sa_mask); + actions.sa_flags = 0; + actions.sa_handler = thread_exit_handler; + sigaction(SIGUSR1, &actions, NULL); + + sigset_t setusr1; + sigemptyset(&setusr1); + sigaddset(&setusr1, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &setusr1, NULL); +#endif + + while (!fuse_session_exited(mt->se)) { + int isforget = 0; + struct fuse_chan *ch = mt->prevch; + struct fuse_buf fbuf = { + .mem = w->buf, + .size = w->bufsize, + }; + int res; + +#if defined(__ANDROID__) + pthread_sigmask(SIG_UNBLOCK, &setusr1, NULL); +#else + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); +#endif + res = fuse_session_receive_buf(mt->se, &fbuf, &ch); +#if defined(__ANDROID__) + pthread_sigmask(SIG_BLOCK, &setusr1, NULL); +#else + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); +#endif + if (res == -EINTR) + continue; + if (res <= 0) { + if (res < 0) { + fuse_session_exit(mt->se); + mt->error = -1; + } + break; + } + + pthread_mutex_lock(&mt->lock); + if (mt->exit) { + pthread_mutex_unlock(&mt->lock); + return NULL; + } + + /* + * This disgusting hack is needed so that zillions of threads + * are not created on a burst of FORGET messages + */ + if (!(fbuf.flags & FUSE_BUF_IS_FD)) { + struct fuse_in_header *in = fbuf.mem; + + if (in->opcode == FUSE_FORGET || + in->opcode == FUSE_BATCH_FORGET) + isforget = 1; + } + + if (!isforget) + mt->numavail--; + if (mt->numavail == 0) + fuse_loop_start_thread(mt); + pthread_mutex_unlock(&mt->lock); + + fuse_session_process_buf(mt->se, &fbuf, ch); + + pthread_mutex_lock(&mt->lock); + if (!isforget) + mt->numavail++; + if (mt->numavail > 10) { + if (mt->exit) { + pthread_mutex_unlock(&mt->lock); + return NULL; + } + list_del_worker(w); + mt->numavail--; + mt->numworker--; + pthread_mutex_unlock(&mt->lock); + + pthread_detach(w->thread_id); + free(w->buf); + free(w); + return NULL; + } + pthread_mutex_unlock(&mt->lock); + } + + sem_post(&mt->finish); + + return NULL; +} + +int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg) +{ + sigset_t oldset; + sigset_t newset; + int res; + pthread_attr_t attr; + char *stack_size; + + /* Override default stack size */ + pthread_attr_init(&attr); + stack_size = getenv(ENVNAME_THREAD_STACK); + if (stack_size && pthread_attr_setstacksize(&attr, atoi(stack_size))) + fprintf(stderr, "fuse: invalid stack size: %s\n", stack_size); + + /* Disallow signal reception in worker threads */ + sigemptyset(&newset); + sigaddset(&newset, SIGTERM); + sigaddset(&newset, SIGINT); + sigaddset(&newset, SIGHUP); + sigaddset(&newset, SIGQUIT); + pthread_sigmask(SIG_BLOCK, &newset, &oldset); + res = pthread_create(thread_id, &attr, func, arg); + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + pthread_attr_destroy(&attr); + if (res != 0) { + fprintf(stderr, "fuse: error creating thread: %s\n", + strerror(res)); + return -1; + } + + return 0; +} + +static int fuse_loop_start_thread(struct fuse_mt *mt) +{ + int res; + struct fuse_worker *w = malloc(sizeof(struct fuse_worker)); + if (!w) { + fprintf(stderr, "fuse: failed to allocate worker structure\n"); + return -1; + } + memset(w, 0, sizeof(struct fuse_worker)); + w->bufsize = fuse_chan_bufsize(mt->prevch); + w->buf = malloc(w->bufsize); + w->mt = mt; + if (!w->buf) { + fprintf(stderr, "fuse: failed to allocate read buffer\n"); + free(w); + return -1; + } + + res = fuse_start_thread(&w->thread_id, fuse_do_work, w); + if (res == -1) { + free(w->buf); + free(w); + return -1; + } + list_add_worker(w, &mt->main); + mt->numavail ++; + mt->numworker ++; + + return 0; +} + +static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w) +{ + pthread_join(w->thread_id, NULL); + pthread_mutex_lock(&mt->lock); + list_del_worker(w); + pthread_mutex_unlock(&mt->lock); + free(w->buf); + free(w); +} + +int fuse_session_loop_mt(struct fuse_session *se) +{ + int err; + struct fuse_mt mt; + struct fuse_worker *w; + + memset(&mt, 0, sizeof(struct fuse_mt)); + mt.se = se; + mt.prevch = fuse_session_next_chan(se, NULL); + mt.error = 0; + mt.numworker = 0; + mt.numavail = 0; + mt.main.thread_id = pthread_self(); + mt.main.prev = mt.main.next = &mt.main; + sem_init(&mt.finish, 0, 0); + fuse_mutex_init(&mt.lock); + + pthread_mutex_lock(&mt.lock); + err = fuse_loop_start_thread(&mt); + pthread_mutex_unlock(&mt.lock); + if (!err) { + /* sem_wait() is interruptible */ + while (!fuse_session_exited(se)) + sem_wait(&mt.finish); + + pthread_mutex_lock(&mt.lock); + for (w = mt.main.next; w != &mt.main; w = w->next) +#if defined(__ANDROID__) + pthread_kill(w->thread_id, SIGUSR1); +#else + pthread_cancel(w->thread_id); +#endif + mt.exit = 1; + pthread_mutex_unlock(&mt.lock); + + while (mt.main.next != &mt.main) + fuse_join_worker(&mt, mt.main.next); + + err = mt.error; + } + + pthread_mutex_destroy(&mt.lock); + sem_destroy(&mt.finish); + fuse_session_reset(se); + return err; +} diff --git a/fuse/fuse_lowlevel.c b/fuse/fuse_lowlevel.c new file mode 100644 index 000000000..5f223c98f --- /dev/null +++ b/fuse/fuse_lowlevel.c @@ -0,0 +1,2968 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#define _GNU_SOURCE + +#include "config.h" +#include "fuse_i.h" +#include "fuse_kernel.h" +#include "fuse_opt.h" +#include "fuse_misc.h" +#include "fuse_common_compat.h" +#include "fuse_lowlevel_compat.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <assert.h> +#include <sys/file.h> + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif +#ifndef F_SETPIPE_SZ +#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) +#endif + + +#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) +#define OFFSET_MAX 0x7fffffffffffffffLL + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +struct fuse_pollhandle { + uint64_t kh; + struct fuse_chan *ch; + struct fuse_ll *f; +}; + +static size_t pagesize; + +static __attribute__((constructor)) void fuse_ll_init_pagesize(void) +{ + pagesize = getpagesize(); +} + +static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) +{ + attr->ino = stbuf->st_ino; + attr->mode = stbuf->st_mode; + attr->nlink = stbuf->st_nlink; + attr->uid = stbuf->st_uid; + attr->gid = stbuf->st_gid; + attr->rdev = stbuf->st_rdev; + attr->size = stbuf->st_size; + attr->blksize = stbuf->st_blksize; + attr->blocks = stbuf->st_blocks; + attr->atime = stbuf->st_atime; + attr->mtime = stbuf->st_mtime; + attr->ctime = stbuf->st_ctime; + attr->atimensec = ST_ATIM_NSEC(stbuf); + attr->mtimensec = ST_MTIM_NSEC(stbuf); + attr->ctimensec = ST_CTIM_NSEC(stbuf); +} + +static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf) +{ + stbuf->st_mode = attr->mode; + stbuf->st_uid = attr->uid; + stbuf->st_gid = attr->gid; + stbuf->st_size = attr->size; + stbuf->st_atime = attr->atime; + stbuf->st_mtime = attr->mtime; + ST_ATIM_NSEC_SET(stbuf, attr->atimensec); + ST_MTIM_NSEC_SET(stbuf, attr->mtimensec); +} + +static size_t iov_length(const struct iovec *iov, size_t count) +{ + size_t seg; + size_t ret = 0; + + for (seg = 0; seg < count; seg++) + ret += iov[seg].iov_len; + return ret; +} + +static void list_init_req(struct fuse_req *req) +{ + req->next = req; + req->prev = req; +} + +static void list_del_req(struct fuse_req *req) +{ + struct fuse_req *prev = req->prev; + struct fuse_req *next = req->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_req(struct fuse_req *req, struct fuse_req *next) +{ + struct fuse_req *prev = next->prev; + req->next = next; + req->prev = prev; + prev->next = req; + next->prev = req; +} + +static void destroy_req(fuse_req_t req) +{ + pthread_mutex_destroy(&req->lock); + free(req); +} + +void fuse_free_req(fuse_req_t req) +{ + int ctr; + struct fuse_ll *f = req->f; + + pthread_mutex_lock(&f->lock); + req->u.ni.func = NULL; + req->u.ni.data = NULL; + list_del_req(req); + ctr = --req->ctr; + pthread_mutex_unlock(&f->lock); + if (!ctr) + destroy_req(req); +} + +static struct fuse_req *fuse_ll_alloc_req(struct fuse_ll *f) +{ + struct fuse_req *req; + + req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req)); + if (req == NULL) { + fprintf(stderr, "fuse: failed to allocate request\n"); + } else { + req->f = f; + req->ctr = 1; + list_init_req(req); + fuse_mutex_init(&req->lock); + } + + return req; +} + + +static int fuse_send_msg(struct fuse_ll *f, struct fuse_chan *ch, + struct iovec *iov, int count) +{ + struct fuse_out_header *out = iov[0].iov_base; + + out->len = iov_length(iov, count); + if (f->debug) { + if (out->unique == 0) { + fprintf(stderr, "NOTIFY: code=%d length=%u\n", + out->error, out->len); + } else if (out->error) { + fprintf(stderr, + " unique: %llu, error: %i (%s), outsize: %i\n", + (unsigned long long) out->unique, out->error, + strerror(-out->error), out->len); + } else { + fprintf(stderr, + " unique: %llu, success, outsize: %i\n", + (unsigned long long) out->unique, out->len); + } + } + + return fuse_chan_send(ch, iov, count); +} + +int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, + int count) +{ + struct fuse_out_header out; + + if (error <= -1000 || error > 0) { + fprintf(stderr, "fuse: bad error value: %i\n", error); + error = -ERANGE; + } + + out.unique = req->unique; + out.error = error; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + return fuse_send_msg(req->f, req->ch, iov, count); +} + +static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov, + int count) +{ + int res; + + res = fuse_send_reply_iov_nofree(req, error, iov, count); + fuse_free_req(req); + return res; +} + +static int send_reply(fuse_req_t req, int error, const void *arg, + size_t argsize) +{ + struct iovec iov[2]; + int count = 1; + if (argsize) { + iov[1].iov_base = (void *) arg; + iov[1].iov_len = argsize; + count++; + } + return send_reply_iov(req, error, iov, count); +} + +int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count) +{ + int res; + struct iovec *padded_iov; + + padded_iov = malloc((count + 1) * sizeof(struct iovec)); + if (padded_iov == NULL) + return fuse_reply_err(req, ENOMEM); + + memcpy(padded_iov + 1, iov, count * sizeof(struct iovec)); + count++; + + res = send_reply_iov(req, 0, padded_iov, count); + free(padded_iov); + + return res; +} + +size_t fuse_dirent_size(size_t namelen) +{ + return FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + namelen); +} + +char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf, + loff_t off) +{ + unsigned namelen = strlen(name); + unsigned entlen = FUSE_NAME_OFFSET + namelen; + unsigned entsize = fuse_dirent_size(namelen); + unsigned padlen = entsize - entlen; + struct fuse_dirent *dirent = (struct fuse_dirent *) buf; + + dirent->ino = stbuf->st_ino; + dirent->off = off; + dirent->namelen = namelen; + dirent->type = (stbuf->st_mode & 0170000) >> 12; + strncpy(dirent->name, name, namelen); + if (padlen) + memset(buf + entlen, 0, padlen); + + return buf + entsize; +} + +size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, + const char *name, const struct stat *stbuf, loff_t off) +{ + size_t entsize; + + (void) req; + entsize = fuse_dirent_size(strlen(name)); + if (entsize <= bufsize && buf) + fuse_add_dirent(buf, name, stbuf, off); + return entsize; +} + +static void convert_statfs(const struct statvfs *stbuf, + struct fuse_kstatfs *kstatfs) +{ + kstatfs->bsize = stbuf->f_bsize; + kstatfs->frsize = stbuf->f_frsize; + kstatfs->blocks = stbuf->f_blocks; + kstatfs->bfree = stbuf->f_bfree; + kstatfs->bavail = stbuf->f_bavail; + kstatfs->files = stbuf->f_files; + kstatfs->ffree = stbuf->f_ffree; + kstatfs->namelen = stbuf->f_namemax; +} + +static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize) +{ + return send_reply(req, 0, arg, argsize); +} + +int fuse_reply_err(fuse_req_t req, int err) +{ + return send_reply(req, -err, NULL, 0); +} + +void fuse_reply_none(fuse_req_t req) +{ + if (req->ch) + fuse_chan_send(req->ch, NULL, 0); + fuse_free_req(req); +} + +static unsigned long calc_timeout_sec(double t) +{ + if (t > (double) ULONG_MAX) + return ULONG_MAX; + else if (t < 0.0) + return 0; + else + return (unsigned long) t; +} + +static unsigned int calc_timeout_nsec(double t) +{ + double f = t - (double) calc_timeout_sec(t); + if (f < 0.0) + return 0; + else if (f >= 0.999999999) + return 999999999; + else + return (unsigned int) (f * 1.0e9); +} + +static void fill_entry(struct fuse_entry_out *arg, + const struct fuse_entry_param *e) +{ + arg->nodeid = e->ino; + arg->generation = e->generation; + arg->entry_valid = calc_timeout_sec(e->entry_timeout); + arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout); + arg->attr_valid = calc_timeout_sec(e->attr_timeout); + arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout); + convert_stat(&e->attr, &arg->attr); +} + +static void fill_open(struct fuse_open_out *arg, + const struct fuse_file_info *f) +{ + arg->fh = f->fh; + if (f->direct_io) + arg->open_flags |= FOPEN_DIRECT_IO; + if (f->keep_cache) + arg->open_flags |= FOPEN_KEEP_CACHE; + if (f->nonseekable) + arg->open_flags |= FOPEN_NONSEEKABLE; +} + +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) +{ + struct fuse_entry_out arg; + size_t size = req->f->conn.proto_minor < 9 ? + FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg); + + /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant + negative entry */ + if (!e->ino && req->f->conn.proto_minor < 4) + return fuse_reply_err(req, ENOENT); + + memset(&arg, 0, sizeof(arg)); + fill_entry(&arg, e); + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, + const struct fuse_file_info *f) +{ + char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)]; + size_t entrysize = req->f->conn.proto_minor < 9 ? + FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out); + struct fuse_entry_out *earg = (struct fuse_entry_out *) buf; + struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize); + + memset(buf, 0, sizeof(buf)); + fill_entry(earg, e); + fill_open(oarg, f); + return send_reply_ok(req, buf, + entrysize + sizeof(struct fuse_open_out)); +} + +int fuse_reply_attr(fuse_req_t req, const struct stat *attr, + double attr_timeout) +{ + struct fuse_attr_out arg; + size_t size = req->f->conn.proto_minor < 9 ? + FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg); + + memset(&arg, 0, sizeof(arg)); + arg.attr_valid = calc_timeout_sec(attr_timeout); + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); + convert_stat(attr, &arg.attr); + + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_readlink(fuse_req_t req, const char *linkname) +{ + return send_reply_ok(req, linkname, strlen(linkname)); +} + +int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f) +{ + struct fuse_open_out arg; + + memset(&arg, 0, sizeof(arg)); + fill_open(&arg, f); + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_write(fuse_req_t req, size_t count) +{ + struct fuse_write_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.size = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size) +{ + return send_reply_ok(req, buf, size); +} + +static int fuse_send_data_iov_fallback(struct fuse_ll *f, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, + size_t len) +{ + struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); + void *mbuf; + int res; + + /* Optimize common case */ + if (buf->count == 1 && buf->idx == 0 && buf->off == 0 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + /* FIXME: also avoid memory copy if there are multiple buffers + but none of them contain an fd */ + + iov[iov_count].iov_base = buf->buf[0].mem; + iov[iov_count].iov_len = len; + iov_count++; + return fuse_send_msg(f, ch, iov, iov_count); + } + + res = posix_memalign(&mbuf, pagesize, len); + if (res != 0) + return res; + + mem_buf.buf[0].mem = mbuf; + res = fuse_buf_copy(&mem_buf, buf, 0); + if (res < 0) { + free(mbuf); + return -res; + } + len = res; + + iov[iov_count].iov_base = mbuf; + iov[iov_count].iov_len = len; + iov_count++; + res = fuse_send_msg(f, ch, iov, iov_count); + free(mbuf); + + return res; +} + +struct fuse_ll_pipe { + size_t size; + int can_grow; + int pipe[2]; +}; + +static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp) +{ + close(llp->pipe[0]); + close(llp->pipe[1]); + free(llp); +} + +#ifdef HAVE_SPLICE +static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_ll *f) +{ + struct fuse_ll_pipe *llp = pthread_getspecific(f->pipe_key); + if (llp == NULL) { + int res; + + llp = malloc(sizeof(struct fuse_ll_pipe)); + if (llp == NULL) + return NULL; + + res = pipe(llp->pipe); + if (res == -1) { + free(llp); + return NULL; + } + + if (fcntl(llp->pipe[0], F_SETFL, O_NONBLOCK) == -1 || + fcntl(llp->pipe[1], F_SETFL, O_NONBLOCK) == -1) { + close(llp->pipe[0]); + close(llp->pipe[1]); + free(llp); + return NULL; + } + + /* + *the default size is 16 pages on linux + */ + llp->size = pagesize * 16; + llp->can_grow = 1; + + pthread_setspecific(f->pipe_key, llp); + } + + return llp; +} +#endif + +static void fuse_ll_clear_pipe(struct fuse_ll *f) +{ + struct fuse_ll_pipe *llp = pthread_getspecific(f->pipe_key); + if (llp) { + pthread_setspecific(f->pipe_key, NULL); + fuse_ll_pipe_free(llp); + } +} + +#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE) +static int read_back(int fd, char *buf, size_t len) +{ + int res; + + res = read(fd, buf, len); + if (res == -1) { + fprintf(stderr, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno)); + return -EIO; + } + if (res != len) { + fprintf(stderr, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len); + return -EIO; + } + return 0; +} + +static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, unsigned int flags) +{ + int res; + size_t len = fuse_buf_size(buf); + struct fuse_out_header *out = iov[0].iov_base; + struct fuse_ll_pipe *llp; + int splice_flags; + size_t pipesize; + size_t total_fd_size; + size_t idx; + size_t headerlen; + struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len); + + if (f->broken_splice_nonblock) + goto fallback; + + if (flags & FUSE_BUF_NO_SPLICE) + goto fallback; + + total_fd_size = 0; + for (idx = buf->idx; idx < buf->count; idx++) { + if (buf->buf[idx].flags & FUSE_BUF_IS_FD) { + total_fd_size = buf->buf[idx].size; + if (idx == buf->idx) + total_fd_size -= buf->off; + } + } + if (total_fd_size < 2 * pagesize) + goto fallback; + + if (f->conn.proto_minor < 14 || + !(f->conn.want & FUSE_CAP_SPLICE_WRITE)) + goto fallback; + + llp = fuse_ll_get_pipe(f); + if (llp == NULL) + goto fallback; + + + headerlen = iov_length(iov, iov_count); + + out->len = headerlen + len; + + /* + * Heuristic for the required pipe size, does not work if the + * source contains less than page size fragments + */ + pipesize = pagesize * (iov_count + buf->count + 1) + out->len; + + if (llp->size < pipesize) { + if (llp->can_grow) { + res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize); + if (res == -1) { + llp->can_grow = 0; + goto fallback; + } + llp->size = res; + } + if (llp->size < pipesize) + goto fallback; + } + + + res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK); + if (res == -1) + goto fallback; + + if (res != headerlen) { + res = -EIO; + fprintf(stderr, "fuse: short vmsplice to pipe: %u/%zu\n", res, + headerlen); + goto clear_pipe; + } + + pipe_buf.buf[0].flags = FUSE_BUF_IS_FD; + pipe_buf.buf[0].fd = llp->pipe[1]; + + res = fuse_buf_copy(&pipe_buf, buf, + FUSE_BUF_FORCE_SPLICE | FUSE_BUF_SPLICE_NONBLOCK); + if (res < 0) { + if (res == -EAGAIN || res == -EINVAL) { + /* + * Should only get EAGAIN on kernels with + * broken SPLICE_F_NONBLOCK support (<= + * 2.6.35) where this error or a short read is + * returned even if the pipe itself is not + * full + * + * EINVAL might mean that splice can't handle + * this combination of input and output. + */ + if (res == -EAGAIN) + f->broken_splice_nonblock = 1; + + pthread_setspecific(f->pipe_key, NULL); + fuse_ll_pipe_free(llp); + goto fallback; + } + res = -res; + goto clear_pipe; + } + + if (res != 0 && res < len) { + struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); + void *mbuf; + size_t now_len = res; + /* + * For regular files a short count is either + * 1) due to EOF, or + * 2) because of broken SPLICE_F_NONBLOCK (see above) + * + * For other inputs it's possible that we overflowed + * the pipe because of small buffer fragments. + */ + + res = posix_memalign(&mbuf, pagesize, len); + if (res != 0) + goto clear_pipe; + + mem_buf.buf[0].mem = mbuf; + mem_buf.off = now_len; + res = fuse_buf_copy(&mem_buf, buf, 0); + if (res > 0) { + char *tmpbuf; + size_t extra_len = res; + /* + * Trickiest case: got more data. Need to get + * back the data from the pipe and then fall + * back to regular write. + */ + tmpbuf = malloc(headerlen); + if (tmpbuf == NULL) { + free(mbuf); + res = ENOMEM; + goto clear_pipe; + } + res = read_back(llp->pipe[0], tmpbuf, headerlen); + if (res != 0) { + free(mbuf); + goto clear_pipe; + } + free(tmpbuf); + res = read_back(llp->pipe[0], mbuf, now_len); + if (res != 0) { + free(mbuf); + goto clear_pipe; + } + len = now_len + extra_len; + iov[iov_count].iov_base = mbuf; + iov[iov_count].iov_len = len; + iov_count++; + res = fuse_send_msg(f, ch, iov, iov_count); + free(mbuf); + return res; + } + free(mbuf); + res = now_len; + } + len = res; + out->len = headerlen + len; + + if (f->debug) { + fprintf(stderr, + " unique: %llu, success, outsize: %i (splice)\n", + (unsigned long long) out->unique, out->len); + } + + splice_flags = 0; + if ((flags & FUSE_BUF_SPLICE_MOVE) && + (f->conn.want & FUSE_CAP_SPLICE_MOVE)) + splice_flags |= SPLICE_F_MOVE; + + res = splice(llp->pipe[0], NULL, + fuse_chan_fd(ch), NULL, out->len, splice_flags); + if (res == -1) { + res = -errno; + perror("fuse: splice from pipe"); + goto clear_pipe; + } + if (res != out->len) { + res = -EIO; + fprintf(stderr, "fuse: short splice from pipe: %u/%u\n", + res, out->len); + goto clear_pipe; + } + return 0; + +clear_pipe: + fuse_ll_clear_pipe(f); + return res; + +fallback: + return fuse_send_data_iov_fallback(f, ch, iov, iov_count, buf, len); +} +#else +static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, unsigned int flags) +{ + size_t len = fuse_buf_size(buf); + (void) flags; + + return fuse_send_data_iov_fallback(f, ch, iov, iov_count, buf, len); +} +#endif + +int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct iovec iov[2]; + struct fuse_out_header out; + int res; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + out.unique = req->unique; + out.error = 0; + + res = fuse_send_data_iov(req->f, req->ch, iov, 1, bufv, flags); + if (res <= 0) { + fuse_free_req(req); + return res; + } else { + return fuse_reply_err(req, res); + } +} + +int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf) +{ + struct fuse_statfs_out arg; + size_t size = req->f->conn.proto_minor < 4 ? + FUSE_COMPAT_STATFS_SIZE : sizeof(arg); + + memset(&arg, 0, sizeof(arg)); + convert_statfs(stbuf, &arg.st); + + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_xattr(fuse_req_t req, size_t count) +{ + struct fuse_getxattr_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.size = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_lock(fuse_req_t req, const struct flock *lock) +{ + struct fuse_lk_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.lk.type = lock->l_type; + if (lock->l_type != F_UNLCK) { + arg.lk.start = lock->l_start; + if (lock->l_len == 0) + arg.lk.end = OFFSET_MAX; + else + arg.lk.end = lock->l_start + lock->l_len - 1; + } + arg.lk.pid = lock->l_pid; + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_bmap(fuse_req_t req, uint64_t idx) +{ + struct fuse_bmap_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.block = idx; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov, + size_t count) +{ + struct fuse_ioctl_iovec *fiov; + size_t i; + + fiov = malloc(sizeof(fiov[0]) * count); + if (!fiov) + return NULL; + + for (i = 0; i < count; i++) { + fiov[i].base = (uintptr_t) iov[i].iov_base; + fiov[i].len = iov[i].iov_len; + } + + return fiov; +} + +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) +{ + struct fuse_ioctl_out arg; + struct fuse_ioctl_iovec *in_fiov = NULL; + struct fuse_ioctl_iovec *out_fiov = NULL; + struct iovec iov[4]; + size_t count = 1; + int res; + + memset(&arg, 0, sizeof(arg)); + arg.flags |= FUSE_IOCTL_RETRY; + arg.in_iovs = in_count; + arg.out_iovs = out_count; + iov[count].iov_base = &arg; + iov[count].iov_len = sizeof(arg); + count++; + + if (req->f->conn.proto_minor < 16) { + if (in_count) { + iov[count].iov_base = (void *)in_iov; + iov[count].iov_len = sizeof(in_iov[0]) * in_count; + count++; + } + + if (out_count) { + iov[count].iov_base = (void *)out_iov; + iov[count].iov_len = sizeof(out_iov[0]) * out_count; + count++; + } + } else { + /* Can't handle non-compat 64bit ioctls on 32bit */ + if (sizeof(void *) == 4 && req->ioctl_64bit) { + res = fuse_reply_err(req, EINVAL); + goto out; + } + + if (in_count) { + in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count); + if (!in_fiov) + goto enomem; + + iov[count].iov_base = (void *)in_fiov; + iov[count].iov_len = sizeof(in_fiov[0]) * in_count; + count++; + } + if (out_count) { + out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count); + if (!out_fiov) + goto enomem; + + iov[count].iov_base = (void *)out_fiov; + iov[count].iov_len = sizeof(out_fiov[0]) * out_count; + count++; + } + } + + res = send_reply_iov(req, 0, iov, count); +out: + free(in_fiov); + free(out_fiov); + + return res; + +enomem: + res = fuse_reply_err(req, ENOMEM); + goto out; +} + +int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size) +{ + struct fuse_ioctl_out arg; + struct iovec iov[3]; + size_t count = 1; + + memset(&arg, 0, sizeof(arg)); + arg.result = result; + iov[count].iov_base = &arg; + iov[count].iov_len = sizeof(arg); + count++; + + if (size) { + iov[count].iov_base = (char *) buf; + iov[count].iov_len = size; + count++; + } + + return send_reply_iov(req, 0, iov, count); +} + +int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, + int count) +{ + struct iovec *padded_iov; + struct fuse_ioctl_out arg; + int res; + + padded_iov = malloc((count + 2) * sizeof(struct iovec)); + if (padded_iov == NULL) + return fuse_reply_err(req, ENOMEM); + + memset(&arg, 0, sizeof(arg)); + arg.result = result; + padded_iov[1].iov_base = &arg; + padded_iov[1].iov_len = sizeof(arg); + + memcpy(&padded_iov[2], iov, count * sizeof(struct iovec)); + + res = send_reply_iov(req, 0, padded_iov, count + 2); + free(padded_iov); + + return res; +} + +int fuse_reply_poll(fuse_req_t req, unsigned revents) +{ + struct fuse_poll_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.revents = revents; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + + if (req->f->op.lookup) + req->f->op.lookup(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg; + + if (req->f->op.forget) + req->f->op.forget(req, nodeid, arg->nlookup); + else + fuse_reply_none(req); +} + +static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg) +{ + struct fuse_batch_forget_in *arg = (void *) inarg; + struct fuse_forget_one *param = (void *) PARAM(arg); + unsigned int i; + + (void) nodeid; + + if (req->f->op.forget_multi) { + req->f->op.forget_multi(req, arg->count, + (struct fuse_forget_data *) param); + } else if (req->f->op.forget) { + for (i = 0; i < arg->count; i++) { + struct fuse_forget_one *forget = ¶m[i]; + struct fuse_req *dummy_req; + + dummy_req = fuse_ll_alloc_req(req->f); + if (dummy_req == NULL) + break; + + dummy_req->unique = req->unique; + dummy_req->ctx = req->ctx; + dummy_req->ch = NULL; + + req->f->op.forget(dummy_req, forget->nodeid, + forget->nlookup); + } + fuse_reply_none(req); + } else { + fuse_reply_none(req); + } +} + +static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_file_info *fip = NULL; + struct fuse_file_info fi; + + if (req->f->conn.proto_minor >= 9) { + struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg; + + if (arg->getattr_flags & FUSE_GETATTR_FH) { + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + fip = &fi; + } + } + + if (req->f->op.getattr) + req->f->op.getattr(req, nodeid, fip); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg; + + if (req->f->op.setattr) { + struct fuse_file_info *fi = NULL; + struct fuse_file_info fi_store; + struct stat stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + convert_attr(arg, &stbuf); + if (arg->valid & FATTR_FH) { + arg->valid &= ~FATTR_FH; + memset(&fi_store, 0, sizeof(fi_store)); + fi = &fi_store; + fi->fh = arg->fh; + fi->fh_old = fi->fh; + } + arg->valid &= + FUSE_SET_ATTR_MODE | + FUSE_SET_ATTR_UID | + FUSE_SET_ATTR_GID | + FUSE_SET_ATTR_SIZE | + FUSE_SET_ATTR_ATIME | + FUSE_SET_ATTR_MTIME | + FUSE_SET_ATTR_ATIME_NOW | + FUSE_SET_ATTR_MTIME_NOW; + + req->f->op.setattr(req, nodeid, &stbuf, arg->valid, fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_access_in *arg = (struct fuse_access_in *) inarg; + + if (req->f->op.access) + req->f->op.access(req, nodeid, arg->mask); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + (void) inarg; + + if (req->f->op.readlink) + req->f->op.readlink(req, nodeid); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg; + char *name = PARAM(arg); + + if (req->f->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + else + name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE; + + if (req->f->op.mknod) + req->f->op.mknod(req, nodeid, name, arg->mode, arg->rdev); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg; + + if (req->f->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + if (req->f->op.mkdir) + req->f->op.mkdir(req, nodeid, PARAM(arg), arg->mode); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + + if (req->f->op.unlink) + req->f->op.unlink(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + + if (req->f->op.rmdir) + req->f->op.rmdir(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1; + + if (req->f->op.symlink) + req->f->op.symlink(req, linkname, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg; + char *oldname = PARAM(arg); + char *newname = oldname + strlen(oldname) + 1; + + if (req->f->op.rename) + req->f->op.rename(req, nodeid, oldname, arg->newdir, newname); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_link_in *arg = (struct fuse_link_in *) inarg; + + if (req->f->op.link) + req->f->op.link(req, arg->oldnodeid, nodeid, PARAM(arg)); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_create_in *arg = (struct fuse_create_in *) inarg; + + if (req->f->op.create) { + struct fuse_file_info fi; + char *name = PARAM(arg); + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->f->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + else + name = (char *) inarg + sizeof(struct fuse_open_in); + + req->f->op.create(req, nodeid, name, arg->mode, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_open_in *arg = (struct fuse_open_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->f->op.open) + req->f->op.open(req, nodeid, &fi); + else + fuse_reply_open(req, &fi); +} + +static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + + if (req->f->op.read) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + if (req->f->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + req->f->op.read(req, nodeid, arg->size, arg->offset, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_write_in *arg = (struct fuse_write_in *) inarg; + struct fuse_file_info fi; + char *param; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + fi.writepage = arg->write_flags & 1; + + if (req->f->conn.proto_minor < 9) { + param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE; + } else { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + param = PARAM(arg); + } + + if (req->f->op.write) + req->f->op.write(req, nodeid, param, arg->size, + arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg, + const struct fuse_buf *ibuf) +{ + struct fuse_ll *f = req->f; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + struct fuse_write_in *arg = (struct fuse_write_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + fi.writepage = arg->write_flags & 1; + + if (req->f->conn.proto_minor < 9) { + bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE; + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + FUSE_COMPAT_WRITE_IN_SIZE; + assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD)); + } else { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) + bufv.buf[0].mem = PARAM(arg); + + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + } + if (bufv.buf[0].size < arg->size) { + fprintf(stderr, "fuse: do_write_buf: buffer size too small\n"); + fuse_reply_err(req, EIO); + goto out; + } + bufv.buf[0].size = arg->size; + + req->f->op.write_buf(req, nodeid, &bufv, arg->offset, &fi); + +out: + /* Need to reset the pipe if ->write_buf() didn't consume all data */ + if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count) + fuse_ll_clear_pipe(f); +} + +static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + fi.flush = 1; + if (req->f->conn.proto_minor >= 7) + fi.lock_owner = arg->lock_owner; + + if (req->f->op.flush) + req->f->op.flush(req, nodeid, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_release_in *arg = (struct fuse_release_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + fi.fh_old = fi.fh; + if (req->f->conn.proto_minor >= 8) { + fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0; + fi.lock_owner = arg->lock_owner; + } + if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) { + fi.flock_release = 1; + fi.lock_owner = arg->lock_owner; + } + + if (req->f->op.release) + req->f->op.release(req, nodeid, &fi); + else + fuse_reply_err(req, 0); +} + +static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + + if (req->f->op.fsync) + req->f->op.fsync(req, nodeid, arg->fsync_flags & 1, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_open_in *arg = (struct fuse_open_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->f->op.opendir) + req->f->op.opendir(req, nodeid, &fi); + else + fuse_reply_open(req, &fi); +} + +static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + + if (req->f->op.readdir) + req->f->op.readdir(req, nodeid, arg->size, arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_release_in *arg = (struct fuse_release_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + fi.fh_old = fi.fh; + + if (req->f->op.releasedir) + req->f->op.releasedir(req, nodeid, &fi); + else + fuse_reply_err(req, 0); +} + +static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + + if (req->f->op.fsyncdir) + req->f->op.fsyncdir(req, nodeid, arg->fsync_flags & 1, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + (void) nodeid; + (void) inarg; + + if (req->f->op.statfs) + req->f->op.statfs(req, nodeid); + else { + struct statvfs buf = { + .f_namemax = 255, + .f_bsize = 512, + }; + fuse_reply_statfs(req, &buf); + } +} + +static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg; + char *name = PARAM(arg); + char *value = name + strlen(name) + 1; + + if (req->f->op.setxattr) + req->f->op.setxattr(req, nodeid, name, value, arg->size, + arg->flags); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg; + + if (req->f->op.getxattr) + req->f->op.getxattr(req, nodeid, PARAM(arg), arg->size); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg; + + if (req->f->op.listxattr) + req->f->op.listxattr(req, nodeid, arg->size); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + + if (req->f->op.removexattr) + req->f->op.removexattr(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void convert_fuse_file_lock(struct fuse_file_lock *fl, + struct flock *flock) +{ + memset(flock, 0, sizeof(struct flock)); + flock->l_type = fl->type; + flock->l_whence = SEEK_SET; + flock->l_start = fl->start; + if (fl->end == OFFSET_MAX) + flock->l_len = 0; + else + flock->l_len = fl->end - fl->start + 1; + flock->l_pid = fl->pid; +} + +static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; + struct fuse_file_info fi; + struct flock flock; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.lock_owner = arg->owner; + + convert_fuse_file_lock(&arg->lk, &flock); + if (req->f->op.getlk) + req->f->op.getlk(req, nodeid, &fi, &flock); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg, int sleep) +{ + struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; + struct fuse_file_info fi; + struct flock flock; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.lock_owner = arg->owner; + + if (arg->lk_flags & FUSE_LK_FLOCK) { + int op = 0; + + switch (arg->lk.type) { + case F_RDLCK: + op = LOCK_SH; + break; + case F_WRLCK: + op = LOCK_EX; + break; + case F_UNLCK: + op = LOCK_UN; + break; + } + if (!sleep) + op |= LOCK_NB; + + if (req->f->op.flock) + req->f->op.flock(req, nodeid, &fi, op); + else + fuse_reply_err(req, ENOSYS); + } else { + convert_fuse_file_lock(&arg->lk, &flock); + if (req->f->op.setlk) + req->f->op.setlk(req, nodeid, &fi, &flock, sleep); + else + fuse_reply_err(req, ENOSYS); + } +} + +static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + do_setlk_common(req, nodeid, inarg, 0); +} + +static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + do_setlk_common(req, nodeid, inarg, 1); +} + +static int find_interrupted(struct fuse_ll *f, struct fuse_req *req) +{ + struct fuse_req *curr; + + for (curr = f->list.next; curr != &f->list; curr = curr->next) { + if (curr->unique == req->u.i.unique) { + fuse_interrupt_func_t func; + void *data; + + curr->ctr++; + pthread_mutex_unlock(&f->lock); + + /* Ugh, ugly locking */ + pthread_mutex_lock(&curr->lock); + pthread_mutex_lock(&f->lock); + curr->interrupted = 1; + func = curr->u.ni.func; + data = curr->u.ni.data; + pthread_mutex_unlock(&f->lock); + if (func) + func(curr, data); + pthread_mutex_unlock(&curr->lock); + + pthread_mutex_lock(&f->lock); + curr->ctr--; + if (!curr->ctr) + destroy_req(curr); + + return 1; + } + } + for (curr = f->interrupts.next; curr != &f->interrupts; + curr = curr->next) { + if (curr->u.i.unique == req->u.i.unique) + return 1; + } + return 0; +} + +static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg; + struct fuse_ll *f = req->f; + + (void) nodeid; + if (f->debug) + fprintf(stderr, "INTERRUPT: %llu\n", + (unsigned long long) arg->unique); + + req->u.i.unique = arg->unique; + + pthread_mutex_lock(&f->lock); + if (find_interrupted(f, req)) + destroy_req(req); + else + list_add_req(req, &f->interrupts); + pthread_mutex_unlock(&f->lock); +} + +static struct fuse_req *check_interrupt(struct fuse_ll *f, struct fuse_req *req) +{ + struct fuse_req *curr; + + for (curr = f->interrupts.next; curr != &f->interrupts; + curr = curr->next) { + if (curr->u.i.unique == req->unique) { + req->interrupted = 1; + list_del_req(curr); + free(curr); + return NULL; + } + } + curr = f->interrupts.next; + if (curr != &f->interrupts) { + list_del_req(curr); + list_init_req(curr); + return curr; + } else + return NULL; +} + +static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg; + + if (req->f->op.bmap) + req->f->op.bmap(req, nodeid, arg->blocksize, arg->block); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg; + unsigned int flags = arg->flags; + void *in_buf = arg->in_size ? PARAM(arg) : NULL; + struct fuse_file_info fi; + + if (flags & FUSE_IOCTL_DIR && + !(req->f->conn.want & FUSE_CAP_IOCTL_DIR)) { + fuse_reply_err(req, ENOTTY); + return; + } + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + + if (sizeof(void *) == 4 && req->f->conn.proto_minor >= 16 && + !(flags & FUSE_IOCTL_32BIT)) { + req->ioctl_64bit = 1; + } + + if (req->f->op.ioctl) + req->f->op.ioctl(req, nodeid, arg->cmd, + (void *)(uintptr_t)arg->arg, &fi, flags, + in_buf, arg->in_size, arg->out_size); + else + fuse_reply_err(req, ENOSYS); +} + +void fuse_pollhandle_destroy(struct fuse_pollhandle *ph) +{ + free(ph); +} + +static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + + if (req->f->op.poll) { + struct fuse_pollhandle *ph = NULL; + + if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) { + ph = malloc(sizeof(struct fuse_pollhandle)); + if (ph == NULL) { + fuse_reply_err(req, ENOMEM); + return; + } + ph->kh = arg->kh; + ph->ch = req->ch; + ph->f = req->f; + } + + req->f->op.poll(req, nodeid, &fi, ph); + } else { + fuse_reply_err(req, ENOSYS); + } +} + +static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->f->op.fallocate) + req->f->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_init_in *arg = (struct fuse_init_in *) inarg; + struct fuse_init_out outarg; + struct fuse_ll *f = req->f; + size_t bufsize = fuse_chan_bufsize(req->ch); + + (void) nodeid; + if (f->debug) { + fprintf(stderr, "INIT: %u.%u\n", arg->major, arg->minor); + if (arg->major == 7 && arg->minor >= 6) { + fprintf(stderr, "flags=0x%08x\n", arg->flags); + fprintf(stderr, "max_readahead=0x%08x\n", + arg->max_readahead); + } + } + f->conn.proto_major = arg->major; + f->conn.proto_minor = arg->minor; + f->conn.capable = 0; + f->conn.want = 0; + + memset(&outarg, 0, sizeof(outarg)); + outarg.major = FUSE_KERNEL_VERSION; + outarg.minor = FUSE_KERNEL_MINOR_VERSION; + + if (arg->major < 7) { + fprintf(stderr, "fuse: unsupported protocol version: %u.%u\n", + arg->major, arg->minor); + fuse_reply_err(req, EPROTO); + return; + } + + if (arg->major > 7) { + /* Wait for a second INIT request with a 7.X version */ + send_reply_ok(req, &outarg, sizeof(outarg)); + return; + } + + if (arg->minor >= 6) { + if (f->conn.async_read) + f->conn.async_read = arg->flags & FUSE_ASYNC_READ; + if (arg->max_readahead < f->conn.max_readahead) + f->conn.max_readahead = arg->max_readahead; + if (arg->flags & FUSE_ASYNC_READ) + f->conn.capable |= FUSE_CAP_ASYNC_READ; + if (arg->flags & FUSE_POSIX_LOCKS) + f->conn.capable |= FUSE_CAP_POSIX_LOCKS; + if (arg->flags & FUSE_ATOMIC_O_TRUNC) + f->conn.capable |= FUSE_CAP_ATOMIC_O_TRUNC; + if (arg->flags & FUSE_EXPORT_SUPPORT) + f->conn.capable |= FUSE_CAP_EXPORT_SUPPORT; + if (arg->flags & FUSE_BIG_WRITES) + f->conn.capable |= FUSE_CAP_BIG_WRITES; + if (arg->flags & FUSE_DONT_MASK) + f->conn.capable |= FUSE_CAP_DONT_MASK; + if (arg->flags & FUSE_FLOCK_LOCKS) + f->conn.capable |= FUSE_CAP_FLOCK_LOCKS; + } else { + f->conn.async_read = 0; + f->conn.max_readahead = 0; + } + + if (req->f->conn.proto_minor >= 14) { +#ifdef HAVE_SPLICE +#ifdef HAVE_VMSPLICE + f->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE; + if (f->splice_write) + f->conn.want |= FUSE_CAP_SPLICE_WRITE; + if (f->splice_move) + f->conn.want |= FUSE_CAP_SPLICE_MOVE; +#endif + f->conn.capable |= FUSE_CAP_SPLICE_READ; + if (f->splice_read) + f->conn.want |= FUSE_CAP_SPLICE_READ; +#endif + } + if (req->f->conn.proto_minor >= 18) + f->conn.capable |= FUSE_CAP_IOCTL_DIR; + + if (f->atomic_o_trunc) + f->conn.want |= FUSE_CAP_ATOMIC_O_TRUNC; + if (f->op.getlk && f->op.setlk && !f->no_remote_posix_lock) + f->conn.want |= FUSE_CAP_POSIX_LOCKS; + if (f->op.flock && !f->no_remote_flock) + f->conn.want |= FUSE_CAP_FLOCK_LOCKS; + if (f->big_writes) + f->conn.want |= FUSE_CAP_BIG_WRITES; + + if (bufsize < FUSE_MIN_READ_BUFFER) { + fprintf(stderr, "fuse: warning: buffer size too small: %zu\n", + bufsize); + bufsize = FUSE_MIN_READ_BUFFER; + } + + bufsize -= 4096; + if (bufsize < f->conn.max_write) + f->conn.max_write = bufsize; + + f->got_init = 1; + if (f->op.init) + f->op.init(f->userdata, &f->conn); + + if (f->no_splice_read) + f->conn.want &= ~FUSE_CAP_SPLICE_READ; + if (f->no_splice_write) + f->conn.want &= ~FUSE_CAP_SPLICE_WRITE; + if (f->no_splice_move) + f->conn.want &= ~FUSE_CAP_SPLICE_MOVE; + + if (f->conn.async_read || (f->conn.want & FUSE_CAP_ASYNC_READ)) + outarg.flags |= FUSE_ASYNC_READ; + if (f->conn.want & FUSE_CAP_POSIX_LOCKS) + outarg.flags |= FUSE_POSIX_LOCKS; + if (f->conn.want & FUSE_CAP_ATOMIC_O_TRUNC) + outarg.flags |= FUSE_ATOMIC_O_TRUNC; + if (f->conn.want & FUSE_CAP_EXPORT_SUPPORT) + outarg.flags |= FUSE_EXPORT_SUPPORT; + if (f->conn.want & FUSE_CAP_BIG_WRITES) + outarg.flags |= FUSE_BIG_WRITES; + if (f->conn.want & FUSE_CAP_DONT_MASK) + outarg.flags |= FUSE_DONT_MASK; + if (f->conn.want & FUSE_CAP_FLOCK_LOCKS) + outarg.flags |= FUSE_FLOCK_LOCKS; + outarg.max_readahead = f->conn.max_readahead; + outarg.max_write = f->conn.max_write; + if (f->conn.proto_minor >= 13) { + if (f->conn.max_background >= (1 << 16)) + f->conn.max_background = (1 << 16) - 1; + if (f->conn.congestion_threshold > f->conn.max_background) + f->conn.congestion_threshold = f->conn.max_background; + if (!f->conn.congestion_threshold) { + f->conn.congestion_threshold = + f->conn.max_background * 3 / 4; + } + + outarg.max_background = f->conn.max_background; + outarg.congestion_threshold = f->conn.congestion_threshold; + } + + if (f->debug) { + fprintf(stderr, " INIT: %u.%u\n", outarg.major, outarg.minor); + fprintf(stderr, " flags=0x%08x\n", outarg.flags); + fprintf(stderr, " max_readahead=0x%08x\n", + outarg.max_readahead); + fprintf(stderr, " max_write=0x%08x\n", outarg.max_write); + fprintf(stderr, " max_background=%i\n", + outarg.max_background); + fprintf(stderr, " congestion_threshold=%i\n", + outarg.congestion_threshold); + } + + send_reply_ok(req, &outarg, arg->minor < 5 ? 8 : sizeof(outarg)); +} + +static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_ll *f = req->f; + + (void) nodeid; + (void) inarg; + + f->got_destroy = 1; + if (f->op.destroy) + f->op.destroy(f->userdata); + + send_reply_ok(req, NULL, 0); +} + +static void list_del_nreq(struct fuse_notify_req *nreq) +{ + struct fuse_notify_req *prev = nreq->prev; + struct fuse_notify_req *next = nreq->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_nreq(struct fuse_notify_req *nreq, + struct fuse_notify_req *next) +{ + struct fuse_notify_req *prev = next->prev; + nreq->next = next; + nreq->prev = prev; + prev->next = nreq; + next->prev = nreq; +} + +static void list_init_nreq(struct fuse_notify_req *nreq) +{ + nreq->next = nreq; + nreq->prev = nreq; +} + +static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg, const struct fuse_buf *buf) +{ + struct fuse_ll *f = req->f; + struct fuse_notify_req *nreq; + struct fuse_notify_req *head; + + pthread_mutex_lock(&f->lock); + head = &f->notify_list; + for (nreq = head->next; nreq != head; nreq = nreq->next) { + if (nreq->unique == req->unique) { + list_del_nreq(nreq); + break; + } + } + pthread_mutex_unlock(&f->lock); + + if (nreq != head) + nreq->reply(nreq, req, nodeid, inarg, buf); +} + +static int send_notify_iov(struct fuse_ll *f, struct fuse_chan *ch, + int notify_code, struct iovec *iov, int count) +{ + struct fuse_out_header out; + + if (!f->got_init) + return -ENOTCONN; + + out.unique = 0; + out.error = notify_code; + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + return fuse_send_msg(f, ch, iov, count); +} + +int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph) +{ + if (ph != NULL) { + struct fuse_notify_poll_wakeup_out outarg; + struct iovec iov[2]; + + outarg.kh = ph->kh; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + return send_notify_iov(ph->f, ph->ch, FUSE_NOTIFY_POLL, iov, 2); + } else { + return 0; + } +} + +int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino, + loff_t off, loff_t len) +{ + struct fuse_notify_inval_inode_out outarg; + struct fuse_ll *f; + struct iovec iov[2]; + + if (!ch) + return -EINVAL; + + f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); + if (!f) + return -ENODEV; + + outarg.ino = ino; + outarg.off = off; + outarg.len = len; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + return send_notify_iov(f, ch, FUSE_NOTIFY_INVAL_INODE, iov, 2); +} + +int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent, + const char *name, size_t namelen) +{ + struct fuse_notify_inval_entry_out outarg; + struct fuse_ll *f; + struct iovec iov[3]; + + if (!ch) + return -EINVAL; + + f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); + if (!f) + return -ENODEV; + + outarg.parent = parent; + outarg.namelen = namelen; + outarg.padding = 0; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + iov[2].iov_base = (void *)name; + iov[2].iov_len = namelen + 1; + + return send_notify_iov(f, ch, FUSE_NOTIFY_INVAL_ENTRY, iov, 3); +} + +int fuse_lowlevel_notify_delete(struct fuse_chan *ch, + fuse_ino_t parent, fuse_ino_t child, + const char *name, size_t namelen) +{ + struct fuse_notify_delete_out outarg; + struct fuse_ll *f; + struct iovec iov[3]; + + if (!ch) + return -EINVAL; + + f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); + if (!f) + return -ENODEV; + + if (f->conn.proto_minor < 18) + return -ENOSYS; + + outarg.parent = parent; + outarg.child = child; + outarg.namelen = namelen; + outarg.padding = 0; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + iov[2].iov_base = (void *)name; + iov[2].iov_len = namelen + 1; + + return send_notify_iov(f, ch, FUSE_NOTIFY_DELETE, iov, 3); +} + +int fuse_lowlevel_notify_store(struct fuse_chan *ch, fuse_ino_t ino, + loff_t offset, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct fuse_out_header out; + struct fuse_notify_store_out outarg; + struct fuse_ll *f; + struct iovec iov[3]; + size_t size = fuse_buf_size(bufv); + int res; + + if (!ch) + return -EINVAL; + + f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); + if (!f) + return -ENODEV; + + if (f->conn.proto_minor < 15) + return -ENOSYS; + + out.unique = 0; + out.error = FUSE_NOTIFY_STORE; + + outarg.nodeid = ino; + outarg.offset = offset; + outarg.size = size; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(out); + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + res = fuse_send_data_iov(f, ch, iov, 2, bufv, flags); + if (res > 0) + res = -res; + + return res; +} + +struct fuse_retrieve_req { + struct fuse_notify_req nreq; + void *cookie; +}; + +static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq, + fuse_req_t req, fuse_ino_t ino, + const void *inarg, + const struct fuse_buf *ibuf) +{ + struct fuse_ll *f = req->f; + struct fuse_retrieve_req *rreq = + container_of(nreq, struct fuse_retrieve_req, nreq); + const struct fuse_notify_retrieve_in *arg = inarg; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + + if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) + bufv.buf[0].mem = PARAM(arg); + + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + sizeof(struct fuse_notify_retrieve_in); + + if (bufv.buf[0].size < arg->size) { + fprintf(stderr, "fuse: retrieve reply: buffer size too small\n"); + fuse_reply_none(req); + goto out; + } + bufv.buf[0].size = arg->size; + + if (req->f->op.retrieve_reply) { + req->f->op.retrieve_reply(req, rreq->cookie, ino, + arg->offset, &bufv); + } else { + fuse_reply_none(req); + } +out: + free(rreq); + if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count) + fuse_ll_clear_pipe(f); +} + +int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, fuse_ino_t ino, + size_t size, loff_t offset, void *cookie) +{ + struct fuse_notify_retrieve_out outarg; + struct fuse_ll *f; + struct iovec iov[2]; + struct fuse_retrieve_req *rreq; + int err; + + if (!ch) + return -EINVAL; + + f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); + if (!f) + return -ENODEV; + + if (f->conn.proto_minor < 15) + return -ENOSYS; + + rreq = malloc(sizeof(*rreq)); + if (rreq == NULL) + return -ENOMEM; + + pthread_mutex_lock(&f->lock); + rreq->cookie = cookie; + rreq->nreq.unique = f->notify_ctr++; + rreq->nreq.reply = fuse_ll_retrieve_reply; + list_add_nreq(&rreq->nreq, &f->notify_list); + pthread_mutex_unlock(&f->lock); + + outarg.notify_unique = rreq->nreq.unique; + outarg.nodeid = ino; + outarg.offset = offset; + outarg.size = size; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + err = send_notify_iov(f, ch, FUSE_NOTIFY_RETRIEVE, iov, 2); + if (err) { + pthread_mutex_lock(&f->lock); + list_del_nreq(&rreq->nreq); + pthread_mutex_unlock(&f->lock); + free(rreq); + } + + return err; +} + +void *fuse_req_userdata(fuse_req_t req) +{ + return req->f->userdata; +} + +const struct fuse_ctx *fuse_req_ctx(fuse_req_t req) +{ + return &req->ctx; +} + +/* + * The size of fuse_ctx got extended, so need to be careful about + * incompatibility (i.e. a new binary cannot work with an old + * library). + */ +const struct fuse_ctx *fuse_req_ctx_compat24(fuse_req_t req); +const struct fuse_ctx *fuse_req_ctx_compat24(fuse_req_t req) +{ + return fuse_req_ctx(req); +} +#ifndef __NetBSD__ +FUSE_SYMVER(".symver fuse_req_ctx_compat24,fuse_req_ctx@FUSE_2.4"); +#endif + + +void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, + void *data) +{ + pthread_mutex_lock(&req->lock); + pthread_mutex_lock(&req->f->lock); + req->u.ni.func = func; + req->u.ni.data = data; + pthread_mutex_unlock(&req->f->lock); + if (req->interrupted && func) + func(req, data); + pthread_mutex_unlock(&req->lock); +} + +int fuse_req_interrupted(fuse_req_t req) +{ + int interrupted; + + pthread_mutex_lock(&req->f->lock); + interrupted = req->interrupted; + pthread_mutex_unlock(&req->f->lock); + + return interrupted; +} + +static struct { + void (*func)(fuse_req_t, fuse_ino_t, const void *); + const char *name; +} fuse_ll_ops[] = { + [FUSE_LOOKUP] = { do_lookup, "LOOKUP" }, + [FUSE_FORGET] = { do_forget, "FORGET" }, + [FUSE_GETATTR] = { do_getattr, "GETATTR" }, + [FUSE_SETATTR] = { do_setattr, "SETATTR" }, + [FUSE_READLINK] = { do_readlink, "READLINK" }, + [FUSE_SYMLINK] = { do_symlink, "SYMLINK" }, + [FUSE_MKNOD] = { do_mknod, "MKNOD" }, + [FUSE_MKDIR] = { do_mkdir, "MKDIR" }, + [FUSE_UNLINK] = { do_unlink, "UNLINK" }, + [FUSE_RMDIR] = { do_rmdir, "RMDIR" }, + [FUSE_RENAME] = { do_rename, "RENAME" }, + [FUSE_LINK] = { do_link, "LINK" }, + [FUSE_OPEN] = { do_open, "OPEN" }, + [FUSE_READ] = { do_read, "READ" }, + [FUSE_WRITE] = { do_write, "WRITE" }, + [FUSE_STATFS] = { do_statfs, "STATFS" }, + [FUSE_RELEASE] = { do_release, "RELEASE" }, + [FUSE_FSYNC] = { do_fsync, "FSYNC" }, + [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" }, + [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" }, + [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" }, + [FUSE_FLUSH] = { do_flush, "FLUSH" }, + [FUSE_INIT] = { do_init, "INIT" }, + [FUSE_OPENDIR] = { do_opendir, "OPENDIR" }, + [FUSE_READDIR] = { do_readdir, "READDIR" }, + [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" }, + [FUSE_GETLK] = { do_getlk, "GETLK" }, + [FUSE_SETLK] = { do_setlk, "SETLK" }, + [FUSE_SETLKW] = { do_setlkw, "SETLKW" }, + [FUSE_ACCESS] = { do_access, "ACCESS" }, + [FUSE_CREATE] = { do_create, "CREATE" }, + [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" }, + [FUSE_BMAP] = { do_bmap, "BMAP" }, + [FUSE_IOCTL] = { do_ioctl, "IOCTL" }, + [FUSE_POLL] = { do_poll, "POLL" }, + [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" }, + [FUSE_DESTROY] = { do_destroy, "DESTROY" }, + [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" }, + [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" }, + [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, +}; + +#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) + +static const char *opname(enum fuse_opcode opcode) +{ + if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) + return "???"; + else + return fuse_ll_ops[opcode].name; +} + +static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst, + struct fuse_bufvec *src) +{ + int res = fuse_buf_copy(dst, src, 0); + if (res < 0) { + fprintf(stderr, "fuse: copy from pipe: %s\n", strerror(-res)); + return res; + } + if (res < fuse_buf_size(dst)) { + fprintf(stderr, "fuse: copy from pipe: short read\n"); + return -1; + } + return 0; +} + +static void fuse_ll_process_buf(void *data, const struct fuse_buf *buf, + struct fuse_chan *ch) +{ + struct fuse_ll *f = (struct fuse_ll *) data; + const size_t write_header_size = sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 }; + struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size); + struct fuse_in_header *in; + const void *inarg; + struct fuse_req *req; + void *mbuf = NULL; + int err; + int res; + + if (buf->flags & FUSE_BUF_IS_FD) { + if (buf->size < tmpbuf.buf[0].size) + tmpbuf.buf[0].size = buf->size; + + mbuf = malloc(tmpbuf.buf[0].size); + if (mbuf == NULL) { + fprintf(stderr, "fuse: failed to allocate header\n"); + goto clear_pipe; + } + tmpbuf.buf[0].mem = mbuf; + + res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv); + if (res < 0) + goto clear_pipe; + + in = mbuf; + } else { + in = buf->mem; + } + + if (f->debug) { + fprintf(stderr, + "unique: %llu, opcode: %s (%i), nodeid: %lu, insize: %zu, pid: %u\n", + (unsigned long long) in->unique, + opname((enum fuse_opcode) in->opcode), in->opcode, + (unsigned long) in->nodeid, buf->size, in->pid); + } + + req = fuse_ll_alloc_req(f); + if (req == NULL) { + struct fuse_out_header out = { + .unique = in->unique, + .error = -ENOMEM, + }; + struct iovec iov = { + .iov_base = &out, + .iov_len = sizeof(struct fuse_out_header), + }; + + fuse_send_msg(f, ch, &iov, 1); + goto clear_pipe; + } + + req->unique = in->unique; + req->ctx.uid = in->uid; + req->ctx.gid = in->gid; + req->ctx.pid = in->pid; + req->ch = ch; + + err = EIO; + if (!f->got_init) { + enum fuse_opcode expected; + + expected = f->cuse_data ? CUSE_INIT : FUSE_INIT; + if (in->opcode != expected) + goto reply_err; + } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT) + goto reply_err; + + err = EACCES; + if (f->allow_root && in->uid != f->owner && in->uid != 0 && + in->opcode != FUSE_INIT && in->opcode != FUSE_READ && + in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC && + in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR && + in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR && + in->opcode != FUSE_NOTIFY_REPLY) + goto reply_err; + + err = ENOSYS; + if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func) + goto reply_err; + if (in->opcode != FUSE_INTERRUPT) { + struct fuse_req *intr; + pthread_mutex_lock(&f->lock); + intr = check_interrupt(f, req); + list_add_req(req, &f->list); + pthread_mutex_unlock(&f->lock); + if (intr) + fuse_reply_err(intr, EAGAIN); + } + + if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size && + (in->opcode != FUSE_WRITE || !f->op.write_buf) && + in->opcode != FUSE_NOTIFY_REPLY) { + void *newmbuf; + + err = ENOMEM; + newmbuf = realloc(mbuf, buf->size); + if (newmbuf == NULL) + goto reply_err; + mbuf = newmbuf; + + tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size); + tmpbuf.buf[0].mem = mbuf + write_header_size; + + res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv); + err = -res; + if (res < 0) + goto reply_err; + + in = mbuf; + } + + inarg = (void *) &in[1]; + if (in->opcode == FUSE_WRITE && f->op.write_buf) + do_write_buf(req, in->nodeid, inarg, buf); + else if (in->opcode == FUSE_NOTIFY_REPLY) + do_notify_reply(req, in->nodeid, inarg, buf); + else + fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg); + +out_free: + free(mbuf); + return; + +reply_err: + fuse_reply_err(req, err); +clear_pipe: + if (buf->flags & FUSE_BUF_IS_FD) + fuse_ll_clear_pipe(f); + goto out_free; +} + +static void fuse_ll_process(void *data, const char *buf, size_t len, + struct fuse_chan *ch) +{ + struct fuse_buf fbuf = { + .mem = (void *) buf, + .size = len, + }; + + fuse_ll_process_buf(data, &fbuf, ch); +} + +enum { + KEY_HELP, + KEY_VERSION, +}; + +static const struct fuse_opt fuse_ll_opts[] = { + { "debug", offsetof(struct fuse_ll, debug), 1 }, + { "-d", offsetof(struct fuse_ll, debug), 1 }, + { "allow_root", offsetof(struct fuse_ll, allow_root), 1 }, + { "max_write=%u", offsetof(struct fuse_ll, conn.max_write), 0 }, + { "max_readahead=%u", offsetof(struct fuse_ll, conn.max_readahead), 0 }, + { "max_background=%u", offsetof(struct fuse_ll, conn.max_background), 0 }, + { "congestion_threshold=%u", + offsetof(struct fuse_ll, conn.congestion_threshold), 0 }, + { "async_read", offsetof(struct fuse_ll, conn.async_read), 1 }, + { "sync_read", offsetof(struct fuse_ll, conn.async_read), 0 }, + { "atomic_o_trunc", offsetof(struct fuse_ll, atomic_o_trunc), 1}, + { "no_remote_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1}, + { "no_remote_lock", offsetof(struct fuse_ll, no_remote_flock), 1}, + { "no_remote_flock", offsetof(struct fuse_ll, no_remote_flock), 1}, + { "no_remote_posix_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1}, + { "big_writes", offsetof(struct fuse_ll, big_writes), 1}, + { "splice_write", offsetof(struct fuse_ll, splice_write), 1}, + { "no_splice_write", offsetof(struct fuse_ll, no_splice_write), 1}, + { "splice_move", offsetof(struct fuse_ll, splice_move), 1}, + { "no_splice_move", offsetof(struct fuse_ll, no_splice_move), 1}, + { "splice_read", offsetof(struct fuse_ll, splice_read), 1}, + { "no_splice_read", offsetof(struct fuse_ll, no_splice_read), 1}, + FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_DISCARD), + FUSE_OPT_KEY("-h", KEY_HELP), + FUSE_OPT_KEY("--help", KEY_HELP), + FUSE_OPT_KEY("-V", KEY_VERSION), + FUSE_OPT_KEY("--version", KEY_VERSION), + FUSE_OPT_END +}; + +static void fuse_ll_version(void) +{ + fprintf(stderr, "using FUSE kernel interface version %i.%i\n", + FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION); +} + +static void fuse_ll_help(void) +{ + fprintf(stderr, +" -o max_write=N set maximum size of write requests\n" +" -o max_readahead=N set maximum readahead\n" +" -o max_background=N set number of maximum background requests\n" +" -o congestion_threshold=N set kernel's congestion threshold\n" +" -o async_read perform reads asynchronously (default)\n" +" -o sync_read perform reads synchronously\n" +" -o atomic_o_trunc enable atomic open+truncate support\n" +" -o big_writes enable larger than 4kB writes\n" +" -o no_remote_lock disable remote file locking\n" +" -o no_remote_flock disable remote file locking (BSD)\n" +" -o no_remote_posix_lock disable remove file locking (POSIX)\n" +" -o [no_]splice_write use splice to write to the fuse device\n" +" -o [no_]splice_move move data while splicing to the fuse device\n" +" -o [no_]splice_read use splice to read from the fuse device\n" +); +} + +static int fuse_ll_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) outargs; + + switch (key) { + case KEY_HELP: + fuse_ll_help(); + break; + + case KEY_VERSION: + fuse_ll_version(); + break; + + default: + fprintf(stderr, "fuse: unknown option `%s'\n", arg); + } + + return -1; +} + +int fuse_lowlevel_is_lib_option(const char *opt) +{ + return fuse_opt_match(fuse_ll_opts, opt); +} + +static void fuse_ll_destroy(void *data) +{ + struct fuse_ll *f = (struct fuse_ll *) data; + struct fuse_ll_pipe *llp; + + if (f->got_init && !f->got_destroy) { + if (f->op.destroy) + f->op.destroy(f->userdata); + } + llp = pthread_getspecific(f->pipe_key); + if (llp != NULL) + fuse_ll_pipe_free(llp); + pthread_key_delete(f->pipe_key); + pthread_mutex_destroy(&f->lock); + free(f->cuse_data); + free(f); +} + +static void fuse_ll_pipe_destructor(void *data) +{ + struct fuse_ll_pipe *llp = data; + fuse_ll_pipe_free(llp); +} + +#ifdef HAVE_SPLICE +static int fuse_ll_receive_buf(struct fuse_session *se, struct fuse_buf *buf, + struct fuse_chan **chp) +{ + struct fuse_chan *ch = *chp; + struct fuse_ll *f = fuse_session_data(se); + size_t bufsize = buf->size; + struct fuse_ll_pipe *llp; + struct fuse_buf tmpbuf; + int err; + int res; + + if (f->conn.proto_minor < 14 || !(f->conn.want & FUSE_CAP_SPLICE_READ)) + goto fallback; + + llp = fuse_ll_get_pipe(f); + if (llp == NULL) + goto fallback; + + if (llp->size < bufsize) { + if (llp->can_grow) { + res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize); + if (res == -1) { + llp->can_grow = 0; + goto fallback; + } + llp->size = res; + } + if (llp->size < bufsize) + goto fallback; + } + + res = splice(fuse_chan_fd(ch), NULL, llp->pipe[1], NULL, bufsize, 0); + err = errno; + + if (fuse_session_exited(se)) + return 0; + + if (res == -1) { + if (err == ENODEV) { + fuse_session_exit(se); + return 0; + } + if (err != EINTR && err != EAGAIN) + perror("fuse: splice from device"); + return -err; + } + + if (res < sizeof(struct fuse_in_header)) { + fprintf(stderr, "short splice from fuse device\n"); + return -EIO; + } + + tmpbuf = (struct fuse_buf) { + .size = res, + .flags = FUSE_BUF_IS_FD, + .fd = llp->pipe[0], + }; + + /* + * Don't bother with zero copy for small requests. + * fuse_loop_mt() needs to check for FORGET so this more than + * just an optimization. + */ + if (res < sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in) + pagesize) { + struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 }; + struct fuse_bufvec dst = { .buf[0] = *buf, .count = 1 }; + + res = fuse_buf_copy(&dst, &src, 0); + if (res < 0) { + fprintf(stderr, "fuse: copy from pipe: %s\n", + strerror(-res)); + fuse_ll_clear_pipe(f); + return res; + } + if (res < tmpbuf.size) { + fprintf(stderr, "fuse: copy from pipe: short read\n"); + fuse_ll_clear_pipe(f); + return -EIO; + } + buf->size = tmpbuf.size; + return buf->size; + } + + *buf = tmpbuf; + + return res; + +fallback: + res = fuse_chan_recv(chp, buf->mem, bufsize); + if (res <= 0) + return res; + + buf->size = res; + + return res; +} +#else +static int fuse_ll_receive_buf(struct fuse_session *se, struct fuse_buf *buf, + struct fuse_chan **chp) +{ + (void) se; + + int res = fuse_chan_recv(chp, buf->mem, buf->size); + if (res <= 0) + return res; + + buf->size = res; + + return res; +} +#endif + + +/* + * always call fuse_lowlevel_new_common() internally, to work around a + * misfeature in the FreeBSD runtime linker, which links the old + * version of a symbol to internal references. + */ +struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + int err; + struct fuse_ll *f; + struct fuse_session *se; + struct fuse_session_ops sop = { + .process = fuse_ll_process, + .destroy = fuse_ll_destroy, + }; + + if (sizeof(struct fuse_lowlevel_ops) < op_size) { + fprintf(stderr, "fuse: warning: library too old, some operations may not work\n"); + op_size = sizeof(struct fuse_lowlevel_ops); + } + + f = (struct fuse_ll *) calloc(1, sizeof(struct fuse_ll)); + if (f == NULL) { + fprintf(stderr, "fuse: failed to allocate fuse object\n"); + goto out; + } + + f->conn.async_read = 1; + f->conn.max_write = UINT_MAX; + f->conn.max_readahead = UINT_MAX; + f->atomic_o_trunc = 0; + list_init_req(&f->list); + list_init_req(&f->interrupts); + list_init_nreq(&f->notify_list); + f->notify_ctr = 1; + fuse_mutex_init(&f->lock); + + err = pthread_key_create(&f->pipe_key, fuse_ll_pipe_destructor); + if (err) { + fprintf(stderr, "fuse: failed to create thread specific key: %s\n", + strerror(err)); + goto out_free; + } + + if (fuse_opt_parse(args, f, fuse_ll_opts, fuse_ll_opt_proc) == -1) + goto out_key_destroy; + + if (f->debug) + fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION); + + memcpy(&f->op, op, op_size); + f->owner = getuid(); + f->userdata = userdata; + + se = fuse_session_new(&sop, f); + if (!se) + goto out_key_destroy; + + se->receive_buf = fuse_ll_receive_buf; + se->process_buf = fuse_ll_process_buf; + + return se; + +out_key_destroy: + pthread_key_delete(f->pipe_key); +out_free: + pthread_mutex_destroy(&f->lock); + free(f); +out: + return NULL; +} + + +struct fuse_session *fuse_lowlevel_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + return fuse_lowlevel_new_common(args, op, op_size, userdata); +} + +#ifdef linux +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) +{ + char *buf; + size_t bufsize = 1024; + char path[128]; + int ret; + int fd; + unsigned long pid = req->ctx.pid; + char *s; + + sprintf(path, "/proc/%lu/task/%lu/status", pid, pid); + +retry: + buf = malloc(bufsize); + if (buf == NULL) + return -ENOMEM; + + ret = -EIO; + fd = open(path, O_RDONLY); + if (fd == -1) + goto out_free; + + ret = read(fd, buf, bufsize); + close(fd); + if (ret == -1) { + ret = -EIO; + goto out_free; + } + + if (ret == bufsize) { + free(buf); + bufsize *= 4; + goto retry; + } + + ret = -EIO; + s = strstr(buf, "\nGroups:"); + if (s == NULL) + goto out_free; + + s += 8; + ret = 0; + while (1) { + char *end; + unsigned long val = strtoul(s, &end, 0); + if (end == s) + break; + + s = end; + if (ret < size) + list[ret] = val; + ret++; + } + +out_free: + free(buf); + return ret; +} +#else /* linux */ +/* + * This is currently not implemented on other than Linux... + */ +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) +{ + return -ENOSYS; +} +#endif + +#if !defined(__FreeBSD__) && !defined(__NetBSD__) + +static void fill_open_compat(struct fuse_open_out *arg, + const struct fuse_file_info_compat *f) +{ + arg->fh = f->fh; + if (f->direct_io) + arg->open_flags |= FOPEN_DIRECT_IO; + if (f->keep_cache) + arg->open_flags |= FOPEN_KEEP_CACHE; +} + +static void convert_statfs_compat(const struct statfs *compatbuf, + struct statvfs *buf) +{ + buf->f_bsize = compatbuf->f_bsize; + buf->f_blocks = compatbuf->f_blocks; + buf->f_bfree = compatbuf->f_bfree; + buf->f_bavail = compatbuf->f_bavail; + buf->f_files = compatbuf->f_files; + buf->f_ffree = compatbuf->f_ffree; + buf->f_namemax = compatbuf->f_namelen; +} + +int fuse_reply_open_compat(fuse_req_t req, + const struct fuse_file_info_compat *f) +{ + struct fuse_open_out arg; + + memset(&arg, 0, sizeof(arg)); + fill_open_compat(&arg, f); + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_statfs_compat(fuse_req_t req, const struct statfs *stbuf) +{ + struct statvfs newbuf; + + memset(&newbuf, 0, sizeof(newbuf)); + convert_statfs_compat(stbuf, &newbuf); + + return fuse_reply_statfs(req, &newbuf); +} + +struct fuse_session *fuse_lowlevel_new_compat(const char *opts, + const struct fuse_lowlevel_ops_compat *op, + size_t op_size, void *userdata) +{ + struct fuse_session *se; + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + + if (opts && + (fuse_opt_add_arg(&args, "") == -1 || + fuse_opt_add_arg(&args, "-o") == -1 || + fuse_opt_add_arg(&args, opts) == -1)) { + fuse_opt_free_args(&args); + return NULL; + } + se = fuse_lowlevel_new(&args, (const struct fuse_lowlevel_ops *) op, + op_size, userdata); + fuse_opt_free_args(&args); + + return se; +} + +struct fuse_ll_compat_conf { + unsigned max_read; + int set_max_read; +}; + +static const struct fuse_opt fuse_ll_opts_compat[] = { + { "max_read=", offsetof(struct fuse_ll_compat_conf, set_max_read), 1 }, + { "max_read=%u", offsetof(struct fuse_ll_compat_conf, max_read), 0 }, + FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_KEEP), + FUSE_OPT_END +}; + +int fuse_sync_compat_args(struct fuse_args *args) +{ + struct fuse_ll_compat_conf conf; + + memset(&conf, 0, sizeof(conf)); + if (fuse_opt_parse(args, &conf, fuse_ll_opts_compat, NULL) == -1) + return -1; + + if (fuse_opt_insert_arg(args, 1, "-osync_read")) + return -1; + + if (conf.set_max_read) { + char tmpbuf[64]; + + sprintf(tmpbuf, "-omax_readahead=%u", conf.max_read); + if (fuse_opt_insert_arg(args, 1, tmpbuf) == -1) + return -1; + } + return 0; +} + +FUSE_SYMVER(".symver fuse_reply_statfs_compat,fuse_reply_statfs@FUSE_2.4"); +FUSE_SYMVER(".symver fuse_reply_open_compat,fuse_reply_open@FUSE_2.4"); +FUSE_SYMVER(".symver fuse_lowlevel_new_compat,fuse_lowlevel_new@FUSE_2.4"); + +#else /* __FreeBSD__ || __NetBSD__ */ + +int fuse_sync_compat_args(struct fuse_args *args) +{ + (void) args; + return 0; +} + +#endif /* __FreeBSD__ || __NetBSD__ */ + +struct fuse_session *fuse_lowlevel_new_compat25(struct fuse_args *args, + const struct fuse_lowlevel_ops_compat25 *op, + size_t op_size, void *userdata) +{ + if (fuse_sync_compat_args(args) == -1) + return NULL; + + return fuse_lowlevel_new_common(args, + (const struct fuse_lowlevel_ops *) op, + op_size, userdata); +} + +FUSE_SYMVER(".symver fuse_lowlevel_new_compat25,fuse_lowlevel_new@FUSE_2.5"); diff --git a/fuse/fuse_misc.h b/fuse/fuse_misc.h new file mode 100644 index 000000000..eedf0e0f7 --- /dev/null +++ b/fuse/fuse_misc.h @@ -0,0 +1,57 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "config.h" +#include <pthread.h> + +/* + Versioned symbols cannot be used in some cases because it + - confuse the dynamic linker in uClibc + - not supported on MacOSX (in MachO binary format) +*/ +#if (!defined(__UCLIBC__) && !defined(__APPLE__)) +#define FUSE_SYMVER(x) __asm__(x) +#else +#define FUSE_SYMVER(x) +#endif + +#ifndef USE_UCLIBC +#define fuse_mutex_init(mut) pthread_mutex_init(mut, NULL) +#else +/* Is this hack still needed? */ +static inline void fuse_mutex_init(pthread_mutex_t *mut) +{ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP); + pthread_mutex_init(mut, &attr); + pthread_mutexattr_destroy(&attr); +} +#endif + +#ifdef HAVE_STRUCT_STAT_ST_ATIM +/* Linux */ +#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec) +#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec) +#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec) +#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val) +#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val) +#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC) +/* FreeBSD */ +#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec) +#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec) +#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec) +#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val) +#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val) +#else +#define ST_ATIM_NSEC(stbuf) 0 +#define ST_CTIM_NSEC(stbuf) 0 +#define ST_MTIM_NSEC(stbuf) 0 +#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0) +#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0) +#endif diff --git a/fuse/fuse_mt.c b/fuse/fuse_mt.c new file mode 100644 index 000000000..fd5ac23e5 --- /dev/null +++ b/fuse/fuse_mt.c @@ -0,0 +1,122 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_lowlevel.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <assert.h> + +struct procdata { + struct fuse *f; + struct fuse_chan *prevch; + struct fuse_session *prevse; + fuse_processor_t proc; + void *data; +}; + +static void mt_session_proc(void *data, const char *buf, size_t len, + struct fuse_chan *ch) +{ + struct procdata *pd = (struct procdata *) data; + struct fuse_cmd *cmd = *(struct fuse_cmd **) buf; + + (void) len; + (void) ch; + pd->proc(pd->f, cmd, pd->data); +} + +static void mt_session_exit(void *data, int val) +{ + struct procdata *pd = (struct procdata *) data; + if (val) + fuse_session_exit(pd->prevse); + else + fuse_session_reset(pd->prevse); +} + +static int mt_session_exited(void *data) +{ + struct procdata *pd = (struct procdata *) data; + return fuse_session_exited(pd->prevse); +} + +static int mt_chan_receive(struct fuse_chan **chp, char *buf, size_t size) +{ + struct fuse_cmd *cmd; + struct procdata *pd = (struct procdata *) fuse_chan_data(*chp); + + assert(size >= sizeof(cmd)); + + cmd = fuse_read_cmd(pd->f); + if (cmd == NULL) + return 0; + + *(struct fuse_cmd **) buf = cmd; + + return sizeof(cmd); +} + +int fuse_loop_mt_proc(struct fuse *f, fuse_processor_t proc, void *data) +{ + int res; + struct procdata pd; + struct fuse_session *prevse = fuse_get_session(f); + struct fuse_session *se; + struct fuse_chan *prevch = fuse_session_next_chan(prevse, NULL); + struct fuse_chan *ch; + struct fuse_session_ops sop = { + .exit = mt_session_exit, + .exited = mt_session_exited, + .process = mt_session_proc, + }; + struct fuse_chan_ops cop = { + .receive = mt_chan_receive, + }; + + pd.f = f; + pd.prevch = prevch; + pd.prevse = prevse; + pd.proc = proc; + pd.data = data; + + se = fuse_session_new(&sop, &pd); + if (se == NULL) + return -1; + + ch = fuse_chan_new(&cop, fuse_chan_fd(prevch), + sizeof(struct fuse_cmd *), &pd); + if (ch == NULL) { + fuse_session_destroy(se); + return -1; + } + fuse_session_add_chan(se, ch); + res = fuse_session_loop_mt(se); + fuse_session_destroy(se); + return res; +} + +int fuse_loop_mt(struct fuse *f) +{ + if (f == NULL) + return -1; + + int res = fuse_start_cleanup_thread(f); + if (res) + return -1; + + res = fuse_session_loop_mt(fuse_get_session(f)); + fuse_stop_cleanup_thread(f); + return res; +} + +FUSE_SYMVER(".symver fuse_loop_mt_proc,__fuse_loop_mt@FUSE_UNVERSIONED"); diff --git a/fuse/fuse_opt.c b/fuse/fuse_opt.c new file mode 100644 index 000000000..a2118cedc --- /dev/null +++ b/fuse/fuse_opt.c @@ -0,0 +1,426 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_opt.h" +#include "fuse_misc.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +struct fuse_opt_context { + void *data; + const struct fuse_opt *opt; + fuse_opt_proc_t proc; + int argctr; + int argc; + char **argv; + struct fuse_args outargs; + char *opts; + int nonopt; +}; + +void fuse_opt_free_args(struct fuse_args *args) +{ + if (args) { + if (args->argv && args->allocated) { + int i; + for (i = 0; i < args->argc; i++) + free(args->argv[i]); + free(args->argv); + } + args->argc = 0; + args->argv = NULL; + args->allocated = 0; + } +} + +static int alloc_failed(void) +{ + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; +} + +int fuse_opt_add_arg(struct fuse_args *args, const char *arg) +{ + char **newargv; + char *newarg; + + assert(!args->argv || args->allocated); + + newarg = strdup(arg); + if (!newarg) + return alloc_failed(); + + newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *)); + if (!newargv) { + free(newarg); + return alloc_failed(); + } + + args->argv = newargv; + args->allocated = 1; + args->argv[args->argc++] = newarg; + args->argv[args->argc] = NULL; + return 0; +} + +static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos, + const char *arg) +{ + assert(pos <= args->argc); + if (fuse_opt_add_arg(args, arg) == -1) + return -1; + + if (pos != args->argc - 1) { + char *newarg = args->argv[args->argc - 1]; + memmove(&args->argv[pos + 1], &args->argv[pos], + sizeof(char *) * (args->argc - pos - 1)); + args->argv[pos] = newarg; + } + return 0; +} + +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg) +{ + return fuse_opt_insert_arg_common(args, pos, arg); +} + +int fuse_opt_insert_arg_compat(struct fuse_args *args, int pos, + const char *arg); +int fuse_opt_insert_arg_compat(struct fuse_args *args, int pos, const char *arg) +{ + return fuse_opt_insert_arg_common(args, pos, arg); +} + +static int next_arg(struct fuse_opt_context *ctx, const char *opt) +{ + if (ctx->argctr + 1 >= ctx->argc) { + fprintf(stderr, "fuse: missing argument after `%s'\n", opt); + return -1; + } + ctx->argctr++; + return 0; +} + +static int add_arg(struct fuse_opt_context *ctx, const char *arg) +{ + return fuse_opt_add_arg(&ctx->outargs, arg); +} + +static int add_opt_common(char **opts, const char *opt, int esc) +{ + unsigned oldlen = *opts ? strlen(*opts) : 0; + char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1); + + if (!d) + return alloc_failed(); + + *opts = d; + if (oldlen) { + d += oldlen; + *d++ = ','; + } + + for (; *opt; opt++) { + if (esc && (*opt == ',' || *opt == '\\')) + *d++ = '\\'; + *d++ = *opt; + } + *d = '\0'; + + return 0; +} + +int fuse_opt_add_opt(char **opts, const char *opt) +{ + return add_opt_common(opts, opt, 0); +} + +int fuse_opt_add_opt_escaped(char **opts, const char *opt) +{ + return add_opt_common(opts, opt, 1); +} + +static int add_opt(struct fuse_opt_context *ctx, const char *opt) +{ + return add_opt_common(&ctx->opts, opt, 1); +} + +static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key, + int iso) +{ + if (key == FUSE_OPT_KEY_DISCARD) + return 0; + + if (key != FUSE_OPT_KEY_KEEP && ctx->proc) { + int res = ctx->proc(ctx->data, arg, key, &ctx->outargs); + if (res == -1 || !res) + return res; + } + if (iso) + return add_opt(ctx, arg); + else + return add_arg(ctx, arg); +} + +static int match_template(const char *t, const char *arg, unsigned *sepp) +{ + int arglen = strlen(arg); + const char *sep = strchr(t, '='); + sep = sep ? sep : strchr(t, ' '); + if (sep && (!sep[1] || sep[1] == '%')) { + int tlen = sep - t; + if (sep[0] == '=') + tlen ++; + if (arglen >= tlen && strncmp(arg, t, tlen) == 0) { + *sepp = sep - t; + return 1; + } + } + if (strcmp(t, arg) == 0) { + *sepp = 0; + return 1; + } + return 0; +} + +static const struct fuse_opt *find_opt(const struct fuse_opt *opt, + const char *arg, unsigned *sepp) +{ + for (; opt && opt->templ; opt++) + if (match_template(opt->templ, arg, sepp)) + return opt; + return NULL; +} + +int fuse_opt_match(const struct fuse_opt *opts, const char *opt) +{ + unsigned dummy; + return find_opt(opts, opt, &dummy) ? 1 : 0; +} + +static int process_opt_param(void *var, const char *format, const char *param, + const char *arg) +{ + assert(format[0] == '%'); + if (format[1] == 's') { + char *copy = strdup(param); + if (!copy) + return alloc_failed(); + + *(char **) var = copy; + } else { + if (sscanf(param, format, var) != 1) { + fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg); + return -1; + } + } + return 0; +} + +static int process_opt(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + if (opt->offset == -1U) { + if (call_proc(ctx, arg, opt->value, iso) == -1) + return -1; + } else { + void *var = ctx->data + opt->offset; + if (sep && opt->templ[sep + 1]) { + const char *param = arg + sep; + if (opt->templ[sep] == '=') + param ++; + if (process_opt_param(var, opt->templ + sep + 1, + param, arg) == -1) + return -1; + } else + *(int *)var = opt->value; + } + return 0; +} + +static int process_opt_sep_arg(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + int res; + char *newarg; + char *param; + + if (next_arg(ctx, arg) == -1) + return -1; + + param = ctx->argv[ctx->argctr]; + newarg = malloc(sep + strlen(param) + 1); + if (!newarg) + return alloc_failed(); + + memcpy(newarg, arg, sep); + strcpy(newarg + sep, param); + res = process_opt(ctx, opt, sep, newarg, iso); + free(newarg); + + return res; +} + +static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso) +{ + unsigned sep; + const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep); + if (opt) { + for (; opt; opt = find_opt(opt + 1, arg, &sep)) { + int res; + if (sep && opt->templ[sep] == ' ' && !arg[sep]) + res = process_opt_sep_arg(ctx, opt, sep, arg, + iso); + else + res = process_opt(ctx, opt, sep, arg, iso); + if (res == -1) + return -1; + } + return 0; + } else + return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso); +} + +static int process_real_option_group(struct fuse_opt_context *ctx, char *opts) +{ + char *s = opts; + char *d = s; + int end = 0; + + while (!end) { + if (*s == '\0') + end = 1; + if (*s == ',' || end) { + int res; + + *d = '\0'; + res = process_gopt(ctx, opts, 1); + if (res == -1) + return -1; + d = opts; + } else { + if (s[0] == '\\' && s[1] != '\0') { + s++; + if (s[0] >= '0' && s[0] <= '3' && + s[1] >= '0' && s[1] <= '7' && + s[2] >= '0' && s[2] <= '7') { + *d++ = (s[0] - '0') * 0100 + + (s[1] - '0') * 0010 + + (s[2] - '0'); + s += 2; + } else { + *d++ = *s; + } + } else { + *d++ = *s; + } + } + s++; + } + + return 0; +} + +static int process_option_group(struct fuse_opt_context *ctx, const char *opts) +{ + int res; + char *copy = strdup(opts); + + if (!copy) { + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; + } + res = process_real_option_group(ctx, copy); + free(copy); + return res; +} + +static int process_one(struct fuse_opt_context *ctx, const char *arg) +{ + if (ctx->nonopt || arg[0] != '-') + return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0); + else if (arg[1] == 'o') { + if (arg[2]) + return process_option_group(ctx, arg + 2); + else { + if (next_arg(ctx, arg) == -1) + return -1; + + return process_option_group(ctx, + ctx->argv[ctx->argctr]); + } + } else if (arg[1] == '-' && !arg[2]) { + if (add_arg(ctx, arg) == -1) + return -1; + ctx->nonopt = ctx->outargs.argc; + return 0; + } else + return process_gopt(ctx, arg, 0); +} + +static int opt_parse(struct fuse_opt_context *ctx) +{ + if (ctx->argc) { + if (add_arg(ctx, ctx->argv[0]) == -1) + return -1; + } + + for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++) + if (process_one(ctx, ctx->argv[ctx->argctr]) == -1) + return -1; + + if (ctx->opts) { + if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 || + fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1) + return -1; + } + + /* If option separator ("--") is the last argument, remove it */ + if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc && + strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) { + free(ctx->outargs.argv[ctx->outargs.argc - 1]); + ctx->outargs.argv[--ctx->outargs.argc] = NULL; + } + + return 0; +} + +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc) +{ + int res; + struct fuse_opt_context ctx = { + .data = data, + .opt = opts, + .proc = proc, + }; + + if (!args || !args->argv || !args->argc) + return 0; + + ctx.argc = args->argc; + ctx.argv = args->argv; + + res = opt_parse(&ctx); + if (res != -1) { + struct fuse_args tmp = *args; + *args = ctx.outargs; + ctx.outargs = tmp; + } + free(ctx.opts); + fuse_opt_free_args(&ctx.outargs); + return res; +} + +/* This symbol version was mistakenly added to the version script */ +FUSE_SYMVER(".symver fuse_opt_insert_arg_compat,fuse_opt_insert_arg@FUSE_2.5"); diff --git a/fuse/fuse_session.c b/fuse/fuse_session.c new file mode 100644 index 000000000..18c8c4249 --- /dev/null +++ b/fuse/fuse_session.c @@ -0,0 +1,243 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_common_compat.h" +#include "fuse_lowlevel_compat.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> + +struct fuse_chan { + struct fuse_chan_ops op; + + struct fuse_session *se; + + int fd; + + size_t bufsize; + + void *data; + + int compat; +}; + +struct fuse_session *fuse_session_new(struct fuse_session_ops *op, void *data) +{ + struct fuse_session *se = (struct fuse_session *) malloc(sizeof(*se)); + if (se == NULL) { + fprintf(stderr, "fuse: failed to allocate session\n"); + return NULL; + } + + memset(se, 0, sizeof(*se)); + se->op = *op; + se->data = data; + + return se; +} + +void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch) +{ + assert(se->ch == NULL); + assert(ch->se == NULL); + se->ch = ch; + ch->se = se; +} + +void fuse_session_remove_chan(struct fuse_chan *ch) +{ + struct fuse_session *se = ch->se; + if (se) { + assert(se->ch == ch); + se->ch = NULL; + ch->se = NULL; + } +} + +struct fuse_chan *fuse_session_next_chan(struct fuse_session *se, + struct fuse_chan *ch) +{ + assert(ch == NULL || ch == se->ch); + if (ch == NULL) + return se->ch; + else + return NULL; +} + +void fuse_session_process(struct fuse_session *se, const char *buf, size_t len, + struct fuse_chan *ch) +{ + se->op.process(se->data, buf, len, ch); +} + +void fuse_session_process_buf(struct fuse_session *se, + const struct fuse_buf *buf, struct fuse_chan *ch) +{ + if (se->process_buf) { + se->process_buf(se->data, buf, ch); + } else { + assert(!(buf->flags & FUSE_BUF_IS_FD)); + fuse_session_process(se->data, buf->mem, buf->size, ch); + } +} + +int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf, + struct fuse_chan **chp) +{ + int res; + + if (se->receive_buf) { + res = se->receive_buf(se, buf, chp); + } else { + res = fuse_chan_recv(chp, buf->mem, buf->size); + if (res > 0) + buf->size = res; + } + + return res; +} + + +void fuse_session_destroy(struct fuse_session *se) +{ + if (se->op.destroy) + se->op.destroy(se->data); + if (se->ch != NULL) + fuse_chan_destroy(se->ch); + free(se); +} + +void fuse_session_exit(struct fuse_session *se) +{ + if (se->op.exit) + se->op.exit(se->data, 1); + se->exited = 1; +} + +void fuse_session_reset(struct fuse_session *se) +{ + if (se->op.exit) + se->op.exit(se->data, 0); + se->exited = 0; +} + +int fuse_session_exited(struct fuse_session *se) +{ + if (se->op.exited) + return se->op.exited(se->data); + else + return se->exited; +} + +void *fuse_session_data(struct fuse_session *se) +{ + return se->data; +} + +static struct fuse_chan *fuse_chan_new_common(struct fuse_chan_ops *op, int fd, + size_t bufsize, void *data, + int compat) +{ + struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch)); + if (ch == NULL) { + fprintf(stderr, "fuse: failed to allocate channel\n"); + return NULL; + } + + memset(ch, 0, sizeof(*ch)); + ch->op = *op; + ch->fd = fd; + ch->bufsize = bufsize; + ch->data = data; + ch->compat = compat; + + return ch; +} + +struct fuse_chan *fuse_chan_new(struct fuse_chan_ops *op, int fd, + size_t bufsize, void *data) +{ + return fuse_chan_new_common(op, fd, bufsize, data, 0); +} + +struct fuse_chan *fuse_chan_new_compat24(struct fuse_chan_ops_compat24 *op, + int fd, size_t bufsize, void *data) +{ + return fuse_chan_new_common((struct fuse_chan_ops *) op, fd, bufsize, + data, 24); +} + +int fuse_chan_fd(struct fuse_chan *ch) +{ + return ch->fd; +} + +int fuse_chan_clearfd(struct fuse_chan *ch) +{ + if (ch == NULL) + return -1; + + int fd = ch->fd; + ch->fd = -1; + return fd; +} + +size_t fuse_chan_bufsize(struct fuse_chan *ch) +{ + return ch->bufsize; +} + +void *fuse_chan_data(struct fuse_chan *ch) +{ + return ch->data; +} + +struct fuse_session *fuse_chan_session(struct fuse_chan *ch) +{ + return ch->se; +} + +int fuse_chan_recv(struct fuse_chan **chp, char *buf, size_t size) +{ + struct fuse_chan *ch = *chp; + if (ch->compat) + return ((struct fuse_chan_ops_compat24 *) &ch->op) + ->receive(ch, buf, size); + else + return ch->op.receive(chp, buf, size); +} + +int fuse_chan_receive(struct fuse_chan *ch, char *buf, size_t size) +{ + int res; + + res = fuse_chan_recv(&ch, buf, size); + return res >= 0 ? res : (res != -EINTR && res != -EAGAIN) ? -1 : 0; +} + +int fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[], size_t count) +{ + return ch->op.send(ch, iov, count); +} + +void fuse_chan_destroy(struct fuse_chan *ch) +{ + fuse_session_remove_chan(ch); + if (ch->op.destroy) + ch->op.destroy(ch); + free(ch); +} + +#ifndef __FreeBSD__ +FUSE_SYMVER(".symver fuse_chan_new_compat24,fuse_chan_new@FUSE_2.4"); +#endif diff --git a/fuse/fuse_signals.c b/fuse/fuse_signals.c new file mode 100644 index 000000000..353cb24bd --- /dev/null +++ b/fuse/fuse_signals.c @@ -0,0 +1,72 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_lowlevel.h" + +#include <stdio.h> +#include <string.h> +#include <signal.h> + +static struct fuse_session *fuse_instance; + +static void exit_handler(int sig) +{ + (void) sig; + if (fuse_instance) + fuse_session_exit(fuse_instance); +} + +static int set_one_signal_handler(int sig, void (*handler)(int), int remove) +{ + struct sigaction sa; + struct sigaction old_sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = remove ? SIG_DFL : handler; + sigemptyset(&(sa.sa_mask)); + sa.sa_flags = 0; + + if (sigaction(sig, NULL, &old_sa) == -1) { + perror("fuse: cannot get old signal handler"); + return -1; + } + + if (old_sa.sa_handler == (remove ? handler : SIG_DFL) && + sigaction(sig, &sa, NULL) == -1) { + perror("fuse: cannot set signal handler"); + return -1; + } + return 0; +} + +int fuse_set_signal_handlers(struct fuse_session *se) +{ + if (set_one_signal_handler(SIGHUP, exit_handler, 0) == -1 || + set_one_signal_handler(SIGINT, exit_handler, 0) == -1 || + set_one_signal_handler(SIGTERM, exit_handler, 0) == -1 || + set_one_signal_handler(SIGPIPE, SIG_IGN, 0) == -1) + return -1; + + fuse_instance = se; + return 0; +} + +void fuse_remove_signal_handlers(struct fuse_session *se) +{ + if (fuse_instance != se) + fprintf(stderr, + "fuse: fuse_remove_signal_handlers: unknown session\n"); + else + fuse_instance = NULL; + + set_one_signal_handler(SIGHUP, exit_handler, 1); + set_one_signal_handler(SIGINT, exit_handler, 1); + set_one_signal_handler(SIGTERM, exit_handler, 1); + set_one_signal_handler(SIGPIPE, SIG_IGN, 1); +} + diff --git a/fuse/fuse_versionscript b/fuse/fuse_versionscript new file mode 100644 index 000000000..de16ab2e6 --- /dev/null +++ b/fuse/fuse_versionscript @@ -0,0 +1,210 @@ +FUSE_UNVERSIONED { +}; + +FUSE_2.2 { + global: + fuse_destroy; + fuse_exit; + fuse_exited; + fuse_invalidate; + fuse_is_lib_option; + fuse_loop; + fuse_loop_mt; + fuse_loop_mt_proc; + fuse_main; + fuse_main_compat1; + fuse_main_compat2; + fuse_mount_compat1; + fuse_new_compat1; + fuse_new_compat2; + fuse_process_cmd; + fuse_read_cmd; + fuse_set_getcontext_func; + fuse_setup_compat2; +}; + +FUSE_2.4 { + global: + fuse_add_dirent; + fuse_chan_bufsize; + fuse_chan_data; + fuse_chan_destroy; + fuse_chan_fd; + fuse_chan_receive; + fuse_chan_send; + fuse_chan_session; + fuse_dirent_size; + fuse_kern_chan_new; + fuse_lowlevel_is_lib_option; + fuse_reply_attr; + fuse_reply_buf; + fuse_reply_entry; + fuse_reply_err; + fuse_reply_none; + fuse_reply_readlink; + fuse_reply_write; + fuse_reply_xattr; + fuse_req_userdata; + fuse_session_add_chan; + fuse_session_destroy; + fuse_session_exit; + fuse_session_exited; + fuse_session_loop; + fuse_session_loop_mt; + fuse_session_new; + fuse_session_next_chan; + fuse_session_process; + fuse_session_reset; +} FUSE_2.2; + +FUSE_2.5 { + global: + fuse_lowlevel_new_compat; + fuse_main_real_compat22; + fuse_mount_compat22; + fuse_new_compat22; + fuse_opt_parse; + fuse_opt_add_opt; + fuse_opt_add_arg; + fuse_opt_free_args; + fuse_opt_match; + fuse_parse_cmdline; + fuse_remove_signal_handlers; + fuse_reply_create; + fuse_reply_open; + fuse_reply_open_compat; + fuse_reply_statfs; + fuse_reply_statfs_compat; + fuse_setup_compat22; + fuse_set_signal_handlers; +} FUSE_2.4; + +FUSE_2.6 { + global: + fuse_add_direntry; + fuse_chan_new; + fuse_chan_new_compat24; + fuse_chan_recv; + fuse_daemonize; + fuse_get_session; + fuse_interrupted; + fuse_lowlevel_new; + fuse_lowlevel_new_compat25; + fuse_main_real; + fuse_main_real_compat25; + fuse_mount; + fuse_mount_compat25; + fuse_new; + fuse_new_compat25; + fuse_opt_insert_arg; + fuse_reply_lock; + fuse_req_interrupt_func; + fuse_req_interrupted; + fuse_session_remove_chan; + fuse_setup; + fuse_setup_compat25; + fuse_teardown; + fuse_teardown_compat22; + fuse_unmount; + fuse_unmount_compat22; +} FUSE_2.5; + +FUSE_2.7 { + global: + fuse_fs_access; + fuse_fs_bmap; + fuse_fs_chmod; + fuse_fs_chown; + fuse_fs_create; + fuse_fs_destroy; + fuse_fs_fgetattr; + fuse_fs_flush; + fuse_fs_fsync; + fuse_fs_fsyncdir; + fuse_fs_ftruncate; + fuse_fs_getattr; + fuse_fs_getxattr; + fuse_fs_init; + fuse_fs_link; + fuse_fs_listxattr; + fuse_fs_lock; + fuse_fs_mkdir; + fuse_fs_mknod; + fuse_fs_new; + fuse_fs_open; + fuse_fs_opendir; + fuse_fs_read; + fuse_fs_readdir; + fuse_fs_readlink; + fuse_fs_release; + fuse_fs_releasedir; + fuse_fs_removexattr; + fuse_fs_rename; + fuse_fs_rmdir; + fuse_fs_setxattr; + fuse_fs_statfs; + fuse_fs_symlink; + fuse_fs_truncate; + fuse_fs_unlink; + fuse_fs_utimens; + fuse_fs_write; + fuse_register_module; + fuse_reply_iov; + fuse_version; +} FUSE_2.6; + +FUSE_2.7.5 { + global: + fuse_reply_bmap; +} FUSE_2.7; + +FUSE_2.8 { + global: + cuse_lowlevel_new; + cuse_lowlevel_main; + cuse_lowlevel_setup; + cuse_lowlevel_teardown; + fuse_fs_ioctl; + fuse_fs_poll; + fuse_get_context; + fuse_getgroups; + fuse_lowlevel_notify_inval_entry; + fuse_lowlevel_notify_inval_inode; + fuse_lowlevel_notify_poll; + fuse_notify_poll; + fuse_opt_add_opt_escaped; + fuse_pollhandle_destroy; + fuse_reply_ioctl; + fuse_reply_ioctl_iov; + fuse_reply_ioctl_retry; + fuse_reply_poll; + fuse_req_ctx; + fuse_req_getgroups; + fuse_session_data; +} FUSE_2.7.5; + +FUSE_2.9 { + global: + fuse_buf_copy; + fuse_buf_size; + fuse_fs_read_buf; + fuse_fs_write_buf; + fuse_lowlevel_notify_retrieve; + fuse_lowlevel_notify_store; + fuse_reply_data; + fuse_session_process_buf; + fuse_session_receive_buf; + fuse_start_cleanup_thread; + fuse_stop_cleanup_thread; + fuse_clean_cache; + fuse_lowlevel_notify_delete; + fuse_fs_flock; +} FUSE_2.8; + +FUSE_2.9.1 { + global: + fuse_fs_fallocate; + + local: + *; +} FUSE_2.9; diff --git a/fuse/helper.c b/fuse/helper.c new file mode 100644 index 000000000..c5349bfc0 --- /dev/null +++ b/fuse/helper.c @@ -0,0 +1,480 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "fuse_lowlevel.h" +#include "fuse_common_compat.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <sys/param.h> + +enum { + KEY_HELP, + KEY_HELP_NOHEADER, + KEY_VERSION, +}; + +struct helper_opts { + int singlethread; + int foreground; + int nodefault_subtype; + char *mountpoint; +}; + +#define FUSE_HELPER_OPT(t, p) { t, offsetof(struct helper_opts, p), 1 } + +static const struct fuse_opt fuse_helper_opts[] = { + FUSE_HELPER_OPT("-d", foreground), + FUSE_HELPER_OPT("debug", foreground), + FUSE_HELPER_OPT("-f", foreground), + FUSE_HELPER_OPT("-s", singlethread), + FUSE_HELPER_OPT("fsname=", nodefault_subtype), + FUSE_HELPER_OPT("subtype=", nodefault_subtype), + + FUSE_OPT_KEY("-h", KEY_HELP), + FUSE_OPT_KEY("--help", KEY_HELP), + FUSE_OPT_KEY("-ho", KEY_HELP_NOHEADER), + FUSE_OPT_KEY("-V", KEY_VERSION), + FUSE_OPT_KEY("--version", KEY_VERSION), + FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP), + FUSE_OPT_END +}; + +static void usage(const char *progname) +{ + fprintf(stderr, + "usage: %s mountpoint [options]\n\n", progname); + fprintf(stderr, + "general options:\n" + " -o opt,[opt...] mount options\n" + " -h --help print help\n" + " -V --version print version\n" + "\n"); +} + +static void helper_help(void) +{ + fprintf(stderr, + "FUSE options:\n" + " -d -o debug enable debug output (implies -f)\n" + " -f foreground operation\n" + " -s disable multi-threaded operation\n" + "\n" + ); +} + +static void helper_version(void) +{ + fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION); +} + +static int fuse_helper_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + struct helper_opts *hopts = data; + + switch (key) { + case KEY_HELP: + usage(outargs->argv[0]); + /* fall through */ + + case KEY_HELP_NOHEADER: + helper_help(); + return fuse_opt_add_arg(outargs, "-h"); + + case KEY_VERSION: + helper_version(); + return 1; + + case FUSE_OPT_KEY_NONOPT: + if (!hopts->mountpoint) { + char mountpoint[PATH_MAX]; + if (realpath(arg, mountpoint) == NULL) { + fprintf(stderr, + "fuse: bad mount point `%s': %s\n", + arg, strerror(errno)); + return -1; + } + return fuse_opt_add_opt(&hopts->mountpoint, mountpoint); + } else { + fprintf(stderr, "fuse: invalid argument `%s'\n", arg); + return -1; + } + + default: + return 1; + } +} + +static int add_default_subtype(const char *progname, struct fuse_args *args) +{ + int res; + char *subtype_opt; + const char *basename = strrchr(progname, '/'); + if (basename == NULL) + basename = progname; + else if (basename[1] != '\0') + basename++; + + subtype_opt = (char *) malloc(strlen(basename) + 64); + if (subtype_opt == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); + return -1; + } + sprintf(subtype_opt, "-osubtype=%s", basename); + res = fuse_opt_add_arg(args, subtype_opt); + free(subtype_opt); + return res; +} + +int fuse_parse_cmdline(struct fuse_args *args, char **mountpoint, + int *multithreaded, int *foreground) +{ + int res; + struct helper_opts hopts; + + memset(&hopts, 0, sizeof(hopts)); + res = fuse_opt_parse(args, &hopts, fuse_helper_opts, + fuse_helper_opt_proc); + if (res == -1) + return -1; + + if (!hopts.nodefault_subtype) { + res = add_default_subtype(args->argv[0], args); + if (res == -1) + goto err; + } + if (mountpoint) + *mountpoint = hopts.mountpoint; + else + free(hopts.mountpoint); + + if (multithreaded) + *multithreaded = !hopts.singlethread; + if (foreground) + *foreground = hopts.foreground; + return 0; + +err: + free(hopts.mountpoint); + return -1; +} + +int fuse_daemonize(int foreground) +{ + if (!foreground) { + int nullfd; + + /* + * demonize current process by forking it and killing the + * parent. This makes current process as a child of 'init'. + */ + switch(fork()) { + case -1: + perror("fuse_daemonize: fork"); + return -1; + case 0: + break; + default: + _exit(0); + } + + if (setsid() == -1) { + perror("fuse_daemonize: setsid"); + return -1; + } + + (void) chdir("/"); + + nullfd = open("/dev/null", O_RDWR, 0); + if (nullfd != -1) { + (void) dup2(nullfd, 0); + (void) dup2(nullfd, 1); + (void) dup2(nullfd, 2); + if (nullfd > 2) + close(nullfd); + } + } + return 0; +} + +static struct fuse_chan *fuse_mount_common(const char *mountpoint, + struct fuse_args *args) +{ + struct fuse_chan *ch; + int fd; + + /* + * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos + * would ensue. + */ + do { + fd = open("/dev/null", O_RDWR); + if (fd > 2) + close(fd); + } while (fd >= 0 && fd <= 2); + + fd = fuse_mount_compat25(mountpoint, args); + if (fd == -1) + return NULL; + + ch = fuse_kern_chan_new(fd); + if (!ch) + fuse_kern_unmount(mountpoint, fd); + + return ch; +} + +struct fuse_chan *fuse_mount(const char *mountpoint, struct fuse_args *args) +{ + return fuse_mount_common(mountpoint, args); +} + +static void fuse_unmount_common(const char *mountpoint, struct fuse_chan *ch) +{ + if (mountpoint) { + int fd = ch ? fuse_chan_clearfd(ch) : -1; + fuse_kern_unmount(mountpoint, fd); + if (ch) + fuse_chan_destroy(ch); + } +} + +void fuse_unmount(const char *mountpoint, struct fuse_chan *ch) +{ + fuse_unmount_common(mountpoint, ch); +} + +struct fuse *fuse_setup_common(int argc, char *argv[], + const struct fuse_operations *op, + size_t op_size, + char **mountpoint, + int *multithreaded, + int *fd, + void *user_data, + int compat) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_chan *ch; + struct fuse *fuse; + int foreground; + int res; + + res = fuse_parse_cmdline(&args, mountpoint, multithreaded, &foreground); + if (res == -1) + return NULL; + + ch = fuse_mount_common(*mountpoint, &args); + if (!ch) { + fuse_opt_free_args(&args); + goto err_free; + } + + fuse = fuse_new_common(ch, &args, op, op_size, user_data, compat); + fuse_opt_free_args(&args); + if (fuse == NULL) + goto err_unmount; + + res = fuse_daemonize(foreground); + if (res == -1) + goto err_unmount; + + res = fuse_set_signal_handlers(fuse_get_session(fuse)); + if (res == -1) + goto err_unmount; + + if (fd) + *fd = fuse_chan_fd(ch); + + return fuse; + +err_unmount: + fuse_unmount_common(*mountpoint, ch); + if (fuse) + fuse_destroy(fuse); +err_free: + free(*mountpoint); + return NULL; +} + +struct fuse *fuse_setup(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + char **mountpoint, int *multithreaded, void *user_data) +{ + return fuse_setup_common(argc, argv, op, op_size, mountpoint, + multithreaded, NULL, user_data, 0); +} + +static void fuse_teardown_common(struct fuse *fuse, char *mountpoint) +{ + struct fuse_session *se = fuse_get_session(fuse); + struct fuse_chan *ch = fuse_session_next_chan(se, NULL); + fuse_remove_signal_handlers(se); + fuse_unmount_common(mountpoint, ch); + fuse_destroy(fuse); + free(mountpoint); +} + +void fuse_teardown(struct fuse *fuse, char *mountpoint) +{ + fuse_teardown_common(fuse, mountpoint); +} + +static int fuse_main_common(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + void *user_data, int compat) +{ + struct fuse *fuse; + char *mountpoint; + int multithreaded; + int res; + + fuse = fuse_setup_common(argc, argv, op, op_size, &mountpoint, + &multithreaded, NULL, user_data, compat); + if (fuse == NULL) + return 1; + + if (multithreaded) + res = fuse_loop_mt(fuse); + else + res = fuse_loop(fuse); + + fuse_teardown_common(fuse, mountpoint); + if (res == -1) + return 1; + + return 0; +} + +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + return fuse_main_common(argc, argv, op, op_size, user_data, 0); +} + +#undef fuse_main +int fuse_main(void); +int fuse_main(void) +{ + fprintf(stderr, "fuse_main(): This function does not exist\n"); + return -1; +} + +int fuse_version(void) +{ + return FUSE_VERSION; +} + +#include "fuse_compat.h" + +#if !defined(__FreeBSD__) && !defined(__NetBSD__) + +struct fuse *fuse_setup_compat22(int argc, char *argv[], + const struct fuse_operations_compat22 *op, + size_t op_size, char **mountpoint, + int *multithreaded, int *fd) +{ + return fuse_setup_common(argc, argv, (struct fuse_operations *) op, + op_size, mountpoint, multithreaded, fd, NULL, + 22); +} + +struct fuse *fuse_setup_compat2(int argc, char *argv[], + const struct fuse_operations_compat2 *op, + char **mountpoint, int *multithreaded, + int *fd) +{ + return fuse_setup_common(argc, argv, (struct fuse_operations *) op, + sizeof(struct fuse_operations_compat2), + mountpoint, multithreaded, fd, NULL, 21); +} + +int fuse_main_real_compat22(int argc, char *argv[], + const struct fuse_operations_compat22 *op, + size_t op_size) +{ + return fuse_main_common(argc, argv, (struct fuse_operations *) op, + op_size, NULL, 22); +} + +void fuse_main_compat1(int argc, char *argv[], + const struct fuse_operations_compat1 *op) +{ + fuse_main_common(argc, argv, (struct fuse_operations *) op, + sizeof(struct fuse_operations_compat1), NULL, 11); +} + +int fuse_main_compat2(int argc, char *argv[], + const struct fuse_operations_compat2 *op) +{ + return fuse_main_common(argc, argv, (struct fuse_operations *) op, + sizeof(struct fuse_operations_compat2), NULL, + 21); +} + +int fuse_mount_compat1(const char *mountpoint, const char *args[]) +{ + /* just ignore mount args for now */ + (void) args; + return fuse_mount_compat22(mountpoint, NULL); +} + +FUSE_SYMVER(".symver fuse_setup_compat2,__fuse_setup@FUSE_UNVERSIONED"); +FUSE_SYMVER(".symver fuse_setup_compat22,fuse_setup@FUSE_2.2"); +FUSE_SYMVER(".symver fuse_teardown,__fuse_teardown@FUSE_UNVERSIONED"); +FUSE_SYMVER(".symver fuse_main_compat2,fuse_main@FUSE_UNVERSIONED"); +FUSE_SYMVER(".symver fuse_main_real_compat22,fuse_main_real@FUSE_2.2"); + +#endif /* __FreeBSD__ || __NetBSD__ */ + + +struct fuse *fuse_setup_compat25(int argc, char *argv[], + const struct fuse_operations_compat25 *op, + size_t op_size, char **mountpoint, + int *multithreaded, int *fd) +{ + return fuse_setup_common(argc, argv, (struct fuse_operations *) op, + op_size, mountpoint, multithreaded, fd, NULL, + 25); +} + +int fuse_main_real_compat25(int argc, char *argv[], + const struct fuse_operations_compat25 *op, + size_t op_size) +{ + return fuse_main_common(argc, argv, (struct fuse_operations *) op, + op_size, NULL, 25); +} + +void fuse_teardown_compat22(struct fuse *fuse, int fd, char *mountpoint) +{ + (void) fd; + fuse_teardown_common(fuse, mountpoint); +} + +int fuse_mount_compat25(const char *mountpoint, struct fuse_args *args) +{ + return fuse_kern_mount(mountpoint, args); +} + +FUSE_SYMVER(".symver fuse_setup_compat25,fuse_setup@FUSE_2.5"); +FUSE_SYMVER(".symver fuse_teardown_compat22,fuse_teardown@FUSE_2.2"); +FUSE_SYMVER(".symver fuse_main_real_compat25,fuse_main_real@FUSE_2.5"); +FUSE_SYMVER(".symver fuse_mount_compat25,fuse_mount@FUSE_2.5"); diff --git a/fuse/include/Makefile.am b/fuse/include/Makefile.am new file mode 100644 index 000000000..663e164cc --- /dev/null +++ b/fuse/include/Makefile.am @@ -0,0 +1,17 @@ +## Process this file with automake to produce Makefile.in + +fuseincludedir=$(includedir)/fuse + +fuseinclude_HEADERS = \ + fuse.h \ + fuse_compat.h \ + fuse_common.h \ + fuse_common_compat.h \ + fuse_lowlevel.h \ + fuse_lowlevel_compat.h \ + fuse_opt.h \ + cuse_lowlevel.h + +include_HEADERS = old/fuse.h ulockmgr.h + +noinst_HEADERS = fuse_kernel.h diff --git a/fuse/include/cuse_lowlevel.h b/fuse/include/cuse_lowlevel.h new file mode 100644 index 000000000..fb445a773 --- /dev/null +++ b/fuse/include/cuse_lowlevel.h @@ -0,0 +1,87 @@ +/* + CUSE: Character device in Userspace + Copyright (C) 2008-2009 SUSE Linux Products GmbH + Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. + + Read example/cusexmp.c for usages. +*/ + +#ifndef _CUSE_LOWLEVEL_H_ +#define _CUSE_LOWLEVEL_H_ + +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION 29 +#endif + +#include "fuse_lowlevel.h" + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/uio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */ + +struct fuse_session; + +struct cuse_info { + unsigned dev_major; + unsigned dev_minor; + unsigned dev_info_argc; + const char **dev_info_argv; + unsigned flags; +}; + +/* + * Most ops behave almost identically to the matching fuse_lowlevel + * ops except that they don't take @ino. + * + * init_done : called after initialization is complete + * read/write : always direct IO, simultaneous operations allowed + * ioctl : might be in unrestricted mode depending on ci->flags + */ +struct cuse_lowlevel_ops { + void (*init) (void *userdata, struct fuse_conn_info *conn); + void (*init_done) (void *userdata); + void (*destroy) (void *userdata); + void (*open) (fuse_req_t req, struct fuse_file_info *fi); + void (*read) (fuse_req_t req, size_t size, loff_t off, + struct fuse_file_info *fi); + void (*write) (fuse_req_t req, const char *buf, size_t size, loff_t off, + struct fuse_file_info *fi); + void (*flush) (fuse_req_t req, struct fuse_file_info *fi); + void (*release) (fuse_req_t req, struct fuse_file_info *fi); + void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi); + void (*ioctl) (fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); + void (*poll) (fuse_req_t req, struct fuse_file_info *fi, + struct fuse_pollhandle *ph); +}; + +struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + void *userdata); + +struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + int *multithreaded, void *userdata); + +void cuse_lowlevel_teardown(struct fuse_session *se); + +int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, void *userdata); + +#ifdef __cplusplus +} +#endif + +#endif /* _CUSE_LOWLEVEL_H_ */ diff --git a/fuse/include/fuse.h b/fuse/include/fuse.h new file mode 100644 index 000000000..b82325ec2 --- /dev/null +++ b/fuse/include/fuse.h @@ -0,0 +1,1064 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#ifndef _FUSE_H_ +#define _FUSE_H_ + +/** @file + * + * This file defines the library interface of FUSE + * + * IMPORTANT: you should define FUSE_USE_VERSION before including this + * header. To use the newest API define it to 26 (recommended for any + * new application), to use the old API define it to 21 (default) 22 + * or 25, to use the even older 1.X API define it to 11. + */ + +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION 21 +#endif + +#include "fuse_common.h" + +#include <fcntl.h> +#include <time.h> +#include <utime.h> +#include <sys/types.h> +#include <sys/stat.h> +#if defined(__ANDROID__) +#include <pthread.h> +#endif +#include <sys/statvfs.h> +#include <sys/uio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------- * + * Basic FUSE API * + * ----------------------------------------------------------- */ + +/** Handle for a FUSE filesystem */ +struct fuse; + +/** Structure containing a raw command */ +struct fuse_cmd; + +/** Function to add an entry in a readdir() operation + * + * @param buf the buffer passed to the readdir() operation + * @param name the file name of the directory entry + * @param stat file attributes, can be NULL + * @param off offset of the next entry or zero + * @return 1 if buffer is full, zero otherwise + */ +typedef int (*fuse_fill_dir_t) (void *buf, const char *name, + const struct stat *stbuf, loff_t off); + +/* Used by deprecated getdir() method */ +typedef struct fuse_dirhandle *fuse_dirh_t; +typedef int (*fuse_dirfil_t) (fuse_dirh_t h, const char *name, int type, + ino_t ino); + +/** + * The file system operations: + * + * Most of these should work very similarly to the well known UNIX + * file system operations. A major exception is that instead of + * returning an error in 'errno', the operation should return the + * negated error value (-errno) directly. + * + * All methods are optional, but some are essential for a useful + * filesystem (e.g. getattr). Open, flush, release, fsync, opendir, + * 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. + * + * Similar to stat(). The 'st_dev' and 'st_blksize' fields are + * ignored. The 'st_ino' field is ignored except if the 'use_ino' + * mount option is given. + */ + int (*getattr) (const char *, struct stat *); + + /** Read the target of a symbolic link + * + * The buffer should be filled with a null terminated string. The + * buffer size argument includes the space for the terminating + * null character. If the linkname is too long to fit in the + * buffer, it should be truncated. The return value should be 0 + * for success. + */ + int (*readlink) (const char *, char *, size_t); + + /* Deprecated, use readdir() instead */ + int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t); + + /** Create a file node + * + * This is called for creation of all non-directory, non-symlink + * nodes. If the filesystem defines a create() method, then for + * regular files that will be called instead. + */ + int (*mknod) (const char *, mode_t, dev_t); + + /** 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 */ + int (*unlink) (const char *); + + /** Remove a directory */ + int (*rmdir) (const char *); + + /** Create a symbolic link */ + int (*symlink) (const char *, const char *); + + /** Rename a file */ + int (*rename) (const char *, const char *); + + /** Create a hard link to a file */ + int (*link) (const char *, const char *); + + /** Change the permission bits of a file */ + int (*chmod) (const char *, mode_t); + + /** Change the owner and group of a file */ + int (*chown) (const char *, uid_t, gid_t); + + /** Change the size of a file */ + int (*truncate) (const char *, loff_t); + + /** Change the access and/or modification times of a file + * + * Deprecated, use utimens() instead. + */ + int (*utime) (const char *, struct utimbuf *); + + /** File open operation + * + * 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 + */ + int (*open) (const char *, struct fuse_file_info *); + + /** Read data from an open file + * + * Read should return exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the + * 'direct_io' mount option is specified, in which case the return + * value of the read system call will reflect the return value of + * this operation. + * + * Changed in version 2.2 + */ + int (*read) (const char *, char *, size_t, loff_t, + struct fuse_file_info *); + + /** Write data to an open file + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the 'direct_io' + * mount option is specified (see read operation). + * + * Changed in version 2.2 + */ + int (*write) (const char *, const char *, size_t, loff_t, + struct fuse_file_info *); + + /** Get file system statistics + * + * The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored + * + * Replaced 'struct statfs' parameter with 'struct statvfs' in + * version 2.5 + */ + int (*statfs) (const char *, struct statvfs *); + + /** Possibly flush cached data + * + * BIG NOTE: This is not equivalent to fsync(). It's not a + * request to sync dirty data. + * + * Flush is called on each close() of a file descriptor. So if a + * filesystem wants to return write errors in close() and the file + * has cached dirty data, this is a good place to write back data + * and return any errors. Since many applications ignore close() + * errors this is not always useful. + * + * NOTE: The flush() method may be called more than once for each + * open(). This happens if more than one file descriptor refers + * to an opened file due to dup(), dup2() or fork() calls. It is + * not possible to determine if a flush is final, so each flush + * should be treated equally. Multiple write-flush sequences are + * relatively rare, so this shouldn't be a problem. + * + * Filesystems shouldn't assume that flush will always be called + * after some writes, or that if will be called at all. + * + * Changed in version 2.2 + */ + int (*flush) (const char *, struct fuse_file_info *); + + /** Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open() call there will be exactly one release() call + * with the same flags and file descriptor. It is possible to + * have a file opened more than once, in which case only the last + * release will mean, that no more reads/writes will happen on the + * file. The return value of release is ignored. + * + * Changed in version 2.2 + */ + int (*release) (const char *, struct fuse_file_info *); + + /** Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + * + * Changed in version 2.2 + */ + int (*fsync) (const char *, int, struct fuse_file_info *); + + /** Set extended attributes */ + int (*setxattr) (const char *, const char *, const char *, size_t, int); + + /** Get extended attributes */ + int (*getxattr) (const char *, const char *, char *, size_t); + + /** List extended attributes */ + int (*listxattr) (const char *, char *, size_t); + + /** Remove extended attributes */ + int (*removexattr) (const char *, const char *); + + /** Open 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, closedir and fsyncdir. + * + * Introduced in version 2.3 + */ + int (*opendir) (const char *, struct fuse_file_info *); + + /** Read directory + * + * This supersedes the old getdir() interface. New applications + * should use this. + * + * The filesystem may choose between two modes of operation: + * + * 1) The readdir implementation ignores the offset parameter, and + * passes zero to the filler function's offset. The filler + * function will not return '1' (unless an error happens), so the + * whole directory is read in a single readdir operation. This + * works just like the old getdir() method. + * + * 2) The readdir implementation keeps track of the offsets of the + * directory entries. It uses the offset parameter and always + * passes non-zero offset to the filler function. When the buffer + * is full (or an error happens) the filler function will return + * '1'. + * + * Introduced in version 2.3 + */ + int (*readdir) (const char *, void *, fuse_fill_dir_t, loff_t, + struct fuse_file_info *); + + /** Release directory + * + * Introduced in version 2.3 + */ + int (*releasedir) (const char *, struct fuse_file_info *); + + /** Synchronize directory contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data + * + * Introduced in version 2.3 + */ + int (*fsyncdir) (const char *, int, struct fuse_file_info *); + + /** + * Initialize filesystem + * + * The return value will passed in the private_data field of + * fuse_context to all file operations and as a parameter to the + * destroy() method. + * + * Introduced in version 2.3 + * Changed in version 2.6 + */ + void *(*init) (struct fuse_conn_info *conn); + + /** + * Clean up filesystem + * + * Called on filesystem exit. + * + * Introduced in version 2.3 + */ + void (*destroy) (void *); + + /** + * Check file access permissions + * + * This will be called for the access() system call. If the + * 'default_permissions' mount option is given, this method is not + * called. + * + * This method is not called under Linux kernel versions 2.4.x + * + * Introduced in version 2.5 + */ + int (*access) (const char *, int); + + /** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. + * + * Introduced in version 2.5 + */ + int (*create) (const char *, mode_t, struct fuse_file_info *); + + /** + * Change the size of an open file + * + * This method is called instead of the truncate() method if the + * truncation was invoked from an ftruncate() system call. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the truncate() method will be + * called instead. + * + * Introduced in version 2.5 + */ + int (*ftruncate) (const char *, loff_t, struct fuse_file_info *); + + /** + * Get attributes from an open file + * + * This method is called instead of the getattr() method if the + * file information is available. + * + * Currently this is only called after the create() method if that + * is implemented (see above). Later it may be called for + * invocations of fstat() too. + * + * Introduced in version 2.5 + */ + int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *); + + /** + * Perform POSIX file locking operation + * + * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW. + * + * For the meaning of fields in 'struct flock' see the man page + * for fcntl(2). The l_whence field will always be set to + * SEEK_SET. + * + * For checking lock ownership, the 'fuse_file_info->owner' + * argument must be used. + * + * For F_GETLK operation, the library will first check currently + * held locks, and if a conflicting lock is found it will return + * 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 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 + * value, or it may leave this field zero. + * + * For F_SETLK and F_SETLKW the l_pid field will be set to the pid + * of the process performing the locking operation. + * + * 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.6 + */ + int (*lock) (const char *, struct fuse_file_info *, int cmd, + struct flock *); + + /** + * 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]); + + /** + * Map block index within file to block index within device + * + * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + * + * Introduced in version 2.6 + */ + int (*bmap) (const char *, size_t blocksize, uint64_t *idx); + + /** + * 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, loff_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, loff_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, loff_t, loff_t, + struct fuse_file_info *); +}; + +/** Extra context that may be needed by some filesystems + * + * The uid, gid and pid fields are not filled in case of a writepage + * operation. + */ +struct fuse_context { + /** Pointer to the fuse object */ + struct fuse *fuse; + + /** User ID of the calling process */ + uid_t uid; + + /** Group ID of the calling process */ + gid_t gid; + + /** Thread ID of the calling process */ + pid_t pid; + + /** Private filesystem data */ + void *private_data; + + /** Umask of the calling process (introduced in version 2.8) */ + mode_t umask; +}; + +/** + * Main function of FUSE. + * + * This is for the lazy. This is all that has to be called from the + * main() function. + * + * This function does the following: + * - parses command line options (-d -s and -h) + * - passes relevant mount options to the fuse_mount() + * - installs signal handlers for INT, HUP, TERM and PIPE + * - registers an exit handler to unmount the filesystem on program exit + * - creates a fuse handle + * - registers the operations + * - calls either the single-threaded or the multi-threaded event loop + * + * Note: this is currently implemented as a macro. + * + * @param argc the argument counter passed to the main() function + * @param argv the argument vector passed to the main() function + * @param op the file system operation + * @param user_data user data supplied in the context during the init() method + * @return 0 on success, nonzero on failure + */ +/* + int fuse_main(int argc, char *argv[], const struct fuse_operations *op, + void *user_data); +*/ +#define fuse_main(argc, argv, op, user_data) \ + fuse_main_real(argc, argv, op, sizeof(*(op)), user_data) + +/* ----------------------------------------------------------- * + * More detailed API * + * ----------------------------------------------------------- */ + +/** + * Create a new FUSE filesystem. + * + * @param ch the communication channel + * @param args argument vector + * @param op the filesystem operations + * @param op_size the size of the fuse_operations structure + * @param user_data user data supplied in the context during the init() method + * @return the created FUSE handle + */ +struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + void *user_data); + +/** + * Destroy the FUSE handle. + * + * The communication channel attached to the handle is also destroyed. + * + * NOTE: This function does not unmount the filesystem. If this is + * needed, call fuse_unmount() before calling this function. + * + * @param f the FUSE handle + */ +void fuse_destroy(struct fuse *f); + +/** + * FUSE event loop. + * + * Requests from the kernel are processed, and the appropriate + * operations are called. + * + * @param f the FUSE handle + * @return 0 if no error occurred, -1 otherwise + */ +int fuse_loop(struct fuse *f); + +/** + * Exit from event loop + * + * @param f the FUSE handle + */ +void fuse_exit(struct fuse *f); + +/** + * FUSE event loop with multiple threads + * + * Requests from the kernel are processed, and the appropriate + * operations are called. Request are processed in parallel by + * distributing them between multiple threads. + * + * Calling this function requires the pthreads library to be linked to + * the application. + * + * @param f the FUSE handle + * @return 0 if no error occurred, -1 otherwise + */ +int fuse_loop_mt(struct fuse *f); + +/** + * Get the current context + * + * The context is only valid for the duration of a filesystem + * operation, and thus must not be stored and used later. + * + * @return the context + */ +struct fuse_context *fuse_get_context(void); + +/** + * 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 + * + * @return 1 if the request has been interrupted, 0 otherwise + */ +int fuse_interrupted(void); + +/** + * Obsolete, doesn't do anything + * + * @return -EINVAL + */ +int fuse_invalidate(struct fuse *f, const char *path); + +/* Deprecated, don't use */ +int fuse_is_lib_option(const char *opt); + +/** + * The real main function + * + * Do not call this directly, use fuse_main() + */ +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 + */ + +/** + * Fuse filesystem object + * + * This is opaque object represents a filesystem layer + */ +struct fuse_fs; + +/* + * These functions call the relevant filesystem operation, and return + * the result. + * + * If the operation is not defined, they return -ENOSYS, with the + * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir, + * fuse_fs_releasedir and fuse_fs_statfs, which return 0. + */ + +int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf); +int fuse_fs_fgetattr(struct fuse_fs *fs, const char *path, struct stat *buf, + struct fuse_file_info *fi); +int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, + const char *newpath); +int fuse_fs_unlink(struct fuse_fs *fs, const char *path); +int fuse_fs_rmdir(struct fuse_fs *fs, const char *path); +int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, + const char *path); +int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath); +int fuse_fs_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +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, + loff_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, loff_t off, + struct fuse_file_info *fi); +int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf, + size_t size, loff_t off, struct fuse_file_info *fi); +int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec *buf, loff_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, + struct fuse_file_info *fi); +int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf); +int fuse_fs_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, + fuse_fill_dir_t filler, loff_t off, + struct fuse_file_info *fi); +int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi); +int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +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, loff_t size); +int fuse_fs_ftruncate(struct fuse_fs *fs, const char *path, loff_t size, + struct fuse_file_info *fi); +int fuse_fs_utimens(struct fuse_fs *fs, const char *path, + const struct timespec tv[2]); +int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask); +int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, + size_t len); +int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, + dev_t rdev); +int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode); +int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, + const char *value, size_t size, int flags); +int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, + char *value, size_t size); +int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, + size_t size); +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, + loff_t offset, loff_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); + +/** + * Create a new fuse filesystem object + * + * This is usually called from the factory of a fuse module to create + * a new instance of a filesystem. + * + * @param op the filesystem operations + * @param op_size the size of the fuse_operations structure + * @param user_data user data supplied in the context during the init() method + * @return a new filesystem object + */ +struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, + void *user_data); + +/** + * Filesystem module + * + * Filesystem modules are registered with the FUSE_REGISTER_MODULE() + * macro. + * + * If the "-omodules=modname:..." option is present, filesystem + * objects are created and pushed onto the stack with the 'factory' + * function. + */ +struct fuse_module { + /** + * Name of filesystem + */ + const char *name; + + /** + * Factory for creating filesystem objects + * + * The function may use and remove options from 'args' that belong + * to this module. + * + * For now the 'fs' vector always contains exactly one filesystem. + * This is the filesystem which will be below the newly created + * filesystem in the stack. + * + * @param args the command line arguments + * @param fs NULL terminated filesystem object vector + * @return the new filesystem object + */ + struct fuse_fs *(*factory)(struct fuse_args *args, + struct fuse_fs *fs[]); + + struct fuse_module *next; + struct fusemod_so *so; + int ctr; +}; + +/** + * Register a filesystem module + * + * This function is used by FUSE_REGISTER_MODULE and there's usually + * no need to call it directly + */ +void fuse_register_module(struct fuse_module *mod); + +/** + * Register filesystem module + * + * For the parameters, see description of the fields in 'struct + * fuse_module' + */ +#define FUSE_REGISTER_MODULE(name_, factory_) \ + static __attribute__((constructor)) void name_ ## _register(void) \ + { \ + static struct fuse_module mod = \ + { #name_, factory_, NULL, NULL, 0 }; \ + fuse_register_module(&mod); \ + } + + +/* ----------------------------------------------------------- * + * Advanced API for event handling, don't worry about this... * + * ----------------------------------------------------------- */ + +/* NOTE: the following functions are deprecated, and will be removed + from the 3.0 API. Use the lowlevel session functions instead */ + +/** Function type used to process commands */ +typedef void (*fuse_processor_t)(struct fuse *, struct fuse_cmd *, void *); + +/** This is the part of fuse_main() before the event loop */ +struct fuse *fuse_setup(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + char **mountpoint, int *multithreaded, + void *user_data); + +/** This is the part of fuse_main() after the event loop */ +void fuse_teardown(struct fuse *fuse, char *mountpoint); + +/** Read a single command. If none are read, return NULL */ +struct fuse_cmd *fuse_read_cmd(struct fuse *f); + +/** Process a single command */ +void fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd); + +/** Multi threaded event loop, which calls the custom command + processor function */ +int fuse_loop_mt_proc(struct fuse *f, fuse_processor_t proc, void *data); + +/** Return the exited flag, which indicates if fuse_exit() has been + called */ +int fuse_exited(struct fuse *f); + +/** This function is obsolete and implemented as a no-op */ +void fuse_set_getcontext_func(struct fuse_context *(*func)(void)); + +/** Get session from fuse object */ +struct fuse_session *fuse_get_session(struct fuse *f); + +/* ----------------------------------------------------------- * + * Compatibility stuff * + * ----------------------------------------------------------- */ + +#if FUSE_USE_VERSION < 26 +# include "fuse_compat.h" +# undef fuse_main +# if FUSE_USE_VERSION == 25 +# define fuse_main(argc, argv, op) \ + fuse_main_real_compat25(argc, argv, op, sizeof(*(op))) +# define fuse_new fuse_new_compat25 +# define fuse_setup fuse_setup_compat25 +# define fuse_teardown fuse_teardown_compat22 +# define fuse_operations fuse_operations_compat25 +# elif FUSE_USE_VERSION == 22 +# define fuse_main(argc, argv, op) \ + fuse_main_real_compat22(argc, argv, op, sizeof(*(op))) +# define fuse_new fuse_new_compat22 +# define fuse_setup fuse_setup_compat22 +# define fuse_teardown fuse_teardown_compat22 +# define fuse_operations fuse_operations_compat22 +# define fuse_file_info fuse_file_info_compat +# elif FUSE_USE_VERSION == 24 +# error Compatibility with high-level API version 24 not supported +# else +# define fuse_dirfil_t fuse_dirfil_t_compat +# define __fuse_read_cmd fuse_read_cmd +# define __fuse_process_cmd fuse_process_cmd +# define __fuse_loop_mt fuse_loop_mt_proc +# if FUSE_USE_VERSION == 21 +# define fuse_operations fuse_operations_compat2 +# define fuse_main fuse_main_compat2 +# define fuse_new fuse_new_compat2 +# define __fuse_setup fuse_setup_compat2 +# define __fuse_teardown fuse_teardown_compat22 +# define __fuse_exited fuse_exited +# define __fuse_set_getcontext_func fuse_set_getcontext_func +# else +# define fuse_statfs fuse_statfs_compat1 +# define fuse_operations fuse_operations_compat1 +# define fuse_main fuse_main_compat1 +# define fuse_new fuse_new_compat1 +# define FUSE_DEBUG FUSE_DEBUG_COMPAT1 +# endif +# endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _FUSE_H_ */ diff --git a/fuse/include/fuse_common.h b/fuse/include/fuse_common.h new file mode 100644 index 000000000..f08778bd1 --- /dev/null +++ b/fuse/include/fuse_common.h @@ -0,0 +1,505 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +/** @file */ + +#if !defined(_FUSE_H_) && !defined(_FUSE_LOWLEVEL_H_) +#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead." +#endif + +#ifndef _FUSE_COMMON_H_ +#define _FUSE_COMMON_H_ + +#include "fuse_opt.h" +#include <stdint.h> +#include <sys/types.h> + +/** Major version of FUSE library interface */ +#define FUSE_MAJOR_VERSION 2 + +/** Minor version of FUSE library interface */ +#define FUSE_MINOR_VERSION 9 + +#define FUSE_MAKE_VERSION(maj, min) ((maj) * 10 + (min)) +#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION) + +/* This interface uses 64 bit off_t */ +#if _FILE_OFFSET_BITS != 64 +#error Please add -D_FILE_OFFSET_BITS=64 to your compile flags! +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Information about open files + * + * Changed in version 2.5 + */ +struct fuse_file_info { + /** Open flags. Available in open() and release() */ + int flags; + + /** Old file handle, don't use */ + unsigned long fh_old; + + /** In case of a write operation indicates if this was caused by a + writepage */ + int writepage; + + /** Can be filled in by open, to use direct I/O on this file. + Introduced in version 2.4 */ + unsigned int direct_io : 1; + + /** Can be filled in by open, to indicate, that cached file data + need not be invalidated. Introduced in version 2.4 */ + unsigned int keep_cache : 1; + + /** Indicates a flush operation. Set in flush operation, also + maybe set in highlevel lock operation and lowlevel release + 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 : 27; + + /** File handle. May be filled in by filesystem in open(). + Available in all other file operations */ + uint64_t fh; + + /** Lock owner id. Available in locking operations and flush */ + 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 + * + * Some of the elements are read-write, these can be changed to + * indicate the value requested by the filesystem. The requested + * value must usually be smaller than the indicated value. + */ +struct fuse_conn_info { + /** + * Major version of the protocol (read-only) + */ + unsigned proto_major; + + /** + * Minor version of the protocol (read-only) + */ + unsigned proto_minor; + + /** + * Is asynchronous read supported (read-write) + */ + unsigned async_read; + + /** + * Maximum size of the write buffer + */ + unsigned max_write; + + /** + * Maximum readahead + */ + 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[23]; +}; + +struct fuse_session; +struct fuse_chan; +struct fuse_pollhandle; + +/** + * Create a FUSE mountpoint + * + * Returns a control file descriptor suitable for passing to + * fuse_new() + * + * @param mountpoint the mount point path + * @param args argument vector + * @return the communication channel on success, NULL on failure + */ +struct fuse_chan *fuse_mount(const char *mountpoint, struct fuse_args *args); + +/** + * Umount a FUSE mountpoint + * + * @param mountpoint the mount point path + * @param ch the communication channel + */ +void fuse_unmount(const char *mountpoint, struct fuse_chan *ch); + +/** + * Parse common options + * + * The following options are parsed: + * + * '-f' foreground + * '-d' '-odebug' foreground, but keep the debug option + * '-s' single threaded + * '-h' '--help' help + * '-ho' help without header + * '-ofsname=..' file system name, if not present, then set to the program + * name + * + * All parameters may be NULL + * + * @param args argument vector + * @param mountpoint the returned mountpoint, should be freed after use + * @param multithreaded set to 1 unless the '-s' option is present + * @param foreground set to 1 if one of the relevant options is present + * @return 0 on success, -1 on failure + */ +int fuse_parse_cmdline(struct fuse_args *args, char **mountpoint, + int *multithreaded, int *foreground); + +/** + * Go into the background + * + * @param foreground if true, stay in the foreground + * @return 0 on success, -1 on failure + */ +int fuse_daemonize(int foreground); + +/** + * Get the version of the library + * + * @return the version + */ +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. + */ + loff_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 * + * ----------------------------------------------------------- */ + +/** + * Exit session on HUP, TERM and INT signals and ignore PIPE signal + * + * Stores session in a global variable. May only be called once per + * process until fuse_remove_signal_handlers() is called. + * + * @param se the session to exit + * @return 0 on success, -1 on failure + */ +int fuse_set_signal_handlers(struct fuse_session *se); + +/** + * Restore default signal handlers + * + * Resets global session. After this fuse_set_signal_handlers() may + * be called again. + * + * @param se the same session as given in fuse_set_signal_handlers() + */ +void fuse_remove_signal_handlers(struct fuse_session *se); + +/* ----------------------------------------------------------- * + * Compatibility stuff * + * ----------------------------------------------------------- */ + +#if FUSE_USE_VERSION < 26 +# ifdef __FreeBSD__ +# if FUSE_USE_VERSION < 25 +# error On FreeBSD API version 25 or greater must be used +# endif +# endif +# include "fuse_common_compat.h" +# undef FUSE_MINOR_VERSION +# undef fuse_main +# define fuse_unmount fuse_unmount_compat22 +# if FUSE_USE_VERSION == 25 +# define FUSE_MINOR_VERSION 5 +# define fuse_mount fuse_mount_compat25 +# elif FUSE_USE_VERSION == 24 || FUSE_USE_VERSION == 22 +# define FUSE_MINOR_VERSION 4 +# define fuse_mount fuse_mount_compat22 +# elif FUSE_USE_VERSION == 21 +# define FUSE_MINOR_VERSION 1 +# define fuse_mount fuse_mount_compat22 +# elif FUSE_USE_VERSION == 11 +# warning Compatibility with API version 11 is deprecated +# undef FUSE_MAJOR_VERSION +# define FUSE_MAJOR_VERSION 1 +# define FUSE_MINOR_VERSION 1 +# define fuse_mount fuse_mount_compat1 +# else +# error Compatibility with API version other than 21, 22, 24, 25 and 11 not supported +# endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _FUSE_COMMON_H_ */ diff --git a/fuse/include/fuse_common_compat.h b/fuse/include/fuse_common_compat.h new file mode 100644 index 000000000..34440ff71 --- /dev/null +++ b/fuse/include/fuse_common_compat.h @@ -0,0 +1,26 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +/* these definitions provide source compatibility to prior versions. + Do not include this file directly! */ + +struct fuse_file_info_compat { + int flags; + unsigned long fh; + int writepage; + unsigned int direct_io : 1; + unsigned int keep_cache : 1; +}; + +int fuse_mount_compat25(const char *mountpoint, struct fuse_args *args); + +int fuse_mount_compat22(const char *mountpoint, const char *opts); + +int fuse_mount_compat1(const char *mountpoint, const char *args[]); + +void fuse_unmount_compat22(const char *mountpoint); diff --git a/fuse/include/fuse_compat.h b/fuse/include/fuse_compat.h new file mode 100644 index 000000000..b825deebf --- /dev/null +++ b/fuse/include/fuse_compat.h @@ -0,0 +1,203 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +/* these definitions provide source compatibility to prior versions. + Do not include this file directly! */ + +#include "fuse_lowlevel.h" + +struct fuse_operations_compat25 { + int (*getattr) (const char *, struct stat *); + int (*readlink) (const char *, char *, size_t); + int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t); + int (*mknod) (const char *, mode_t, dev_t); + int (*mkdir) (const char *, mode_t); + int (*unlink) (const char *); + int (*rmdir) (const char *); + int (*symlink) (const char *, const char *); + int (*rename) (const char *, const char *); + int (*link) (const char *, const char *); + int (*chmod) (const char *, mode_t); + int (*chown) (const char *, uid_t, gid_t); + int (*truncate) (const char *, loff_t); + int (*utime) (const char *, struct utimbuf *); + int (*open) (const char *, struct fuse_file_info *); + int (*read) (const char *, char *, size_t, loff_t, + struct fuse_file_info *); + int (*write) (const char *, const char *, size_t, loff_t, + struct fuse_file_info *); + int (*statfs) (const char *, struct statvfs *); + int (*flush) (const char *, struct fuse_file_info *); + int (*release) (const char *, struct fuse_file_info *); + int (*fsync) (const char *, int, struct fuse_file_info *); + int (*setxattr) (const char *, const char *, const char *, size_t, int); + int (*getxattr) (const char *, const char *, char *, size_t); + int (*listxattr) (const char *, char *, size_t); + int (*removexattr) (const char *, const char *); + int (*opendir) (const char *, struct fuse_file_info *); + int (*readdir) (const char *, void *, fuse_fill_dir_t, loff_t, + struct fuse_file_info *); + int (*releasedir) (const char *, struct fuse_file_info *); + int (*fsyncdir) (const char *, int, struct fuse_file_info *); + void *(*init) (void); + void (*destroy) (void *); + int (*access) (const char *, int); + int (*create) (const char *, mode_t, struct fuse_file_info *); + int (*ftruncate) (const char *, loff_t, struct fuse_file_info *); + int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *); +}; + +struct fuse *fuse_new_compat25(int fd, struct fuse_args *args, + const struct fuse_operations_compat25 *op, + size_t op_size); + +int fuse_main_real_compat25(int argc, char *argv[], + const struct fuse_operations_compat25 *op, + size_t op_size); + +struct fuse *fuse_setup_compat25(int argc, char *argv[], + const struct fuse_operations_compat25 *op, + size_t op_size, char **mountpoint, + int *multithreaded, int *fd); + +void fuse_teardown_compat22(struct fuse *fuse, int fd, char *mountpoint); + +#if !defined(__FreeBSD__) && !defined(__NetBSD__) +#include <sys/statfs.h> + +struct fuse_operations_compat22 { + int (*getattr) (const char *, struct stat *); + int (*readlink) (const char *, char *, size_t); + int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t); + int (*mknod) (const char *, mode_t, dev_t); + int (*mkdir) (const char *, mode_t); + int (*unlink) (const char *); + int (*rmdir) (const char *); + int (*symlink) (const char *, const char *); + int (*rename) (const char *, const char *); + int (*link) (const char *, const char *); + int (*chmod) (const char *, mode_t); + int (*chown) (const char *, uid_t, gid_t); + int (*truncate) (const char *, loff_t); + int (*utime) (const char *, struct utimbuf *); + int (*open) (const char *, struct fuse_file_info_compat *); + int (*read) (const char *, char *, size_t, loff_t, + struct fuse_file_info_compat *); + int (*write) (const char *, const char *, size_t, loff_t, + struct fuse_file_info_compat *); + int (*statfs) (const char *, struct statfs *); + int (*flush) (const char *, struct fuse_file_info_compat *); + int (*release) (const char *, struct fuse_file_info_compat *); + int (*fsync) (const char *, int, struct fuse_file_info_compat *); + int (*setxattr) (const char *, const char *, const char *, size_t, int); + int (*getxattr) (const char *, const char *, char *, size_t); + int (*listxattr) (const char *, char *, size_t); + int (*removexattr) (const char *, const char *); + int (*opendir) (const char *, struct fuse_file_info_compat *); + int (*readdir) (const char *, void *, fuse_fill_dir_t, loff_t, + struct fuse_file_info_compat *); + int (*releasedir) (const char *, struct fuse_file_info_compat *); + int (*fsyncdir) (const char *, int, struct fuse_file_info_compat *); + void *(*init) (void); + void (*destroy) (void *); +}; + +struct fuse *fuse_new_compat22(int fd, const char *opts, + const struct fuse_operations_compat22 *op, + size_t op_size); + +struct fuse *fuse_setup_compat22(int argc, char *argv[], + const struct fuse_operations_compat22 *op, + size_t op_size, char **mountpoint, + int *multithreaded, int *fd); + +int fuse_main_real_compat22(int argc, char *argv[], + const struct fuse_operations_compat22 *op, + size_t op_size); + +typedef int (*fuse_dirfil_t_compat) (fuse_dirh_t h, const char *name, int type); +struct fuse_operations_compat2 { + int (*getattr) (const char *, struct stat *); + int (*readlink) (const char *, char *, size_t); + int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t_compat); + int (*mknod) (const char *, mode_t, dev_t); + int (*mkdir) (const char *, mode_t); + int (*unlink) (const char *); + int (*rmdir) (const char *); + int (*symlink) (const char *, const char *); + int (*rename) (const char *, const char *); + int (*link) (const char *, const char *); + int (*chmod) (const char *, mode_t); + int (*chown) (const char *, uid_t, gid_t); + int (*truncate) (const char *, loff_t); + int (*utime) (const char *, struct utimbuf *); + int (*open) (const char *, int); + int (*read) (const char *, char *, size_t, loff_t); + int (*write) (const char *, const char *, size_t, loff_t); + int (*statfs) (const char *, struct statfs *); + int (*flush) (const char *); + int (*release) (const char *, int); + int (*fsync) (const char *, int); + int (*setxattr) (const char *, const char *, const char *, + size_t, int); + int (*getxattr) (const char *, const char *, char *, size_t); + int (*listxattr) (const char *, char *, size_t); + int (*removexattr) (const char *, const char *); +}; + +int fuse_main_compat2(int argc, char *argv[], + const struct fuse_operations_compat2 *op); + +struct fuse *fuse_new_compat2(int fd, const char *opts, + const struct fuse_operations_compat2 *op); + +struct fuse *fuse_setup_compat2(int argc, char *argv[], + const struct fuse_operations_compat2 *op, + char **mountpoint, int *multithreaded, int *fd); + +struct fuse_statfs_compat1 { + long block_size; + long blocks; + long blocks_free; + long files; + long files_free; + long namelen; +}; + +struct fuse_operations_compat1 { + int (*getattr) (const char *, struct stat *); + int (*readlink) (const char *, char *, size_t); + int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t_compat); + int (*mknod) (const char *, mode_t, dev_t); + int (*mkdir) (const char *, mode_t); + int (*unlink) (const char *); + int (*rmdir) (const char *); + int (*symlink) (const char *, const char *); + int (*rename) (const char *, const char *); + int (*link) (const char *, const char *); + int (*chmod) (const char *, mode_t); + int (*chown) (const char *, uid_t, gid_t); + int (*truncate) (const char *, loff_t); + int (*utime) (const char *, struct utimbuf *); + int (*open) (const char *, int); + int (*read) (const char *, char *, size_t, loff_t); + int (*write) (const char *, const char *, size_t, loff_t); + int (*statfs) (struct fuse_statfs_compat1 *); + int (*release) (const char *, int); + int (*fsync) (const char *, int); +}; + +#define FUSE_DEBUG_COMPAT1 (1 << 1) + +struct fuse *fuse_new_compat1(int fd, int flags, + const struct fuse_operations_compat1 *op); + +void fuse_main_compat1(int argc, char *argv[], + const struct fuse_operations_compat1 *op); + +#endif /* __FreeBSD__ || __NetBSD__ */ diff --git a/fuse/include/fuse_kernel.h b/fuse/include/fuse_kernel.h new file mode 100644 index 000000000..c632b58fb --- /dev/null +++ b/fuse/include/fuse_kernel.h @@ -0,0 +1,691 @@ +/* + This file defines the kernel interface of FUSE + Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. + + This -- and only this -- header file may also be distributed under + the terms of the BSD Licence as follows: + + Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +/* + * This file defines the kernel interface of FUSE + * + * Protocol changelog: + * + * 7.9: + * - new fuse_getattr_in input argument of GETATTR + * - add lk_flags in fuse_lk_in + * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in + * - add blksize field to fuse_attr + * - add file flags field to fuse_read_in and fuse_write_in + * + * 7.10 + * - add nonseekable open flag + * + * 7.11 + * - add IOCTL message + * - add unsolicited notification support + * - add POLL message and NOTIFY_POLL notification + * + * 7.12 + * - add umask flag to input argument of open, mknod and mkdir + * - add notification messages for invalidation of inodes and + * directory entries + * + * 7.13 + * - make max number of background requests and congestion threshold + * tunables + * + * 7.14 + * - add splice support to fuse device + * + * 7.15 + * - add store notify + * - add retrieve notify + * + * 7.16 + * - add BATCH_FORGET request + * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct + * fuse_ioctl_iovec' instead of ambiguous 'struct iovec' + * - add FUSE_IOCTL_32BIT flag + * + * 7.17 + * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK + * + * 7.18 + * - add FUSE_IOCTL_DIR flag + * - add FUSE_NOTIFY_DELETE + * + * 7.19 + * - add FUSE_FALLOCATE + */ + +#ifndef _LINUX_FUSE_H +#define _LINUX_FUSE_H + +#include <sys/types.h> +#define __u64 uint64_t +#define __s64 int64_t +#define __u32 uint32_t +#define __s32 int32_t +#define __u16 uint16_t + +/* + * Version negotiation: + * + * Both the kernel and userspace send the version they support in the + * INIT request and reply respectively. + * + * If the major versions match then both shall use the smallest + * of the two minor versions for communication. + * + * If the kernel supports a larger major version, then userspace shall + * reply with the major version it supports, ignore the rest of the + * INIT message and expect a new INIT message from the kernel with a + * matching major version. + * + * If the library supports a larger major version, then it shall fall + * back to the major protocol version sent by the kernel for + * communication and reply with that major version (and an arbitrary + * supported minor version). + */ + +/** Version number of this interface */ +#define FUSE_KERNEL_VERSION 7 + +/** Minor version number of this interface */ +#define FUSE_KERNEL_MINOR_VERSION 19 + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/* Make sure all structures are padded to 64bit boundary, so 32bit + userspace works under 64bit kernels */ + +struct fuse_attr { + __u64 ino; + __u64 size; + __u64 blocks; + __u64 atime; + __u64 mtime; + __u64 ctime; + __u32 atimensec; + __u32 mtimensec; + __u32 ctimensec; + __u32 mode; + __u32 nlink; + __u32 uid; + __u32 gid; + __u32 rdev; + __u32 blksize; + __u32 padding; +}; + +struct fuse_kstatfs { + __u64 blocks; + __u64 bfree; + __u64 bavail; + __u64 files; + __u64 ffree; + __u32 bsize; + __u32 namelen; + __u32 frsize; + __u32 padding; + __u32 spare[6]; +}; + +struct fuse_file_lock { + __u64 start; + __u64 end; + __u32 type; + __u32 pid; /* tgid */ +}; + +/** + * Bitmasks for fuse_setattr_in.valid + */ +#define FATTR_MODE (1 << 0) +#define FATTR_UID (1 << 1) +#define FATTR_GID (1 << 2) +#define FATTR_SIZE (1 << 3) +#define FATTR_ATIME (1 << 4) +#define FATTR_MTIME (1 << 5) +#define FATTR_FH (1 << 6) +#define FATTR_ATIME_NOW (1 << 7) +#define FATTR_MTIME_NOW (1 << 8) +#define FATTR_LOCKOWNER (1 << 9) + +/** + * Flags returned by the OPEN request + * + * FOPEN_DIRECT_IO: bypass page cache for this open file + * FOPEN_KEEP_CACHE: don't invalidate the data cache on open + * FOPEN_NONSEEKABLE: the file is not seekable + */ +#define FOPEN_DIRECT_IO (1 << 0) +#define FOPEN_KEEP_CACHE (1 << 1) +#define FOPEN_NONSEEKABLE (1 << 2) + +/** + * INIT request/reply flags + * + * FUSE_POSIX_LOCKS: remote locking for POSIX file locks + * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." + * FUSE_DONT_MASK: don't apply umask to file mode on create operations + * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks + */ +#define FUSE_ASYNC_READ (1 << 0) +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) +#define FUSE_FLOCK_LOCKS (1 << 10) + +/** + * CUSE INIT request/reply flags + * + * CUSE_UNRESTRICTED_IOCTL: use unrestricted ioctl + */ +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) + +/** + * Release flags + */ +#define FUSE_RELEASE_FLUSH (1 << 0) +#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1) + +/** + * Getattr flags + */ +#define FUSE_GETATTR_FH (1 << 0) + +/** + * Lock flags + */ +#define FUSE_LK_FLOCK (1 << 0) + +/** + * WRITE flags + * + * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed + * FUSE_WRITE_LOCKOWNER: lock_owner field is valid + */ +#define FUSE_WRITE_CACHE (1 << 0) +#define FUSE_WRITE_LOCKOWNER (1 << 1) + +/** + * Read flags + */ +#define FUSE_READ_LOCKOWNER (1 << 1) + +/** + * 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_32BIT: 32bit ioctl + * 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_32BIT (1 << 3) +#define FUSE_IOCTL_DIR (1 << 4) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Poll flags + * + * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify + */ +#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0) + +enum fuse_opcode { + FUSE_LOOKUP = 1, + FUSE_FORGET = 2, /* no reply */ + FUSE_GETATTR = 3, + FUSE_SETATTR = 4, + FUSE_READLINK = 5, + FUSE_SYMLINK = 6, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, + FUSE_OPEN = 14, + FUSE_READ = 15, + FUSE_WRITE = 16, + FUSE_STATFS = 17, + FUSE_RELEASE = 18, + FUSE_FSYNC = 20, + FUSE_SETXATTR = 21, + FUSE_GETXATTR = 22, + FUSE_LISTXATTR = 23, + FUSE_REMOVEXATTR = 24, + FUSE_FLUSH = 25, + FUSE_INIT = 26, + FUSE_OPENDIR = 27, + FUSE_READDIR = 28, + FUSE_RELEASEDIR = 29, + FUSE_FSYNCDIR = 30, + FUSE_GETLK = 31, + FUSE_SETLK = 32, + FUSE_SETLKW = 33, + FUSE_ACCESS = 34, + FUSE_CREATE = 35, + FUSE_INTERRUPT = 36, + FUSE_BMAP = 37, + FUSE_DESTROY = 38, + FUSE_IOCTL = 39, + FUSE_POLL = 40, + FUSE_NOTIFY_REPLY = 41, + FUSE_BATCH_FORGET = 42, + FUSE_FALLOCATE = 43, + + /* CUSE specific operations */ + CUSE_INIT = 4096, +}; + +enum fuse_notify_code { + FUSE_NOTIFY_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_STORE = 4, + FUSE_NOTIFY_RETRIEVE = 5, + FUSE_NOTIFY_DELETE = 6, + FUSE_NOTIFY_CODE_MAX, +}; + +/* The read buffer is required to be at least 8k, but may be much larger */ +#define FUSE_MIN_READ_BUFFER 8192 + +#define FUSE_COMPAT_ENTRY_OUT_SIZE 120 + +struct fuse_entry_out { + __u64 nodeid; /* Inode ID */ + __u64 generation; /* Inode generation: nodeid:gen must + be unique for the fs's lifetime */ + __u64 entry_valid; /* Cache timeout for the name */ + __u64 attr_valid; /* Cache timeout for the attributes */ + __u32 entry_valid_nsec; + __u32 attr_valid_nsec; + struct fuse_attr attr; +}; + +struct fuse_forget_in { + __u64 nlookup; +}; + +struct fuse_forget_one { + __u64 nodeid; + __u64 nlookup; +}; + +struct fuse_batch_forget_in { + __u32 count; + __u32 dummy; +}; + +struct fuse_getattr_in { + __u32 getattr_flags; + __u32 dummy; + __u64 fh; +}; + +#define FUSE_COMPAT_ATTR_OUT_SIZE 96 + +struct fuse_attr_out { + __u64 attr_valid; /* Cache timeout for the attributes */ + __u32 attr_valid_nsec; + __u32 dummy; + struct fuse_attr attr; +}; + +#define FUSE_COMPAT_MKNOD_IN_SIZE 8 + +struct fuse_mknod_in { + __u32 mode; + __u32 rdev; + __u32 umask; + __u32 padding; +}; + +struct fuse_mkdir_in { + __u32 mode; + __u32 umask; +}; + +struct fuse_rename_in { + __u64 newdir; +}; + +struct fuse_link_in { + __u64 oldnodeid; +}; + +struct fuse_setattr_in { + __u32 valid; + __u32 padding; + __u64 fh; + __u64 size; + __u64 lock_owner; + __u64 atime; + __u64 mtime; + __u64 unused2; + __u32 atimensec; + __u32 mtimensec; + __u32 unused3; + __u32 mode; + __u32 unused4; + __u32 uid; + __u32 gid; + __u32 unused5; +}; + +struct fuse_open_in { + __u32 flags; + __u32 unused; +}; + +struct fuse_create_in { + __u32 flags; + __u32 mode; + __u32 umask; + __u32 padding; +}; + +struct fuse_open_out { + __u64 fh; + __u32 open_flags; + __u32 padding; +}; + +struct fuse_release_in { + __u64 fh; + __u32 flags; + __u32 release_flags; + __u64 lock_owner; +}; + +struct fuse_flush_in { + __u64 fh; + __u32 unused; + __u32 padding; + __u64 lock_owner; +}; + +struct fuse_read_in { + __u64 fh; + __u64 offset; + __u32 size; + __u32 read_flags; + __u64 lock_owner; + __u32 flags; + __u32 padding; +}; + +#define FUSE_COMPAT_WRITE_IN_SIZE 24 + +struct fuse_write_in { + __u64 fh; + __u64 offset; + __u32 size; + __u32 write_flags; + __u64 lock_owner; + __u32 flags; + __u32 padding; +}; + +struct fuse_write_out { + __u32 size; + __u32 padding; +}; + +#define FUSE_COMPAT_STATFS_SIZE 48 + +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; + +struct fuse_fsync_in { + __u64 fh; + __u32 fsync_flags; + __u32 padding; +}; + +struct fuse_setxattr_in { + __u32 size; + __u32 flags; +}; + +struct fuse_getxattr_in { + __u32 size; + __u32 padding; +}; + +struct fuse_getxattr_out { + __u32 size; + __u32 padding; +}; + +struct fuse_lk_in { + __u64 fh; + __u64 owner; + struct fuse_file_lock lk; + __u32 lk_flags; + __u32 padding; +}; + +struct fuse_lk_out { + struct fuse_file_lock lk; +}; + +struct fuse_access_in { + __u32 mask; + __u32 padding; +}; + +struct fuse_init_in { + __u32 major; + __u32 minor; + __u32 max_readahead; + __u32 flags; +}; + +struct fuse_init_out { + __u32 major; + __u32 minor; + __u32 max_readahead; + __u32 flags; + __u16 max_background; + __u16 congestion_threshold; + __u32 max_write; +}; + +#define CUSE_INIT_INFO_MAX 4096 + +struct cuse_init_in { + __u32 major; + __u32 minor; + __u32 unused; + __u32 flags; +}; + +struct cuse_init_out { + __u32 major; + __u32 minor; + __u32 unused; + __u32 flags; + __u32 max_read; + __u32 max_write; + __u32 dev_major; /* chardev major */ + __u32 dev_minor; /* chardev minor */ + __u32 spare[10]; +}; + +struct fuse_interrupt_in { + __u64 unique; +}; + +struct fuse_bmap_in { + __u64 block; + __u32 blocksize; + __u32 padding; +}; + +struct fuse_bmap_out { + __u64 block; +}; + +struct fuse_ioctl_in { + __u64 fh; + __u32 flags; + __u32 cmd; + __u64 arg; + __u32 in_size; + __u32 out_size; +}; + +struct fuse_ioctl_iovec { + __u64 base; + __u64 len; +}; + +struct fuse_ioctl_out { + __s32 result; + __u32 flags; + __u32 in_iovs; + __u32 out_iovs; +}; + +struct fuse_poll_in { + __u64 fh; + __u64 kh; + __u32 flags; + __u32 padding; +}; + +struct fuse_poll_out { + __u32 revents; + __u32 padding; +}; + +struct fuse_notify_poll_wakeup_out { + __u64 kh; +}; + +struct fuse_fallocate_in { + __u64 fh; + __u64 offset; + __u64 length; + __u32 mode; + __u32 padding; +}; + +struct fuse_in_header { + __u32 len; + __u32 opcode; + __u64 unique; + __u64 nodeid; + __u32 uid; + __u32 gid; + __u32 pid; + __u32 padding; +}; + +struct fuse_out_header { + __u32 len; + __s32 error; + __u64 unique; +}; + +struct fuse_dirent { + __u64 ino; + __u64 off; + __u32 namelen; + __u32 type; + char name[]; +}; + +#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) +#define FUSE_DIRENT_ALIGN(x) (((x) + sizeof(__u64) - 1) & ~(sizeof(__u64) - 1)) +#define FUSE_DIRENT_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) + +struct fuse_notify_inval_inode_out { + __u64 ino; + __s64 off; + __s64 len; +}; + +struct fuse_notify_inval_entry_out { + __u64 parent; + __u32 namelen; + __u32 padding; +}; + +struct fuse_notify_delete_out { + __u64 parent; + __u64 child; + __u32 namelen; + __u32 padding; +}; + +struct fuse_notify_store_out { + __u64 nodeid; + __u64 offset; + __u32 size; + __u32 padding; +}; + +struct fuse_notify_retrieve_out { + __u64 notify_unique; + __u64 nodeid; + __u64 offset; + __u32 size; + __u32 padding; +}; + +/* Matches the size of fuse_write_in */ +struct fuse_notify_retrieve_in { + __u64 dummy1; + __u64 offset; + __u32 size; + __u32 dummy2; + __u64 dummy3; + __u64 dummy4; +}; + +#endif /* _LINUX_FUSE_H */ diff --git a/fuse/include/fuse_lowlevel.h b/fuse/include/fuse_lowlevel.h new file mode 100644 index 000000000..6971f732e --- /dev/null +++ b/fuse/include/fuse_lowlevel.h @@ -0,0 +1,1845 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#ifndef _FUSE_LOWLEVEL_H_ +#define _FUSE_LOWLEVEL_H_ + +/** @file + * + * Low level API + * + * IMPORTANT: you should define FUSE_USE_VERSION before including this + * header. To use the newest API define it to 26 (recommended for any + * new application), to use the old API define it to 24 (default) or + * 25 + */ + +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION 24 +#endif + +#include "fuse_common.h" + +#include <utime.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <sys/uio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------- * + * Miscellaneous definitions * + * ----------------------------------------------------------- */ + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/** Inode number type */ +typedef unsigned long fuse_ino_t; + +/** Request pointer type */ +typedef struct fuse_req *fuse_req_t; + +/** + * Session + * + * This provides hooks for processing requests, and exiting + */ +struct fuse_session; + +/** + * Channel + * + * A communication channel, providing hooks for sending and receiving + * messages + */ +struct fuse_chan; + +/** Directory entry parameters supplied to fuse_reply_entry() */ +struct fuse_entry_param { + /** Unique inode number + * + * In lookup, zero means negative entry (from version 2.5) + * Returning ENOENT also means negative entry, but by setting zero + * ino the kernel may cache negative entries for entry_timeout + * seconds. + */ + fuse_ino_t ino; + + /** Generation number for this entry. + * + * 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; + + /** Inode attributes. + * + * Even if attr_timeout == 0, attr must be correct. For example, + * for open(), FUSE uses attr.st_size from lookup() to determine + * how many bytes to request. If this value is not correct, + * incorrect data will be returned. + */ + struct stat attr; + + /** Validity timeout (in seconds) for the attributes */ + double attr_timeout; + + /** Validity timeout (in seconds) for the name */ + double entry_timeout; +}; + +/** Additional context associated with requests */ +struct fuse_ctx { + /** User ID of the calling process */ + uid_t uid; + + /** Group ID of the calling process */ + gid_t gid; + + /** 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 */ +#define FUSE_SET_ATTR_MODE (1 << 0) +#define FUSE_SET_ATTR_UID (1 << 1) +#define FUSE_SET_ATTR_GID (1 << 2) +#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 * + * ----------------------------------------------------------- */ + +/** + * Low level filesystem operations + * + * Most of the methods (with the exception of init and destroy) + * receive a request handle (fuse_req_t) as their first argument. + * This handle must be passed to one of the specified reply functions. + * + * This may be done inside the method invocation, or after the call + * has returned. The request handle is valid until one of the reply + * functions is called. + * + * Other pointer arguments (name, fuse_file_info, etc) are not valid + * after the call has returned, so if they are needed later, their + * contents have to be copied. + * + * The filesystem sometimes needs to handle a return value of -ENOENT + * from the reply function, which means, that the request was + * interrupted, and the reply discarded. For example if + * fuse_reply_open() return -ENOENT means, that the release method for + * this file will not be called. + */ +struct fuse_lowlevel_ops { + /** + * Initialize filesystem + * + * Called before any other filesystem method + * + * There's no reply to this function + * + * @param userdata the user data passed to fuse_lowlevel_new() + */ + void (*init) (void *userdata, struct fuse_conn_info *conn); + + /** + * Clean up filesystem + * + * Called on filesystem exit + * + * There's no reply to this function + * + * @param userdata the user data passed to fuse_lowlevel_new() + */ + void (*destroy) (void *userdata); + + /** + * Look up a directory entry by name and get its attributes. + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name the name to look up + */ + void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Forget about an inode + * + * This function is called when the kernel removes an inode + * from its internal caches. + * + * 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. + * + * 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. + * + * 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 + * + * @param req request handle + * @param ino the inode number + * @param nlookup the number of lookups to forget + */ + void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup); + + /** + * Get file attributes + * + * Valid replies: + * fuse_reply_attr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi for future use, currently always NULL + */ + void (*getattr) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Set file attributes + * + * In the 'attr' argument only members indicated by the 'to_set' + * bitmask contain valid values. Other members contain undefined + * values. + * + * If the setattr was invoked from the ftruncate() system call + * under Linux kernel versions 2.6.15 or later, the fi->fh will + * contain the value set by the open method or will be undefined + * if the open method didn't set any value. Otherwise (not + * ftruncate call, or kernel version earlier than 2.6.15) the fi + * parameter will be NULL. + * + * Valid replies: + * fuse_reply_attr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param attr the attributes + * @param to_set bit mask of attributes which should be set + * @param fi file information, or NULL + * + * Changed in version 2.5: + * file information filled in for ftruncate + */ + void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi); + + /** + * Read symbolic link + * + * Valid replies: + * fuse_reply_readlink + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + */ + void (*readlink) (fuse_req_t req, fuse_ino_t ino); + + /** + * Create file node + * + * Create a regular file, character device, block device, fifo or + * socket node. + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode file type and mode with which to create the new file + * @param rdev the device number (only valid if created file is a device) + */ + void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev); + + /** + * Create a directory + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode with which to create the new file + */ + void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode); + + /** + * 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 + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to remove + */ + void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * 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 + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to remove + */ + void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Create a symbolic link + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param link the contents of the symbolic link + * @param parent inode number of the parent directory + * @param name to create + */ + void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, + 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 + * + * @param req request handle + * @param parent inode number of the old parent directory + * @param name old name + * @param newparent inode number of the new parent directory + * @param newname new name + */ + void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname); + + /** + * Create a hard link + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param ino the old inode number + * @param newparent inode number of the new parent directory + * @param newname new name to create + */ + void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname); + + /** + * Open a file + * + * Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and + * O_TRUNC) are available in fi->flags. + * + * Filesystem may store an arbitrary file handle (pointer, index, + * etc) in fi->fh, and use this in other all other file operations + * (read, write, flush, release, fsync). + * + * Filesystem may also implement stateless file I/O and not store + * anything in fi->fh. + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in <fuse_common.h> for more details. + * + * Valid replies: + * fuse_reply_open + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*open) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Read data + * + * Read should send exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the file + * has been opened in 'direct_io' mode, in which case the return + * value of the read system call will reflect the return value of + * this operation. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_iov + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size number of bytes to read + * @param off offset to read from + * @param fi file information + */ + void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off, + struct fuse_file_info *fi); + + /** + * Write data + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the file has + * been opened in 'direct_io' mode, in which case the return value + * of the write system call will reflect the return value of this + * operation. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param buf data to write + * @param size number of bytes to write + * @param off offset to write to + * @param fi file information + */ + void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, loff_t off, struct fuse_file_info *fi); + + /** + * Flush method + * + * This is called on each close() of the opened file. + * + * Since file descriptors can be duplicated (dup, dup2, fork), for + * one open call there may be many flush calls. + * + * Filesystems shouldn't assume that flush will always be called + * after some writes, or that if will be called at all. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * NOTE: the name of the method is misleading, since (unlike + * fsync) the filesystem is not forced to flush pending writes. + * One reason to flush data, is if the filesystem wants to return + * write errors. + * + * If the filesystem supports file locking operations (setlk, + * getlk) it should remove all locks belonging to 'fi->owner'. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*flush) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open call there will be exactly one release call. + * + * The filesystem may reply with an error, but error values are + * not returned to close() or munmap() which triggered the + * release. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * fi->flags will contain the same flags as for open. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*release) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param datasync flag indicating if only data should be flushed + * @param fi file information + */ + void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + + /** + * Open a directory + * + * Filesystem may store an arbitrary file handle (pointer, index, + * etc) in fi->fh, and use this in other all other directory + * stream operations (readdir, releasedir, fsyncdir). + * + * Filesystem may also implement stateless directory I/O and not + * store anything in fi->fh, though that makes it impossible to + * implement standard conforming directory stream operations in + * case the contents of the directory can change between opendir + * and releasedir. + * + * Valid replies: + * fuse_reply_open + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*opendir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Read directory + * + * Send a buffer filled using fuse_add_direntry(), with size not + * exceeding the requested size. Send an empty buffer on end of + * stream. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum number of bytes to send + * @param off offset to continue reading the directory stream + * @param fi file information + */ + void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off, + struct fuse_file_info *fi); + + /** + * Release an open directory + * + * For every opendir call there will be exactly one releasedir + * call. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*releasedir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Synchronize directory contents + * + * If the datasync parameter is non-zero, then only the directory + * contents should be flushed, not the meta data. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param datasync flag indicating if only data should be flushed + * @param fi file information + */ + void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + + /** + * Get file system statistics + * + * Valid replies: + * fuse_reply_statfs + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number, zero means "undefined" + */ + void (*statfs) (fuse_req_t req, fuse_ino_t ino); + + /** + * Set an extended attribute + * + * Valid replies: + * fuse_reply_err + */ + void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags); + + /** + * Get an extended attribute + * + * If size is zero, the size of the value should be sent with + * fuse_reply_xattr. + * + * If the size is non-zero, and the value fits in the buffer, the + * value should be sent with fuse_reply_buf. + * + * If the size is too small for the value, the ERANGE error should + * be sent. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_xattr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param name of the extended attribute + * @param size maximum size of the value to send + */ + void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size); + + /** + * List extended attribute names + * + * If size is zero, the total size of the attribute list should be + * sent with fuse_reply_xattr. + * + * If the size is non-zero, and the null character separated + * attribute list fits in the buffer, the list should be sent with + * fuse_reply_buf. + * + * If the size is too small for the list, the ERANGE error should + * be sent. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_xattr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum size of the list to send + */ + void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); + + /** + * Remove an extended attribute + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param name of the extended attribute + */ + void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); + + /** + * Check file access permissions + * + * This will be called for the access() system call. If the + * 'default_permissions' mount option is given, this method is not + * called. + * + * This method is not called under Linux kernel versions 2.4.x + * + * Introduced in version 2.5 + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param mask requested access mode + */ + void (*access) (fuse_req_t req, fuse_ino_t ino, int mask); + + /** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * Open flags (with the exception of O_NOCTTY) are available in + * fi->flags. + * + * Filesystem may store an arbitrary file handle (pointer, index, + * etc) in fi->fh, and use this in other all other file operations + * (read, write, flush, release, fsync). + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in <fuse_common.h> for more details. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. + * + * Introduced in version 2.5 + * + * Valid replies: + * fuse_reply_create + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode file type and mode with which to create the new file + * @param fi file information + */ + void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi); + + /** + * Test for a POSIX file lock + * + * Introduced in version 2.6 + * + * Valid replies: + * fuse_reply_lock + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param lock the region/type to test + */ + void (*getlk) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock); + + /** + * Acquire, modify or release a POSIX file lock + * + * For POSIX threads (NPTL) there's a 1-1 relation between pid and + * owner, but otherwise this is not always the case. For checking + * lock ownership, 'fi->owner' must be used. The l_pid field in + * 'struct flock' should only be used to fill in this field in + * getlk(). + * + * 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.6 + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param lock the region/type to set + * @param sleep locking operation may sleep + */ + void (*setlk) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, + struct flock *lock, int sleep); + + /** + * Map block index within file to block index within device + * + * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + * + * Introduced in version 2.6 + * + * Valid replies: + * fuse_reply_bmap + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param blocksize unit of block index + * @param idx block index within file + */ + 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. + * + * 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, loff_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, + loff_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, + loff_t offset, loff_t length, struct fuse_file_info *fi); +}; + +/** + * Reply with an error code or success + * + * Possible requests: + * all except forget + * + * unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, + * removexattr and setlk may send a zero code + * + * @param req request handle + * @param err the positive error value, or zero for success + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_err(fuse_req_t req, int err); + +/** + * Don't send reply + * + * Possible requests: + * forget + * + * @param req request handle + */ +void fuse_reply_none(fuse_req_t req); + +/** + * Reply with a directory entry + * + * 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 + */ +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e); + +/** + * Reply with a directory entry and open parameters + * + * currently the following members of 'fi' are used: + * fh, direct_io, keep_cache + * + * Possible requests: + * create + * + * Side effects: + * increments the lookup count on success + * + * @param req request handle + * @param e the entry parameters + * @param fi file information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, + const struct fuse_file_info *fi); + +/** + * Reply with attributes + * + * Possible requests: + * getattr, setattr + * + * @param req request handle + * @param attr the attributes + * @param attr_timeout validity timeout (in seconds) for the attributes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_attr(fuse_req_t req, const struct stat *attr, + double attr_timeout); + +/** + * Reply with the contents of a symbolic link + * + * Possible requests: + * readlink + * + * @param req request handle + * @param link symbolic link contents + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_readlink(fuse_req_t req, const char *link); + +/** + * Reply with open parameters + * + * currently the following members of 'fi' are used: + * fh, direct_io, keep_cache + * + * Possible requests: + * open, opendir + * + * @param req request handle + * @param fi file information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi); + +/** + * Reply with number of bytes written + * + * Possible requests: + * write + * + * @param req request handle + * @param count the number of bytes written + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_write(fuse_req_t req, size_t count); + +/** + * Reply with data + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * @param req request handle + * @param buf buffer containing data + * @param size the size of data in bytes + * @return zero for success, -errno for failure to send reply + */ +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 + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * @param req request handle + * @param iov the vector containing the data + * @param count the size of vector + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count); + +/** + * Reply with filesystem statistics + * + * Possible requests: + * statfs + * + * @param req request handle + * @param stbuf filesystem statistics + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf); + +/** + * Reply with needed buffer size + * + * Possible requests: + * getxattr, listxattr + * + * @param req request handle + * @param count the buffer size needed in bytes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_xattr(fuse_req_t req, size_t count); + +/** + * Reply with file lock information + * + * Possible requests: + * getlk + * + * @param req request handle + * @param lock the lock information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_lock(fuse_req_t req, const struct flock *lock); + +/** + * Reply with block index + * + * Possible requests: + * bmap + * + * @param req request handle + * @param idx block index within device + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_bmap(fuse_req_t req, uint64_t idx); + +/* ----------------------------------------------------------- * + * Filling a buffer in readdir * + * ----------------------------------------------------------- */ + +/** + * Add a directory entry to the buffer + * + * 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 + * larger than the buffer size, the operation failed. + * + * From the 'stbuf' argument the st_ino field and bits 12-15 of the + * st_mode field are used. The other fields are ignored. + * + * Note: offsets do not necessarily represent physical offsets, and + * could be any marker, that enables the implementation to find a + * specific point in the directory stream. + * + * @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 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 + */ +size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, + const char *name, const struct stat *stbuf, + loff_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, + loff_t off, loff_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, + loff_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, loff_t offset, void *cookie); + + +/* ----------------------------------------------------------- * + * Utility functions * + * ----------------------------------------------------------- */ + +/** + * Get the userdata from the request + * + * @param req request handle + * @return the user data passed to fuse_lowlevel_new() + */ +void *fuse_req_userdata(fuse_req_t req); + +/** + * Get the context from the request + * + * The pointer returned by this function will only be valid for the + * request's lifetime + * + * @param req request handle + * @return the context structure + */ +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 + * + * @param req interrupted request + * @param data user data + */ +typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data); + +/** + * Register/unregister callback for an interrupt + * + * If an interrupt has already happened, then the callback function is + * called from within this function, hence it's not possible for + * interrupts to be lost. + * + * @param req request handle + * @param func the callback function or NULL for unregister + * @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); + +/** + * Check if a request has already been interrupted + * + * @param req request handle + * @return 1 if the request has been interrupted, 0 otherwise + */ +int fuse_req_interrupted(fuse_req_t req); + +/* ----------------------------------------------------------- * + * Filesystem setup * + * ----------------------------------------------------------- */ + +/* Deprecated, don't use */ +int fuse_lowlevel_is_lib_option(const char *opt); + +/** + * Create a low level session + * + * @param args argument vector + * @param op the low level filesystem operations + * @param op_size sizeof(struct fuse_lowlevel_ops) + * @param userdata user data + * @return the created session object, or NULL on failure + */ +struct fuse_session *fuse_lowlevel_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); + +/* ----------------------------------------------------------- * + * Session interface * + * ----------------------------------------------------------- */ + +/** + * Session operations + * + * This is used in session creation + */ +struct fuse_session_ops { + /** + * Hook to process a request (mandatory) + * + * @param data user data passed to fuse_session_new() + * @param buf buffer containing the raw request + * @param len request length + * @param ch channel on which the request was received + */ + void (*process) (void *data, const char *buf, size_t len, + struct fuse_chan *ch); + + /** + * Hook for session exit and reset (optional) + * + * @param data user data passed to fuse_session_new() + * @param val exited status (1 - exited, 0 - not exited) + */ + void (*exit) (void *data, int val); + + /** + * Hook for querying the current exited status (optional) + * + * @param data user data passed to fuse_session_new() + * @return 1 if exited, 0 if not exited + */ + int (*exited) (void *data); + + /** + * Hook for cleaning up the channel on destroy (optional) + * + * @param data user data passed to fuse_session_new() + */ + void (*destroy) (void *data); +}; + +/** + * Create a new session + * + * @param op session operations + * @param data user data + * @return new session object, or NULL on failure + */ +struct fuse_session *fuse_session_new(struct fuse_session_ops *op, void *data); + +/** + * Assign a channel to a session + * + * Note: currently only a single channel may be assigned. This may + * change in the future + * + * If a session is destroyed, the assigned channel is also destroyed + * + * @param se the session + * @param ch the channel + */ +void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch); + +/** + * Remove a channel from a session + * + * If the channel is not assigned to a session, then this is a no-op + * + * @param ch the channel to remove + */ +void fuse_session_remove_chan(struct fuse_chan *ch); + +/** + * Iterate over the channels assigned to a session + * + * The iterating function needs to start with a NULL channel, and + * after that needs to pass the previously returned channel to the + * function. + * + * @param se the session + * @param ch the previous channel, or NULL + * @return the next channel, or NULL if no more channels exist + */ +struct fuse_chan *fuse_session_next_chan(struct fuse_session *se, + struct fuse_chan *ch); + +/** + * Process a raw request + * + * @param se the session + * @param buf buffer containing the raw request + * @param len request length + * @param ch channel on which the request was received + */ +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 + * + * @param se the session + */ +void fuse_session_destroy(struct fuse_session *se); + +/** + * Exit a session + * + * @param se the session + */ +void fuse_session_exit(struct fuse_session *se); + +/** + * Reset the exited status of a session + * + * @param se the session + */ +void fuse_session_reset(struct fuse_session *se); + +/** + * Query the exited status of a session + * + * @param se the session + * @return 1 if exited, 0 if not exited + */ +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 + * + * @param se the session + * @return 0 on success, -1 on error + */ +int fuse_session_loop(struct fuse_session *se); + +/** + * Enter a multi-threaded event loop + * + * @param se the session + * @return 0 on success, -1 on error + */ +int fuse_session_loop_mt(struct fuse_session *se); + +/* ----------------------------------------------------------- * + * Channel interface * + * ----------------------------------------------------------- */ + +/** + * Channel operations + * + * This is used in channel creation + */ +struct fuse_chan_ops { + /** + * Hook for receiving a raw request + * + * @param ch pointer to the channel + * @param buf the buffer to store the request in + * @param size the size of the buffer + * @return the actual size of the raw request, or -1 on error + */ + int (*receive)(struct fuse_chan **chp, char *buf, size_t size); + + /** + * Hook for sending a raw reply + * + * A return value of -ENOENT means, that the request was + * interrupted, and the reply was discarded + * + * @param ch the channel + * @param iov vector of blocks + * @param count the number of blocks in vector + * @return zero on success, -errno on failure + */ + int (*send)(struct fuse_chan *ch, const struct iovec iov[], + size_t count); + + /** + * Destroy the channel + * + * @param ch the channel + */ + void (*destroy)(struct fuse_chan *ch); +}; + +/** + * Create a new channel + * + * @param op channel operations + * @param fd file descriptor of the channel + * @param bufsize the minimal receive buffer size + * @param data user data + * @return the new channel object, or NULL on failure + */ +struct fuse_chan *fuse_chan_new(struct fuse_chan_ops *op, int fd, + size_t bufsize, void *data); + +/** + * Query the file descriptor of the channel + * + * @param ch the channel + * @return the file descriptor passed to fuse_chan_new() + */ +int fuse_chan_fd(struct fuse_chan *ch); + +/** + * Query the minimal receive buffer size + * + * @param ch the channel + * @return the buffer size passed to fuse_chan_new() + */ +size_t fuse_chan_bufsize(struct fuse_chan *ch); + +/** + * Query the user data + * + * @param ch the channel + * @return the user data passed to fuse_chan_new() + */ +void *fuse_chan_data(struct fuse_chan *ch); + +/** + * Query the session to which this channel is assigned + * + * @param ch the channel + * @return the session, or NULL if the channel is not assigned + */ +struct fuse_session *fuse_chan_session(struct fuse_chan *ch); + +/** + * Receive a raw request + * + * A return value of -ENODEV means, that the filesystem was unmounted + * + * @param ch pointer to the channel + * @param buf the buffer to store the request in + * @param size the size of the buffer + * @return the actual size of the raw request, or -errno on error + */ +int fuse_chan_recv(struct fuse_chan **ch, char *buf, size_t size); + +/** + * Send a raw reply + * + * A return value of -ENOENT means, that the request was + * interrupted, and the reply was discarded + * + * @param ch the channel + * @param iov vector of blocks + * @param count the number of blocks in vector + * @return zero on success, -errno on failure + */ +int fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[], + size_t count); + +/** + * Destroy a channel + * + * @param ch the channel + */ +void fuse_chan_destroy(struct fuse_chan *ch); + +/* ----------------------------------------------------------- * + * Compatibility stuff * + * ----------------------------------------------------------- */ + +#if FUSE_USE_VERSION < 26 +# include "fuse_lowlevel_compat.h" +# define fuse_chan_ops fuse_chan_ops_compat24 +# define fuse_chan_new fuse_chan_new_compat24 +# if FUSE_USE_VERSION == 25 +# define fuse_lowlevel_ops fuse_lowlevel_ops_compat25 +# define fuse_lowlevel_new fuse_lowlevel_new_compat25 +# elif FUSE_USE_VERSION == 24 +# define fuse_lowlevel_ops fuse_lowlevel_ops_compat +# define fuse_lowlevel_new fuse_lowlevel_new_compat +# define fuse_file_info fuse_file_info_compat +# define fuse_reply_statfs fuse_reply_statfs_compat +# define fuse_reply_open fuse_reply_open_compat +# else +# error Compatibility with low-level API version < 24 not supported +# endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _FUSE_LOWLEVEL_H_ */ diff --git a/fuse/include/fuse_lowlevel_compat.h b/fuse/include/fuse_lowlevel_compat.h new file mode 100644 index 000000000..f13adbdf3 --- /dev/null +++ b/fuse/include/fuse_lowlevel_compat.h @@ -0,0 +1,157 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +/* these definitions provide source compatibility to prior versions. + Do not include this file directly! */ + +#include "fuse_common.h" + +struct fuse_lowlevel_ops_compat25 { + void (*init) (void *userdata); + void (*destroy) (void *userdata); + void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup); + void (*getattr) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi); + void (*readlink) (fuse_req_t req, fuse_ino_t ino); + void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev); + void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode); + void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name); + void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname); + void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname); + void (*open) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off, + struct fuse_file_info *fi); + void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, loff_t off, struct fuse_file_info *fi); + void (*flush) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + void (*release) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + void (*opendir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off, + struct fuse_file_info *fi); + void (*releasedir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + void (*statfs) (fuse_req_t req); + void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags); + void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size); + void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); + void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); + void (*access) (fuse_req_t req, fuse_ino_t ino, int mask); + void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi); +}; + +struct fuse_session *fuse_lowlevel_new_compat25(struct fuse_args *args, + const struct fuse_lowlevel_ops_compat25 *op, + size_t op_size, void *userdata); + +size_t fuse_dirent_size(size_t namelen); + +char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf, + loff_t off); + +#if !defined(__FreeBSD__) && !defined(__NetBSD__) + +#include <sys/statfs.h> + +struct fuse_lowlevel_ops_compat { + void (*init) (void *userdata); + void (*destroy) (void *userdata); + void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup); + void (*getattr) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info_compat *fi); + void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info_compat *fi); + void (*readlink) (fuse_req_t req, fuse_ino_t ino); + void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev); + void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode); + void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); + void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name); + void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname); + void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname); + void (*open) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info_compat *fi); + void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off, + struct fuse_file_info_compat *fi); + void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, loff_t off, struct fuse_file_info_compat *fi); + void (*flush) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info_compat *fi); + void (*release) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info_compat *fi); + void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info_compat *fi); + void (*opendir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info_compat *fi); + void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off, + struct fuse_file_info_compat *fi); + void (*releasedir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info_compat *fi); + void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info_compat *fi); + void (*statfs) (fuse_req_t req); + void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags); + void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size); + void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); + void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); + void (*access) (fuse_req_t req, fuse_ino_t ino, int mask); + void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info_compat *fi); +}; + +int fuse_reply_statfs_compat(fuse_req_t req, const struct statfs *stbuf); + +int fuse_reply_open_compat(fuse_req_t req, + const struct fuse_file_info_compat *fi); + +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__ || __NetBSD__ */ + +struct fuse_chan_ops_compat24 { + int (*receive)(struct fuse_chan *ch, char *buf, size_t size); + int (*send)(struct fuse_chan *ch, const struct iovec iov[], + size_t count); + void (*destroy)(struct fuse_chan *ch); +}; + +struct fuse_chan *fuse_chan_new_compat24(struct fuse_chan_ops_compat24 *op, + int fd, size_t bufsize, void *data); + +int fuse_chan_receive(struct fuse_chan *ch, char *buf, size_t size); +struct fuse_chan *fuse_kern_chan_new(int fd); diff --git a/fuse/include/fuse_opt.h b/fuse/include/fuse_opt.h new file mode 100644 index 000000000..add0a3089 --- /dev/null +++ b/fuse/include/fuse_opt.h @@ -0,0 +1,270 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#ifndef _FUSE_OPT_H_ +#define _FUSE_OPT_H_ + +/** @file + * + * This file defines the option parsing interface of FUSE + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Option description + * + * 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 + * each match is executed. + * + * There are three possible actions in case of a match: + * + * i) An integer (int or unsigned) variable determined by 'offset' is + * set to 'value' + * + * ii) The processing function is called, with 'value' as the key + * + * iii) An integer (any) or string (char *) variable determined by + * 'offset' is set to the value of an option parameter + * + * 'offset' should normally be either set to + * + * - 'offsetof(struct foo, member)' actions i) and iii) + * + * - -1 action ii) + * + * The 'offsetof()' macro is defined in the <stddef.h> header. + * + * The template determines which options match, and also have an + * effect on the action. Normally the action is either i) or ii), but + * if a format is present in the template, then action iii) is + * performed. + * + * The types of templates are: + * + * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only + * themselves. Invalid values are "--" and anything beginning + * with "-o" + * + * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or + * the relevant option in a comma separated option list + * + * 3) "bar=", "--foo=", etc. These are variations of 1) and 2) + * which have a parameter + * + * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform + * action iii). + * + * 5) "-x ", etc. Matches either "-xparam" or "-x param" as + * two separate arguments + * + * 6) "-x %s", etc. Combination of 4) and 5) + * + * If the format is "%s", memory is allocated for the string unlike + * with scanf(). + */ +struct fuse_opt { + /** Matching template and optional parameter formatting */ + const char *templ; + + /** + * Offset of variable within 'data' parameter of fuse_opt_parse() + * or -1 + */ + unsigned long offset; + + /** + * Value to set the variable to, or to be passed as 'key' to the + * processing function. Ignored if template has a format + */ + int value; +}; + +/** + * Key option. In case of a match, the processing function will be + * called with the specified key. + */ +#define FUSE_OPT_KEY(templ, key) { templ, -1U, key } + +/** + * Last option. An array of 'struct fuse_opt' must end with a NULL + * template value + */ +#define FUSE_OPT_END { NULL, 0, 0 } + +/** + * Argument list + */ +struct fuse_args { + /** Argument count */ + int argc; + + /** Argument vector. NULL terminated */ + char **argv; + + /** Is 'argv' allocated? */ + int allocated; +}; + +/** + * Initializer for 'struct fuse_args' + */ +#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 } + +/** + * Key value passed to the processing function if an option did not + * match any template + */ +#define FUSE_OPT_KEY_OPT -1 + +/** + * Key value passed to the processing function for all non-options + * + * Non-options are the arguments beginning with a character other than + * '-' or all arguments after the special '--' option + */ +#define FUSE_OPT_KEY_NONOPT -2 + +/** + * Special key value for options to keep + * + * Argument is not passed to processing function, but behave as if the + * processing function returned 1 + */ +#define FUSE_OPT_KEY_KEEP -3 + +/** + * Special key value for options to discard + * + * Argument is not passed to processing function, but behave as if the + * processing function returned zero + */ +#define FUSE_OPT_KEY_DISCARD -4 + +/** + * Processing function + * + * This function is called if + * - option did not match any 'struct fuse_opt' + * - argument is a non-option + * - option did match and offset was set to -1 + * + * 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 argument option of the + * form "-xfoo" before this function is called. + * + * Options of the form '-ofoo' are passed to this function without the + * '-o' prefix. + * + * The return value of this function determines whether this argument + * is to be inserted into the output argument vector, or discarded. + * + * @param data is the user data passed to the fuse_opt_parse() function + * @param arg is the whole argument or option + * @param key determines why the processing function was called + * @param outargs the current output argument list + * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept + */ +typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, + struct fuse_args *outargs); + +/** + * Option parsing function + * + * If 'args' was returned from a previous call to fuse_opt_parse() or + * it was constructed from + * + * A NULL 'args' is equivalent to an empty argument vector + * + * A NULL 'opts' is equivalent to an 'opts' array containing a single + * end marker + * + * A NULL 'proc' is equivalent to a processing function always + * returning '1' + * + * @param args is the input and output argument list + * @param data is the user data + * @param opts is the option description array + * @param proc is the processing function + * @return -1 on error, 0 on success + */ +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc); + +/** + * Add an option 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(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 + * + * @param args is the structure containing the current argument list + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_arg(struct fuse_args *args, const char *arg); + +/** + * Add an argument at the specified position in a NULL terminated + * argument vector + * + * Adds the argument to the N-th position. This is useful for adding + * 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 + * @param pos is the position at which to add the argument + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg); + +/** + * Free the contents of argument list + * + * The structure itself is not freed + * + * @param args is the structure containing the argument list + */ +void fuse_opt_free_args(struct fuse_args *args); + + +/** + * Check if an option matches + * + * @param opts is the option description array + * @param opt is the option to match + * @return 1 if a match is found, 0 if not + */ +int fuse_opt_match(const struct fuse_opt opts[], const char *opt); + +#ifdef __cplusplus +} +#endif + +#endif /* _FUSE_OPT_H_ */ diff --git a/fuse/include/old/fuse.h b/fuse/include/old/fuse.h new file mode 100644 index 000000000..3db0945a9 --- /dev/null +++ b/fuse/include/old/fuse.h @@ -0,0 +1,9 @@ +/* + This header is for compatibility with older software using FUSE. + + Please use 'pkg-config --cflags fuse' to set include path. The + correct usage is still '#include <fuse.h>', not '#include + <fuse/fuse.h>'. +*/ + +#include "fuse/fuse.h" diff --git a/fuse/include/ulockmgr.h b/fuse/include/ulockmgr.h new file mode 100644 index 000000000..c3ceef5d0 --- /dev/null +++ b/fuse/include/ulockmgr.h @@ -0,0 +1,26 @@ +/* + libulockmgr: Userspace Lock Manager Library + Copyright (C) 2006 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "fuse_lowlevel.h" + +#include <stdint.h> +#include <fcntl.h> +#include <sys/types.h> + +/** + * Perform POSIX locking operation + * + * @param fd the file descriptor + * @param cmd the locking command (F_GETFL, F_SETLK or F_SETLKW) + * @param lock the lock parameters + * @param owner the lock owner ID cookie + * @param owner_len length of the lock owner ID cookie + * @return 0 on success -errno on error + */ +int ulockmgr_op(int fd, int cmd, struct flock *lock, const void *owner, + size_t owner_len); diff --git a/fuse/modules/iconv.c b/fuse/modules/iconv.c new file mode 100644 index 000000000..9b78cfd4b --- /dev/null +++ b/fuse/modules/iconv.c @@ -0,0 +1,739 @@ +/* + fuse iconv module: file name charset conversion + Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#define FUSE_USE_VERSION 26 + +#include <fuse.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <iconv.h> +#include <pthread.h> +#include <locale.h> +#include <langinfo.h> + +struct iconv { + struct fuse_fs *next; + pthread_mutex_t lock; + char *from_code; + char *to_code; + iconv_t tofs; + iconv_t fromfs; +}; + +struct iconv_dh { + struct iconv *ic; + void *prev_buf; + fuse_fill_dir_t prev_filler; +}; + +static struct iconv *iconv_get(void) +{ + return fuse_get_context()->private_data; +} + +static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp, + int fromfs) +{ + size_t pathlen; + size_t newpathlen; + char *newpath; + size_t plen; + char *p; + size_t res; + int err; + + if (path == NULL) { + *newpathp = NULL; + return 0; + } + + pathlen = strlen(path); + newpathlen = pathlen * 4; + newpath = malloc(newpathlen + 1); + if (!newpath) + return -ENOMEM; + + plen = newpathlen; + p = newpath; + pthread_mutex_lock(&ic->lock); + do { + res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path, + &pathlen, &p, &plen); + if (res == (size_t) -1) { + char *tmp; + size_t inc; + + err = -EILSEQ; + if (errno != E2BIG) + goto err; + + inc = (pathlen + 1) * 4; + newpathlen += inc; + tmp = realloc(newpath, newpathlen + 1); + err = -ENOMEM; + if (!tmp) + goto err; + + p = tmp + (p - newpath); + plen += inc; + newpath = tmp; + } + } while (res == (size_t) -1); + pthread_mutex_unlock(&ic->lock); + *p = '\0'; + *newpathp = newpath; + return 0; + +err: + iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL); + pthread_mutex_unlock(&ic->lock); + free(newpath); + return err; +} + +static int iconv_getattr(const char *path, struct stat *stbuf) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_getattr(ic->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int iconv_fgetattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fgetattr(ic->next, newpath, stbuf, fi); + free(newpath); + } + return err; +} + +static int iconv_access(const char *path, int mask) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_access(ic->next, newpath, mask); + free(newpath); + } + return err; +} + +static int iconv_readlink(const char *path, char *buf, size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_readlink(ic->next, newpath, buf, size); + if (!err) { + char *newlink; + err = iconv_convpath(ic, buf, &newlink, 1); + if (!err) { + strncpy(buf, newlink, size - 1); + buf[size - 1] = '\0'; + free(newlink); + } + } + free(newpath); + } + return err; +} + +static int iconv_opendir(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_opendir(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_dir_fill(void *buf, const char *name, + const struct stat *stbuf, loff_t off) +{ + struct iconv_dh *dh = buf; + char *newname; + int res = 0; + if (iconv_convpath(dh->ic, name, &newname, 1) == 0) { + res = dh->prev_filler(dh->prev_buf, newname, stbuf, off); + free(newname); + } + return res; +} + +static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + loff_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + struct iconv_dh dh; + dh.ic = ic; + dh.prev_buf = buf; + dh.prev_filler = filler; + err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill, + offset, fi); + free(newpath); + } + return err; +} + +static int iconv_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_releasedir(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_mknod(const char *path, mode_t mode, dev_t rdev) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_mknod(ic->next, newpath, mode, rdev); + free(newpath); + } + return err; +} + +static int iconv_mkdir(const char *path, mode_t mode) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_mkdir(ic->next, newpath, mode); + free(newpath); + } + return err; +} + +static int iconv_unlink(const char *path) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_unlink(ic->next, newpath); + free(newpath); + } + return err; +} + +static int iconv_rmdir(const char *path) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_rmdir(ic->next, newpath); + free(newpath); + } + return err; +} + +static int iconv_symlink(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_symlink(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_rename(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_rename(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_link(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_link(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_chmod(const char *path, mode_t mode) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_chmod(ic->next, newpath, mode); + free(newpath); + } + return err; +} + +static int iconv_chown(const char *path, uid_t uid, gid_t gid) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_chown(ic->next, newpath, uid, gid); + free(newpath); + } + return err; +} + +static int iconv_truncate(const char *path, loff_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_truncate(ic->next, newpath, size); + free(newpath); + } + return err; +} + +static int iconv_ftruncate(const char *path, loff_t size, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_ftruncate(ic->next, newpath, size, fi); + free(newpath); + } + return err; +} + +static int iconv_utimens(const char *path, const struct timespec ts[2]) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_utimens(ic->next, newpath, ts); + free(newpath); + } + return err; +} + +static int iconv_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_create(ic->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int iconv_open_file(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_open(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, loff_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi); + free(newpath); + } + return err; +} + +static int iconv_write_buf(const char *path, struct fuse_bufvec *buf, + loff_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi); + free(newpath); + } + return err; +} + +static int iconv_statfs(const char *path, struct statvfs *stbuf) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_statfs(ic->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int iconv_flush(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_flush(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_release(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_release(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int iconv_fsyncdir(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int iconv_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_setxattr(ic->next, newpath, name, value, size, + flags); + free(newpath); + } + return err; +} + +static int iconv_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_getxattr(ic->next, newpath, name, value, size); + free(newpath); + } + return err; +} + +static int iconv_listxattr(const char *path, char *list, size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_listxattr(ic->next, newpath, list, size); + free(newpath); + } + return err; +} + +static int iconv_removexattr(const char *path, const char *name) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_removexattr(ic->next, newpath, name); + free(newpath); + } + return err; +} + +static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock); + free(newpath); + } + return err; +} + +static int iconv_flock(const char *path, struct fuse_file_info *fi, int op) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_flock(ic->next, newpath, fi, op); + free(newpath); + } + return err; +} + +static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_bmap(ic->next, newpath, blocksize, idx); + free(newpath); + } + return err; +} + +static void *iconv_init(struct fuse_conn_info *conn) +{ + struct iconv *ic = iconv_get(); + fuse_fs_init(ic->next, conn); + return ic; +} + +static void iconv_destroy(void *data) +{ + struct iconv *ic = data; + fuse_fs_destroy(ic->next); + iconv_close(ic->tofs); + iconv_close(ic->fromfs); + pthread_mutex_destroy(&ic->lock); + free(ic->from_code); + free(ic->to_code); + free(ic); +} + +static const struct fuse_operations iconv_oper = { + .destroy = iconv_destroy, + .init = iconv_init, + .getattr = iconv_getattr, + .fgetattr = iconv_fgetattr, + .access = iconv_access, + .readlink = iconv_readlink, + .opendir = iconv_opendir, + .readdir = iconv_readdir, + .releasedir = iconv_releasedir, + .mknod = iconv_mknod, + .mkdir = iconv_mkdir, + .symlink = iconv_symlink, + .unlink = iconv_unlink, + .rmdir = iconv_rmdir, + .rename = iconv_rename, + .link = iconv_link, + .chmod = iconv_chmod, + .chown = iconv_chown, + .truncate = iconv_truncate, + .ftruncate = iconv_ftruncate, + .utimens = iconv_utimens, + .create = iconv_create, + .open = iconv_open_file, + .read_buf = iconv_read_buf, + .write_buf = iconv_write_buf, + .statfs = iconv_statfs, + .flush = iconv_flush, + .release = iconv_release, + .fsync = iconv_fsync, + .fsyncdir = iconv_fsyncdir, + .setxattr = iconv_setxattr, + .getxattr = iconv_getxattr, + .listxattr = iconv_listxattr, + .removexattr = iconv_removexattr, + .lock = iconv_lock, + .flock = iconv_flock, + .bmap = iconv_bmap, + + .flag_nullpath_ok = 1, + .flag_nopath = 1, +}; + +static const struct fuse_opt iconv_opts[] = { + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + { "from_code=%s", offsetof(struct iconv, from_code), 0 }, + { "to_code=%s", offsetof(struct iconv, to_code), 1 }, + FUSE_OPT_END +}; + +static void iconv_help(void) +{ + char *old = strdup(setlocale(LC_CTYPE, "")); + char *charmap = strdup(nl_langinfo(CODESET)); + setlocale(LC_CTYPE, old); + free(old); + fprintf(stderr, +" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n" +" -o to_code=CHARSET new encoding of the file names (default: %s)\n", + charmap); + free(charmap); +} + +static int iconv_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) arg; (void) outargs; + + if (!key) { + iconv_help(); + return -1; + } + + return 1; +} + +static struct fuse_fs *iconv_new(struct fuse_args *args, + struct fuse_fs *next[]) +{ + struct fuse_fs *fs; + struct iconv *ic; + char *old = NULL; + const char *from; + const char *to; + + ic = calloc(1, sizeof(struct iconv)); + if (ic == NULL) { + fprintf(stderr, "fuse-iconv: memory allocation failed\n"); + return NULL; + } + + if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1) + goto out_free; + + if (!next[0] || next[1]) { + fprintf(stderr, "fuse-iconv: exactly one next filesystem required\n"); + goto out_free; + } + + from = ic->from_code ? ic->from_code : "UTF-8"; + to = ic->to_code ? ic->to_code : ""; + /* FIXME: detect charset equivalence? */ + if (!to[0]) + old = strdup(setlocale(LC_CTYPE, "")); + ic->tofs = iconv_open(from, to); + if (ic->tofs == (iconv_t) -1) { + fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n", + to, from); + goto out_free; + } + ic->fromfs = iconv_open(to, from); + if (ic->tofs == (iconv_t) -1) { + fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n", + from, to); + goto out_iconv_close_to; + } + if (old) { + setlocale(LC_CTYPE, old); + free(old); + } + + ic->next = next[0]; + fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic); + if (!fs) + goto out_iconv_close_from; + + return fs; + +out_iconv_close_from: + iconv_close(ic->fromfs); +out_iconv_close_to: + iconv_close(ic->tofs); +out_free: + free(ic->from_code); + free(ic->to_code); + free(ic); + if (old) { + setlocale(LC_CTYPE, old); + free(old); + } + return NULL; +} + +FUSE_REGISTER_MODULE(iconv, iconv_new); diff --git a/fuse/modules/subdir.c b/fuse/modules/subdir.c new file mode 100644 index 000000000..05b3379af --- /dev/null +++ b/fuse/modules/subdir.c @@ -0,0 +1,697 @@ +/* + fuse subdir module: offset paths with a base directory + Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#define FUSE_USE_VERSION 26 + +#include <fuse.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> + +struct subdir { + char *base; + size_t baselen; + int rellinks; + struct fuse_fs *next; +}; + +static struct subdir *subdir_get(void) +{ + return fuse_get_context()->private_data; +} + +static int subdir_addpath(struct subdir *d, const char *path, char **newpathp) +{ + char *newpath = NULL; + + if (path != NULL) { + unsigned newlen = d->baselen + strlen(path); + + newpath = malloc(newlen + 2); + if (!newpath) + return -ENOMEM; + + if (path[0] == '/') + path++; + strcpy(newpath, d->base); + strcpy(newpath + d->baselen, path); + if (!newpath[0]) + strcpy(newpath, "."); + } + *newpathp = newpath; + + return 0; +} + +static int subdir_getattr(const char *path, struct stat *stbuf) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_getattr(d->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int subdir_fgetattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fgetattr(d->next, newpath, stbuf, fi); + free(newpath); + } + return err; +} + +static int subdir_access(const char *path, int mask) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_access(d->next, newpath, mask); + free(newpath); + } + return err; +} + + +static int count_components(const char *p) +{ + int ctr; + + for (; *p == '/'; p++); + for (ctr = 0; *p; ctr++) { + for (; *p && *p != '/'; p++); + for (; *p == '/'; p++); + } + return ctr; +} + +static void strip_common(const char **sp, const char **tp) +{ + const char *s = *sp; + const char *t = *tp; + do { + for (; *s == '/'; s++); + for (; *t == '/'; t++); + *tp = t; + *sp = s; + for (; *s == *t && *s && *s != '/'; s++, t++); + } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t)); +} + +static void transform_symlink(struct subdir *d, const char *path, + char *buf, size_t size) +{ + const char *l = buf; + size_t llen; + char *s; + int dotdots; + int i; + + if (l[0] != '/' || d->base[0] != '/') + return; + + strip_common(&l, &path); + if (l - buf < (long) d->baselen) + return; + + dotdots = count_components(path); + if (!dotdots) + return; + dotdots--; + + llen = strlen(l); + if (dotdots * 3 + llen + 2 > size) + return; + + s = buf + dotdots * 3; + if (llen) + memmove(s, l, llen + 1); + else if (!dotdots) + strcpy(s, "."); + else + *s = '\0'; + + for (s = buf, i = 0; i < dotdots; i++, s += 3) + memcpy(s, "../", 3); +} + + +static int subdir_readlink(const char *path, char *buf, size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_readlink(d->next, newpath, buf, size); + if (!err && d->rellinks) + transform_symlink(d, newpath, buf, size); + free(newpath); + } + return err; +} + +static int subdir_opendir(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_opendir(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_readdir(const char *path, void *buf, + fuse_fill_dir_t filler, loff_t offset, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_readdir(d->next, newpath, buf, filler, offset, + fi); + free(newpath); + } + return err; +} + +static int subdir_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_releasedir(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_mknod(const char *path, mode_t mode, dev_t rdev) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_mknod(d->next, newpath, mode, rdev); + free(newpath); + } + return err; +} + +static int subdir_mkdir(const char *path, mode_t mode) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_mkdir(d->next, newpath, mode); + free(newpath); + } + return err; +} + +static int subdir_unlink(const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_unlink(d->next, newpath); + free(newpath); + } + return err; +} + +static int subdir_rmdir(const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_rmdir(d->next, newpath); + free(newpath); + } + return err; +} + +static int subdir_symlink(const char *from, const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_symlink(d->next, from, newpath); + free(newpath); + } + return err; +} + +static int subdir_rename(const char *from, const char *to) +{ + struct subdir *d = subdir_get(); + char *newfrom; + char *newto; + int err = subdir_addpath(d, from, &newfrom); + if (!err) { + err = subdir_addpath(d, to, &newto); + if (!err) { + err = fuse_fs_rename(d->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int subdir_link(const char *from, const char *to) +{ + struct subdir *d = subdir_get(); + char *newfrom; + char *newto; + int err = subdir_addpath(d, from, &newfrom); + if (!err) { + err = subdir_addpath(d, to, &newto); + if (!err) { + err = fuse_fs_link(d->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int subdir_chmod(const char *path, mode_t mode) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_chmod(d->next, newpath, mode); + free(newpath); + } + return err; +} + +static int subdir_chown(const char *path, uid_t uid, gid_t gid) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_chown(d->next, newpath, uid, gid); + free(newpath); + } + return err; +} + +static int subdir_truncate(const char *path, loff_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_truncate(d->next, newpath, size); + free(newpath); + } + return err; +} + +static int subdir_ftruncate(const char *path, loff_t size, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_ftruncate(d->next, newpath, size, fi); + free(newpath); + } + return err; +} + +static int subdir_utimens(const char *path, const struct timespec ts[2]) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_utimens(d->next, newpath, ts); + free(newpath); + } + return err; +} + +static int subdir_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_create(d->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int subdir_open(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_open(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, loff_t offset, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi); + free(newpath); + } + return err; +} + +static int subdir_write_buf(const char *path, struct fuse_bufvec *buf, + loff_t offset, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi); + free(newpath); + } + return err; +} + +static int subdir_statfs(const char *path, struct statvfs *stbuf) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_statfs(d->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int subdir_flush(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_flush(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_release(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_release(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fsync(d->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int subdir_fsyncdir(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int subdir_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_setxattr(d->next, newpath, name, value, size, + flags); + free(newpath); + } + return err; +} + +static int subdir_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_getxattr(d->next, newpath, name, value, size); + free(newpath); + } + return err; +} + +static int subdir_listxattr(const char *path, char *list, size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_listxattr(d->next, newpath, list, size); + free(newpath); + } + return err; +} + +static int subdir_removexattr(const char *path, const char *name) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_removexattr(d->next, newpath, name); + free(newpath); + } + return err; +} + +static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_lock(d->next, newpath, fi, cmd, lock); + free(newpath); + } + return err; +} + +static int subdir_flock(const char *path, struct fuse_file_info *fi, int op) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_flock(d->next, newpath, fi, op); + free(newpath); + } + return err; +} + +static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_bmap(d->next, newpath, blocksize, idx); + free(newpath); + } + return err; +} + +static void *subdir_init(struct fuse_conn_info *conn) +{ + struct subdir *d = subdir_get(); + fuse_fs_init(d->next, conn); + return d; +} + +static void subdir_destroy(void *data) +{ + struct subdir *d = data; + fuse_fs_destroy(d->next); + free(d->base); + free(d); +} + +static const struct fuse_operations subdir_oper = { + .destroy = subdir_destroy, + .init = subdir_init, + .getattr = subdir_getattr, + .fgetattr = subdir_fgetattr, + .access = subdir_access, + .readlink = subdir_readlink, + .opendir = subdir_opendir, + .readdir = subdir_readdir, + .releasedir = subdir_releasedir, + .mknod = subdir_mknod, + .mkdir = subdir_mkdir, + .symlink = subdir_symlink, + .unlink = subdir_unlink, + .rmdir = subdir_rmdir, + .rename = subdir_rename, + .link = subdir_link, + .chmod = subdir_chmod, + .chown = subdir_chown, + .truncate = subdir_truncate, + .ftruncate = subdir_ftruncate, + .utimens = subdir_utimens, + .create = subdir_create, + .open = subdir_open, + .read_buf = subdir_read_buf, + .write_buf = subdir_write_buf, + .statfs = subdir_statfs, + .flush = subdir_flush, + .release = subdir_release, + .fsync = subdir_fsync, + .fsyncdir = subdir_fsyncdir, + .setxattr = subdir_setxattr, + .getxattr = subdir_getxattr, + .listxattr = subdir_listxattr, + .removexattr = subdir_removexattr, + .lock = subdir_lock, + .flock = subdir_flock, + .bmap = subdir_bmap, + + .flag_nullpath_ok = 1, + .flag_nopath = 1, +}; + +static const struct fuse_opt subdir_opts[] = { + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + { "subdir=%s", offsetof(struct subdir, base), 0 }, + { "rellinks", offsetof(struct subdir, rellinks), 1 }, + { "norellinks", offsetof(struct subdir, rellinks), 0 }, + FUSE_OPT_END +}; + +static void subdir_help(void) +{ + fprintf(stderr, +" -o subdir=DIR prepend this directory to all paths (mandatory)\n" +" -o [no]rellinks transform absolute symlinks to relative\n"); +} + +static int subdir_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) arg; (void) outargs; + + if (!key) { + subdir_help(); + return -1; + } + + return 1; +} + +static struct fuse_fs *subdir_new(struct fuse_args *args, + struct fuse_fs *next[]) +{ + struct fuse_fs *fs; + struct subdir *d; + + d = calloc(1, sizeof(struct subdir)); + if (d == NULL) { + fprintf(stderr, "fuse-subdir: memory allocation failed\n"); + return NULL; + } + + if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1) + goto out_free; + + if (!next[0] || next[1]) { + fprintf(stderr, "fuse-subdir: exactly one next filesystem required\n"); + goto out_free; + } + + if (!d->base) { + fprintf(stderr, "fuse-subdir: missing 'subdir' option\n"); + goto out_free; + } + + if (d->base[0] && d->base[strlen(d->base)-1] != '/') { + char *tmp = realloc(d->base, strlen(d->base) + 2); + if (!tmp) { + fprintf(stderr, "fuse-subdir: memory allocation failed\n"); + goto out_free; + } + d->base = tmp; + strcat(d->base, "/"); + } + d->baselen = strlen(d->base); + d->next = next[0]; + fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d); + if (!fs) + goto out_free; + return fs; + +out_free: + free(d->base); + free(d); + return NULL; +} + +FUSE_REGISTER_MODULE(subdir, subdir_new); diff --git a/fuse/mount.c b/fuse/mount.c new file mode 100644 index 000000000..873685634 --- /dev/null +++ b/fuse/mount.c @@ -0,0 +1,644 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "fuse_common_compat.h" +#include "mount_util.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stddef.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <sys/mount.h> + +#ifdef __NetBSD__ +#include <perfuse.h> + +#define MS_RDONLY MNT_RDONLY +#define MS_NOSUID MNT_NOSUID +#define MS_NODEV MNT_NODEV +#define MS_NOEXEC MNT_NOEXEC +#define MS_SYNCHRONOUS MNT_SYNCHRONOUS +#define MS_NOATIME MNT_NOATIME + +#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) +#endif + +#define FUSERMOUNT_PROG "fusermount" +#define FUSE_COMMFD_ENV "_FUSE_COMMFD" + +#if defined(__ANDROID__) && !defined(FUSERMOUNT_DIR) +# define FUSERMOUNT_DIR "/system/xbin" +#endif + +#ifndef HAVE_FORK +#define fork() vfork() +#endif + +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 +#endif + +enum { + KEY_KERN_FLAG, + KEY_KERN_OPT, + KEY_FUSERMOUNT_OPT, + KEY_SUBTYPE_OPT, + KEY_MTAB_OPT, + KEY_ALLOW_ROOT, + KEY_RO, + KEY_HELP, + KEY_VERSION, +}; + +struct mount_opts { + int allow_other; + int allow_root; + int ishelp; + int flags; + int nonempty; + int auto_unmount; + int blkdev; + char *fsname; + char *subtype; + char *subtype_opt; + char *mtab_opts; + char *fusermount_opts; + char *kernel_opts; +}; + +#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 } + +static const struct fuse_opt fuse_mount_opts[] = { + FUSE_MOUNT_OPT("allow_other", allow_other), + FUSE_MOUNT_OPT("allow_root", allow_root), + FUSE_MOUNT_OPT("nonempty", nonempty), + FUSE_MOUNT_OPT("blkdev", blkdev), + FUSE_MOUNT_OPT("auto_unmount", auto_unmount), + FUSE_MOUNT_OPT("fsname=%s", fsname), + FUSE_MOUNT_OPT("subtype=%s", subtype), + FUSE_OPT_KEY("allow_other", KEY_KERN_OPT), + FUSE_OPT_KEY("allow_root", KEY_ALLOW_ROOT), + FUSE_OPT_KEY("nonempty", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT), + FUSE_OPT_KEY("large_read", KEY_KERN_OPT), + FUSE_OPT_KEY("blksize=", KEY_KERN_OPT), + FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT), + FUSE_OPT_KEY("max_read=", KEY_KERN_OPT), + FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("user=", KEY_MTAB_OPT), + FUSE_OPT_KEY("-r", KEY_RO), + FUSE_OPT_KEY("ro", KEY_KERN_FLAG), + FUSE_OPT_KEY("rw", KEY_KERN_FLAG), + FUSE_OPT_KEY("suid", KEY_KERN_FLAG), + FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG), + FUSE_OPT_KEY("dev", KEY_KERN_FLAG), + FUSE_OPT_KEY("nodev", KEY_KERN_FLAG), + FUSE_OPT_KEY("exec", KEY_KERN_FLAG), + FUSE_OPT_KEY("noexec", KEY_KERN_FLAG), + FUSE_OPT_KEY("async", KEY_KERN_FLAG), + FUSE_OPT_KEY("sync", KEY_KERN_FLAG), + FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG), + FUSE_OPT_KEY("atime", KEY_KERN_FLAG), + FUSE_OPT_KEY("noatime", KEY_KERN_FLAG), + FUSE_OPT_KEY("-h", KEY_HELP), + FUSE_OPT_KEY("--help", KEY_HELP), + FUSE_OPT_KEY("-V", KEY_VERSION), + FUSE_OPT_KEY("--version", KEY_VERSION), + FUSE_OPT_END +}; + +static void mount_help(void) +{ + fprintf(stderr, +" -o allow_other allow access to other users\n" +" -o allow_root allow access to root\n" +" -o auto_unmount auto unmount on process termination\n" +" -o nonempty allow mounts over non-empty file/dir\n" +" -o default_permissions enable permission checking by kernel\n" +" -o fsname=NAME set filesystem name\n" +" -o subtype=NAME set filesystem type\n" +" -o large_read issue large read requests (2.4 only)\n" +" -o max_read=N set maximum size of read requests\n" +"\n"); +} + +static void exec_fusermount(const char *argv[]) +{ + execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (char **) argv); + execvp(FUSERMOUNT_PROG, (char **) argv); +} + +static void mount_version(void) +{ + int pid = fork(); + if (!pid) { + const char *argv[] = { FUSERMOUNT_PROG, "--version", NULL }; + exec_fusermount(argv); + _exit(1); + } else if (pid != -1) + waitpid(pid, NULL, 0); +} + +struct mount_flags { + const char *opt; + unsigned long flag; + int on; +}; + +static const struct mount_flags mount_flags[] = { + {"rw", MS_RDONLY, 0}, + {"ro", MS_RDONLY, 1}, + {"suid", MS_NOSUID, 0}, + {"nosuid", MS_NOSUID, 1}, + {"dev", MS_NODEV, 0}, + {"nodev", MS_NODEV, 1}, + {"exec", MS_NOEXEC, 0}, + {"noexec", MS_NOEXEC, 1}, + {"async", MS_SYNCHRONOUS, 0}, + {"sync", MS_SYNCHRONOUS, 1}, + {"atime", MS_NOATIME, 0}, + {"noatime", MS_NOATIME, 1}, +#ifndef __NetBSD__ + {"dirsync", MS_DIRSYNC, 1}, +#endif + {NULL, 0, 0} +}; + +static void set_mount_flag(const char *s, int *flags) +{ + int i; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + const char *opt = mount_flags[i].opt; + if (strcmp(opt, s) == 0) { + if (mount_flags[i].on) + *flags |= mount_flags[i].flag; + else + *flags &= ~mount_flags[i].flag; + return; + } + } + fprintf(stderr, "fuse: internal error, can't find mount flag\n"); + abort(); +} + +static int fuse_mount_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + struct mount_opts *mo = data; + + switch (key) { + case KEY_ALLOW_ROOT: + if (fuse_opt_add_opt(&mo->kernel_opts, "allow_other") == -1 || + fuse_opt_add_arg(outargs, "-oallow_root") == -1) + return -1; + return 0; + + case KEY_RO: + arg = "ro"; + /* fall through */ + case KEY_KERN_FLAG: + set_mount_flag(arg, &mo->flags); + return 0; + + case KEY_KERN_OPT: + return fuse_opt_add_opt(&mo->kernel_opts, arg); + + case KEY_FUSERMOUNT_OPT: + return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg); + + case KEY_SUBTYPE_OPT: + return fuse_opt_add_opt(&mo->subtype_opt, arg); + + case KEY_MTAB_OPT: + return fuse_opt_add_opt(&mo->mtab_opts, arg); + + case KEY_HELP: + mount_help(); + mo->ishelp = 1; + break; + + case KEY_VERSION: + mount_version(); + mo->ishelp = 1; + break; + } + return 1; +} + +/* return value: + * >= 0 => fd + * -1 => error + */ +static int receive_fd(int fd) +{ + struct msghdr msg; + struct iovec iov; + char buf[1]; + int rv; + size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)]; + struct cmsghdr *cmsg; + + iov.iov_base = buf; + iov.iov_len = 1; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + /* old BSD implementations should use msg_accrights instead of + * msg_control; the interface is different. */ + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof(ccmsg); + + while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR); + if (rv == -1) { + perror("recvmsg"); + return -1; + } + if(!rv) { + /* EOF */ + return -1; + } + + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg->cmsg_type != SCM_RIGHTS) { + fprintf(stderr, "got control message of unknown type %d\n", + cmsg->cmsg_type); + return -1; + } + return *(int*)CMSG_DATA(cmsg); +} + +void fuse_kern_unmount(const char *mountpoint, int fd) +{ + int res; + int pid; + + if (!mountpoint) + return; + + if (fd != -1) { + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = 0; + res = poll(&pfd, 1, 0); + + /* Need to close file descriptor, otherwise synchronous umount + would recurse into filesystem, and deadlock. + + Caller expects fuse_kern_unmount to close the fd, so close it + anyway. */ + close(fd); + + /* If file poll returns POLLERR on the device file descriptor, + then the filesystem is already unmounted */ + if (res == 1 && (pfd.revents & POLLERR)) + return; + } + + if (geteuid() == 0) { + fuse_mnt_umount("fuse", mountpoint, mountpoint, 1); + return; + } + + res = umount2(mountpoint, 2); + if (res == 0) + return; + + pid = fork(); + if(pid == -1) + return; + + if(pid == 0) { + const char *argv[] = { FUSERMOUNT_PROG, "-u", "-q", "-z", + "--", mountpoint, NULL }; + + exec_fusermount(argv); + _exit(1); + } + waitpid(pid, NULL, 0); +} + +void fuse_unmount_compat22(const char *mountpoint) +{ + fuse_kern_unmount(mountpoint, -1); +} + +static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, + const char *opts, int quiet) +{ + int fds[2], pid; + int res; + int rv; + + if (!mountpoint) { + fprintf(stderr, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); + if(res == -1) { + perror("fuse: socketpair() failed"); + return -1; + } + + pid = fork(); + if(pid == -1) { + perror("fuse: fork() failed"); + close(fds[0]); + close(fds[1]); + return -1; + } + + if(pid == 0) { + char env[10]; + const char *argv[32]; + int a = 0; + + if (quiet) { + int fd = open("/dev/null", O_RDONLY); + if (fd != -1) { + dup2(fd, 1); + dup2(fd, 2); + } + } + + argv[a++] = FUSERMOUNT_PROG; + if (opts) { + argv[a++] = "-o"; + argv[a++] = opts; + } + argv[a++] = "--"; + argv[a++] = mountpoint; + argv[a++] = NULL; + + close(fds[1]); + fcntl(fds[0], F_SETFD, 0); + snprintf(env, sizeof(env), "%i", fds[0]); + setenv(FUSE_COMMFD_ENV, env, 1); + exec_fusermount(argv); + perror("fuse: failed to exec fusermount"); + _exit(1); + } + + close(fds[0]); + rv = receive_fd(fds[1]); + + if (!mo->auto_unmount) { + /* with auto_unmount option fusermount will not exit until + this socket is closed */ + close(fds[1]); + waitpid(pid, NULL, 0); /* bury zombie */ + } + + return rv; +} + +int fuse_mount_compat22(const char *mountpoint, const char *opts) +{ + struct mount_opts mo; + memset(&mo, 0, sizeof(mo)); + mo.flags = MS_NOSUID | MS_NODEV; + + return fuse_mount_fusermount(mountpoint, &mo, opts, 0); +} + +static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, + const char *mnt_opts) +{ + char tmp[128]; + const char *devname = "/dev/fuse"; + char *source = NULL; + char *type = NULL; + struct stat stbuf; + int fd; + int res; + + if (!mnt) { + fprintf(stderr, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = stat(mnt, &stbuf); + if (res == -1) { + fprintf(stderr ,"fuse: failed to access mountpoint %s: %s\n", + mnt, strerror(errno)); + return -1; + } + + if (!mo->nonempty) { + res = fuse_mnt_check_empty("fuse", mnt, stbuf.st_mode, + stbuf.st_size); + if (res == -1) + return -1; + } + + if (mo->auto_unmount) { + /* Tell the caller to fallback to fusermount because + auto-unmount does not work otherwise. */ + return -2; + } + + fd = open(devname, O_RDWR); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT) + fprintf(stderr, "fuse: device not found, try 'modprobe fuse' first\n"); + else + fprintf(stderr, "fuse: failed to open %s: %s\n", + devname, strerror(errno)); + return -1; + } + + snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u", + fd, stbuf.st_mode & S_IFMT, getuid(), getgid()); + + res = fuse_opt_add_opt(&mo->kernel_opts, tmp); + if (res == -1) + goto out_close; + + source = malloc((mo->fsname ? strlen(mo->fsname) : 0) + + (mo->subtype ? strlen(mo->subtype) : 0) + + strlen(devname) + 32); + + type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32); + if (!type || !source) { + fprintf(stderr, "fuse: failed to allocate memory\n"); + goto out_close; + } + + strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); + if (mo->subtype) { + strcat(type, "."); + strcat(type, mo->subtype); + } + strcpy(source, + mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname)); + + res = mount(source, mnt, type, mo->flags, mo->kernel_opts); + if (res == -1 && errno == ENODEV && mo->subtype) { + /* Probably missing subtype support */ + strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); + if (mo->fsname) { + if (!mo->blkdev) + sprintf(source, "%s#%s", mo->subtype, + mo->fsname); + } else { + strcpy(source, type); + } + res = mount(source, mnt, type, mo->flags, mo->kernel_opts); + } + if (res == -1) { + /* + * Maybe kernel doesn't support unprivileged mounts, in this + * case try falling back to fusermount + */ + if (errno == EPERM) { + res = -2; + } else { + int errno_save = errno; + if (mo->blkdev && errno == ENODEV && + !fuse_mnt_check_fuseblk()) + fprintf(stderr, + "fuse: 'fuseblk' support missing\n"); + else + fprintf(stderr, "fuse: mount failed: %s\n", + strerror(errno_save)); + } + + goto out_close; + } + +#ifndef __NetBSD__ +#ifndef IGNORE_MTAB + if (geteuid() == 0) { + char *newmnt = fuse_mnt_resolve_path("fuse", mnt); + res = -1; + if (!newmnt) + goto out_umount; + + res = fuse_mnt_add_mount("fuse", source, newmnt, type, + mnt_opts); + free(newmnt); + if (res == -1) + goto out_umount; + } +#endif /* IGNORE_MTAB */ +#endif /* __NetBSD__ */ + free(type); + free(source); + + return fd; + +out_umount: + umount2(mnt, 2); /* lazy umount */ +out_close: + free(type); + free(source); + close(fd); + return res; +} + +static int get_mnt_flag_opts(char **mnt_optsp, int flags) +{ + int i; + + if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1) + return -1; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + if (mount_flags[i].on && (flags & mount_flags[i].flag) && + fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1) + return -1; + } + return 0; +} + +int fuse_kern_mount(const char *mountpoint, struct fuse_args *args) +{ + struct mount_opts mo; + int res = -1; + char *mnt_opts = NULL; + + memset(&mo, 0, sizeof(mo)); + mo.flags = MS_NOSUID | MS_NODEV; + + if (args && + fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) + return -1; + + if (mo.allow_other && mo.allow_root) { + fprintf(stderr, "fuse: 'allow_other' and 'allow_root' options are mutually exclusive\n"); + goto out; + } + res = 0; + if (mo.ishelp) + goto out; + + res = -1; + if (get_mnt_flag_opts(&mnt_opts, mo.flags) == -1) + goto out; + if (mo.kernel_opts && fuse_opt_add_opt(&mnt_opts, mo.kernel_opts) == -1) + goto out; + if (mo.mtab_opts && fuse_opt_add_opt(&mnt_opts, mo.mtab_opts) == -1) + goto out; + + res = fuse_mount_sys(mountpoint, &mo, mnt_opts); + if (res == -2) { + if (mo.fusermount_opts && + fuse_opt_add_opt(&mnt_opts, mo.fusermount_opts) == -1) + goto out; + + if (mo.subtype) { + char *tmp_opts = NULL; + + res = -1; + if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 || + fuse_opt_add_opt(&tmp_opts, mo.subtype_opt) == -1) { + free(tmp_opts); + goto out; + } + + res = fuse_mount_fusermount(mountpoint, &mo, tmp_opts, 1); + free(tmp_opts); + if (res == -1) + res = fuse_mount_fusermount(mountpoint, &mo, + mnt_opts, 0); + } else { + res = fuse_mount_fusermount(mountpoint, &mo, mnt_opts, 0); + } + } +out: + free(mnt_opts); + free(mo.fsname); + free(mo.subtype); + free(mo.fusermount_opts); + free(mo.subtype_opt); + free(mo.kernel_opts); + free(mo.mtab_opts); + return res; +} + +FUSE_SYMVER(".symver fuse_mount_compat22,fuse_mount@FUSE_2.2"); +FUSE_SYMVER(".symver fuse_unmount_compat22,fuse_unmount@FUSE_2.2"); diff --git a/fuse/mount_bsd.c b/fuse/mount_bsd.c new file mode 100644 index 000000000..3aec3e3eb --- /dev/null +++ b/fuse/mount_bsd.c @@ -0,0 +1,391 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" + +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/sysctl.h> +#include <sys/user.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stddef.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <paths.h> +#include <limits.h> + +#define FUSERMOUNT_PROG "mount_fusefs" +#define FUSE_DEV_TRUNK "/dev/fuse" + +enum { + KEY_ALLOW_ROOT, + KEY_RO, + KEY_HELP, + KEY_VERSION, + KEY_KERN +}; + +struct mount_opts { + int allow_other; + int allow_root; + int ishelp; + char *kernel_opts; +}; + +#define FUSE_DUAL_OPT_KEY(templ, key) \ + FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key) + +static const struct fuse_opt fuse_mount_opts[] = { + { "allow_other", offsetof(struct mount_opts, allow_other), 1 }, + { "allow_root", offsetof(struct mount_opts, allow_root), 1 }, + FUSE_OPT_KEY("allow_root", KEY_ALLOW_ROOT), + FUSE_OPT_KEY("-r", KEY_RO), + FUSE_OPT_KEY("-h", KEY_HELP), + FUSE_OPT_KEY("--help", KEY_HELP), + FUSE_OPT_KEY("-V", KEY_VERSION), + FUSE_OPT_KEY("--version", KEY_VERSION), + /* standard FreeBSD mount options */ + FUSE_DUAL_OPT_KEY("dev", KEY_KERN), + FUSE_DUAL_OPT_KEY("async", KEY_KERN), + FUSE_DUAL_OPT_KEY("atime", KEY_KERN), + FUSE_DUAL_OPT_KEY("dev", KEY_KERN), + FUSE_DUAL_OPT_KEY("exec", KEY_KERN), + FUSE_DUAL_OPT_KEY("suid", KEY_KERN), + FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN), + FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN), + FUSE_DUAL_OPT_KEY("sync", KEY_KERN), + FUSE_DUAL_OPT_KEY("union", KEY_KERN), + FUSE_DUAL_OPT_KEY("userquota", KEY_KERN), + FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN), + FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN), + FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN), + FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN), + FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN), + FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN), + FUSE_DUAL_OPT_KEY("acls", KEY_KERN), + FUSE_DUAL_OPT_KEY("force", KEY_KERN), + FUSE_DUAL_OPT_KEY("update", KEY_KERN), + FUSE_DUAL_OPT_KEY("ro", KEY_KERN), + FUSE_DUAL_OPT_KEY("rw", KEY_KERN), + FUSE_DUAL_OPT_KEY("auto", KEY_KERN), + /* options supported under both Linux and FBSD */ + FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN), + FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN), + FUSE_OPT_KEY("max_read=", KEY_KERN), + FUSE_OPT_KEY("subtype=", KEY_KERN), + /* FBSD FUSE specific mount options */ + FUSE_DUAL_OPT_KEY("private", KEY_KERN), + FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN), + FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN), + FUSE_OPT_KEY("nosync_unmount", KEY_KERN), + /* stock FBSD mountopt parsing routine lets anything be negated... */ + /* + * Linux specific mount options, but let just the mount util + * handle them + */ + FUSE_OPT_KEY("fsname=", KEY_KERN), + FUSE_OPT_KEY("nonempty", KEY_KERN), + FUSE_OPT_KEY("large_read", KEY_KERN), + FUSE_OPT_END +}; + +static void mount_help(void) +{ + fprintf(stderr, + " -o allow_root allow access to root\n" + ); + system(FUSERMOUNT_PROG " --help"); + fputc('\n', stderr); +} + +static void mount_version(void) +{ + system(FUSERMOUNT_PROG " --version"); +} + +static int fuse_mount_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + struct mount_opts *mo = data; + + switch (key) { + case KEY_ALLOW_ROOT: + if (fuse_opt_add_opt(&mo->kernel_opts, "allow_other") == -1 || + fuse_opt_add_arg(outargs, "-oallow_root") == -1) + return -1; + return 0; + + case KEY_RO: + arg = "ro"; + /* fall through */ + + case KEY_KERN: + return fuse_opt_add_opt(&mo->kernel_opts, arg); + + case KEY_HELP: + mount_help(); + mo->ishelp = 1; + break; + + case KEY_VERSION: + mount_version(); + mo->ishelp = 1; + break; + } + return 1; +} + +void fuse_unmount_compat22(const char *mountpoint) +{ + char dev[128]; + char *ssc, *umount_cmd; + FILE *sf; + int rv; + char seekscript[] = + /* error message is annoying in help output */ + "exec 2>/dev/null; " + "/usr/bin/fstat " FUSE_DEV_TRUNK "* | " + "/usr/bin/awk 'BEGIN{ getline; if (! ($3 == \"PID\" && $10 == \"NAME\")) exit 1; }; " + " { if ($3 == %d) print $10; }' | " + "/usr/bin/sort | " + "/usr/bin/uniq | " + "/usr/bin/awk '{ i += 1; if (i > 1){ exit 1; }; printf; }; END{ if (i == 0) exit 1; }'"; + + (void) mountpoint; + + /* + * If we don't know the fd, we have to resort to the scripted + * solution -- iterating over the fd-s is unpractical, as we + * don't know how many of open files we have. (This could be + * looked up in procfs -- however, that's optional on FBSD; or + * read out from the kmem -- however, that's bound to + * privileges (in fact, that's what happens when we call the + * setgid kmem fstat(1) utility). + */ + if (asprintf(&ssc, seekscript, getpid()) == -1) + return; + + errno = 0; + sf = popen(ssc, "r"); + free(ssc); + if (! sf) + return; + + fgets(dev, sizeof(dev), sf); + rv = pclose(sf); + if (rv) + return; + + if (asprintf(&umount_cmd, "/sbin/umount %s", dev) == -1) + return; + system(umount_cmd); + free(umount_cmd); +} + +static void do_unmount(char *dev, int fd) +{ + char device_path[SPECNAMELEN + 12]; + const char *argv[4]; + const char umount_cmd[] = "/sbin/umount"; + pid_t pid; + + snprintf(device_path, SPECNAMELEN + 12, _PATH_DEV "%s", dev); + + argv[0] = umount_cmd; + argv[1] = "-f"; + argv[2] = device_path; + argv[3] = NULL; + + pid = fork(); + + if (pid == -1) + return; + + if (pid == 0) { + close(fd); + execvp(umount_cmd, (char **)argv); + exit(1); + } + + waitpid(pid, NULL, 0); +} + +void fuse_kern_unmount(const char *mountpoint, int fd) +{ + char *ep, dev[128]; + struct stat sbuf; + + (void)mountpoint; + + if (fstat(fd, &sbuf) == -1) + goto out; + + devname_r(sbuf.st_rdev, S_IFCHR, dev, 128); + + if (strncmp(dev, "fuse", 4)) + goto out; + + strtol(dev + 4, &ep, 10); + if (*ep != '\0') + goto out; + + do_unmount(dev, fd); + +out: + close(fd); +} + +/* Check if kernel is doing init in background */ +static int init_backgrounded(void) +{ + unsigned ibg, len; + + len = sizeof(ibg); + + if (sysctlbyname("vfs.fuse.init_backgrounded", &ibg, &len, NULL, 0)) + return 0; + + return ibg; +} + + +static int fuse_mount_core(const char *mountpoint, const char *opts) +{ + const char *mountprog = FUSERMOUNT_PROG; + int fd; + char *fdnam, *dev; + pid_t pid, cpid; + int status; + + fdnam = getenv("FUSE_DEV_FD"); + + if (fdnam) { + char *ep; + + fd = strtol(fdnam, &ep, 10); + + if (*ep != '\0') { + fprintf(stderr, "invalid value given in FUSE_DEV_FD\n"); + return -1; + } + + if (fd < 0) + return -1; + + goto mount; + } + + dev = getenv("FUSE_DEV_NAME"); + + if (! dev) + dev = (char *)FUSE_DEV_TRUNK; + + if ((fd = open(dev, O_RDWR)) < 0) { + perror("fuse: failed to open fuse device"); + return -1; + } + +mount: + if (getenv("FUSE_NO_MOUNT") || ! mountpoint) + goto out; + + pid = fork(); + cpid = pid; + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + return -1; + } + + if (pid == 0) { + if (! init_backgrounded()) { + /* + * If init is not backgrounded, we have to + * call the mount util backgrounded, to avoid + * deadlock. + */ + + pid = fork(); + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + exit(1); + } + } + + if (pid == 0) { + const char *argv[32]; + int a = 0; + + if (! fdnam && asprintf(&fdnam, "%d", fd) == -1) { + perror("fuse: failed to assemble mount arguments"); + exit(1); + } + + argv[a++] = mountprog; + if (opts) { + argv[a++] = "-o"; + argv[a++] = opts; + } + argv[a++] = fdnam; + argv[a++] = mountpoint; + argv[a++] = NULL; + execvp(mountprog, (char **) argv); + perror("fuse: failed to exec mount program"); + exit(1); + } + + exit(0); + } + + if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) { + perror("fuse: failed to mount file system"); + close(fd); + return -1; + } + +out: + return fd; +} + +int fuse_kern_mount(const char *mountpoint, struct fuse_args *args) +{ + struct mount_opts mo; + int res = -1; + + memset(&mo, 0, sizeof(mo)); + /* mount util should not try to spawn the daemon */ + setenv("MOUNT_FUSEFS_SAFE", "1", 1); + /* to notify the mount util it's called from lib */ + setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1); + + if (args && + fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) + return -1; + + if (mo.allow_other && mo.allow_root) { + fprintf(stderr, "fuse: 'allow_other' and 'allow_root' options are mutually exclusive\n"); + goto out; + } + if (mo.ishelp) + return 0; + + res = fuse_mount_core(mountpoint, mo.kernel_opts); +out: + free(mo.kernel_opts); + return res; +} + +FUSE_SYMVER(".symver fuse_unmount_compat22,fuse_unmount@FUSE_2.2"); diff --git a/fuse/mount_util.c b/fuse/mount_util.c new file mode 100644 index 000000000..020b223dc --- /dev/null +++ b/fuse/mount_util.c @@ -0,0 +1,368 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "mount_util.h" +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <paths.h> +#ifndef __NetBSD__ +#include <mntent.h> +#endif +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/mount.h> +#include <sys/param.h> +#if defined(__ANDROID__) +#include <paths.h> +#endif + +#ifdef __NetBSD__ +#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) +#define mtab_needs_update(mnt) 0 +#elif defined(__ANDROID__) +#define mtab_needs_update(mnt) 0 +#else +static int mtab_needs_update(const char *mnt) +{ + int res; + struct stat stbuf; + + /* If mtab is within new mount, don't touch it */ + if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 && + _PATH_MOUNTED[strlen(mnt)] == '/') + return 0; + + /* + * Skip mtab update if /etc/mtab: + * + * - doesn't exist, + * - is a symlink, + * - is on a read-only filesystem. + */ + res = lstat(_PATH_MOUNTED, &stbuf); + if (res == -1) { + if (errno == ENOENT) + return 0; + } else { + uid_t ruid; + int err; + + if (S_ISLNK(stbuf.st_mode)) + return 0; + + ruid = getuid(); + if (ruid != 0) + setreuid(0, -1); + + res = access(_PATH_MOUNTED, W_OK); + err = (res == -1) ? errno : 0; + if (ruid != 0) + setreuid(ruid, -1); + + if (err == EROFS) + return 0; + } + + return 1; +} +#endif /* __NetBSD__ */ + +static int add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + setuid(geteuid()); + execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i", + "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env); + fprintf(stderr, "%s: failed to execute /bin/mount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) + res = -1; + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + return res; +} + +int fuse_mnt_add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts) +{ + if (!mtab_needs_update(mnt)) + return 0; + + return add_mount(progname, fsname, mnt, type, opts); +} + +static int exec_umount(const char *progname, const char *rel_mnt, int lazy) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + setuid(geteuid()); + if (lazy) { + execle("/bin/umount", "/bin/umount", "-i", rel_mnt, + "-l", NULL, &env); + } else { + execle("/bin/umount", "/bin/umount", "-i", rel_mnt, + NULL, &env); + } + fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) { + res = -1; + } + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return res; + +} + +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy) +{ + int res; + + if (!mtab_needs_update(abs_mnt)) { + res = umount2(rel_mnt, lazy ? 2 : 0); + if (res == -1) + fprintf(stderr, "%s: failed to unmount %s: %s\n", + progname, abs_mnt, strerror(errno)); + return res; + } + + return exec_umount(progname, rel_mnt, lazy); +} + +static int remove_mount(const char *progname, const char *mnt) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + setuid(geteuid()); + execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i", + "--fake", mnt, NULL, &env); + fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) + res = -1; + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return res; +} + +int fuse_mnt_remove_mount(const char *progname, const char *mnt) +{ + if (!mtab_needs_update(mnt)) + return 0; + + return remove_mount(progname, mnt); +} + +char *fuse_mnt_resolve_path(const char *progname, const char *orig) +{ + char buf[PATH_MAX]; + char *copy; + char *dst; + char *end; + char *lastcomp; + const char *toresolv; + + if (!orig[0]) { + fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname, + orig); + return NULL; + } + + copy = strdup(orig); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return NULL; + } + + toresolv = copy; + lastcomp = NULL; + for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --); + if (end[0] != '/') { + char *tmp; + end[1] = '\0'; + tmp = strrchr(copy, '/'); + if (tmp == NULL) { + lastcomp = copy; + toresolv = "."; + } else { + lastcomp = tmp + 1; + if (tmp == copy) + toresolv = "/"; + } + if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) { + lastcomp = NULL; + toresolv = copy; + } + else if (tmp) + tmp[0] = '\0'; + } + if (realpath(toresolv, buf) == NULL) { + fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig, + strerror(errno)); + free(copy); + return NULL; + } + if (lastcomp == NULL) + dst = strdup(buf); + else { + dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1); + if (dst) { + unsigned buflen = strlen(buf); + if (buflen && buf[buflen-1] == '/') + sprintf(dst, "%s%s", buf, lastcomp); + else + sprintf(dst, "%s/%s", buf, lastcomp); + } + } + free(copy); + if (dst == NULL) + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return dst; +} + +int fuse_mnt_check_empty(const char *progname, const char *mnt, + mode_t rootmode, loff_t rootsize) +{ + int isempty = 1; + + if (S_ISDIR(rootmode)) { + struct dirent *ent; + DIR *dp = opendir(mnt); + if (dp == NULL) { + fprintf(stderr, + "%s: failed to open mountpoint for reading: %s\n", + progname, strerror(errno)); + return -1; + } + while ((ent = readdir(dp)) != NULL) { + if (strcmp(ent->d_name, ".") != 0 && + strcmp(ent->d_name, "..") != 0) { + isempty = 0; + break; + } + } + closedir(dp); + } else if (rootsize) + isempty = 0; + + if (!isempty) { + fprintf(stderr, "%s: mountpoint is not empty\n", progname); + fprintf(stderr, "%s: if you are sure this is safe, use the 'nonempty' mount option\n", progname); + return -1; + } + return 0; +} + +int fuse_mnt_check_fuseblk(void) +{ + char buf[256]; + FILE *f = fopen("/proc/filesystems", "r"); + if (!f) + return 1; + + while (fgets(buf, sizeof(buf), f)) + if (strstr(buf, "fuseblk\n")) { + fclose(f); + return 1; + } + + fclose(f); + return 0; +} diff --git a/fuse/mount_util.h b/fuse/mount_util.h new file mode 100644 index 000000000..0e0f564d7 --- /dev/null +++ b/fuse/mount_util.h @@ -0,0 +1,19 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include <sys/types.h> + +int fuse_mnt_add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts); +int fuse_mnt_remove_mount(const char *progname, const char *mnt); +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy); +char *fuse_mnt_resolve_path(const char *progname, const char *orig); +int fuse_mnt_check_empty(const char *progname, const char *mnt, + mode_t rootmode, loff_t rootsize); +int fuse_mnt_check_fuseblk(void); diff --git a/fuse/ulockmgr.c b/fuse/ulockmgr.c new file mode 100644 index 000000000..ebd68c6a7 --- /dev/null +++ b/fuse/ulockmgr.c @@ -0,0 +1,444 @@ +/* + libulockmgr: Userspace Lock Manager Library + Copyright (C) 2006 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +/* #define DEBUG 1 */ + +#include "ulockmgr.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> +#include <errno.h> +#include <assert.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/wait.h> + +struct message { + unsigned intr : 1; + unsigned nofd : 1; + pthread_t thr; + int cmd; + int fd; + struct flock lock; + int error; +}; + +struct fd_store { + struct fd_store *next; + int fd; + int inuse; +}; + +struct owner { + struct owner *next; + struct owner *prev; + struct fd_store *fds; + void *id; + size_t id_len; + int cfd; +}; + +static pthread_mutex_t ulockmgr_lock; +static int ulockmgr_cfd = -1; +static struct owner owner_list = { .next = &owner_list, .prev = &owner_list }; + +#define MAX_SEND_FDS 2 + +static void list_del_owner(struct owner *owner) +{ + struct owner *prev = owner->prev; + struct owner *next = owner->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_owner(struct owner *owner, struct owner *next) +{ + struct owner *prev = next->prev; + owner->next = next; + owner->prev = prev; + prev->next = owner; + next->prev = owner; +} + +/* + * There's a bug in the linux kernel (< 2.6.22) recv() implementation + * on AF_UNIX, SOCK_STREAM sockets, that could cause it to return + * zero, even if data was available. Retrying the recv will return + * the data in this case. + */ +static int do_recv(int sock, void *buf, size_t len, int flags) +{ + int res = recv(sock, buf, len, flags); + if (res == 0) + res = recv(sock, buf, len, flags); + + return res; +} + +static int ulockmgr_send_message(int sock, void *buf, size_t buflen, + int *fdp, int numfds) +{ + struct msghdr msg; + struct cmsghdr *p_cmsg; + struct iovec vec; + size_t cmsgbuf[CMSG_SPACE(sizeof(int) * MAX_SEND_FDS) / sizeof(size_t)]; + int res; + + assert(numfds <= MAX_SEND_FDS); + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + p_cmsg = CMSG_FIRSTHDR(&msg); + p_cmsg->cmsg_level = SOL_SOCKET; + p_cmsg->cmsg_type = SCM_RIGHTS; + p_cmsg->cmsg_len = CMSG_LEN(sizeof(int) * numfds); + memcpy(CMSG_DATA(p_cmsg), fdp, sizeof(int) * numfds); + msg.msg_controllen = p_cmsg->cmsg_len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + vec.iov_base = buf; + vec.iov_len = buflen; + res = sendmsg(sock, &msg, MSG_NOSIGNAL); + if (res == -1) { + perror("libulockmgr: sendmsg"); + return -1; + } + if ((size_t) res != buflen) { + fprintf(stderr, "libulockmgr: sendmsg short\n"); + return -1; + } + return 0; +} + +static int ulockmgr_start_daemon(void) +{ + int sv[2]; + int res; + char tmp[64]; + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (res == -1) { + perror("libulockmgr: socketpair"); + return -1; + } + snprintf(tmp, sizeof(tmp), "exec ulockmgr_server %i", sv[0]); + res = system(tmp); + close(sv[0]); + if (res == -1 || !WIFEXITED(res) || WEXITSTATUS(res) != 0) { + close(sv[1]); + return -1; + } + ulockmgr_cfd = sv[1]; + return 0; +} + +static struct owner *ulockmgr_new_owner(const void *id, size_t id_len) +{ + int sv[2]; + int res; + char c = 'm'; + struct owner *o; + + if (ulockmgr_cfd == -1 && ulockmgr_start_daemon() == -1) + return NULL; + + o = calloc(1, sizeof(struct owner) + id_len); + if (!o) { + fprintf(stderr, "libulockmgr: failed to allocate memory\n"); + return NULL; + } + o->id = o + 1; + o->id_len = id_len; + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (res == -1) { + perror("libulockmgr: socketpair"); + goto out_free; + } + res = ulockmgr_send_message(ulockmgr_cfd, &c, sizeof(c), &sv[0], 1); + close(sv[0]); + if (res == -1) { + close(ulockmgr_cfd); + ulockmgr_cfd = -1; + goto out_close; + } + + o->cfd = sv[1]; + memcpy(o->id, id, id_len); + list_add_owner(o, &owner_list); + + return o; + +out_close: + close(sv[1]); +out_free: + free(o); + return NULL; +} + +static int ulockmgr_send_request(struct message *msg, const void *id, + size_t id_len) +{ + int sv[2]; + int cfd; + struct owner *o; + struct fd_store *f = NULL; + struct fd_store *newf = NULL; + struct fd_store **fp; + int fd = msg->fd; + int cmd = msg->cmd; + int res; + int unlockall = (cmd == F_SETLK && msg->lock.l_type == F_UNLCK && + msg->lock.l_start == 0 && msg->lock.l_len == 0); + + for (o = owner_list.next; o != &owner_list; o = o->next) + if (o->id_len == id_len && memcmp(o->id, id, id_len) == 0) + break; + + if (o == &owner_list) + o = NULL; + + if (!o && cmd != F_GETLK && msg->lock.l_type != F_UNLCK) + o = ulockmgr_new_owner(id, id_len); + + if (!o) { + if (cmd == F_GETLK) { + res = fcntl(msg->fd, F_GETLK, &msg->lock); + return (res == -1) ? -errno : 0; + } else if (msg->lock.l_type == F_UNLCK) + return 0; + else + return -ENOLCK; + } + + if (unlockall) + msg->nofd = 1; + else { + for (fp = &o->fds; *fp; fp = &(*fp)->next) { + f = *fp; + if (f->fd == fd) { + msg->nofd = 1; + break; + } + } + } + + if (!msg->nofd) { + newf = f = calloc(1, sizeof(struct fd_store)); + if (!f) { + fprintf(stderr, "libulockmgr: failed to allocate memory\n"); + return -ENOLCK; + } + } + + res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + if (res == -1) { + perror("libulockmgr: socketpair"); + free(newf); + return -ENOLCK; + } + + cfd = sv[1]; + sv[1] = msg->fd; + res = ulockmgr_send_message(o->cfd, msg, sizeof(struct message), sv, + msg->nofd ? 1 : 2); + close(sv[0]); + if (res == -1) { + free(newf); + close(cfd); + return -EIO; + } + + if (newf) { + newf->fd = msg->fd; + newf->next = o->fds; + o->fds = newf; + } + if (f) + f->inuse++; + + res = do_recv(cfd, msg, sizeof(struct message), MSG_WAITALL); + if (res == -1) { + perror("libulockmgr: recv"); + msg->error = EIO; + } else if (res != sizeof(struct message)) { + fprintf(stderr, "libulockmgr: recv short\n"); + msg->error = EIO; + } else if (cmd == F_SETLKW && msg->error == EAGAIN) { + pthread_mutex_unlock(&ulockmgr_lock); + while (1) { + sigset_t old; + sigset_t unblock; + int errno_save; + + sigemptyset(&unblock); + sigaddset(&unblock, SIGUSR1); + pthread_sigmask(SIG_UNBLOCK, &unblock, &old); + res = do_recv(cfd, msg, sizeof(struct message), + MSG_WAITALL); + errno_save = errno; + pthread_sigmask(SIG_SETMASK, &old, NULL); + if (res == sizeof(struct message)) + break; + else if (res >= 0) { + fprintf(stderr, "libulockmgr: recv short\n"); + msg->error = EIO; + break; + } else if (errno_save != EINTR) { + errno = errno_save; + perror("libulockmgr: recv"); + msg->error = EIO; + break; + } + msg->intr = 1; + res = send(o->cfd, msg, sizeof(struct message), + MSG_NOSIGNAL); + if (res == -1) { + perror("libulockmgr: send"); + msg->error = EIO; + break; + } + if (res != sizeof(struct message)) { + fprintf(stderr, "libulockmgr: send short\n"); + msg->error = EIO; + break; + } + } + pthread_mutex_lock(&ulockmgr_lock); + + } + if (f) + f->inuse--; + close(cfd); + if (unlockall) { + for (fp = &o->fds; *fp;) { + f = *fp; + if (f->fd == fd && !f->inuse) { + *fp = f->next; + free(f); + } else + fp = &f->next; + } + if (!o->fds) { + list_del_owner(o); + close(o->cfd); + free(o); + } + /* Force OK on unlock-all, since it _will_ succeed once the + owner is deleted */ + msg->error = 0; + } + + return -msg->error; +} + +#ifdef DEBUG +static uint32_t owner_hash(const unsigned char *id, size_t id_len) +{ + uint32_t h = 0; + size_t i; + for (i = 0; i < id_len; i++) + h = ((h << 8) | (h >> 24)) ^ id[i]; + + return h; +} +#endif + +static int ulockmgr_canonicalize(int fd, struct flock *lock) +{ + loff_t offset; + if (lock->l_whence == SEEK_CUR) { + offset = lseek(fd, 0, SEEK_CUR); + if (offset == (loff_t) -1) + return -errno; + } else if (lock->l_whence == SEEK_END) { + struct stat stbuf; + int res = fstat(fd, &stbuf); + if (res == -1) + return -errno; + + offset = stbuf.st_size; + } else + offset = 0; + + lock->l_whence = SEEK_SET; + lock->l_start += offset; + + if (lock->l_start < 0) + return -EINVAL; + + if (lock->l_len < 0) { + lock->l_start += lock->l_len; + if (lock->l_start < 0) + return -EINVAL; + lock->l_len = -lock->l_len; + } + if (lock->l_len && lock->l_start + lock->l_len - 1 < 0) + return -EINVAL; + + return 0; +} + +int ulockmgr_op(int fd, int cmd, struct flock *lock, const void *owner, + size_t owner_len) +{ + int err; + struct message msg; + sigset_t old; + sigset_t block; + + if (cmd != F_GETLK && cmd != F_SETLK && cmd != F_SETLKW) + return -EINVAL; + + if (lock->l_type != F_RDLCK && lock->l_type != F_WRLCK && + lock->l_type != F_UNLCK) + return -EINVAL; + + if (lock->l_whence != SEEK_SET && lock->l_whence != SEEK_CUR && + lock->l_whence != SEEK_END) + return -EINVAL; + +#ifdef DEBUG + fprintf(stderr, "libulockmgr: %i %i %i %lli %lli own: 0x%08x\n", + cmd, lock->l_type, lock->l_whence, lock->l_start, lock->l_len, + owner_hash(owner, owner_len)); +#endif + + /* Unlock should never block anyway */ + if (cmd == F_SETLKW && lock->l_type == F_UNLCK) + cmd = F_SETLK; + + memset(&msg, 0, sizeof(struct message)); + msg.cmd = cmd; + msg.fd = fd; + msg.lock = *lock; + err = ulockmgr_canonicalize(fd, &msg.lock); + if (err) + return err; + + sigemptyset(&block); + sigaddset(&block, SIGUSR1); + pthread_sigmask(SIG_BLOCK, &block, &old); + pthread_mutex_lock(&ulockmgr_lock); + err = ulockmgr_send_request(&msg, owner, owner_len); + pthread_mutex_unlock(&ulockmgr_lock); + pthread_sigmask(SIG_SETMASK, &old, NULL); + if (!err && cmd == F_GETLK) { + if (msg.lock.l_type == F_UNLCK) + lock->l_type = F_UNLCK; + else + *lock = msg.lock; + } + + return err; +} diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp index 1c7e98f01..45c79f90b 100644 --- a/fuse_sideload.cpp +++ b/fuse_sideload.cpp @@ -45,8 +45,9 @@ #include <errno.h> #include <fcntl.h> -#include <limits.h> // PATH_MAX -#include <linux/fuse.h> +#include <limits.h> +#include "fuse.h" +#include <pthread.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -57,13 +58,19 @@ #include <sys/uio.h> #include <unistd.h> +#ifdef USE_MINCRYPT +#include "mincrypt/sha256.h" +#define SHA256_DIGEST_LENGTH SHA256_DIGEST_SIZE +#else +#include <openssl/sha.h> +#endif + #include <array> #include <string> #include <vector> -#include <android-base/stringprintf.h> -#include <android-base/unique_fd.h> -#include <openssl/sha.h> +//#include <android-base/stringprintf.h> +//#include <android-base/unique_fd.h> static constexpr uint64_t PACKAGE_FILE_ID = FUSE_ROOT_ID + 1; static constexpr uint64_t EXIT_FLAG_ID = FUSE_ROOT_ID + 2; @@ -73,8 +80,12 @@ static constexpr int NO_STATUS_EXIT = 2; using SHA256Digest = std::array<uint8_t, SHA256_DIGEST_LENGTH>; +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + struct fuse_data { - android::base::unique_fd ffd; // file descriptor for the fuse socket + int ffd; // file descriptor for the fuse socket provider_vtab vtab; @@ -91,8 +102,8 @@ struct fuse_data { uint8_t* extra_block; // another block of storage for reads that span two blocks - std::vector<SHA256Digest> - hashes; // SHA-256 hash of each block (all zeros if block hasn't been read yet) + uint8_t* hashes; // SHA-256 hash of each block (all zeros + // if block hasn't been read yet) }; static void fuse_reply(const fuse_data* fd, uint64_t unique, const void* data, size_t len) { @@ -151,7 +162,7 @@ static int handle_init(void* data, fuse_data* fd, const fuse_in_header* hdr) { static void fill_attr(fuse_attr* attr, const fuse_data* fd, uint64_t nodeid, uint64_t size, uint32_t mode) { - *attr = {}; + memset(attr, 0, sizeof(*attr)); attr->nlink = 1; attr->uid = fd->uid; attr->gid = fd->gid; @@ -164,7 +175,8 @@ static void fill_attr(fuse_attr* attr, const fuse_data* fd, uint64_t nodeid, uin } static int handle_getattr(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) { - fuse_attr_out out = {}; + struct fuse_attr_out out; + memset(&out, 0, sizeof(out)); out.attr_valid = 10; if (hdr->nodeid == FUSE_ROOT_ID) { @@ -184,7 +196,8 @@ static int handle_getattr(void* /* data */, const fuse_data* fd, const fuse_in_h static int handle_lookup(void* data, const fuse_data* fd, const fuse_in_header* hdr) { if (data == nullptr) return -ENOENT; - fuse_entry_out out = {}; + struct fuse_entry_out out; + memset(&out, 0, sizeof(out)); out.entry_valid = 10; out.attr_valid = 10; @@ -209,7 +222,8 @@ static int handle_open(void* /* data */, const fuse_data* fd, const fuse_in_head if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; - fuse_open_out out = {}; + struct fuse_open_out out; + memset(&out, 0, sizeof(out)); out.fh = 10; // an arbitrary number; we always use the same handle fuse_reply(fd, hdr->unique, &out, sizeof(out)); return NO_STATUS; @@ -256,22 +270,26 @@ static int fetch_block(fuse_data* fd, uint32_t block) { // time we've read this block). // - Otherwise, return -EINVAL for the read. - SHA256Digest hash; - SHA256(fd->block_data, fd->block_size, hash.data()); - - const SHA256Digest& blockhash = fd->hashes[block]; - if (hash == blockhash) { + uint8_t hash[SHA256_DIGEST_LENGTH]; +#ifdef USE_MINCRYPT + SHA256_hash(fd->block_data, fd->block_size, hash); +#else + SHA256(fd->block_data, fd->block_size, hash); +#endif + uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH; + if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) { return 0; } - for (uint8_t i : blockhash) { - if (i != 0) { + int i; + for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + if (blockhash[i] != 0) { fd->curr_block = -1; return -EIO; } } - fd->hashes[block] = hash; + memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); return 0; } @@ -356,7 +374,8 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl return -1; } - fuse_data fd = {}; + fuse_data fd; + memset(&fd, 0, sizeof(fd)); fd.vtab = vtab; fd.file_size = file_size; fd.block_size = block_size; @@ -369,8 +388,14 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl goto done; } - // All hashes will be zero-initialized. - fd.hashes.resize(fd.file_blocks); + fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH); + if (fd.hashes == NULL) { + fprintf(stderr, "failed to allocate %d bites for hashes\n", + fd.file_blocks * SHA256_DIGEST_LENGTH); + result = -1; + goto done; + } + fd.uid = getuid(); fd.gid = getgid(); @@ -388,7 +413,7 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl goto done; } - fd.ffd.reset(open("/dev/fuse", O_RDWR)); + fd.ffd = open("/dev/fuse", O_RDWR); if (!fd.ffd) { perror("open /dev/fuse"); result = -1; @@ -396,13 +421,15 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl } { - std::string opts = android::base::StringPrintf( - "fd=%d,user_id=%d,group_id=%d,max_read=%u,allow_other,rootmode=040000", fd.ffd.get(), - fd.uid, fd.gid, block_size); - - result = mount("/dev/fuse", mount_point, "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, - opts.c_str()); - if (result == -1) { + char opts[256]; + snprintf(opts, sizeof(opts), + ("fd=%d,user_id=%d,group_id=%d,max_read=%u," + "allow_other,rootmode=040000"), + fd.ffd, fd.uid, fd.gid, block_size); + + result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse", + MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); + if (result < 0) { perror("mount"); goto done; } @@ -490,3 +517,9 @@ done: return result; } + +extern "C" int run_old_fuse_sideload(const struct provider_vtab& vtab, void* cookie __unused, + uint64_t file_size, uint32_t block_size) +{ + return run_fuse_sideload(vtab, file_size, block_size, FUSE_SIDELOAD_HOST_MOUNTPOINT); +} diff --git a/fuse_sideload.h b/fuse_sideload.h index 1b34cbdb0..1ea1eb8d8 100644 --- a/fuse_sideload.h +++ b/fuse_sideload.h @@ -17,6 +17,10 @@ #ifndef __FUSE_SIDELOAD_H #define __FUSE_SIDELOAD_H +#ifdef USE_FUSE_SIDELOAD22 +#include "fuse_sideload22.h" +#else + #include <functional> // Define the filenames created by the sideload FUSE filesystem. @@ -37,4 +41,15 @@ struct provider_vtab { int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size, const char* mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT); +#ifdef __cplusplus +extern "C" { +#endif +int run_old_fuse_sideload(const struct provider_vtab& vtab, void* cookie, + uint64_t file_size, uint32_t block_size); +#ifdef __cplusplus +} +#endif + +#endif + #endif diff --git a/fuse_sideload22.cpp b/fuse_sideload22.cpp new file mode 100644 index 000000000..f57d479b0 --- /dev/null +++ b/fuse_sideload22.cpp @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This module creates a special filesystem containing two files. +// +// "/sideload/package.zip" appears to be a normal file, but reading +// from it causes data to be fetched from the adb host. We can use +// this to sideload packages over an adb connection without having to +// store the entire package in RAM on the device. +// +// Because we may not trust the adb host, this filesystem maintains +// the following invariant: each read of a given position returns the +// same data as the first read at that position. That is, once a +// section of the file is read, future reads of that section return +// the same data. (Otherwise, a malicious adb host process could +// return one set of bits when the package is read for signature +// verification, and then different bits for when the package is +// accessed by the installer.) If the adb host returns something +// different than it did on the first read, the reader of the file +// will see their read fail with EINVAL. +// +// The other file, "/sideload/exit", is used to control the subprocess +// that creates this filesystem. Calling stat() on the exit file +// causes the filesystem to be unmounted and the adb process on the +// device shut down. +// +// Note that only the minimal set of file operations needed for these +// two files is implemented. In particular, you can't opendir() or +// readdir() on the "/sideload" directory; ls on it won't work. + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include "fuse.h" +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/inotify.h> +#include <sys/mount.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <unistd.h> + +#ifdef USE_MINCRYPT +#include "mincrypt/sha256.h" +#define SHA256_DIGEST_LENGTH SHA256_DIGEST_SIZE +#else +#include <openssl/sha.h> +#endif + +#include "fuse_sideload.h" + +#define PACKAGE_FILE_ID (FUSE_ROOT_ID+1) +#define EXIT_FLAG_ID (FUSE_ROOT_ID+2) + +#define NO_STATUS 1 +#define NO_STATUS_EXIT 2 + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +struct fuse_data { + int ffd; // file descriptor for the fuse socket + + struct provider_vtab* vtab; + void* cookie; + + uint64_t file_size; // bytes + + uint32_t block_size; // block size that the adb host is using to send the file to us + uint32_t file_blocks; // file size in block_size blocks + + uid_t uid; + gid_t gid; + + uint32_t curr_block; // cache the block most recently read from the host + uint8_t* block_data; + + uint8_t* extra_block; // another block of storage for reads that + // span two blocks + + uint8_t* hashes; // SHA-256 hash of each block (all zeros + // if block hasn't been read yet) +}; + +static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t len) +{ + struct fuse_out_header hdr; + struct iovec vec[2]; + int res; + + hdr.len = len + sizeof(hdr); + hdr.error = 0; + hdr.unique = unique; + + vec[0].iov_base = &hdr; + vec[0].iov_len = sizeof(hdr); + vec[1].iov_base = /* const_cast */(void*)(data); + vec[1].iov_len = len; + + res = writev(fd->ffd, vec, 2); + if (res < 0) { + printf("*** REPLY FAILED *** %s\n", strerror(errno)); + } +} + +static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + const struct fuse_init_in* req = reinterpret_cast<const struct fuse_init_in*>(data); + struct fuse_init_out out; + size_t fuse_struct_size; + + + /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out + * defined (fuse version 7.6). The structure is the same from 7.6 through + * 7.22. Beginning with 7.23, the structure increased in size and added + * new parameters. + */ + if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) { + printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6", + req->major, req->minor, FUSE_KERNEL_VERSION); + return -1; + } + + out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION); + fuse_struct_size = sizeof(out); +#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) + /* FUSE_KERNEL_VERSION >= 23. */ + + /* If the kernel only works on minor revs older than or equal to 22, + * then use the older structure size since this code only uses the 7.22 + * version of the structure. */ + if (req->minor <= 22) { + fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE; + } +#endif + + out.major = FUSE_KERNEL_VERSION; + out.max_readahead = req->max_readahead; + out.flags = 0; + out.max_background = 32; + out.congestion_threshold = 32; + out.max_write = 4096; + fuse_reply(fd, hdr->unique, &out, fuse_struct_size); + + return NO_STATUS; +} + +static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd, + uint64_t nodeid, uint64_t size, uint32_t mode) { + memset(attr, 0, sizeof(*attr)); + attr->nlink = 1; + attr->uid = fd->uid; + attr->gid = fd->gid; + attr->blksize = 4096; + + attr->ino = nodeid; + attr->size = size; + attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1); + attr->mode = mode; +} + +static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { + struct fuse_attr_out out; + memset(&out, 0, sizeof(out)); + out.attr_valid = 10; + + if (hdr->nodeid == FUSE_ROOT_ID) { + fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555); + } else if (hdr->nodeid == PACKAGE_FILE_ID) { + fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); + } else if (hdr->nodeid == EXIT_FLAG_ID) { + fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); + } else { + return -ENOENT; + } + + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; +} + +static int handle_lookup(void* data, struct fuse_data* fd, + const struct fuse_in_header* hdr) { + struct fuse_entry_out out; + memset(&out, 0, sizeof(out)); + out.entry_valid = 10; + out.attr_valid = 10; + + if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, reinterpret_cast<const char*>(data), + sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) { + out.nodeid = PACKAGE_FILE_ID; + out.generation = PACKAGE_FILE_ID; + fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); + } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, reinterpret_cast<const char*>(data), + sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) { + out.nodeid = EXIT_FLAG_ID; + out.generation = EXIT_FLAG_ID; + fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); + } else { + return -ENOENT; + } + + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; +} + +static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { + if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; + if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; + + struct fuse_open_out out; + memset(&out, 0, sizeof(out)); + out.fh = 10; // an arbitrary number; we always use the same handle + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return NO_STATUS; +} + +static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + return 0; +} + +static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + return 0; +} + +// Fetch a block from the host into fd->curr_block and fd->block_data. +// Returns 0 on successful fetch, negative otherwise. +static int fetch_block(struct fuse_data* fd, uint32_t block) { + if (block == fd->curr_block) { + return 0; + } + + if (block >= fd->file_blocks) { + memset(fd->block_data, 0, fd->block_size); + fd->curr_block = block; + return 0; + } + + size_t fetch_size = fd->block_size; + if (block * fd->block_size + fetch_size > fd->file_size) { + // If we're reading the last (partial) block of the file, + // expect a shorter response from the host, and pad the rest + // of the block with zeroes. + fetch_size = fd->file_size - (block * fd->block_size); + memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size); + } + + int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size); + if (result < 0) return result; + + fd->curr_block = block; + + // Verify the hash of the block we just got from the host. + // + // - If the hash of the just-received data matches the stored hash + // for the block, accept it. + // - If the stored hash is all zeroes, store the new hash and + // accept the block (this is the first time we've read this + // block). + // - Otherwise, return -EINVAL for the read. + + uint8_t hash[SHA256_DIGEST_LENGTH]; +#ifdef USE_MINCRYPT + SHA256_hash(fd->block_data, fd->block_size, hash); +#else + SHA256(fd->block_data, fd->block_size, hash); +#endif + uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH; + if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) { + return 0; + } + + int i; + for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + if (blockhash[i] != 0) { + fd->curr_block = -1; + return -EIO; + } + } + + memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); + return 0; +} + +static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + const struct fuse_read_in* req = reinterpret_cast<const struct fuse_read_in*>(data); + struct fuse_out_header outhdr; + struct iovec vec[3]; + int vec_used; + int result; + + if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; + + uint64_t offset = req->offset; + uint32_t size = req->size; + + // The docs on the fuse kernel interface are vague about what to + // do when a read request extends past the end of the file. We + // can return a short read -- the return structure does include a + // length field -- but in testing that caused the program using + // the file to segfault. (I speculate that this is due to the + // reading program accessing it via mmap; maybe mmap dislikes when + // you return something short of a whole page?) To fix this we + // zero-pad reads that extend past the end of the file so we're + // always returning exactly as many bytes as were requested. + // (Users of the mapped file have to know its real length anyway.) + + outhdr.len = sizeof(outhdr) + size; + outhdr.error = 0; + outhdr.unique = hdr->unique; + vec[0].iov_base = &outhdr; + vec[0].iov_len = sizeof(outhdr); + + uint32_t block = offset / fd->block_size; + result = fetch_block(fd, block); + if (result != 0) return result; + + // Two cases: + // + // - the read request is entirely within this block. In this + // case we can reply immediately. + // + // - the read request goes over into the next block. Note that + // since we mount the filesystem with max_read=block_size, a + // read can never span more than two blocks. In this case we + // copy the block to extra_block and issue a fetch for the + // following block. + + uint32_t block_offset = offset - (block * fd->block_size); + + if (size + block_offset <= fd->block_size) { + // First case: the read fits entirely in the first block. + + vec[1].iov_base = fd->block_data + block_offset; + vec[1].iov_len = size; + vec_used = 2; + } else { + // Second case: the read spills over into the next block. + + memcpy(fd->extra_block, fd->block_data + block_offset, + fd->block_size - block_offset); + vec[1].iov_base = fd->extra_block; + vec[1].iov_len = fd->block_size - block_offset; + + result = fetch_block(fd, block+1); + if (result != 0) return result; + vec[2].iov_base = fd->block_data; + vec[2].iov_len = size - vec[1].iov_len; + vec_used = 3; + } + + if (writev(fd->ffd, vec, vec_used) < 0) { + printf("*** READ REPLY FAILED: %s ***\n", strerror(errno)); + } + return NO_STATUS; +} + +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size) +{ + int result; + + // If something's already mounted on our mountpoint, try to remove + // it. (Mostly in case of a previous abnormal exit.) + umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE); + + if (block_size < 1024) { + fprintf(stderr, "block size (%u) is too small\n", block_size); + return -1; + } + if (block_size > (1<<22)) { // 4 MiB + fprintf(stderr, "block size (%u) is too large\n", block_size); + return -1; + } + + struct fuse_data fd; + memset(&fd, 0, sizeof(fd)); + fd.vtab = vtab; + fd.cookie = cookie; + fd.file_size = file_size; + fd.block_size = block_size; + fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1); + + if (fd.file_blocks > (1<<18)) { + fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); + result = -1; + goto done; + } + + fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH); + if (fd.hashes == NULL) { + fprintf(stderr, "failed to allocate %d bites for hashes\n", + fd.file_blocks * SHA256_DIGEST_LENGTH); + result = -1; + goto done; + } + + fd.uid = getuid(); + fd.gid = getgid(); + + fd.curr_block = -1; + fd.block_data = (uint8_t*)malloc(block_size); + if (fd.block_data == NULL) { + fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); + result = -1; + goto done; + } + fd.extra_block = (uint8_t*)malloc(block_size); + if (fd.extra_block == NULL) { + fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); + result = -1; + goto done; + } + + fd.ffd = open("/dev/fuse", O_RDWR); + if (fd.ffd < 0) { + perror("open /dev/fuse"); + result = -1; + goto done; + } + + char opts[256]; + snprintf(opts, sizeof(opts), + ("fd=%d,user_id=%d,group_id=%d,max_read=%u," + "allow_other,rootmode=040000"), + fd.ffd, fd.uid, fd.gid, block_size); + + result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, + "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); + if (result < 0) { + perror("mount"); + goto done; + } + uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8]; + for (;;) { + ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); + if (len == -1) { + perror("read request"); + if (errno == ENODEV) { + result = -1; + break; + } + continue; + } + + if ((size_t)len < sizeof(struct fuse_in_header)) { + fprintf(stderr, "request too short: len=%zu\n", (size_t)len); + continue; + } + + struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer; + void* data = request_buffer + sizeof(struct fuse_in_header); + + result = -ENOSYS; + + switch (hdr->opcode) { + case FUSE_INIT: + result = handle_init(data, &fd, hdr); + break; + + case FUSE_LOOKUP: + result = handle_lookup(data, &fd, hdr); + break; + + case FUSE_GETATTR: + result = handle_getattr(data, &fd, hdr); + break; + + case FUSE_OPEN: + result = handle_open(data, &fd, hdr); + break; + + case FUSE_READ: + result = handle_read(data, &fd, hdr); + break; + + case FUSE_FLUSH: + result = handle_flush(data, &fd, hdr); + break; + + case FUSE_RELEASE: + result = handle_release(data, &fd, hdr); + break; + + default: + fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); + break; + } + + if (result == NO_STATUS_EXIT) { + result = 0; + break; + } + + if (result != NO_STATUS) { + struct fuse_out_header outhdr; + outhdr.len = sizeof(outhdr); + outhdr.error = result; + outhdr.unique = hdr->unique; + TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr))); + } + } + + done: + fd.vtab->close(fd.cookie); + + result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); + if (result < 0) { + printf("fuse_sideload umount failed: %s\n", strerror(errno)); + } + + if (fd.ffd) close(fd.ffd); + free(fd.hashes); + free(fd.block_data); + free(fd.extra_block); + + return result; +} + +extern "C" int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size) +{ + return run_fuse_sideload(vtab, cookie, file_size, block_size); +} diff --git a/fuse_sideload22.h b/fuse_sideload22.h new file mode 100644 index 000000000..98254d46b --- /dev/null +++ b/fuse_sideload22.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FUSE_SIDELOAD22_H +#define __FUSE_SIDELOAD22_H + +// define the filenames created by the sideload FUSE filesystem +#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload" +#define FUSE_SIDELOAD_HOST_FILENAME "package.zip" +#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME) +#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit" +#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG) + +struct provider_vtab { + // read a block + int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size); + + // close down + void (*close)(void* cookie); +}; + +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size); + +#ifdef __cplusplus +extern "C" { +#endif +int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size); +#ifdef __cplusplus +} +#endif + +#endif |