blob: 5c73895ee71357f9f4147488f9976d7b01cd3470 [file] [log] [blame]
/*
* 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 ",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"
#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 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)) \
<= ((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 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 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++;
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 (ext2fs_has_feature_huge_file(fs->super))
b += ((long long) inode->osd2.linux2.l_i_blocks_hi) << 32;
if (!ext2fs_has_feature_huge_file(fs->super) ||
!(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_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;
inode.i_gid = ctxt->gid;
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;
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) ||
!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;
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 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 (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 != (uid_t) ~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 != (gid_t) ~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 = (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 +
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;
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 (!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;</