--- /dev/null
+/*
+ * fuse2fs.c - FUSE server for e2fsprogs.
+ *
+ * Copyright (C) 2014 Oracle.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+#define _FILE_OFFSET_BITS 64
+#define FUSE_USE_VERSION 29
+#define _GNU_SOURCE
+#include "config.h"
+#include <pthread.h>
+#ifdef __linux__
+# include <linux/fs.h>
+# include <linux/falloc.h>
+# include <linux/xattr.h>
+# define FUSE_PLATFORM_OPTS ",nonempty,big_writes"
+# ifdef HAVE_SYS_ACL_H
+# define TRANSLATE_LINUX_ACLS
+# endif
+#else
+# define FUSE_PLATFORM_OPTS ""
+#endif
+#ifdef TRANSLATE_LINUX_ACLS
+# include <sys/acl.h>
+#endif
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fuse.h>
+#include <inttypes.h>
+#include "ext2fs/ext2fs.h"
+#include "ext2fs/ext2_fs.h"
+
+#ifdef ENABLE_NLS
+#include <libintl.h>
+#include <locale.h>
+#define _(a) (gettext(a))
+#ifdef gettext_noop
+#define N_(a) gettext_noop(a)
+#else
+#define N_(a) (a)
+#endif
+#define P_(singular, plural, n) (ngettext(singular, plural, n))
+#ifndef NLS_CAT_NAME
+#define NLS_CAT_NAME "e2fsprogs"
+#endif
+#ifndef LOCALEDIR
+#define LOCALEDIR "/usr/share/locale"
+#endif
+#else
+#define _(a) (a)
+#define N_(a) a
+#define P_(singular, plural, n) ((n) == 1 ? (singular) : (plural))
+#endif
+
+static ext2_filsys global_fs; /* Try not to use this directly */
+
+#undef DEBUG
+
+#ifdef DEBUG
+# define dbg_printf(f, a...) do {printf("FUSE2FS-" f, ## a); \
+ fflush(stdout); \
+} while (0)
+#else
+# define dbg_printf(f, a...)
+#endif
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+# ifdef _IOR
+# ifdef _IOW
+# define SUPPORT_I_FLAGS
+# endif
+# endif
+#endif
+
+#ifdef FALLOC_FL_KEEP_SIZE
+# define FL_KEEP_SIZE_FLAG FALLOC_FL_KEEP_SIZE
+# define SUPPORT_FALLOCATE
+#else
+# define FL_KEEP_SIZE_FLAG (0)
+#endif
+
+#ifdef FALLOC_FL_PUNCH_HOLE
+# define FL_PUNCH_HOLE_FLAG FALLOC_FL_PUNCH_HOLE
+#else
+# define FL_PUNCH_HOLE_FLAG (0)
+#endif
+
+errcode_t ext2fs_run_ext3_journal(ext2_filsys *fs);
+
+/* ACL translation stuff */
+#ifdef TRANSLATE_LINUX_ACLS
+/*
+ * Copied from acl_ea.h in libacl source; ACLs have to be sent to and from fuse
+ * in this format... at least on Linux.
+ */
+#define ACL_EA_ACCESS "system.posix_acl_access"
+#define ACL_EA_DEFAULT "system.posix_acl_default"
+
+#define ACL_EA_VERSION 0x0002
+
+typedef struct {
+ u_int16_t e_tag;
+ u_int16_t e_perm;
+ u_int32_t e_id;
+} acl_ea_entry;
+
+typedef struct {
+ u_int32_t a_version;
+ acl_ea_entry a_entries[0];
+} acl_ea_header;
+
+static inline size_t acl_ea_size(int count)
+{
+ return sizeof(acl_ea_header) + count * sizeof(acl_ea_entry);
+}
+
+static inline int acl_ea_count(size_t size)
+{
+ if (size < sizeof(acl_ea_header))
+ return -1;
+ size -= sizeof(acl_ea_header);
+ if (size % sizeof(acl_ea_entry))
+ return -1;
+ return size / sizeof(acl_ea_entry);
+}
+
+/*
+ * ext4 ACL structures, copied from fs/ext4/acl.h.
+ */
+#define EXT4_ACL_VERSION 0x0001
+
+typedef struct {
+ __u16 e_tag;
+ __u16 e_perm;
+ __u32 e_id;
+} ext4_acl_entry;
+
+typedef struct {
+ __u16 e_tag;
+ __u16 e_perm;
+} ext4_acl_entry_short;
+
+typedef struct {
+ __u32 a_version;
+} ext4_acl_header;
+
+static inline size_t ext4_acl_size(int count)
+{
+ if (count <= 4) {
+ return sizeof(ext4_acl_header) +
+ count * sizeof(ext4_acl_entry_short);
+ } else {
+ return sizeof(ext4_acl_header) +
+ 4 * sizeof(ext4_acl_entry_short) +
+ (count - 4) * sizeof(ext4_acl_entry);
+ }
+}
+
+static inline int ext4_acl_count(size_t size)
+{
+ ssize_t s;
+
+ size -= sizeof(ext4_acl_header);
+ s = size - 4 * sizeof(ext4_acl_entry_short);
+ if (s < 0) {
+ if (size % sizeof(ext4_acl_entry_short))
+ return -1;
+ return size / sizeof(ext4_acl_entry_short);
+ }
+ if (s % sizeof(ext4_acl_entry))
+ return -1;
+ return s / sizeof(ext4_acl_entry) + 4;
+}
+
+static errcode_t fuse_to_ext4_acl(acl_ea_header *facl, size_t facl_sz,
+ ext4_acl_header **eacl, size_t *eacl_sz)
+{
+ int i, facl_count;
+ ext4_acl_header *h;
+ size_t h_sz;
+ ext4_acl_entry *e;
+ acl_ea_entry *a;
+ void *hptr;
+ errcode_t err;
+
+ facl_count = acl_ea_count(facl_sz);
+ h_sz = ext4_acl_size(facl_count);
+ if (facl_count < 0 || facl->a_version != ACL_EA_VERSION)
+ return EXT2_ET_INVALID_ARGUMENT;
+
+ err = ext2fs_get_mem(h_sz, &h);
+ if (err)
+ return err;
+
+ h->a_version = ext2fs_cpu_to_le32(EXT4_ACL_VERSION);
+ hptr = h + 1;
+ for (i = 0, a = facl->a_entries; i < facl_count; i++, a++) {
+ e = hptr;
+ e->e_tag = ext2fs_cpu_to_le16(a->e_tag);
+ e->e_perm = ext2fs_cpu_to_le16(a->e_perm);
+
+ switch (a->e_tag) {
+ case ACL_USER:
+ case ACL_GROUP:
+ e->e_id = ext2fs_cpu_to_le32(a->e_id);
+ hptr += sizeof(ext4_acl_entry);
+ break;
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ hptr += sizeof(ext4_acl_entry_short);
+ break;
+ default:
+ err = EXT2_ET_INVALID_ARGUMENT;
+ goto out;
+ }
+ }
+
+ *eacl = h;
+ *eacl_sz = h_sz;
+ return err;
+out:
+ ext2fs_free_mem(&h);
+ return err;
+}
+
+static errcode_t ext4_to_fuse_acl(acl_ea_header **facl, size_t *facl_sz,
+ ext4_acl_header *eacl, size_t eacl_sz)
+{
+ int i, eacl_count;
+ acl_ea_header *f;
+ ext4_acl_entry *e;
+ acl_ea_entry *a;
+ size_t f_sz;
+ void *hptr;
+ errcode_t err;
+
+ eacl_count = ext4_acl_count(eacl_sz);
+ f_sz = acl_ea_size(eacl_count);
+ if (eacl_count < 0 ||
+ eacl->a_version != ext2fs_cpu_to_le32(EXT4_ACL_VERSION))
+ return EXT2_ET_INVALID_ARGUMENT;
+
+ err = ext2fs_get_mem(f_sz, &f);
+ if (err)
+ return err;
+
+ f->a_version = ACL_EA_VERSION;
+ hptr = eacl + 1;
+ for (i = 0, a = f->a_entries; i < eacl_count; i++, a++) {
+ e = hptr;
+ a->e_tag = ext2fs_le16_to_cpu(e->e_tag);
+ a->e_perm = ext2fs_le16_to_cpu(e->e_perm);
+
+ switch (a->e_tag) {
+ case ACL_USER:
+ case ACL_GROUP:
+ a->e_id = ext2fs_le32_to_cpu(e->e_id);
+ hptr += sizeof(ext4_acl_entry);
+ break;
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ hptr += sizeof(ext4_acl_entry_short);
+ break;
+ default:
+ err = EXT2_ET_INVALID_ARGUMENT;
+ goto out;
+ }
+ }
+
+ *facl = f;
+ *facl_sz = f_sz;
+ return err;
+out:
+ ext2fs_free_mem(&f);
+ return err;
+}
+#endif /* TRANSLATE_LINUX_ACLS */
+
+/*
+ * ext2_file_t contains a struct inode, so we can't leave files open.
+ * Use this as a proxy instead.
+ */
+#define FUSE2FS_FILE_MAGIC (0xEF53DEAFUL)
+struct fuse2fs_file_handle {
+ unsigned long magic;
+ ext2_ino_t ino;
+ int open_flags;
+};
+
+/* Main program context */
+#define FUSE2FS_MAGIC (0xEF53DEADUL)
+struct fuse2fs {
+ unsigned long magic;
+ ext2_filsys fs;
+ pthread_mutex_t bfl;
+ int panic_on_error;
+ int minixdf;
+ int alloc_all_blocks;
+ FILE *err_fp;
+ unsigned int next_generation;
+};
+
+#define FUSE2FS_CHECK_MAGIC(fs, ptr, num) do {if ((ptr)->magic != (num)) \
+ return translate_error((fs), 0, EXT2_ET_MAGIC_EXT2_FILE); \
+} while (0)
+
+#define FUSE2FS_CHECK_CONTEXT(ptr) do {if ((ptr)->magic != FUSE2FS_MAGIC) \
+ return translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC); \
+} while (0)
+
+static int __translate_error(ext2_filsys fs, errcode_t err, ext2_ino_t ino,
+ const char *file, int line);
+#define translate_error(fs, ino, err) __translate_error((fs), (err), (ino), \
+ __FILE__, __LINE__)
+
+/* for macosx */
+#ifndef W_OK
+# define W_OK 2
+#endif
+
+#ifndef R_OK
+# define R_OK 4
+#endif
+
+#define EXT4_EPOCH_BITS 2
+#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
+#define EXT4_NSEC_MASK (~0UL << EXT4_EPOCH_BITS)
+
+/*
+ * Extended fields will fit into an inode if the filesystem was formatted
+ * with large inodes (-I 256 or larger) and there are not currently any EAs
+ * consuming all of the available space. For new inodes we always reserve
+ * enough space for the kernel's known extended fields, but for inodes
+ * created with an old kernel this might not have been the case. None of
+ * the extended inode fields is critical for correct filesystem operation.
+ * This macro checks if a certain field fits in the inode. Note that
+ * inode-size = GOOD_OLD_INODE_SIZE + i_extra_isize
+ */
+#define EXT4_FITS_IN_INODE(ext4_inode, field) \
+ ((offsetof(typeof(*ext4_inode), field) + \
+ sizeof((ext4_inode)->field)) \
+ <= (EXT2_GOOD_OLD_INODE_SIZE + \
+ (ext4_inode)->i_extra_isize)) \
+
+static inline __u32 ext4_encode_extra_time(const struct timespec *time)
+{
+ __u32 extra = sizeof(time->tv_sec) > 4 ?
+ ((time->tv_sec - (__s32)time->tv_sec) >> 32) &
+ EXT4_EPOCH_MASK : 0;
+ return extra | (time->tv_nsec << EXT4_EPOCH_BITS);
+}
+
+static inline void ext4_decode_extra_time(struct timespec *time, __u32 extra)
+{
+ if (sizeof(time->tv_sec) > 4 && (extra & EXT4_EPOCH_MASK)) {
+ __u64 extra_bits = extra & EXT4_EPOCH_MASK;
+ /*
+ * Prior to kernel 3.14?, we had a broken decode function,
+ * wherein we effectively did this:
+ * if (extra_bits == 3)
+ * extra_bits = 0;
+ */
+ time->tv_sec += extra_bits << 32;
+ }
+ time->tv_nsec = ((extra) & EXT4_NSEC_MASK) >> EXT4_EPOCH_BITS;
+}
+
+#define EXT4_INODE_SET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ (raw_inode)->xtime = (timespec)->tv_sec; \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ (raw_inode)->xtime ## _extra = \
+ ext4_encode_extra_time(timespec); \
+} while (0)
+
+#define EXT4_EINODE_SET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime)) \
+ (raw_inode)->xtime = (timespec)->tv_sec; \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ (raw_inode)->xtime ## _extra = \
+ ext4_encode_extra_time(timespec); \
+} while (0)
+
+#define EXT4_INODE_GET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ (timespec)->tv_sec = (signed)((raw_inode)->xtime); \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ ext4_decode_extra_time((timespec), \
+ (raw_inode)->xtime ## _extra); \
+ else \
+ (timespec)->tv_nsec = 0; \
+} while (0)
+
+#define EXT4_EINODE_GET_XTIME(xtime, timespec, raw_inode) \
+do { \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime)) \
+ (timespec)->tv_sec = \
+ (signed)((raw_inode)->xtime); \
+ if (EXT4_FITS_IN_INODE(raw_inode, xtime ## _extra)) \
+ ext4_decode_extra_time((timespec), \
+ raw_inode->xtime ## _extra); \
+ else \
+ (timespec)->tv_nsec = 0; \
+} while (0)
+
+static void get_now(struct timespec *now)
+{
+#ifdef CLOCK_REALTIME
+ if (!clock_gettime(CLOCK_REALTIME, now))
+ return;
+#endif
+
+ now->tv_sec = time(NULL);
+ now->tv_nsec = 0;
+}
+
+static void increment_version(struct ext2_inode_large *inode)
+{
+ __u64 ver;
+
+ ver = inode->osd1.linux1.l_i_version;
+ if (EXT4_FITS_IN_INODE(inode, i_version_hi))
+ ver |= (__u64)inode->i_version_hi << 32;
+ ver++;
+ inode->osd1.linux1.l_i_version = ver;
+ if (EXT4_FITS_IN_INODE(inode, i_version_hi))
+ inode->i_version_hi = ver >> 32;
+}
+
+static void init_times(struct ext2_inode_large *inode)
+{
+ struct timespec now;
+
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_atime, &now, inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, inode);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, inode);
+ EXT4_EINODE_SET_XTIME(i_crtime, &now, inode);
+ increment_version(inode);
+}
+
+static int update_ctime(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *pinode)
+{
+ errcode_t err;
+ struct timespec now;
+ struct ext2_inode_large inode;
+
+ get_now(&now);
+
+ /* If user already has a inode buffer, just update that */
+ if (pinode) {
+ increment_version(pinode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, pinode);
+ return 0;
+ }
+
+ /* Otherwise we have to read-modify-write the inode */
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ increment_version(&inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, &inode);
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int update_atime(ext2_filsys fs, ext2_ino_t ino)
+{
+ errcode_t err;
+ struct ext2_inode_large inode, *pinode;
+ struct timespec atime, mtime, now;
+
+ if (!(fs->flags & EXT2_FLAG_RW))
+ return 0;
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ pinode = &inode;
+ EXT4_INODE_GET_XTIME(i_atime, &atime, pinode);
+ EXT4_INODE_GET_XTIME(i_mtime, &mtime, pinode);
+ get_now(&now);
+ /*
+ * If atime is newer than mtime and atime hasn't been updated in thirty
+ * seconds, skip the atime update. Same idea as Linux "relatime".
+ */
+ if (atime.tv_sec >= mtime.tv_sec && atime.tv_sec >= now.tv_sec - 30)
+ return 0;
+ EXT4_INODE_SET_XTIME(i_atime, &now, &inode);
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int update_mtime(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *pinode)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+ struct timespec now;
+
+ if (pinode) {
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, pinode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, pinode);
+ increment_version(pinode);
+ return 0;
+ }
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ get_now(&now);
+ EXT4_INODE_SET_XTIME(i_mtime, &now, &inode);
+ EXT4_INODE_SET_XTIME(i_ctime, &now, &inode);
+ increment_version(&inode);
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ return 0;
+}
+
+static int ext2_file_type(unsigned int mode)
+{
+ if (LINUX_S_ISREG(mode))
+ return EXT2_FT_REG_FILE;
+
+ if (LINUX_S_ISDIR(mode))
+ return EXT2_FT_DIR;
+
+ if (LINUX_S_ISCHR(mode))
+ return EXT2_FT_CHRDEV;
+
+ if (LINUX_S_ISBLK(mode))
+ return EXT2_FT_BLKDEV;
+
+ if (LINUX_S_ISLNK(mode))
+ return EXT2_FT_SYMLINK;
+
+ if (LINUX_S_ISFIFO(mode))
+ return EXT2_FT_FIFO;
+
+ if (LINUX_S_ISSOCK(mode))
+ return EXT2_FT_SOCK;
+
+ return 0;
+}
+
+static int fs_can_allocate(struct fuse2fs *ff, blk64_t num)
+{
+ ext2_filsys fs = ff->fs;
+ blk64_t reserved;
+
+ dbg_printf("%s: Asking for %llu; alloc_all=%d total=%llu free=%llu "
+ "rsvd=%llu\n", __func__, num, ff->alloc_all_blocks,
+ ext2fs_blocks_count(fs->super),
+ ext2fs_free_blocks_count(fs->super),
+ ext2fs_r_blocks_count(fs->super));
+ if (num > ext2fs_blocks_count(fs->super))
+ return 0;
+
+ if (ff->alloc_all_blocks)
+ return 1;
+
+ /*
+ * Different meaning for r_blocks -- libext2fs has bugs where the FS
+ * can get corrupted if it totally runs out of blocks. Avoid this
+ * by refusing to allocate any of the reserve blocks to anybody.
+ */
+ reserved = ext2fs_r_blocks_count(fs->super);
+ if (reserved == 0)
+ reserved = ext2fs_blocks_count(fs->super) / 10;
+ return ext2fs_free_blocks_count(fs->super) > reserved + num;
+}
+
+static int fs_writeable(ext2_filsys fs)
+{
+ return (fs->flags & EXT2_FLAG_RW) && (fs->super->s_error_count == 0);
+}
+
+static int check_inum_access(ext2_filsys fs, ext2_ino_t ino, int mask)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct ext2_inode inode;
+ mode_t perms;
+ errcode_t err;
+
+ /* no writing to read-only or broken fs */
+ if ((mask & W_OK) && !fs_writeable(fs))
+ return -EROFS;
+
+ err = ext2fs_read_inode(fs, ino, &inode);
+ if (err)
+ return translate_error(fs, ino, err);
+ perms = inode.i_mode & 0777;
+
+ dbg_printf("access ino=%d mask=e%s%s%s perms=0%o fuid=%d fgid=%d "
+ "uid=%d gid=%d\n", ino,
+ (mask & R_OK ? "r" : ""), (mask & W_OK ? "w" : ""),
+ (mask & X_OK ? "x" : ""), perms, inode.i_uid, inode.i_gid,
+ ctxt->uid, ctxt->gid);
+
+ /* existence check */
+ if (mask == 0)
+ return 0;
+
+ /* is immutable? */
+ if ((mask & W_OK) &&
+ (inode.i_flags & EXT2_IMMUTABLE_FL))
+ return -EACCES;
+
+ /* Figure out what root's allowed to do */
+ if (ctxt->uid == 0) {
+ /* Non-file access always ok */
+ if (!LINUX_S_ISREG(inode.i_mode))
+ return 0;
+
+ /* R/W access to a file always ok */
+ if (!(mask & X_OK))
+ return 0;
+
+ /* X access to a file ok if a user/group/other can X */
+ if (perms & 0111)
+ return 0;
+
+ /* Trying to execute a file that's not executable. BZZT! */
+ return -EACCES;
+ }
+
+ /* allow owner, if perms match */
+ if (inode.i_uid == ctxt->uid) {
+ if ((mask & (perms >> 6)) == mask)
+ return 0;
+ return -EACCES;
+ }
+
+ /* allow group, if perms match */
+ if (inode.i_gid == ctxt->gid) {
+ if ((mask & (perms >> 3)) == mask)
+ return 0;
+ return -EACCES;
+ }
+
+ /* otherwise check other */
+ if ((mask & perms) == mask)
+ return 0;
+ return -EACCES;
+}
+
+static void op_destroy(void *p)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+
+ if (ff->magic != FUSE2FS_MAGIC) {
+ translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC);
+ return;
+ }
+ fs = ff->fs;
+ dbg_printf("%s: dev=%s\n", __func__, fs->device_name);
+ if (fs->flags & EXT2_FLAG_RW) {
+ fs->super->s_state |= EXT2_VALID_FS;
+ if (fs->super->s_error_count)
+ fs->super->s_state |= EXT2_ERROR_FS;
+ ext2fs_mark_super_dirty(fs);
+ err = ext2fs_set_gdt_csum(fs);
+ if (err)
+ translate_error(fs, 0, err);
+
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ translate_error(fs, 0, err);
+ }
+}
+
+static void *op_init(struct fuse_conn_info *conn)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+
+ if (ff->magic != FUSE2FS_MAGIC) {
+ translate_error(global_fs, 0, EXT2_ET_BAD_MAGIC);
+ return NULL;
+ }
+ fs = ff->fs;
+ dbg_printf("%s: dev=%s\n", __func__, fs->device_name);
+#ifdef FUSE_CAP_IOCTL_DIR
+ conn->want |= FUSE_CAP_IOCTL_DIR;
+#endif
+ if (fs->flags & EXT2_FLAG_RW) {
+ fs->super->s_mnt_count++;
+ fs->super->s_mtime = time(NULL);
+ fs->super->s_state &= ~EXT2_VALID_FS;
+ ext2fs_mark_super_dirty(fs);
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ translate_error(fs, 0, err);
+ }
+ return ff;
+}
+
+static blkcnt_t blocks_from_inode(ext2_filsys fs,
+ struct ext2_inode_large *inode)
+{
+ blkcnt_t b;
+
+ b = inode->i_blocks;
+ if (fs->super->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_HUGE_FILE)
+ b += ((long long) inode->osd2.linux2.l_i_blocks_hi) << 32;
+
+ if (!(fs->super->s_feature_ro_compat &
+ EXT4_FEATURE_RO_COMPAT_HUGE_FILE) ||
+ !(inode->i_flags & EXT4_HUGE_FILE_FL))
+ b *= fs->blocksize / 512;
+ b *= EXT2FS_CLUSTER_RATIO(fs);
+
+ return b;
+}
+
+static int stat_inode(ext2_filsys fs, ext2_ino_t ino, struct stat *statbuf)
+{
+ struct ext2_inode_large inode;
+ dev_t fakedev = 0;
+ errcode_t err;
+ int ret = 0;
+ struct timespec tv;
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, ino, err);
+
+ memcpy(&fakedev, fs->super->s_uuid, sizeof(fakedev));
+ statbuf->st_dev = fakedev;
+ statbuf->st_ino = ino;
+ statbuf->st_mode = inode.i_mode;
+ statbuf->st_nlink = inode.i_links_count;
+ statbuf->st_uid = inode.i_uid;
+ statbuf->st_gid = inode.i_gid;
+ statbuf->st_size = EXT2_I_SIZE(&inode);
+ statbuf->st_blksize = fs->blocksize;
+ statbuf->st_blocks = blocks_from_inode(fs, &inode);
+ EXT4_INODE_GET_XTIME(i_atime, &tv, &inode);
+ statbuf->st_atime = tv.tv_sec;
+ EXT4_INODE_GET_XTIME(i_mtime, &tv, &inode);
+ statbuf->st_mtime = tv.tv_sec;
+ EXT4_INODE_GET_XTIME(i_ctime, &tv, &inode);
+ statbuf->st_ctime = tv.tv_sec;
+ if (LINUX_S_ISCHR(inode.i_mode) ||
+ LINUX_S_ISBLK(inode.i_mode)) {
+ if (inode.i_block[0])
+ statbuf->st_rdev = inode.i_block[0];
+ else
+ statbuf->st_rdev = inode.i_block[1];
+ }
+
+ return ret;
+}
+
+static int op_getattr(const char *path, struct stat *statbuf)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s\n", __func__, path);
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ ret = stat_inode(fs, ino, statbuf);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_readlink(const char *path, char *buf, size_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode inode;
+ unsigned int got;
+ ext2_file_t file;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s\n", __func__, path);
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ err = ext2fs_read_inode(fs, ino, &inode);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ if (!LINUX_S_ISLNK(inode.i_mode)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ len--;
+ if (inode.i_size < len)
+ len = inode.i_size;
+ if (ext2fs_inode_data_blocks2(fs, &inode) ||
+ (inode.i_flags & EXT4_INLINE_DATA_FL)) {
+ /* big/inline symlink */
+
+ err = ext2fs_file_open(fs, ino, 0, &file);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_read(file, buf, len, &got);
+ if (err || got != len) {
+ ext2fs_file_close(file);
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(file);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+ } else
+ /* inline symlink */
+ memcpy(buf, (char *)inode.i_block, len);
+ buf[len] = 0;
+
+ if (fs_writeable(fs)) {
+ ret = update_atime(fs, ino);
+ if (ret)
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_mknod(const char *path, mode_t mode, dev_t dev)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path = strdup(path);
+ errcode_t err;
+ char *node_name, a;
+ int filetype;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s mode=0%o dev=0x%x\n", __func__, path, mode,
+ (unsigned int)dev);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 2)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+ *node_name = a;
+
+ if (LINUX_S_ISCHR(mode))
+ filetype = EXT2_FT_CHRDEV;
+ else if (LINUX_S_ISBLK(mode))
+ filetype = EXT2_FT_BLKDEV;
+ else if (LINUX_S_ISFIFO(mode))
+ filetype = EXT2_FT_FIFO;
+ else if (LINUX_S_ISSOCK(mode))
+ filetype = EXT2_FT_SOCK;
+ else {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ err = ext2fs_new_inode(fs, parent, mode, 0, &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ dbg_printf("%s: create ino=%d/name=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, child, filetype);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_link(fs, parent, node_name, child,
+ filetype);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ memset(&inode, 0, sizeof(inode));
+ inode.i_mode = mode;
+
+ if (dev & ~0xFFFF)
+ inode.i_block[1] = dev;
+ else
+ inode.i_block[0] = dev;
+ inode.i_links_count = 1;
+ inode.i_extra_isize = sizeof(struct ext2_inode_large) -
+ EXT2_GOOD_OLD_INODE_SIZE;
+
+ err = ext2fs_write_new_inode(fs, child, (struct ext2_inode *)&inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_generation = ff->next_generation++;
+ init_times(&inode);
+ err = ext2fs_write_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ ext2fs_inode_alloc_stats2(fs, child, 1, 0);
+
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int op_mkdir(const char *path, mode_t mode)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path = strdup(path);
+ errcode_t err;
+ char *node_name, a;
+ struct ext2_inode_large inode;
+ char *block;
+ blk64_t blk;
+ int ret = 0;
+ mode_t parent_sgid;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s mode=0%o\n", __func__, path, mode);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+ /* Is the parent dir sgid? */
+ err = ext2fs_read_inode_full(fs, parent, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+ parent_sgid = inode.i_mode & S_ISGID;
+
+ *node_name = a;
+
+ err = ext2fs_mkdir(fs, parent, 0, node_name);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_mkdir(fs, parent, 0, node_name);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ /* Still have to update the uid/gid of the dir */
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ dbg_printf("%s: created ino=%d/path=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_uid = ctxt->uid;
+ inode.i_gid = ctxt->gid;
+ inode.i_mode = LINUX_S_IFDIR | (mode & ~(S_ISUID | fs->umask)) |
+ parent_sgid;
+ inode.i_generation = ff->next_generation++;
+
+ err = ext2fs_write_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ /* Rewrite the directory block checksum, having set i_generation */
+ if ((inode.i_flags & EXT4_INLINE_DATA_FL) ||
+ !EXT2_HAS_RO_COMPAT_FEATURE(fs->super,
+ EXT4_FEATURE_RO_COMPAT_METADATA_CSUM))
+ goto out2;
+ err = ext2fs_new_dir_block(fs, child, parent, &block);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+ err = ext2fs_bmap2(fs, child, (struct ext2_inode *)&inode, NULL, 0, 0,
+ NULL, &blk);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out3;
+ }
+ err = ext2fs_write_dir_block4(fs, blk, block, 0, child);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out3;
+ }
+
+out3:
+ ext2fs_free_mem(&block);
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int unlink_file_by_name(ext2_filsys fs, const char *path)
+{
+ errcode_t err;
+ ext2_ino_t dir;
+ char *filename = strdup(path);
+ char *base_name;
+ int ret;
+
+ base_name = strrchr(filename, '/');
+ if (base_name) {
+ *base_name++ = '\0';
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, filename,
+ &dir);
+ if (err) {
+ free(filename);
+ return translate_error(fs, 0, err);
+ }
+ } else {
+ dir = EXT2_ROOT_INO;
+ base_name = filename;
+ }
+
+ ret = check_inum_access(fs, dir, W_OK);
+ if (ret) {
+ free(filename);
+ return ret;
+ }
+
+ dbg_printf("%s: unlinking name=%s from dir=%d\n", __func__,
+ base_name, dir);
+ err = ext2fs_unlink(fs, dir, base_name, 0, 0);
+ free(filename);
+ if (err)
+ return translate_error(fs, dir, err);
+
+ return update_mtime(fs, dir, NULL);
+}
+
+static int remove_inode(struct fuse2fs *ff, ext2_ino_t ino)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+ dbg_printf("%s: put ino=%d links=%d\n", __func__, ino,
+ inode.i_links_count);
+
+ switch (inode.i_links_count) {
+ case 0:
+ return 0; /* XXX: already done? */
+ case 1:
+ inode.i_links_count--;
+ inode.i_dtime = fs->now ? fs->now : time(0);
+ break;
+ default:
+ inode.i_links_count--;
+ }
+
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ if (inode.i_links_count)
+ goto write_out;
+
+ /* Nobody holds this file; free its blocks! */
+ err = ext2fs_free_ext_attr(fs, ino, &inode);
+ if (err)
+ goto write_out;
+
+ if (ext2fs_inode_has_valid_blocks2(fs, (struct ext2_inode *)&inode)) {
+ err = ext2fs_punch(fs, ino, (struct ext2_inode *)&inode, NULL,
+ 0, ~0ULL);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto write_out;
+ }
+ }
+
+ ext2fs_inode_alloc_stats2(fs, ino, -1,
+ LINUX_S_ISDIR(inode.i_mode));
+
+write_out:
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+out:
+ return ret;
+}
+
+static int __op_unlink(struct fuse2fs *ff, const char *path)
+{
+ ext2_filsys fs = ff->fs;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ ret = unlink_file_by_name(fs, path);
+ if (ret)
+ goto out;
+
+ ret = remove_inode(ff, ino);
+ if (ret)
+ goto out;
+out:
+ return ret;
+}
+
+static int op_unlink(const char *path)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ int ret;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ pthread_mutex_lock(&ff->bfl);
+ ret = __op_unlink(ff, path);
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+struct rd_struct {
+ ext2_ino_t parent;
+ int empty;
+};
+
+static int rmdir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
+ int entry EXT2FS_ATTR((unused)),
+ struct ext2_dir_entry *dirent,
+ int offset EXT2FS_ATTR((unused)),
+ int blocksize EXT2FS_ATTR((unused)),
+ char *buf EXT2FS_ATTR((unused)),
+ void *private)
+{
+ struct rd_struct *rds = (struct rd_struct *) private;
+
+ if (dirent->inode == 0)
+ return 0;
+ if (((dirent->name_len & 0xFF) == 1) && (dirent->name[0] == '.'))
+ return 0;
+ if (((dirent->name_len & 0xFF) == 2) && (dirent->name[0] == '.') &&
+ (dirent->name[1] == '.')) {
+ rds->parent = dirent->inode;
+ return 0;
+ }
+ rds->empty = 0;
+ return 0;
+}
+
+static int __op_rmdir(struct fuse2fs *ff, const char *path)
+{
+ ext2_filsys fs = ff->fs;
+ ext2_ino_t child;
+ errcode_t err;
+ struct ext2_inode_large inode;
+ struct rd_struct rds;
+ int ret = 0;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: rmdir path=%s ino=%d\n", __func__, path, child);
+
+ rds.parent = 0;
+ rds.empty = 1;
+
+ err = ext2fs_dir_iterate2(fs, child, 0, 0, rmdir_proc, &rds);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out;
+ }
+
+ if (rds.empty == 0) {
+ ret = -ENOTEMPTY;
+ goto out;
+ }
+
+ ret = unlink_file_by_name(fs, path);
+ if (ret)
+ goto out;
+ /* Directories have to be "removed" twice. */
+ ret = remove_inode(ff, child);
+ if (ret)
+ goto out;
+ ret = remove_inode(ff, child);
+ if (ret)
+ goto out;
+
+ if (rds.parent) {
+ dbg_printf("%s: decr dir=%d link count\n", __func__,
+ rds.parent);
+ err = ext2fs_read_inode_full(fs, rds.parent,
+ (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, rds.parent, err);
+ goto out;
+ }
+ if (inode.i_links_count > 1)
+ inode.i_links_count--;
+ ret = update_mtime(fs, rds.parent, &inode);
+ if (ret)
+ goto out;
+ err = ext2fs_write_inode_full(fs, rds.parent,
+ (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, rds.parent, err);
+ goto out;
+ }
+ }
+
+out:
+ return ret;
+}
+
+static int op_rmdir(const char *path)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ int ret;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ pthread_mutex_lock(&ff->bfl);
+ ret = __op_rmdir(ff, path);
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_symlink(const char *src, const char *dest)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path = strdup(dest);
+ errcode_t err;
+ char *node_name, a;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: symlink %s to %s\n", __func__, src, dest);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ *node_name = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+
+ /* Create symlink */
+ err = ext2fs_symlink(fs, parent, 0, node_name, (char *)src);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_symlink(fs, parent, 0, node_name, (char *)src);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ /* Update parent dir's mtime */
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ /* Still have to update the uid/gid of the symlink */
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &child);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ dbg_printf("%s: symlinking ino=%d/name=%s to dir=%d\n", __func__,
+ child, node_name, parent);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_uid = ctxt->uid;
+ inode.i_gid = ctxt->gid;
+ inode.i_generation = ff->next_generation++;
+
+ err = ext2fs_write_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+struct update_dotdot {
+ ext2_ino_t new_dotdot;
+};
+
+static int update_dotdot_helper(ext2_ino_t dir, int entry,
+ struct ext2_dir_entry *dirent, int offset,
+ int blocksize, char *buf, void *priv_data)
+{
+ struct update_dotdot *ud = priv_data;
+
+ if (ext2fs_dirent_name_len(dirent) == 2 &&
+ dirent->name[0] == '.' && dirent->name[1] == '.') {
+ dirent->inode = ud->new_dotdot;
+ return DIRENT_CHANGED | DIRENT_ABORT;
+ }
+
+ return 0;
+}
+
+static int op_rename(const char *from, const char *to)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t from_ino, to_ino, to_dir_ino, from_dir_ino;
+ char *temp_to = NULL, *temp_from = NULL;
+ char *cp, a;
+ struct ext2_inode inode;
+ struct update_dotdot ud;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: renaming %s to %s\n", __func__, from, to);
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 5)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, from, &from_ino);
+ if (err || from_ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, to, &to_ino);
+ if (err && err != EXT2_ET_FILE_NOT_FOUND) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ if (err == EXT2_ET_FILE_NOT_FOUND)
+ to_ino = 0;
+
+ /* Already the same file? */
+ if (to_ino != 0 && to_ino == from_ino) {
+ ret = 0;
+ goto out;
+ }
+
+ temp_to = strdup(to);
+ if (!temp_to) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ temp_from = strdup(from);
+ if (!temp_from) {
+ ret = -ENOMEM;
+ goto out2;
+ }
+
+ /* Find parent dir of the source and check write access */
+ cp = strrchr(temp_from, '/');
+ if (!cp) {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ a = *(cp + 1);
+ *(cp + 1) = 0;
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_from,
+ &from_dir_ino);
+ *(cp + 1) = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ if (from_dir_ino == 0) {
+ ret = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, from_dir_ino, W_OK);
+ if (ret)
+ goto out2;
+
+ /* Find parent dir of the destination and check write access */
+ cp = strrchr(temp_to, '/');
+ if (!cp) {
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ a = *(cp + 1);
+ *(cp + 1) = 0;
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_to,
+ &to_dir_ino);
+ *(cp + 1) = a;
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+ if (to_dir_ino == 0) {
+ ret = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, to_dir_ino, W_OK);
+ if (ret)
+ goto out2;
+
+ /* If the target exists, unlink it first */
+ if (to_ino != 0) {
+ err = ext2fs_read_inode(fs, to_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_ino, err);
+ goto out2;
+ }
+
+ dbg_printf("%s: unlinking %s ino=%d\n", __func__,
+ LINUX_S_ISDIR(inode.i_mode) ? "dir" : "file",
+ to_ino);
+ if (LINUX_S_ISDIR(inode.i_mode))
+ ret = __op_rmdir(ff, to);
+ else
+ ret = __op_unlink(ff, to);
+ if (ret)
+ goto out2;
+ }
+
+ /* Get ready to do the move */
+ err = ext2fs_read_inode(fs, from_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ /* Link in the new file */
+ dbg_printf("%s: linking ino=%d/path=%s to dir=%d\n", __func__,
+ from_ino, cp + 1, to_dir_ino);
+ err = ext2fs_link(fs, to_dir_ino, cp + 1, from_ino,
+ ext2_file_type(inode.i_mode));
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, to_dir_ino);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_link(fs, to_dir_ino, cp + 1, from_ino,
+ ext2_file_type(inode.i_mode));
+ }
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+
+ /* Update '..' pointer if dir */
+ err = ext2fs_read_inode(fs, from_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ if (LINUX_S_ISDIR(inode.i_mode)) {
+ ud.new_dotdot = to_dir_ino;
+ dbg_printf("%s: updating .. entry for dir=%d\n", __func__,
+ to_dir_ino);
+ err = ext2fs_dir_iterate2(fs, from_ino, 0, NULL,
+ update_dotdot_helper, &ud);
+ if (err) {
+ ret = translate_error(fs, from_ino, err);
+ goto out2;
+ }
+
+ /* Decrease from_dir_ino's links_count */
+ dbg_printf("%s: moving linkcount from dir=%d to dir=%d\n",
+ __func__, from_dir_ino, to_dir_ino);
+ err = ext2fs_read_inode(fs, from_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_dir_ino, err);
+ goto out2;
+ }
+ inode.i_links_count--;
+ err = ext2fs_write_inode(fs, from_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, from_dir_ino, err);
+ goto out2;
+ }
+
+ /* Increase to_dir_ino's links_count */
+ err = ext2fs_read_inode(fs, to_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+ inode.i_links_count++;
+ err = ext2fs_write_inode(fs, to_dir_ino, &inode);
+ if (err) {
+ ret = translate_error(fs, to_dir_ino, err);
+ goto out2;
+ }
+ }
+
+ /* Update timestamps */
+ ret = update_ctime(fs, from_ino, NULL);
+ if (ret)
+ goto out2;
+
+ ret = update_mtime(fs, to_dir_ino, NULL);
+ if (ret)
+ goto out2;
+
+ /* Remove the old file */
+ ret = unlink_file_by_name(fs, from);
+ if (ret)
+ goto out2;
+
+ /* Flush the whole mess out */
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ ret = translate_error(fs, 0, err);
+
+out2:
+ free(temp_from);
+ free(temp_to);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_link(const char *src, const char *dest)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ char *temp_path = strdup(dest);
+ errcode_t err;
+ char *node_name, a;
+ ext2_ino_t parent, ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: src=%s dest=%s\n", __func__, src, dest);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 2)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ *node_name = a;
+ if (err) {
+ err = -ENOENT;
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, src, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ inode.i_links_count++;
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out2;
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ dbg_printf("%s: linking ino=%d/name=%s to dir=%d\n", __func__, ino,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, ino,
+ ext2_file_type(inode.i_mode));
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_link(fs, parent, node_name, ino,
+ ext2_file_type(inode.i_mode));
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int op_chmod(const char *path, mode_t mode)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: path=%s mode=0%o ino=%d\n", __func__, path, mode, ino);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ if (ctxt->uid != 0 && ctxt->uid != inode.i_uid) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ /*
+ * XXX: We should really check that the inode gid is not in /any/
+ * of the user's groups, but FUSE only tells us about the primary
+ * group.
+ */
+ if (ctxt->uid != 0 && ctxt->gid != inode.i_gid)
+ mode &= ~S_ISGID;
+
+ inode.i_mode &= ~0xFFF;
+ inode.i_mode |= mode & 0xFFF;
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_chown(const char *path, uid_t owner, gid_t group)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: path=%s owner=%d group=%d ino=%d\n", __func__,
+ path, owner, group, ino);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ /* FUSE seems to feed us ~0 to mean "don't change" */
+ if (owner != ~0) {
+ /* Only root gets to change UID. */
+ if (ctxt->uid != 0 &&
+ !(inode.i_uid == ctxt->uid && owner == ctxt->uid)) {
+ ret = -EPERM;
+ goto out;
+ }
+ inode.i_uid = owner;
+ }
+
+ if (group != ~0) {
+ /* Only root or the owner get to change GID. */
+ if (ctxt->uid != 0 && inode.i_uid != ctxt->uid) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ /* XXX: We /should/ check group membership but FUSE */
+ inode.i_gid = group;
+ }
+
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_truncate(const char *path, off_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ ext2_file_t file;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d len=%jd\n", __func__, ino, len);
+
+ ret = check_inum_access(fs, ino, W_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_file_open(fs, ino, EXT2_FILE_WRITE, &file);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_set_size2(file, len);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(file);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ ret = update_mtime(fs, ino, NULL);
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return err;
+}
+
+#ifdef __linux__
+static void detect_linux_executable_open(int kernel_flags, int *access_check,
+ int *e2fs_open_flags)
+{
+ /*
+ * On Linux, execve will bleed __FMODE_EXEC into the file mode flags,
+ * and FUSE is more than happy to let that slip through.
+ */
+ if (kernel_flags & 0x20) {
+ *access_check = X_OK;
+ *e2fs_open_flags &= ~EXT2_FILE_WRITE;
+ }
+}
+#else
+static void detect_linux_executable_open(int kernel_flags, int *access_check,
+ int *e2fs_open_flags)
+{
+ /* empty */
+}
+#endif /* __linux__ */
+
+static int __op_open(struct fuse2fs *ff, const char *path,
+ struct fuse_file_info *fp)
+{
+ ext2_filsys fs = ff->fs;
+ errcode_t err;
+ struct fuse2fs_file_handle *file;
+ int check = 0, ret = 0;
+
+ dbg_printf("%s: path=%s\n", __func__, path);
+ err = ext2fs_get_mem(sizeof(*file), &file);
+ if (err)
+ return translate_error(fs, 0, err);
+ file->magic = FUSE2FS_FILE_MAGIC;
+
+ file->open_flags = 0;
+ switch (fp->flags & O_ACCMODE) {
+ case O_RDONLY:
+ check = R_OK;
+ break;
+ case O_WRONLY:
+ check = W_OK;
+ file->open_flags |= EXT2_FILE_WRITE;
+ break;
+ case O_RDWR:
+ check = R_OK | W_OK;
+ file->open_flags |= EXT2_FILE_WRITE;
+ break;
+ }
+
+ detect_linux_executable_open(fp->flags, &check, &file->open_flags);
+
+ if (fp->flags & O_CREAT)
+ file->open_flags |= EXT2_FILE_CREATE;
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &file->ino);
+ if (err || file->ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, file->ino);
+
+ ret = check_inum_access(fs, file->ino, check);
+ if (ret) {
+ /*
+ * In a regular (Linux) fs driver, the kernel will open
+ * binaries for reading if the user has --x privileges (i.e.
+ * execute without read). Since the kernel doesn't have any
+ * way to tell us if it's opening a file via execve, we'll
+ * just assume that allowing access is ok if asking for ro mode
+ * fails but asking for x mode succeeds. Of course we can
+ * also employ undocumented hacks (see above).
+ */
+ if (check == R_OK) {
+ ret = check_inum_access(fs, file->ino, X_OK);
+ if (ret)
+ goto out;
+ } else
+ goto out;
+ }
+ fp->fh = (uint64_t)file;
+
+out:
+ if (ret)
+ ext2fs_free_mem(&file);
+ return ret;
+}
+
+static int op_open(const char *path, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ int ret;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ pthread_mutex_lock(&ff->bfl);
+ ret = __op_open(ff, path, fp);
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_read(const char *path, char *buf, size_t len, off_t offset,
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ ext2_file_t efp;
+ errcode_t err;
+ unsigned int got = 0;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino, offset,
+ len);
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_llseek(efp, offset, SEEK_SET, NULL);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_read(efp, buf, len, &got);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(efp);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ if (fs_writeable(fs)) {
+ ret = update_atime(fs, fh->ino);
+ if (ret)
+ goto out;
+ }
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return got ? got : ret;
+}
+
+static int op_write(const char *path, const char *buf, size_t len, off_t offset,
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ ext2_file_t efp;
+ errcode_t err;
+ unsigned int got = 0;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d off=%jd len=%jd\n", __func__, fh->ino, offset,
+ len);
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_writeable(fs)) {
+ ret = -EROFS;
+ goto out;
+ }
+
+ if (!fs_can_allocate(ff, len / fs->blocksize)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_llseek(efp, offset, SEEK_SET, NULL);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_write(efp, buf, len, &got);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_file_flush(efp);
+ if (err) {
+ got = 0;
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(efp);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ ret = update_mtime(fs, fh->ino, NULL);
+ if (ret)
+ goto out;
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return got ? got : ret;
+}
+
+static int op_release(const char *path, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ pthread_mutex_lock(&ff->bfl);
+ if (fs_writeable(fs) && fh->open_flags & EXT2_FILE_WRITE) {
+ err = ext2fs_flush2(fs, EXT2_FLAG_FLUSH_NO_SYNC);
+ if (err)
+ ret = translate_error(fs, fh->ino, err);
+ }
+ fp->fh = 0;
+ pthread_mutex_unlock(&ff->bfl);
+
+ ext2fs_free_mem(&fh);
+
+ return ret;
+}
+
+static int op_fsync(const char *path, int datasync, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ /* For now, flush everything, even if it's slow */
+ pthread_mutex_lock(&ff->bfl);
+ if (fs_writeable(fs) && fh->open_flags & EXT2_FILE_WRITE) {
+ err = ext2fs_flush2(fs, 0);
+ if (err)
+ ret = translate_error(fs, fh->ino, err);
+ }
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int op_statfs(const char *path, struct statvfs *buf)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ uint64_t fsid, *f;
+ blk64_t overhead, reserved, free;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s\n", __func__, path);
+ buf->f_bsize = fs->blocksize;
+ buf->f_frsize = 0;
+
+ if (ff->minixdf)
+ overhead = 0;
+ else
+ overhead = fs->desc_blocks +
+ fs->group_desc_count *
+ (fs->inode_blocks_per_group + 2);
+ reserved = ext2fs_r_blocks_count(fs->super);
+ if (!reserved)
+ reserved = ext2fs_blocks_count(fs->super) / 10;
+ free = ext2fs_free_blocks_count(fs->super);
+
+ buf->f_blocks = ext2fs_blocks_count(fs->super) - overhead;
+ buf->f_bfree = free;
+ if (free < reserved)
+ buf->f_bavail = 0;
+ else
+ buf->f_bavail = free - reserved;
+ buf->f_files = fs->super->s_inodes_count;
+ buf->f_ffree = fs->super->s_free_inodes_count;
+ buf->f_favail = fs->super->s_free_inodes_count;
+ f = (uint64_t *)fs->super->s_uuid;
+ fsid = *f;
+ f++;
+ fsid ^= *f;
+ buf->f_fsid = fsid;
+ buf->f_flag = 0;
+ if (fs->flags & EXT2_FLAG_RW)
+ buf->f_flag |= ST_RDONLY;
+ buf->f_namemax = EXT2_NAME_LEN;
+
+ return 0;
+}
+
+typedef errcode_t (*xattr_xlate_get)(void **cooked_buf, size_t *cooked_sz,
+ const void *raw_buf, size_t raw_sz);
+typedef errcode_t (*xattr_xlate_set)(const void *cooked_buf, size_t cooked_sz,
+ void **raw_buf, size_t *raw_sz);
+struct xattr_translate {
+ const char *prefix;
+ xattr_xlate_get get;
+ xattr_xlate_set set;
+};
+
+#define XATTR_TRANSLATOR(p, g, s) \
+ {.prefix = (p), \
+ .get = (xattr_xlate_get)(g), \
+ .set = (xattr_xlate_set)(s)}
+
+static struct xattr_translate xattr_translators[] = {
+#ifdef TRANSLATE_LINUX_ACLS
+ XATTR_TRANSLATOR(ACL_EA_ACCESS, ext4_to_fuse_acl, fuse_to_ext4_acl),
+ XATTR_TRANSLATOR(ACL_EA_DEFAULT, ext4_to_fuse_acl, fuse_to_ext4_acl),
+#endif
+ XATTR_TRANSLATOR(NULL, NULL, NULL),
+};
+#undef XATTR_TRANSLATOR
+
+static int op_getxattr(const char *path, const char *key, char *value,
+ size_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ struct xattr_translate *xt;
+ void *ptr, *cptr;
+ size_t plen, clen;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ if (!EXT2_HAS_COMPAT_FEATURE(fs->super,
+ EXT2_FEATURE_COMPAT_EXT_ATTR)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, R_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_xattr_get(h, key, &ptr, &plen);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ for (xt = xattr_translators; xt->prefix != NULL; xt++) {
+ if (strncmp(key, xt->prefix, strlen(xt->prefix)) == 0) {
+ err = xt->get(&cptr, &clen, ptr, plen);
+ if (err)
+ goto out3;
+ ext2fs_free_mem(&ptr);
+ ptr = cptr;
+ plen = clen;
+ }
+ }
+
+ if (!len) {
+ ret = plen;
+ } else if (len < plen) {
+ ret = -ERANGE;
+ } else {
+ memcpy(value, ptr, plen);
+ ret = plen;
+ }
+
+out3:
+ ext2fs_free_mem(&ptr);
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (err)
+ ret = translate_error(fs, ino, err);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int count_buffer_space(char *name, char *value, size_t value_len,
+ void *data)
+{
+ unsigned int *x = data;
+
+ *x = *x + strlen(name) + 1;
+ return 0;
+}
+
+static int copy_names(char *name, char *value, size_t value_len, void *data)
+{
+ char **b = data;
+
+ strncpy(*b, name, strlen(name));
+ *b = *b + strlen(name) + 1;
+
+ return 0;
+}
+
+static int op_listxattr(const char *path, char *names, size_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ unsigned int bufsz;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ if (!EXT2_HAS_COMPAT_FEATURE(fs->super,
+ EXT2_FEATURE_COMPAT_EXT_ATTR)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, R_OK);
+ if (ret)
+ goto out2;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ /* Count buffer space needed for names */
+ bufsz = 0;
+ err = ext2fs_xattrs_iterate(h, count_buffer_space, &bufsz);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ if (len == 0) {
+ ret = bufsz;
+ goto out2;
+ } else if (len < bufsz) {
+ ret = -ERANGE;
+ goto out2;
+ }
+
+ /* Copy names out */
+ memset(names, 0, len);
+ err = ext2fs_xattrs_iterate(h, copy_names, &names);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+ ret = bufsz;
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (err)
+ ret = translate_error(fs, ino, err);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int op_setxattr(const char *path, const char *key, const char *value,
+ size_t len, int flags)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ struct xattr_translate *xt;
+ void *cvalue;
+ size_t clen;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ if (!EXT2_HAS_COMPAT_FEATURE(fs->super,
+ EXT2_FEATURE_COMPAT_EXT_ATTR)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, W_OK);
+ if (ret == -EACCES) {
+ ret = -EPERM;
+ goto out;
+ } else if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ cvalue = (void *)value;
+ clen = len;
+ for (xt = xattr_translators; xt->prefix != NULL; xt++) {
+ if (strncmp(key, xt->prefix, strlen(xt->prefix)) == 0) {
+ err = xt->set(value, len, &cvalue, &clen);
+ if (err)
+ goto out3;
+ }
+ }
+
+ err = ext2fs_xattr_set(h, key, cvalue, clen);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out3;
+ }
+
+ err = ext2fs_xattrs_write(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out3;
+ }
+
+ ret = update_ctime(fs, ino, NULL);
+out3:
+ if (cvalue != value)
+ ext2fs_free_mem(&cvalue);
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (!ret && err)
+ ret = translate_error(fs, ino, err);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int op_removexattr(const char *path, const char *key)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct ext2_xattr_handle *h;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ if (!EXT2_HAS_COMPAT_FEATURE(fs->super,
+ EXT2_FEATURE_COMPAT_EXT_ATTR)) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, W_OK);
+ if (ret)
+ goto out;
+
+ err = ext2fs_xattrs_open(fs, ino, &h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+ err = ext2fs_xattrs_read(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_xattr_remove(h, key);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ err = ext2fs_xattrs_write(h);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out2;
+ }
+
+ ret = update_ctime(fs, ino, NULL);
+out2:
+ err = ext2fs_xattrs_close(&h);
+ if (err)
+ ret = translate_error(fs, ino, err);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+struct readdir_iter {
+ void *buf;
+ fuse_fill_dir_t func;
+};
+
+static int op_readdir_iter(ext2_ino_t dir, int entry,
+ struct ext2_dir_entry *dirent, int offset,
+ int blocksize, char *buf, void *data)
+{
+ struct readdir_iter *i = data;
+ char namebuf[EXT2_NAME_LEN + 1];
+ int ret;
+
+ memcpy(namebuf, dirent->name, dirent->name_len & 0xFF);
+ namebuf[dirent->name_len & 0xFF] = 0;
+ ret = i->func(i->buf, namebuf, NULL, 0);
+ if (ret)
+ return DIRENT_ABORT;
+
+ return 0;
+}
+
+static int op_readdir(const char *path, void *buf, fuse_fill_dir_t fill_func,
+ off_t offset, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ errcode_t err;
+ struct readdir_iter i;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ pthread_mutex_lock(&ff->bfl);
+ i.buf = buf;
+ i.func = fill_func;
+ err = ext2fs_dir_iterate2(fs, fh->ino, 0, NULL, op_readdir_iter, &i);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ if (fs_writeable(fs)) {
+ ret = update_atime(fs, fh->ino);
+ if (ret)
+ goto out;
+ }
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_access(const char *path, int mask)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s mask=0x%x\n", __func__, path, mask);
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err || ino == 0) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+
+ ret = check_inum_access(fs, ino, mask);
+ if (ret)
+ goto out;
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+static int op_create(const char *path, mode_t mode, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t parent, child;
+ char *temp_path = strdup(path);
+ errcode_t err;
+ char *node_name, a;
+ int filetype;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ dbg_printf("%s: path=%s mode=0%o\n", __func__, path, mode);
+ if (!temp_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name = strrchr(temp_path, '/');
+ if (!node_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ node_name++;
+ a = *node_name;
+ *node_name = 0;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_can_allocate(ff, 1)) {
+ ret = -ENOSPC;
+ goto out2;
+ }
+
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, temp_path,
+ &parent);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out2;
+ }
+
+ ret = check_inum_access(fs, parent, W_OK);
+ if (ret)
+ goto out2;
+
+ *node_name = a;
+
+ filetype = ext2_file_type(mode);
+
+ err = ext2fs_new_inode(fs, parent, mode, 0, &child);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ dbg_printf("%s: creating ino=%d/name=%s in dir=%d\n", __func__, child,
+ node_name, parent);
+ err = ext2fs_link(fs, parent, node_name, child, filetype);
+ if (err == EXT2_ET_DIR_NO_SPACE) {
+ err = ext2fs_expand_dir(fs, parent);
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ err = ext2fs_link(fs, parent, node_name, child,
+ filetype);
+ }
+ if (err) {
+ ret = translate_error(fs, parent, err);
+ goto out2;
+ }
+
+ ret = update_mtime(fs, parent, NULL);
+ if (ret)
+ goto out2;
+
+ memset(&inode, 0, sizeof(inode));
+ inode.i_mode = mode;
+ inode.i_links_count = 1;
+ inode.i_extra_isize = sizeof(struct ext2_inode_large) -
+ EXT2_GOOD_OLD_INODE_SIZE;
+ if (fs->super->s_feature_incompat & EXT3_FEATURE_INCOMPAT_EXTENTS) {
+ ext2_extent_handle_t handle;
+
+ inode.i_flags &= ~EXT4_EXTENTS_FL;
+ ret = ext2fs_extent_open2(fs, child,
+ (struct ext2_inode *)&inode, &handle);
+ if (ret)
+ return ret;
+ ext2fs_extent_free(handle);
+ }
+
+ err = ext2fs_write_new_inode(fs, child, (struct ext2_inode *)&inode);
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ inode.i_generation = ff->next_generation++;
+ init_times(&inode);
+ err = ext2fs_write_inode_full(fs, child, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, child, err);
+ goto out2;
+ }
+
+ ext2fs_inode_alloc_stats2(fs, child, 1, 0);
+
+ ret = __op_open(ff, path, fp);
+ if (ret)
+ goto out2;
+out2:
+ pthread_mutex_unlock(&ff->bfl);
+out:
+ free(temp_path);
+ return ret;
+}
+
+static int op_ftruncate(const char *path, off_t len, struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ ext2_file_t efp;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d len=%jd\n", __func__, fh->ino, len);
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_writeable(fs)) {
+ ret = -EROFS;
+ goto out;
+ }
+
+ err = ext2fs_file_open(fs, fh->ino, fh->open_flags, &efp);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ err = ext2fs_file_set_size2(efp, len);
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out2;
+ }
+
+out2:
+ err = ext2fs_file_close(efp);
+ if (ret)
+ goto out;
+ if (err) {
+ ret = translate_error(fs, fh->ino, err);
+ goto out;
+ }
+
+ ret = update_mtime(fs, fh->ino, NULL);
+ if (ret)
+ goto out;
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return 0;
+}
+
+static int op_fgetattr(const char *path, struct stat *statbuf,
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ pthread_mutex_lock(&ff->bfl);
+ ret = stat_inode(fs, fh->ino, statbuf);
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+
+static int op_utimens(const char *path, const struct timespec ctv[2])
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct timespec *tv = (struct timespec *)ctv;
+ ext2_filsys fs;
+ errcode_t err;
+ ext2_ino_t ino;
+ struct ext2_inode_large inode;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d\n", __func__, ino);
+
+ ret = check_inum_access(fs, ino, W_OK);
+ if (ret)
+ goto out;
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+#ifdef UTIME_NOW
+ if (tv[0].tv_nsec == UTIME_NOW)
+ get_now(tv);
+ if (tv[1].tv_nsec == UTIME_NOW)
+ get_now(tv + 1);
+#endif /* UTIME_NOW */
+#ifdef UTIME_OMIT
+ if (tv[0].tv_nsec != UTIME_OMIT)
+ EXT4_INODE_SET_XTIME(i_atime, tv, &inode);
+ if (tv[1].tv_nsec != UTIME_OMIT)
+ EXT4_INODE_SET_XTIME(i_mtime, tv + 1, &inode);
+#endif /* UTIME_OMIT */
+ ret = update_ctime(fs, ino, &inode);
+ if (ret)
+ goto out;
+
+ err = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+#ifdef SUPPORT_I_FLAGS
+static int ioctl_getflags(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ *(__u32 *)data = inode.i_flags & EXT2_FL_USER_VISIBLE;
+ return 0;
+}
+
+#define FUSE2FS_MODIFIABLE_IFLAGS \
+ (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL | EXT2_NODUMP_FL | \
+ EXT2_NOATIME_FL | EXT3_JOURNAL_DATA_FL | EXT2_DIRSYNC_FL | \
+ EXT2_TOPDIR_FL)
+
+static int ioctl_setflags(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret;
+ __u32 flags = *(__u32 *)data;
+ struct fuse_context *ctxt = fuse_get_context();
+
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ if (ctxt->uid != 0 && inode.i_uid != ctxt->uid)
+ return -EPERM;
+
+ if ((inode.i_flags ^ flags) & ~FUSE2FS_MODIFIABLE_IFLAGS)
+ return -EINVAL;
+
+ inode.i_flags = (inode.i_flags & ~FUSE2FS_MODIFIABLE_IFLAGS) |
+ (flags & FUSE2FS_MODIFIABLE_IFLAGS);
+
+ ret = update_ctime(fs, fh->ino, &inode);
+ if (ret)
+ return ret;
+
+ err = ext2fs_write_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+
+static int ioctl_getversion(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ *(__u32 *)data = inode.i_generation;
+ return 0;
+}
+
+static int ioctl_setversion(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ errcode_t err;
+ struct ext2_inode_large inode;
+ int ret;
+ __u32 generation = *(__u32 *)data;
+ struct fuse_context *ctxt = fuse_get_context();
+
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: ino=%d\n", __func__, fh->ino);
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ if (ctxt->uid != 0 && inode.i_uid != ctxt->uid)
+ return -EPERM;
+
+ inode.i_generation = generation;
+
+ ret = update_ctime(fs, fh->ino, &inode);
+ if (ret)
+ return ret;
+
+ err = ext2fs_write_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+#endif /* SUPPORT_I_FLAGS */
+
+#ifdef FITRIM
+static int ioctl_fitrim(ext2_filsys fs, struct fuse2fs_file_handle *fh,
+ void *data)
+{
+ struct fstrim_range *fr = data;
+ blk64_t start, end, max_blocks, b, cleared;
+ errcode_t err = 0;
+
+ start = fr->start / fs->blocksize;
+ end = (fr->start + fr->len - 1) / fs->blocksize;
+ dbg_printf("%s: start=%llu end=%llu\n", __func__, start, end);
+
+ if (start < fs->super->s_first_data_block)
+ start = fs->super->s_first_data_block;
+ if (start >= ext2fs_blocks_count(fs->super))
+ start = ext2fs_blocks_count(fs->super) - 1;
+
+ if (end < fs->super->s_first_data_block)
+ end = fs->super->s_first_data_block;
+ if (end >= ext2fs_blocks_count(fs->super))
+ end = ext2fs_blocks_count(fs->super) - 1;
+
+ cleared = 0;
+ max_blocks = 2048ULL * 1024 * 1024 / fs->blocksize;
+
+ fr->len = 0;
+ while (start <= end) {
+ err = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
+ start, end, &start);
+ if (err == ENOENT)
+ return 0;
+ else if (err)
+ return translate_error(fs, fh->ino, err);
+
+ b = start + max_blocks < end ? start + max_blocks : end;
+ err = ext2fs_find_first_set_block_bitmap2(fs->block_map,
+ start, b, &b);
+ if (err && err != ENOENT)
+ return translate_error(fs, fh->ino, err);
+ if (b - start >= fr->minlen) {
+ err = io_channel_discard(fs->io, start, b - start);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ cleared += b - start;
+ fr->len = cleared * fs->blocksize;
+ }
+ start = b + 1;
+ }
+
+ return err;
+}
+#endif /* FITRIM */
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+static int op_ioctl(const char *path, int cmd, void *arg,
+ struct fuse_file_info *fp, unsigned int flags, void *data)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ switch (cmd) {
+#ifdef SUPPORT_I_FLAGS
+ case EXT2_IOC_GETFLAGS:
+ ret = ioctl_getflags(fs, fh, data);
+ break;
+ case EXT2_IOC_SETFLAGS:
+ ret = ioctl_setflags(fs, fh, data);
+ break;
+ case EXT2_IOC_GETVERSION:
+ ret = ioctl_getversion(fs, fh, data);
+ break;
+ case EXT2_IOC_SETVERSION:
+ ret = ioctl_setversion(fs, fh, data);
+ break;
+#endif
+#ifdef FITRIM
+ case FITRIM:
+ ret = ioctl_fitrim(fs, fh, data);
+ break;
+#endif
+ default:
+ dbg_printf("%s: Unknown ioctl %d\n", __func__, cmd);
+ ret = -ENOTTY;
+ }
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+#endif /* FUSE 28 */
+
+static int op_bmap(const char *path, size_t blocksize, uint64_t *idx)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs;
+ ext2_ino_t ino;
+ errcode_t err;
+ int ret = 0;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ pthread_mutex_lock(&ff->bfl);
+ err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, path, &ino);
+ if (err) {
+ ret = translate_error(fs, 0, err);
+ goto out;
+ }
+ dbg_printf("%s: ino=%d blk=%"PRIu64"\n", __func__, ino, *idx);
+
+ err = ext2fs_bmap2(fs, ino, NULL, NULL, 0, *idx, 0, (blk64_t *)idx);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&ff->bfl);
+ return ret;
+}
+
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
+# ifdef SUPPORT_FALLOCATE
+static int fallocate_helper(struct fuse_file_info *fp, int mode, off_t offset,
+ off_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ struct ext2_inode_large inode;
+ blk64_t start, end;
+ __u64 fsize;
+ errcode_t err;
+ int flags;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ start = offset / fs->blocksize;
+ end = (offset + len - 1) / fs->blocksize;
+ dbg_printf("%s: ino=%d mode=0x%x start=%jd end=%llu\n", __func__,
+ fh->ino, mode, offset / fs->blocksize, end);
+ if (!fs_can_allocate(ff, len / fs->blocksize))
+ return -ENOSPC;
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return err;
+ fsize = EXT2_I_SIZE(&inode);
+
+ /* Allocate a bunch of blocks */
+ flags = (mode & FL_KEEP_SIZE_FLAG ? 0 :
+ EXT2_FALLOCATE_INIT_BEYOND_EOF);
+ err = ext2fs_fallocate(fs, flags, fh->ino,
+ (struct ext2_inode *)&inode,
+ ~0ULL, start, end - start + 1);
+ if (err && err != EXT2_ET_BLOCK_ALLOC_FAIL)
+ return translate_error(fs, fh->ino, err);
+
+ /* Update i_size */
+ if (!(mode & FL_KEEP_SIZE_FLAG)) {
+ if (offset + len > fsize) {
+ err = ext2fs_inode_size_set(fs,
+ (struct ext2_inode *)&inode,
+ offset + len);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ }
+ }
+
+ err = update_mtime(fs, fh->ino, &inode);
+ if (err)
+ return err;
+
+ err = ext2fs_write_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return err;
+}
+
+static errcode_t clean_block_middle(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *inode, off_t offset,
+ off_t len, char **buf)
+{
+ blk64_t blk;
+ off_t residue;
+ int retflags;
+ errcode_t err;
+
+ residue = offset % fs->blocksize;
+ if (residue == 0)
+ return 0;
+
+ if (!*buf) {
+ err = ext2fs_get_mem(fs->blocksize, buf);
+ if (err)
+ return err;
+ }
+
+ err = ext2fs_bmap2(fs, ino, (struct ext2_inode *)inode, *buf, 0,
+ offset / fs->blocksize, &retflags, &blk);
+ if (err)
+ return err;
+ if (!blk || (retflags & BMAP_RET_UNINIT))
+ return 0;
+
+ err = io_channel_read_blk(fs->io, blk, 1, *buf);
+ if (err)
+ return err;
+
+ memset(*buf + residue, 0, len);
+
+ return io_channel_write_blk(fs->io, blk, 1, *buf);
+}
+
+static errcode_t clean_block_edge(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *inode, off_t offset,
+ int clean_before, char **buf)
+{
+ blk64_t blk;
+ int retflags;
+ off_t residue;
+ errcode_t err;
+
+ residue = offset % fs->blocksize;
+ if (residue == 0)
+ return 0;
+
+ if (!*buf) {
+ err = ext2fs_get_mem(fs->blocksize, buf);
+ if (err)
+ return err;
+ }
+
+ err = ext2fs_bmap2(fs, ino, (struct ext2_inode *)inode, *buf, 0,
+ offset / fs->blocksize, &retflags, &blk);
+ if (err)
+ return err;
+
+ err = io_channel_read_blk(fs->io, blk, 1, *buf);
+ if (err)
+ return err;
+ if (!blk || (retflags & BMAP_RET_UNINIT))
+ return 0;
+
+ if (clean_before)
+ memset(*buf, 0, residue);
+ else
+ memset(*buf + residue, 0, fs->blocksize - residue);
+
+ return io_channel_write_blk(fs->io, blk, 1, *buf);
+}
+
+static int punch_helper(struct fuse_file_info *fp, int mode, off_t offset,
+ off_t len)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ struct fuse2fs_file_handle *fh = (struct fuse2fs_file_handle *)fp->fh;
+ ext2_filsys fs;
+ struct ext2_inode_large inode;
+ blk64_t start, end;
+ errcode_t err;
+ char *buf = NULL;
+
+ FUSE2FS_CHECK_CONTEXT(ff);
+ fs = ff->fs;
+ FUSE2FS_CHECK_MAGIC(fs, fh, FUSE2FS_FILE_MAGIC);
+ dbg_printf("%s: offset=%jd len=%jd\n", __func__, offset, len);
+
+ /* kernel ext4 punch requires this flag to be set */
+ if (!(mode & FL_KEEP_SIZE_FLAG))
+ return -EINVAL;
+
+ /* Punch out a bunch of blocks */
+ start = (offset + fs->blocksize - 1) / fs->blocksize;
+ end = (offset + len - fs->blocksize) / fs->blocksize;
+ dbg_printf("%s: ino=%d mode=0x%x start=%llu end=%llu\n", __func__,
+ fh->ino, mode, start, end);
+
+ memset(&inode, 0, sizeof(inode));
+ err = ext2fs_read_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ /* Zero everything before the first block and after the last block */
+ if ((offset / fs->blocksize) == ((offset + len) / fs->blocksize))
+ err = clean_block_middle(fs, fh->ino, &inode, offset,
+ len, &buf);
+ else {
+ err = clean_block_edge(fs, fh->ino, &inode, offset, 0, &buf);
+ if (!err)
+ err = clean_block_edge(fs, fh->ino, &inode,
+ offset + len, 1, &buf);
+ }
+ if (buf)
+ ext2fs_free_mem(&buf);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ /* Unmap full blocks in the middle */
+ if (start <= end) {
+ err = ext2fs_punch(fs, fh->ino, (struct ext2_inode *)&inode,
+ NULL, start, end);
+ if (err)
+ return translate_error(fs, fh->ino, err);
+ }
+
+ err = update_mtime(fs, fh->ino, &inode);
+ if (err)
+ return err;
+
+ err = ext2fs_write_inode_full(fs, fh->ino, (struct ext2_inode *)&inode,
+ sizeof(inode));
+ if (err)
+ return translate_error(fs, fh->ino, err);
+
+ return 0;
+}
+
+static int op_fallocate(const char *path, int mode, off_t offset, off_t len,
+ struct fuse_file_info *fp)
+{
+ struct fuse_context *ctxt = fuse_get_context();
+ struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
+ ext2_filsys fs = ff->fs;
+ int ret;
+
+ /* Catch unknown flags */
+ if (mode & ~(FL_PUNCH_HOLE_FLAG | FL_KEEP_SIZE_FLAG))
+ return -EINVAL;
+
+ pthread_mutex_lock(&ff->bfl);
+ if (!fs_writeable(fs)) {
+ ret = -EROFS;
+ goto out;
+ }
+ if (mode & FL_PUNCH_HOLE_FLAG)
+ ret = punch_helper(fp, mode, offset, len);
+ else
+ ret = fallocate_helper(fp, mode, offset, len);
+out:
+ pthread_mutex_unlock(&ff->bfl);
+
+ return ret;
+}
+# endif /* SUPPORT_FALLOCATE */
+#endif /* FUSE 29 */
+
+static struct fuse_operations fs_ops = {
+ .init = op_init,
+ .destroy = op_destroy,
+ .getattr = op_getattr,
+ .readlink = op_readlink,
+ .mknod = op_mknod,
+ .mkdir = op_mkdir,
+ .unlink = op_unlink,
+ .rmdir = op_rmdir,
+ .symlink = op_symlink,
+ .rename = op_rename,
+ .link = op_link,
+ .chmod = op_chmod,
+ .chown = op_chown,
+ .truncate = op_truncate,
+ .open = op_open,
+ .read = op_read,
+ .write = op_write,
+ .statfs = op_statfs,
+ .release = op_release,
+ .fsync = op_fsync,
+ .setxattr = op_setxattr,
+ .getxattr = op_getxattr,
+ .listxattr = op_listxattr,
+ .removexattr = op_removexattr,
+ .opendir = op_open,
+ .readdir = op_readdir,
+ .releasedir = op_release,
+ .fsyncdir = op_fsync,
+ .access = op_access,
+ .create = op_create,
+ .ftruncate = op_ftruncate,
+ .fgetattr = op_fgetattr,
+ .utimens = op_utimens,
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
+# if defined(UTIME_NOW) || defined(UTIME_OMIT)
+ .flag_utime_omit_ok = 1,
+# endif
+#endif
+ .bmap = op_bmap,
+#ifdef SUPERFLUOUS
+ .lock = op_lock,
+ .poll = op_poll,
+#endif
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 8)
+ .ioctl = op_ioctl,
+ .flag_nullpath_ok = 1,
+#endif
+#if FUSE_VERSION >= FUSE_MAKE_VERSION(2, 9)
+ .flag_nopath = 1,
+# ifdef SUPPORT_FALLOCATE
+ .fallocate = op_fallocate,
+# endif
+#endif
+};
+
+static int get_random_bytes(void *p, size_t sz)
+{
+ int fd;
+ ssize_t r;
+
+ fd = open("/dev/random", O_RDONLY);
+ if (fd < 0) {
+ perror("/dev/random");
+ return 0;
+ }
+
+ r = read(fd, p, sz);
+
+ close(fd);
+ return r == sz;
+}
+
+static void print_help(const char *progname)
+{
+ printf(_("Usage: %s dev mntpt [-o options] [fuse_args]\n"), progname);
+}
+
+int main(int argc, char *argv[])
+{
+ errcode_t err;
+ char *tok, *arg, *logfile;
+ int i;
+ int readwrite = 1, panic_on_error = 0, minixdf = 0;
+ struct fuse2fs *ff;
+ char extra_args[BUFSIZ];
+ int ret = 0, flags = EXT2_FLAG_64BITS | EXT2_FLAG_EXCLUSIVE;
+
+ if (argc < 2) {
+ print_help(argv[0]);
+ return 1;
+ }
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "--help") == 0) {
+ print_help(argv[0]);
+ return 1;
+ }
+ }
+
+ for (i = 1; i < argc - 1; i++) {
+ if (strcmp(argv[i], "-o"))
+ continue;
+ arg = argv[i + 1];
+ while ((tok = strtok(arg, ","))) {
+ arg = NULL;
+ if (!strcmp(tok, "ro"))
+ readwrite = 0;
+ else if (!strcmp(tok, "errors=panic"))
+ panic_on_error = 1;
+ else if (!strcmp(tok, "minixdf"))
+ minixdf = 1;
+ }
+ }
+
+ if (!readwrite)
+ printf("%s", _("Mounting read-only.\n"));
+
+#ifdef ENABLE_NLS
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+ set_com_err_gettext(gettext);
+#endif
+ add_error_table(&et_ext2_error_table);
+
+ ff = calloc(1, sizeof(*ff));
+ if (!ff) {
+ perror("init");
+ return 1;
+ }
+ ff->magic = FUSE2FS_MAGIC;
+ ff->panic_on_error = panic_on_error;
+ ff->minixdf = minixdf;
+
+ /* Set up error logging */
+ logfile = getenv("FUSE2FS_LOGFILE");
+ if (logfile) {
+ ff->err_fp = fopen(logfile, "a");
+ if (!ff->err_fp) {
+ perror(logfile);
+ goto out_nofs;
+ }
+ } else
+ ff->err_fp = stderr;
+
+ /* Will we allow users to allocate every last block? */
+ if (getenv("FUSE2FS_ALLOC_ALL_BLOCKS")) {
+ printf(_("%s: Allowing users to allocate all blocks. "
+ "This is dangerous!\n"), argv[1]);
+ ff->alloc_all_blocks = 1;
+ }
+
+ /* Start up the fs (while we still can use stdout) */
+ ret = 2;
+ if (readwrite)
+ flags |= EXT2_FLAG_RW;
+ err = ext2fs_open2(argv[1], NULL, flags, 0, 0, unix_io_manager,
+ &global_fs);
+ if (err) {
+ printf(_("%s: %s.\n"), argv[1], error_message(err));
+ printf(_("Please run e2fsck -fy %s.\n"), argv[1]);
+ goto out_nofs;
+ }
+ ff->fs = global_fs;
+ global_fs->priv_data = ff;
+
+ ret = 3;
+ if (EXT2_HAS_INCOMPAT_FEATURE(global_fs->super,
+ EXT3_FEATURE_INCOMPAT_RECOVER)) {
+ if (readwrite) {
+ printf(_("%s: recovering journal\n"), argv[1]);
+ err = ext2fs_run_ext3_journal(&global_fs);
+ if (err) {
+ printf(_("%s: %s.\n"), argv[1],
+ error_message(err));
+ printf(_("Please run e2fsck -fy %s.\n"),
+ argv[1]);
+ goto out;
+ }
+ global_fs->super->s_feature_incompat &=
+ ~EXT3_FEATURE_INCOMPAT_RECOVER;
+ ext2fs_mark_super_dirty(global_fs);
+ } else {
+ printf("%s", _("Journal needs recovery; running "
+ "`e2fsck -E journal_only' is required.\n"));
+ goto out;
+ }
+ }
+
+ if (readwrite) {
+ if (EXT2_HAS_COMPAT_FEATURE(global_fs->super,
+ EXT3_FEATURE_COMPAT_HAS_JOURNAL))
+ printf(_("%s: Writing to the journal is not supported.\n"),
+ argv[1]);
+ err = ext2fs_read_inode_bitmap(global_fs);
+ if (err) {
+ translate_error(global_fs, 0, err);
+ goto out;
+ }
+ err = ext2fs_read_block_bitmap(global_fs);
+ if (err) {
+ translate_error(global_fs, 0, err);
+ goto out;
+ }
+ }
+
+ if (!(global_fs->super->s_state & EXT2_VALID_FS))
+ printf("%s", _("Warning: Mounting unchecked fs, running e2fsck "
+ "is recommended.\n"));
+ if (global_fs->super->s_max_mnt_count > 0 &&
+ global_fs->super->s_mnt_count >= global_fs->super->s_max_mnt_count)
+ printf("%s", _("Warning: Maximal mount count reached, running "
+ "e2fsck is recommended.\n"));
+ if (global_fs->super->s_checkinterval > 0 &&
+ global_fs->super->s_lastcheck +
+ global_fs->super->s_checkinterval <= time(0))
+ printf("%s", _("Warning: Check time reached; running e2fsck "
+ "is recommended.\n"));
+ if (global_fs->super->s_last_orphan)
+ printf("%s",
+ _("Orphans detected; running e2fsck is recommended.\n"));
+
+ if (global_fs->super->s_state & EXT2_ERROR_FS) {
+ printf("%s",
+ _("Errors detected; running e2fsck is required.\n"));
+ goto out;
+ }
+
+ /* Initialize generation counter */
+ get_random_bytes(&ff->next_generation, sizeof(unsigned int));
+
+ /* Stuff in some fuse parameters of our own */
+ snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=ext4,use_ino,"
+ "fsname=%s,attr_timeout=0,allow_other" FUSE_PLATFORM_OPTS,
+ argv[1]);
+ argv[0] = argv[1];
+ argv[1] = argv[2];
+ argv[2] = extra_args;
+
+ pthread_mutex_init(&ff->bfl, NULL);
+ fuse_main(argc, argv, &fs_ops, ff);
+ pthread_mutex_destroy(&ff->bfl);
+
+ ret = 0;
+out:
+ err = ext2fs_close(global_fs);
+ if (err)
+ com_err(argv[0], err, "while closing fs");
+ global_fs = NULL;
+out_nofs:
+ free(ff);
+
+ return ret;
+}
+
+static int __translate_error(ext2_filsys fs, errcode_t err, ext2_ino_t ino,
+ const char *file, int line)
+{
+ struct timespec now;
+ int ret = err;
+ struct fuse2fs *ff = fs->priv_data;
+ int is_err = 0;
+
+ /* Translate ext2 error to unix error code */
+ if (err < EXT2_ET_BASE)
+ goto no_translation;
+ switch (err) {
+ case EXT2_ET_NO_MEMORY:
+ case EXT2_ET_TDB_ERR_OOM:
+ ret = -ENOMEM;
+ break;
+ case EXT2_ET_INVALID_ARGUMENT:
+ case EXT2_ET_LLSEEK_FAILED:
+ ret = -EINVAL;
+ break;
+ case EXT2_ET_NO_DIRECTORY:
+ ret = -ENOTDIR;
+ break;
+ case EXT2_ET_FILE_NOT_FOUND:
+ ret = -ENOENT;
+ break;
+ case EXT2_ET_DIR_NO_SPACE:
+ is_err = 1;
+ case EXT2_ET_TOOSMALL:
+ case EXT2_ET_BLOCK_ALLOC_FAIL:
+ case EXT2_ET_INODE_ALLOC_FAIL:
+ case EXT2_ET_EA_NO_SPACE:
+ ret = -ENOSPC;
+ break;
+ case EXT2_ET_SYMLINK_LOOP:
+ ret = -EMLINK;
+ break;
+ case EXT2_ET_FILE_TOO_BIG:
+ ret = -EFBIG;
+ break;
+ case EXT2_ET_TDB_ERR_EXISTS:
+ case EXT2_ET_FILE_EXISTS:
+ ret = -EEXIST;
+ break;
+ case EXT2_ET_MMP_FAILED:
+ case EXT2_ET_MMP_FSCK_ON:
+ ret = -EBUSY;
+ break;
+ case EXT2_ET_EA_KEY_NOT_FOUND:
+#ifdef ENODATA
+ ret = -ENODATA;
+#else
+ ret = -ENOENT;
+#endif
+ break;
+ /* Sometimes fuse returns a garbage file handle pointer to us... */
+ case EXT2_ET_MAGIC_EXT2_FILE:
+ ret = -EFAULT;
+ break;
+ case EXT2_ET_UNIMPLEMENTED:
+ ret = -EOPNOTSUPP;
+ break;
+ default:
+ is_err = 1;
+ ret = -EIO;
+ break;
+ }
+
+no_translation:
+ if (!is_err)
+ return ret;
+
+ if (ino)
+ fprintf(ff->err_fp, "FUSE2FS (%s): %s (inode #%d) at %s:%d.\n",
+ fs && fs->device_name ? fs->device_name : "???",
+ error_message(err), ino, file, line);
+ else
+ fprintf(ff->err_fp, "FUSE2FS (%s): %s at %s:%d.\n",
+ fs && fs->device_name ? fs->device_name : "???",
+ error_message(err), file, line);
+ fflush(ff->err_fp);
+
+ /* Make a note in the error log */
+ get_now(&now);
+ fs->super->s_last_error_time = now.tv_sec;
+ fs->super->s_last_error_ino = ino;
+ fs->super->s_last_error_line = line;
+ fs->super->s_last_error_block = err; /* Yeah... */
+ strncpy((char *)fs->super->s_last_error_func, file,
+ sizeof(fs->super->s_last_error_func));
+ if (fs->super->s_first_error_time == 0) {
+ fs->super->s_first_error_time = now.tv_sec;
+ fs->super->s_first_error_ino = ino;
+ fs->super->s_first_error_line = line;
+ fs->super->s_first_error_block = err;
+ strncpy((char *)fs->super->s_first_error_func, file,
+ sizeof(fs->super->s_first_error_func));
+ }
+
+ fs->super->s_error_count++;
+ ext2fs_mark_super_dirty(fs);
+ ext2fs_flush(fs);
+ if (ff->panic_on_error)
+ abort();
+
+ return ret;
+}