e2fsprogs/misc/fuse2fs.c
Andreas Dilger ca8bc9240a Add post-2038 timestamp support to e2fsprogs
The ext4 kernel code implemented support for s_mtime_hi,
s_wtime_hi, and related timestamp fields to avoid timestamp
overflow in 2038, but similar handling is not in e2fsprogs.

Add helper macros for the superblock _hi timestamp fields
ext2fs_super_tstamp_get() and ext2fs_super_tstamp_set().

Add helper macro for inode _extra timestamp fields
ext2fs_inode_xtime_get() and ext2fs_inode_xtime_set().

Add helper macro ext2fs_actual_inode_size() to avoid open
coding the i_extra_isize check in multiple places.

Remove inode_time_to_string() since this is unused once callers
change to time_to_string(ext2fs_inode_xtime_get()) directly.

Fix inode_includes() macro to properly wrap "inode" parameter,
and rename to ext2fs_inode_includes() to avoid potential name
clashes.  Use this to check inode field inclusion in debugfs
instead of bare constants for inode field offsets.

Use these interfaces to access timestamps in debugfs, e2fsck,
libext2fs, fuse2fs, tune2fs, and e2undo.

Signed-off-by: Andreas Dilger <adilger@dilger.ca>
Link: https://lore.kernel.org/r/20230927054016.16645-1-adilger@dilger.ca
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
2024-03-31 13:54:33 -04:00

4010 lines
92 KiB
C

