lkml.org 
[lkml]   [2014]   [Jan]   [7]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
From
SubjectRe: [RFC PATCH] fs: xattr-based FS_IOC_[GS]ETFLAGS interface
On Mon 06-01-14 18:58:55, Darrick J. Wong wrote:
> This is a proof of concept interface for replacing the contentious
> FS_IOC_[GS]ETFLAGS interface with one that presents itself as the
> xattr 'system.iflags'. Instead of using integer inode flags, this
> interface uses a comma-separated string of words, such as
> "extents,immutable" to describe the inode flags. This ought to enable
> all filesystems to introduce arbitrary flags, with any sort of backend
> they want, while also taking advantage of generic fs flags.
>
> The initial conversion is for ext4, though given the similarities of
> most filesystems' implementations, converting the rest shouldn't be
> difficult. How we get everyone to agree on common flag names is
> anyone's guess.
>
> Includes some helper methods to handle the string<->int conversion,
> and a tweak to the generic xattr code to allow setting iflags on an
> immutable file.
I have to say I'm not thrilled by the idea of juggling strings in
userspace and in kernel to set a flag for an inode...

Honza

> Usage:
> # setfattr -n system.iflags -v 'extents,append' /path/file
> # getfattr -n system.iflags /path/file
> system.iflags="secrm,unrm,sync,immutable,nodump,noatime,journal_data,extents"
>
> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> ---
> fs/ext4/Makefile | 2
> fs/ext4/ext4.h | 3 +
> fs/ext4/ioctl.c | 198 ++++++++++++++++++++++++--------------------
> fs/ext4/xattr.c | 1
> fs/ext4/xattr.h | 1
> fs/ext4/xattr_iflags.c | 90 ++++++++++++++++++++
> fs/xattr.c | 9 ++
> include/linux/strflags.h | 27 ++++++
> include/uapi/linux/xattr.h | 2
> lib/Makefile | 2
> lib/strflags.c | 117 ++++++++++++++++++++++++++
> 11 files changed, 358 insertions(+), 94 deletions(-)
> create mode 100644 fs/ext4/xattr_iflags.c
> create mode 100644 include/linux/strflags.h
> create mode 100644 lib/strflags.c
>
> diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile
> index 0310fec..7126958 100644
> --- a/fs/ext4/Makefile
> +++ b/fs/ext4/Makefile
> @@ -8,7 +8,7 @@ ext4-y := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o page-io.o \
> ioctl.o namei.o super.o symlink.o hash.o resize.o extents.o \
> ext4_jbd2.o migrate.o mballoc.o block_validity.o move_extent.o \
> mmp.o indirect.o extents_status.o xattr.o xattr_user.o \
> - xattr_trusted.o inline.o
> + xattr_trusted.o inline.o xattr_iflags.o
>
> ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o
> ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index bf6c5cd..24837ab 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -2660,6 +2660,9 @@ extern void ext4_inline_data_truncate(struct inode *inode, int *has_inline);
>
> extern int ext4_convert_inline_data(struct inode *inode);
>
> +/* ioctl.c */
> +extern int ext4_set_iflags(struct inode *inode, int flags);
> +
> /* namei.c */
> extern const struct inode_operations ext4_dir_inode_operations;
> extern const struct inode_operations ext4_special_inode_operations;
> diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c
> index 60589b6..c6f21c9 100644
> --- a/fs/ext4/ioctl.c
> +++ b/fs/ext4/ioctl.c
> @@ -213,12 +213,115 @@ swap_boot_out:
> return err;
> }
>
> +/*
> + * Set inode flags. Assume we already have mnt_want_write_file and i_mutex.
> + */
> +int ext4_set_iflags(struct inode *inode, int flags)
> +{
> + handle_t *handle = NULL;
> + int err, migrate = 0;
> + struct ext4_iloc iloc;
> + unsigned int oldflags, mask, i;
> + unsigned int jflag;
> + struct ext4_inode_info *ei = EXT4_I(inode);
> +
> + if (!inode_owner_or_capable(inode))
> + return -EACCES;
> +
> + flags = ext4_mask_flags(inode->i_mode, flags);
> +
> + err = -EPERM;
> + /* Is it quota file? Do not allow user to mess with it */
> + if (IS_NOQUOTA(inode))
> + goto flags_out;
> +
> + oldflags = ei->i_flags;
> +
> + /* The JOURNAL_DATA flag is modifiable only by root */
> + jflag = flags & EXT4_JOURNAL_DATA_FL;
> +
> + /*
> + * The IMMUTABLE and APPEND_ONLY flags can only be changed by
> + * the relevant capability.
> + *
> + * This test looks nicer. Thanks to Pauline Middelink
> + */
> + if ((flags ^ oldflags) & (EXT4_APPEND_FL | EXT4_IMMUTABLE_FL)) {
> + if (!capable(CAP_LINUX_IMMUTABLE))
> + goto flags_out;
> + }
> +
> + /*
> + * The JOURNAL_DATA flag can only be changed by
> + * the relevant capability.
> + */
> + if ((jflag ^ oldflags) & (EXT4_JOURNAL_DATA_FL)) {
> + if (!capable(CAP_SYS_RESOURCE))
> + goto flags_out;
> + }
> + if ((flags ^ oldflags) & EXT4_EXTENTS_FL)
> + migrate = 1;
> +
> + if (flags & EXT4_EOFBLOCKS_FL) {
> + /* we don't support adding EOFBLOCKS flag */
> + if (!(oldflags & EXT4_EOFBLOCKS_FL)) {
> + err = -EOPNOTSUPP;
> + goto flags_out;
> + }
> + } else if (oldflags & EXT4_EOFBLOCKS_FL)
> + ext4_truncate(inode);
> +
> + handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
> + if (IS_ERR(handle)) {
> + err = PTR_ERR(handle);
> + goto flags_out;
> + }
> + if (IS_SYNC(inode))
> + ext4_handle_sync(handle);
> + err = ext4_reserve_inode_write(handle, inode, &iloc);
> + if (err)
> + goto flags_err;
> +
> + for (i = 0, mask = 1; i < 32; i++, mask <<= 1) {
> + if (!(mask & EXT4_FL_USER_MODIFIABLE))
> + continue;
> + if (mask & flags)
> + ext4_set_inode_flag(inode, i);
> + else
> + ext4_clear_inode_flag(inode, i);
> + }
> +
> + ext4_set_inode_flags(inode);
> + inode->i_ctime = ext4_current_time(inode);
> +
> + err = ext4_mark_iloc_dirty(handle, inode, &iloc);
> +flags_err:
> + ext4_journal_stop(handle);
> + if (err)
> + goto flags_out;
> +
> + if ((jflag ^ oldflags) & (EXT4_JOURNAL_DATA_FL))
> + err = ext4_change_inode_journal_flag(inode, jflag);
> + if (err)
> + goto flags_out;
> + if (migrate) {
> + if (flags & EXT4_EXTENTS_FL)
> + err = ext4_ext_migrate(inode);
> + else
> + err = ext4_ind_migrate(inode);
> + }
> +
> +flags_out:
> + return err;
> +}
> +
> long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> {
> struct inode *inode = file_inode(filp);
> struct super_block *sb = inode->i_sb;
> struct ext4_inode_info *ei = EXT4_I(inode);
> unsigned int flags;
> + int err;
>
> ext4_debug("cmd = %u, arg = %lu\n", cmd, arg);
>
> @@ -227,13 +330,7 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> ext4_get_inode_flags(ei);
> flags = ei->i_flags & EXT4_FL_USER_VISIBLE;
> return put_user(flags, (int __user *) arg);
> - case EXT4_IOC_SETFLAGS: {
> - handle_t *handle = NULL;
> - int err, migrate = 0;
> - struct ext4_iloc iloc;
> - unsigned int oldflags, mask, i;
> - unsigned int jflag;
> -
> + case EXT4_IOC_SETFLAGS:
> if (!inode_owner_or_capable(inode))
> return -EACCES;
>
> @@ -244,95 +341,12 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> if (err)
> return err;
>
> - flags = ext4_mask_flags(inode->i_mode, flags);
> -
> - err = -EPERM;
> mutex_lock(&inode->i_mutex);
> - /* Is it quota file? Do not allow user to mess with it */
> - if (IS_NOQUOTA(inode))
> - goto flags_out;
> -
> - oldflags = ei->i_flags;
> -
> - /* The JOURNAL_DATA flag is modifiable only by root */
> - jflag = flags & EXT4_JOURNAL_DATA_FL;
> -
> - /*
> - * The IMMUTABLE and APPEND_ONLY flags can only be changed by
> - * the relevant capability.
> - *
> - * This test looks nicer. Thanks to Pauline Middelink
> - */
> - if ((flags ^ oldflags) & (EXT4_APPEND_FL | EXT4_IMMUTABLE_FL)) {
> - if (!capable(CAP_LINUX_IMMUTABLE))
> - goto flags_out;
> - }
> -
> - /*
> - * The JOURNAL_DATA flag can only be changed by
> - * the relevant capability.
> - */
> - if ((jflag ^ oldflags) & (EXT4_JOURNAL_DATA_FL)) {
> - if (!capable(CAP_SYS_RESOURCE))
> - goto flags_out;
> - }
> - if ((flags ^ oldflags) & EXT4_EXTENTS_FL)
> - migrate = 1;
> -
> - if (flags & EXT4_EOFBLOCKS_FL) {
> - /* we don't support adding EOFBLOCKS flag */
> - if (!(oldflags & EXT4_EOFBLOCKS_FL)) {
> - err = -EOPNOTSUPP;
> - goto flags_out;
> - }
> - } else if (oldflags & EXT4_EOFBLOCKS_FL)
> - ext4_truncate(inode);
> -
> - handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
> - if (IS_ERR(handle)) {
> - err = PTR_ERR(handle);
> - goto flags_out;
> - }
> - if (IS_SYNC(inode))
> - ext4_handle_sync(handle);
> - err = ext4_reserve_inode_write(handle, inode, &iloc);
> - if (err)
> - goto flags_err;
> -
> - for (i = 0, mask = 1; i < 32; i++, mask <<= 1) {
> - if (!(mask & EXT4_FL_USER_MODIFIABLE))
> - continue;
> - if (mask & flags)
> - ext4_set_inode_flag(inode, i);
> - else
> - ext4_clear_inode_flag(inode, i);
> - }
> -
> - ext4_set_inode_flags(inode);
> - inode->i_ctime = ext4_current_time(inode);
> -
> - err = ext4_mark_iloc_dirty(handle, inode, &iloc);
> -flags_err:
> - ext4_journal_stop(handle);
> - if (err)
> - goto flags_out;
> -
> - if ((jflag ^ oldflags) & (EXT4_JOURNAL_DATA_FL))
> - err = ext4_change_inode_journal_flag(inode, jflag);
> - if (err)
> - goto flags_out;
> - if (migrate) {
> - if (flags & EXT4_EXTENTS_FL)
> - err = ext4_ext_migrate(inode);
> - else
> - err = ext4_ind_migrate(inode);
> - }
> -
> -flags_out:
> + err = ext4_set_iflags(inode, flags);
> mutex_unlock(&inode->i_mutex);
> +
> mnt_drop_write_file(filp);
> return err;
> - }
> case EXT4_IOC_GETVERSION:
> case EXT4_IOC_GETVERSION_OLD:
> return put_user(inode->i_generation, (int __user *) arg);
> diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
> index 1423c48..64963e4 100644
> --- a/fs/ext4/xattr.c
> +++ b/fs/ext4/xattr.c
> @@ -105,6 +105,7 @@ static const struct xattr_handler *ext4_xattr_handler_map[] = {
> };
>
> const struct xattr_handler *ext4_xattr_handlers[] = {
> + &ext4_xattr_iflags_handler,
> &ext4_xattr_user_handler,
> &ext4_xattr_trusted_handler,
> #ifdef CONFIG_EXT4_FS_POSIX_ACL
> diff --git a/fs/ext4/xattr.h b/fs/ext4/xattr.h
> index c767dbd..2559583 100644
> --- a/fs/ext4/xattr.h
> +++ b/fs/ext4/xattr.h
> @@ -99,6 +99,7 @@ extern const struct xattr_handler ext4_xattr_trusted_handler;
> extern const struct xattr_handler ext4_xattr_acl_access_handler;
> extern const struct xattr_handler ext4_xattr_acl_default_handler;
> extern const struct xattr_handler ext4_xattr_security_handler;
> +extern const struct xattr_handler ext4_xattr_iflags_handler;
>
> extern ssize_t ext4_listxattr(struct dentry *, char *, size_t);
>
> diff --git a/fs/ext4/xattr_iflags.c b/fs/ext4/xattr_iflags.c
> new file mode 100644
> index 0000000..e3c5472
> --- /dev/null
> +++ b/fs/ext4/xattr_iflags.c
> @@ -0,0 +1,90 @@
> +/*
> + * linux/fs/ext4/xattr_iflags.c
> + * Handler for using the extended attribute interface to store inode flags.
> + *
> + * Copyright (C) 2014 Oracle, Darrick J. Wong <darrick.wong@oracle.com>
> + */
> +#include <linux/string.h>
> +#include <linux/fs.h>
> +#include <linux/security.h>
> +#include <linux/slab.h>
> +#include <linux/strflags.h>
> +#include "ext4_jbd2.h"
> +#include "ext4.h"
> +#include "xattr.h"
> +
> +static struct flag_db ext4_iflags = {
> + .flag_sz = sizeof(int),
> + .maps = {
> + {"secrm", EXT4_SECRM_FL},
> + {"unrm", EXT4_UNRM_FL},
> + {"compr", EXT4_COMPR_FL},
> + {"sync", EXT4_SYNC_FL},
> + {"immutable", EXT4_IMMUTABLE_FL},
> + {"append", EXT4_APPEND_FL},
> + {"nodump", EXT4_NODUMP_FL},
> + {"noatime", EXT4_NOATIME_FL},
> + {"htree_dir", EXT4_INDEX_FL},
> + {"journal_data", EXT4_JOURNAL_DATA_FL},
> + {"notail", EXT4_NOTAIL_FL},
> + {"dirsync", EXT4_DIRSYNC_FL},
> + {"topdir", EXT4_TOPDIR_FL},
> + {"extents", EXT4_EXTENTS_FL},
> + {NULL, 0},
> + }
> +};
> +
> +static size_t
> +ext4_xattr_iflags_list(struct dentry *dentry, char *list, size_t list_size,
> + const char *name, size_t name_len, int type)
> +{
> + const size_t prefix_len = sizeof(XATTR_NAME_IFLAGS)-1;
> + const size_t total_len = prefix_len + name_len + 1;
> +
> + if (list && total_len <= list_size) {
> + memcpy(list, XATTR_NAME_IFLAGS, prefix_len);
> + memcpy(list+prefix_len, name, name_len);
> + list[prefix_len + name_len] = '\0';
> + }
> + return total_len;
> +}
> +
> +static int
> +ext4_xattr_iflags_get(struct dentry *dentry, const char *name,
> + void *buffer, size_t size, int type)
> +{
> + struct ext4_inode_info *ei;
> +
> + ei = EXT4_I(dentry->d_inode);
> + return flags_to_string(&ext4_iflags, &ei->i_flags, buffer, size);
> +}
> +
> +static int
> +ext4_xattr_iflags_set(struct dentry *dentry, const char *name,
> + const void *value, size_t size, int flags, int type)
> +{
> + char *string = (char *)value;
> + int ret, moo;
> +
> + string = kmalloc(size + 1, GFP_KERNEL);
> + if (!string)
> + return -ENOMEM;
> + memcpy(string, value, size);
> + string[size] = 0;
> +
> + ret = string_to_flags(&ext4_iflags, string, size, &moo);
> + if (ret)
> + goto out;
> +
> + ret = ext4_set_iflags(dentry->d_inode, moo);
> +out:
> + kfree(string);
> + return ret;
> +}
> +
> +const struct xattr_handler ext4_xattr_iflags_handler = {
> + .prefix = XATTR_NAME_IFLAGS,
> + .list = ext4_xattr_iflags_list,
> + .get = ext4_xattr_iflags_get,
> + .set = ext4_xattr_iflags_set,
> +};
> diff --git a/fs/xattr.c b/fs/xattr.c
> index 3377dff..bf0fe89 100644
> --- a/fs/xattr.c
> +++ b/fs/xattr.c
> @@ -32,6 +32,15 @@ static int
> xattr_permission(struct inode *inode, const char *name, int mask)
> {
> /*
> + * Always allow read/write of inode flag attribute.
> + */
> + if (!strcmp(name, XATTR_NAME_IFLAGS)) {
> + if (mask & MAY_WRITE)
> + return inode_owner_or_capable(inode) ? 0 : -EACCES;
> + return 0;
> + }
> +
> + /*
> * We can never set or remove an extended attribute on a read-only
> * filesystem or on an immutable / append-only inode.
> */
> diff --git a/include/linux/strflags.h b/include/linux/strflags.h
> new file mode 100644
> index 0000000..6c8067c
> --- /dev/null
> +++ b/include/linux/strflags.h
> @@ -0,0 +1,27 @@
> +/*
> + * File: linux/strflags.h
> + * Convert flags to pretty strings.
> + *
> + * Copyright (C) 2014 Oracle, Darrick J. Wong <darrick.wong@oracle.com>
> + */
> +#ifndef _LINUX_STRFLAGS_H_
> +#define _LINUX_STRFLAGS_H_
> +
> +#include <linux/types.h>
> +
> +struct flag_map {
> + const char *string;
> + unsigned long flag;
> +};
> +
> +struct flag_db {
> + size_t flag_sz;
> + struct flag_map maps[];
> +};
> +
> +int flags_to_string(struct flag_db *flagdb, void *flags, char *buf,
> + size_t buf_len);
> +int string_to_flags(struct flag_db *flagdb, char *buf, size_t buf_len,
> + void *flags);
> +
> +#endif /* _LINUX_STRFLAGS_H_ */
> diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h
> index e4629b9..8cd78ae 100644
> --- a/include/uapi/linux/xattr.h
> +++ b/include/uapi/linux/xattr.h
> @@ -63,5 +63,7 @@
> #define XATTR_POSIX_ACL_DEFAULT "posix_acl_default"
> #define XATTR_NAME_POSIX_ACL_DEFAULT XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_DEFAULT
>
> +#define XATTR_IFLAGS "iflags"
> +#define XATTR_NAME_IFLAGS XATTR_SYSTEM_PREFIX XATTR_IFLAGS
>
> #endif /* _UAPI_LINUX_XATTR_H */
> diff --git a/lib/Makefile b/lib/Makefile
> index a459c31..f3a517d 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -26,7 +26,7 @@ obj-y += bcd.o div64.o sort.o parser.o halfmd4.o debug_locks.o random32.o \
> bust_spinlocks.o hexdump.o kasprintf.o bitmap.o scatterlist.o \
> gcd.o lcm.o list_sort.o uuid.o flex_array.o iovec.o clz_ctz.o \
> bsearch.o find_last_bit.o find_next_bit.o llist.o memweight.o kfifo.o \
> - percpu-refcount.o percpu_ida.o
> + percpu-refcount.o percpu_ida.o strflags.o
> obj-y += string_helpers.o
> obj-$(CONFIG_TEST_STRING_HELPERS) += test-string_helpers.o
> obj-y += kstrtox.o
> diff --git a/lib/strflags.c b/lib/strflags.c
> new file mode 100644
> index 0000000..d1a34e7
> --- /dev/null
> +++ b/lib/strflags.c
> @@ -0,0 +1,117 @@
> +/*
> + * File: lib/strflags.c
> + * Convert flags to pretty strings.
> + *
> + * Copyright (C) 2014 Oracle, Darrick J. Wong <darrick.wong@oracle.com>
> + */
> +#include <linux/kernel.h>
> +#include <linux/string.h>
> +#include <linux/strflags.h>
> +#include <linux/errno.h>
> +
> +int flags_to_string(struct flag_db *flagdb, void *flags, char *buf,
> + size_t buf_len)
> +{
> + struct flag_map *mapping;
> + char *b;
> + unsigned long fl;
> + int used, first;
> + size_t sz;
> +
> + switch (flagdb->flag_sz) {
> + case 1:
> + fl = *(u8 *)flags;
> + break;
> + case 2:
> + fl = *(u16 *)flags;
> + break;
> + case 4:
> + fl = *(u32 *)flags;
> + break;
> + case 8:
> + fl = *(u64 *)flags;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + /* Calculating buffer size */
> + sz = 0;
> + for (mapping = flagdb->maps; mapping->string; mapping++) {
> + if (mapping->flag & fl) {
> + sz += strlen(mapping->string);
> + sz++;
> + }
> + }
> +
> + if (buf == NULL)
> + return sz;
> + if (buf_len < sz)
> + return -ERANGE;
> +
> + /* Stringify the flags */
> + b = buf;
> + first = 1;
> + for (mapping = flagdb->maps; mapping->string; mapping++) {
> + if (mapping->flag & fl) {
> + used = snprintf(b, buf_len, "%s%s",
> + (first ? "" : ","),
> + mapping->string);
> + first = 0;
> + buf_len -= used;
> + b += used;
> + }
> + }
> +
> + return b - buf;
> +}
> +EXPORT_SYMBOL_GPL(flags_to_string);
> +
> +int string_to_flags(struct flag_db *flagdb, char *buf, size_t buflen,
> + void *flags)
> +{
> + struct flag_map *mapping;
> + unsigned long fl = 0;
> + char *p, *q;
> + char c;
> + int found;
> +
> + for (p = buf; p < buf + buflen; p = q + 1) {
> + for (q = p; *q != 0 && *q != ','; q++)
> + ; /* empty */
> + if (p == q)
> + continue;
> +
> + c = *q; *q = 0;
> + found = 0;
> + for (mapping = flagdb->maps; mapping->string; mapping++)
> + if (strcmp(p, mapping->string) == 0) {
> + fl |= mapping->flag;
> + found = 1;
> + }
> + if (!found)
> + return -EINVAL;
> + *q = c;
> + p = q + 1;
> + }
> +
> + switch (flagdb->flag_sz) {
> + case 1:
> + *(u8 *)flags = fl;
> + break;
> + case 2:
> + *(u16 *)flags = fl;
> + break;
> + case 4:
> + *(u32 *)flags = fl;
> + break;
> + case 8:
> + *(u64 *)flags = fl;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(string_to_flags);
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Jan Kara <jack@suse.cz>
SUSE Labs, CR


\
 
 \ /
  Last update: 2014-01-07 14:41    [W:0.083 / U:0.812 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site