/*
* 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
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "config.h"
#include <pthread.h>
#ifdef __linux__
# include <linux/fs.h>
# include <linux/falloc.h>
# include <linux/xattr.h>
# define FUSE_PLATFORM_OPTS ",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"
#include "../version.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);
#ifdef CONFIG_JBD_DEBUG /* Enabled by configure --enable-jbd-debug */
int journal_enable_debug = -1;
#endif
/* 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;
#if __GNUC_PREREQ (4, 8)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
acl_ea_entry a_entries[0];
#if __GNUC_PREREQ (4, 8)
#pragma GCC diagnostic pop
#endif
} 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;
unsigned char *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 = (unsigned char *) (h + 1);
for (i = 0, a = facl->a_entries; i < facl_count; i++, a++) {
e = (ext4_acl_entry *) 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;
unsigned char *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 = (unsigned char *) (eacl + 1);
for (i = 0, a = f->a_entries; i < eacl_count; i++, a++) {
e = (ext4_acl_entry *) 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;
char *device;
int ro;
int debug;
int no_default_opts;
int panic_on_error;
int minixdf;
int fakeroot;
int alloc_all_blocks;
int norecovery;
unsigned long offset;
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)) \
<= ((size_t) 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, mode_t mask)
{
struct fuse_context *ctxt = fuse_get_context();
struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
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_uid(inode),
inode_gid(inode), 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 (ff->fakeroot || 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_uid(inode) == ctxt->uid) {
if ((mask & (perms >> 6)) == mask)
return 0;
return -EACCES;
}
/* allow group, if perms match */
if (inode_gid(inode) == 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 EXT2FS_ATTR((unused)))
{
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++;
ext2fs_set_tstamp(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 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_uid(inode);
statbuf->st_gid = inode_gid(inode);
statbuf->st_size = EXT2_I_SIZE(&inode);
statbuf->st_blksize = fs->blocksize;
statbuf->st_blocks = ext2fs_get_stat_i_blocks(fs,
(struct ext2_inode *)&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_is_fast_symlink(&inode))
memcpy(buf, (char *)inode.i_block, len);
else {
/* 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;
}
}
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;
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);
temp_path = strdup(path);
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;
inode.i_uid = ctxt->uid;
ext2fs_set_i_uid_high(inode, ctxt->uid >> 16);
inode.i_gid = ctxt->gid;
ext2fs_set_i_gid_high(inode, ctxt->gid >> 16);
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;
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);
temp_path = strdup(path);
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;
ext2fs_set_i_uid_high(inode, ctxt->uid >> 16);
inode.i_gid = ctxt->gid;
ext2fs_set_i_gid_high(inode, ctxt->gid >> 16);
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) ||
!ext2fs_has_feature_metadata_csum(fs->super))
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;
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);
temp_path = strdup(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, 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, 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;
ext2fs_set_i_uid_high(inode, ctxt->uid >> 16);
inode.i_gid = ctxt->gid;
ext2fs_set_i_gid_high(inode, ctxt->gid >> 16);
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 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 *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;
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);
temp_path = strdup(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 (!ff->fakeroot && ctxt->uid != 0 && ctxt->uid != inode_uid(inode)) {
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 (!ff->fakeroot && ctxt->uid != 0 && ctxt->gid != inode_gid(inode))
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 != (uid_t) ~0) {
/* Only root gets to change UID. */
if (!ff->fakeroot && ctxt->uid != 0 &&
!(inode_uid(inode) == ctxt->uid && owner == ctxt->uid)) {
ret = -EPERM;
goto out;
}
inode.i_uid = owner;
ext2fs_set_i_uid_high(inode, owner >> 16);
}
if (group != (gid_t) ~0) {
/* Only root or the owner get to change GID. */
if (!ff->fakeroot && ctxt->uid != 0 &&
inode_uid(inode) != ctxt->uid) {
ret = -EPERM;
goto out;
}
/* XXX: We /should/ check group membership but FUSE */
inode.i_gid = group;
ext2fs_set_i_gid_high(inode, group >> 16);
}
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 = (uintptr_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 EXT2FS_ATTR((unused)), 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 *)(uintptr_t)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 ? (int) got : ret;
}
static int op_write(const char *path EXT2FS_ATTR((unused)),
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 *)(uintptr_t)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 ? (int) got : ret;
}
static int op_release(const char *path EXT2FS_ATTR((unused)),
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 *)(uintptr_t)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 EXT2FS_ATTR((unused)),
int datasync EXT2FS_ATTR((unused)),
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 *)(uintptr_t)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 EXT2FS_ATTR((unused)),
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 +
(blk64_t)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,
const 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 (!ext2fs_has_feature_xattr(fs->super)) {
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 EXT2FS_ATTR((unused)),
size_t value_len EXT2FS_ATTR((unused)),
void *data)
{
unsigned int *x = data;
*x = *x + strlen(name) + 1;
return 0;
}
static int copy_names(char *name, char *value EXT2FS_ATTR((unused)),
size_t value_len EXT2FS_ATTR((unused)), void *data)
{
char **b = data;
size_t name_len = strlen(name);
memcpy(*b, name, name_len + 1);
*b = *b + name_len + 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 (!ext2fs_has_feature_xattr(fs->super)) {
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 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;
}
/* 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 EXT2FS_ATTR((unused)),
const char *key, const char *value,
size_t len, int flags EXT2FS_ATTR((unused)))
{
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;
const 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 (!ext2fs_has_feature_xattr(fs->super)) {
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 = 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;
}
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 (!ext2fs_has_feature_xattr(fs->super)) {
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;
}
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 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 *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 EXT2FS_ATTR((unused)),
void *buf, fuse_fill_dir_t fill_func,
off_t offset EXT2FS_ATTR((unused)),
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 *)(uintptr_t)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;
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);
temp_path = strdup(path);
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;
inode.i_uid = ctxt->uid;
ext2fs_set_i_uid_high(inode, ctxt->uid >> 16);
inode.i_gid = ctxt->gid;
ext2fs_set_i_gid_high(inode, ctxt->gid >> 16);
if (ext2fs_has_feature_extents(fs->super)) {
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 EXT2FS_ATTR((unused)),
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 *)(uintptr_t)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 EXT2FS_ATTR((unused)),
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 *)(uintptr_t)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[2];
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;
}
tv[0] = ctv[0];
tv[1] = ctv[1];
#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();
struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
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 (!ff->fakeroot && ctxt->uid != 0 && inode_uid(inode) != 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();
struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data;
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 (!ff->fakeroot && ctxt->uid != 0 && inode_uid(inode) != 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 EXT2FS_ATTR((unused)), int cmd,
void *arg EXT2FS_ATTR((unused)),
struct fuse_file_info *fp,
unsigned int flags EXT2FS_ATTR((unused)), 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 *)(uintptr_t)fp->fh;
ext2_filsys fs;
int ret = 0;
FUSE2FS_CHECK_CONTEXT(ff);
fs = ff->fs;
pthread_mutex_lock(&ff->bfl);
switch ((unsigned long) 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 EXT2FS_ATTR((unused)),
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 *)(uintptr_t)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 ((__u64) 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 *)(uintptr_t)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 EXT2FS_ATTR((unused)), 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/urandom", O_RDONLY);
if (fd < 0) {
perror("/dev/urandom");
return 0;
}
r = read(fd, p, sz);
close(fd);
return (size_t) r == sz;
}
enum {
FUSE2FS_VERSION,
FUSE2FS_HELP,
FUSE2FS_HELPFULL,
};
#define FUSE2FS_OPT(t, p, v) { t, offsetof(struct fuse2fs, p), v }
static struct fuse_opt fuse2fs_opts[] = {
FUSE2FS_OPT("ro", ro, 1),
FUSE2FS_OPT("errors=panic", panic_on_error, 1),
FUSE2FS_OPT("minixdf", minixdf, 1),
FUSE2FS_OPT("fakeroot", fakeroot, 1),
FUSE2FS_OPT("fuse2fs_debug", debug, 1),
FUSE2FS_OPT("no_default_opts", no_default_opts, 1),
FUSE2FS_OPT("norecovery", norecovery, 1),
FUSE2FS_OPT("offset=%lu", offset, 0),
FUSE_OPT_KEY("-V", FUSE2FS_VERSION),
FUSE_OPT_KEY("--version", FUSE2FS_VERSION),
FUSE_OPT_KEY("-h", FUSE2FS_HELP),
FUSE_OPT_KEY("--help", FUSE2FS_HELP),
FUSE_OPT_KEY("--helpfull", FUSE2FS_HELPFULL),
FUSE_OPT_END
};
static int fuse2fs_opt_proc(void *data, const char *arg,
int key, struct fuse_args *outargs)
{
struct fuse2fs *ff = data;
switch (key) {
case FUSE_OPT_KEY_NONOPT:
if (!ff->device) {
ff->device = strdup(arg);
return 0;
}
return 1;
case FUSE2FS_HELP:
case FUSE2FS_HELPFULL:
fprintf(stderr,
"usage: %s device/image mountpoint [options]\n"
"\n"
"general options:\n"
" -o opt,[opt...] mount options\n"
" -h --help print help\n"
" -V --version print version\n"
"\n"
"fuse2fs options:\n"
" -o ro read-only mount\n"
" -o errors=panic dump core on error\n"
" -o minixdf minix-style df\n"
" -o fakeroot pretend to be root for permission checks\n"
" -o no_default_opts do not include default fuse options\n"
" -o offset=<bytes> similar to mount -o offset=<bytes>, mount the partition starting at <bytes>\n"
" -o norecovery don't replay the journal (implies ro)\n"
" -o fuse2fs_debug enable fuse2fs debugging\n"
"\n",
outargs->argv[0]);
if (key == FUSE2FS_HELPFULL) {
fuse_opt_add_arg(outargs, "-ho");
fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
} else {
fprintf(stderr, "Try --helpfull to get a list of "
"all flags, including the FUSE options.\n");
}
exit(1);
case FUSE2FS_VERSION:
fprintf(stderr, "fuse2fs %s (%s)\n", E2FSPROGS_VERSION,
E2FSPROGS_DATE);
fuse_opt_add_arg(outargs, "--version");
fuse_main(outargs->argc, outargs->argv, &fs_ops, NULL);
exit(0);
}
return 1;
}
int main(int argc, char *argv[])
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct fuse2fs fctx;
errcode_t err;
char *logfile;
char extra_args[BUFSIZ];
int ret = 0;
int flags = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | EXT2_FLAG_EXCLUSIVE;
memset(&fctx, 0, sizeof(fctx));
fctx.magic = FUSE2FS_MAGIC;
fuse_opt_parse(&args, &fctx, fuse2fs_opts, fuse2fs_opt_proc);
if (fctx.device == NULL) {
fprintf(stderr, "Missing ext4 device/image\n");
fprintf(stderr, "See '%s -h' for usage\n", argv[0]);
exit(1);
}
if (fctx.norecovery)
fctx.ro = 1;
if (fctx.ro)
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);
/* Set up error logging */
logfile = getenv("FUSE2FS_LOGFILE");
if (logfile) {
fctx.err_fp = fopen(logfile, "a");
if (!fctx.err_fp) {
perror(logfile);
goto out;
}
} else
fctx.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"), fctx.device);
fctx.alloc_all_blocks = 1;
}
/* Start up the fs (while we still can use stdout) */
ret = 2;
if (!fctx.ro)
flags |= EXT2_FLAG_RW;
char options[50];
sprintf(options, "offset=%lu", fctx.offset);
err = ext2fs_open2(fctx.device, options, flags, 0, 0, unix_io_manager,
&global_fs);
if (err) {
printf(_("%s: %s.\n"), fctx.device, error_message(err));
printf(_("Please run e2fsck -fy %s.\n"), fctx.device);
goto out;
}
fctx.fs = global_fs;
global_fs->priv_data = &fctx;
ret = 3;
if (ext2fs_has_feature_journal_needs_recovery(global_fs->super)) {
if (fctx.norecovery) {
printf(_("%s: mounting read-only without "
"recovering journal\n"),
fctx.device);
} else if (!fctx.ro) {
printf(_("%s: recovering journal\n"), fctx.device);
err = ext2fs_run_ext3_journal(&global_fs);
if (err) {
printf(_("%s: %s.\n"), fctx.device,
error_message(err));
printf(_("Please run e2fsck -fy %s.\n"),
fctx.device);
goto out;
}
ext2fs_clear_feature_journal_needs_recovery(global_fs->super);
ext2fs_mark_super_dirty(global_fs);
} else {
printf("%s", _("Journal needs recovery; running "
"`e2fsck -E journal_only' is required.\n"));
goto out;
}
}
if (!fctx.ro) {
if (ext2fs_has_feature_journal(global_fs->super))
printf(_("%s: Writing to the journal is not supported.\n"),
fctx.device);
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 &&
(time_t) (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(&fctx.next_generation, sizeof(unsigned int));
/* Set up default fuse parameters */
snprintf(extra_args, BUFSIZ, "-okernel_cache,subtype=ext4,use_ino,"
"fsname=%s,attr_timeout=0" FUSE_PLATFORM_OPTS,
fctx.device);
if (fctx.no_default_opts == 0)
fuse_opt_add_arg(&args, extra_args);
if (fctx.fakeroot) {
#ifdef HAVE_MOUNT_NODEV
fuse_opt_add_arg(&args,"-onodev");
#endif
#ifdef HAVE_MOUNT_NOSUID
fuse_opt_add_arg(&args,"-onosuid");
#endif
}
if (fctx.debug) {
int i;
printf("fuse arguments:");
for (i = 0; i < args.argc; i++)
printf(" '%s'", args.argv[i]);
printf("\n");
}
pthread_mutex_init(&fctx.bfl, NULL);
fuse_main(args.argc, args.argv, &fs_ops, &fctx);
pthread_mutex_destroy(&fctx.bfl);
ret = 0;
out:
if (global_fs) {
err = ext2fs_close(global_fs);
if (err)
com_err(argv[0], err, "while closing fs");
global_fs = NULL;
}
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;
/* fallthrough */
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->device_name ? fs->device_name : "???",
error_message(err), ino, file, line);
else
fprintf(ff->err_fp, "FUSE2FS (%s): %s at %s:%d.\n",
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);
ext2fs_set_tstamp(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 (ext2fs_get_tstamp(fs->super, s_first_error_time) == 0) {
ext2fs_set_tstamp(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;
}