scoutfs: add basic xattr support

Add basic support for extended attributes.  The next steps are
to add support for more prefixes, including ACLs, and to properly
delete them on unlink.

Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Zach Brown
2016-07-04 10:59:43 -07:00
parent cedeacacb8
commit 59b1f62df8
8 changed files with 373 additions and 4 deletions
+1 -1
View File
@@ -12,6 +12,6 @@ scoutfs-y += first.o
scoutfs-y += block.o btree.o buddy.o counters.o crc.o dir.o filerw.o \
inode.o ioctl.o msg.o name.o scoutfs_trace.o super.o trace.o \
trans.o treap.o
trans.o treap.o xattr.o
scoutfs-y += last.o
+6
View File
@@ -15,6 +15,7 @@
#include <linux/slab.h>
#include <linux/crc32c.h>
#include <linux/uio.h>
#include <linux/xattr.h>
#include "format.h"
#include "dir.h"
@@ -24,6 +25,7 @@
#include "btree.h"
#include "trans.h"
#include "name.h"
#include "xattr.h"
/*
* Directory entries are stored in entries with offsets calculated from
@@ -451,6 +453,10 @@ const struct inode_operations scoutfs_dir_iops = {
.mkdir = scoutfs_mkdir,
.unlink = scoutfs_unlink,
.rmdir = scoutfs_unlink,
.setxattr = scoutfs_setxattr,
.getxattr = scoutfs_getxattr,
.listxattr = scoutfs_listxattr,
.removexattr = scoutfs_removexattr,
};
void scoutfs_dir_exit(void)
+13 -2
View File
@@ -51,8 +51,9 @@ struct scoutfs_key {
* have to stress about cleverly allocating the types.
*/
#define SCOUTFS_INODE_KEY 1
#define SCOUTFS_DIRENT_KEY 2
#define SCOUTFS_DATA_KEY 3
#define SCOUTFS_XATTR_KEY 2
#define SCOUTFS_DIRENT_KEY 3
#define SCOUTFS_DATA_KEY 4
#define SCOUTFS_MAX_ITEM_LEN 2048
@@ -237,4 +238,14 @@ enum {
SCOUTFS_DT_WHT,
};
#define SCOUTFS_MAX_XATTR_NAME_LEN 255
#define SCOUTFS_MAX_XATTR_VALUE_LEN 255
#define SCOUTFS_XATTR_HASH_MASK 7ULL
struct scoutfs_xattr {
__u8 name_len;
__u8 value_len;
__u8 name[0];
} __packed;
#endif
+12 -1
View File
@@ -14,6 +14,7 @@
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/xattr.h>
#include "format.h"
#include "super.h"
@@ -23,6 +24,7 @@
#include "dir.h"
#include "filerw.h"
#include "scoutfs_trace.h"
#include "xattr.h"
/*
* XXX
@@ -36,6 +38,8 @@ static void scoutfs_inode_ctor(void *obj)
{
struct scoutfs_inode_info *ci = obj;
init_rwsem(&ci->xattr_rwsem);
inode_init_once(&ci->inode);
}
@@ -63,6 +67,13 @@ void scoutfs_destroy_inode(struct inode *inode)
call_rcu(&inode->i_rcu, scoutfs_i_callback);
}
static const struct inode_operations scoutfs_file_iops = {
.setxattr = scoutfs_setxattr,
.getxattr = scoutfs_getxattr,
.listxattr = scoutfs_listxattr,
.removexattr = scoutfs_removexattr,
};
/*
* Called once new inode allocation or inode reading has initialized
* enough of the inode for us to set the ops based on the mode.
@@ -72,7 +83,7 @@ static void set_inode_ops(struct inode *inode)
switch (inode->i_mode & S_IFMT) {
case S_IFREG:
inode->i_mapping->a_ops = &scoutfs_file_aops;
// inode->i_op = &scoutfs_file_iops;
inode->i_op = &scoutfs_file_iops;
inode->i_fop = &scoutfs_file_fops;
break;
case S_IFDIR:
+2
View File
@@ -5,6 +5,8 @@ struct scoutfs_inode_info {
u64 ino;
u32 salt;
struct rw_semaphore xattr_rwsem;
struct inode inode;
};
+1
View File
@@ -22,6 +22,7 @@
#include "format.h"
#include "inode.h"
#include "dir.h"
#include "xattr.h"
#include "msg.h"
#include "block.h"
#include "counters.h"
+327
View File
@@ -0,0 +1,327 @@
/*
* Copyright (C) 2016 Versity Software, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/dcache.h>
#include <linux/xattr.h>
#include "format.h"
#include "inode.h"
#include "key.h"
#include "super.h"
#include "btree.h"
#include "trans.h"
#include "name.h"
#include "xattr.h"
#include "trace.h"
/*
* xattrs are stored in items with offsets set to the hash of their
* name. The item's value contains the xattr name and value.
*
* We reserve a few low bits of the key offset for hash collisions.
* Lookup walks collisions looking for an xattr with its name and create
* looks for a hole in the colliding key space for the new xattr.
*
* Usually btree block locking would protect the atomicity of xattr
* value updates. Lookups would have to wait for modification to
* finish. But the collision items are updated with multiple btree
* operations. And we insert new items before deleting the old so that
* we can always unwind on errors. This means that there can be
* multiple versions of an xattr in the btree. So we add an inode rw
* semaphore around xattr operations.
*
* XXX
* - add acl support and call generic xattr->handlers for SYSTEM
* - remove all xattrs on unlink
*/
/* the value immediately follows the name and there is no null termination */
static char *xat_value(struct scoutfs_xattr *xat)
{
return &xat->name[xat->name_len];
}
static unsigned int xat_bytes(unsigned int name_len, unsigned int value_len)
{
return offsetof(struct scoutfs_xattr, name[name_len + value_len]);
}
/*
* The caller provides an initialized cursor.
*
* If we return > 0 then the cursor points to an xattr with the given
* name and the caller must clean up the cursor.
*
* Returns 0 when no matching xattr is found or -errno on error.
*/
static int lookup_xattr(struct inode *inode, const char *name,
unsigned int name_len,
struct scoutfs_btree_cursor *curs)
{
struct super_block *sb = inode->i_sb;
struct scoutfs_key first;
struct scoutfs_key last;
struct scoutfs_xattr *xat;
int ret;
u64 h;
if (name_len > SCOUTFS_MAX_XATTR_NAME_LEN)
return -EINVAL;
/* XXX could be a lookup helper? */
h = scoutfs_name_hash(name, name_len) & ~SCOUTFS_XATTR_HASH_MASK;
scoutfs_set_key(&first, scoutfs_ino(inode), SCOUTFS_XATTR_KEY, h);
scoutfs_set_key(&last, scoutfs_ino(inode), SCOUTFS_XATTR_KEY,
h | SCOUTFS_XATTR_HASH_MASK);
while ((ret = scoutfs_btree_next(sb, &first, &last, curs)) > 0) {
xat = curs->val;
if (scoutfs_names_equal(name, name_len, xat->name,
xat->name_len))
break;
}
if (ret <= 0)
scoutfs_btree_release(curs);
return ret;
}
/*
* Insert a new xattr and set the caller's key to the key that we used.
* The caller is responsible for managing transactions and locking.
*/
static int insert_xattr(struct inode *inode, const char *name,
unsigned int name_len, const void *value, size_t size,
struct scoutfs_key *key)
{
struct super_block *sb = inode->i_sb;
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
struct scoutfs_xattr *xat;
struct scoutfs_key first;
struct scoutfs_key last;
int ret;
u64 h;
if (name_len > SCOUTFS_MAX_XATTR_NAME_LEN ||
size > SCOUTFS_MAX_XATTR_NAME_LEN)
return -EINVAL;
/* XXX could be a lookup helper? */
h = scoutfs_name_hash(name, name_len) & ~SCOUTFS_XATTR_HASH_MASK;
scoutfs_set_key(&first, scoutfs_ino(inode), SCOUTFS_XATTR_KEY, h);
scoutfs_set_key(&last, scoutfs_ino(inode), SCOUTFS_XATTR_KEY,
h | SCOUTFS_XATTR_HASH_MASK);
/* find the first unoccupied key offset after the hashed name */
ret = scoutfs_btree_hole(sb, &first, &last, key);
if (ret)
return ret;
ret = scoutfs_btree_insert(sb, key, xat_bytes(name_len, size), &curs);
if (!ret) {
xat = curs.val;
xat->name_len = name_len;
xat->value_len = size;
memcpy(xat->name, name, name_len);
memcpy(xat_value(xat), value, size);
scoutfs_btree_release(&curs);
}
return ret;
}
/*
* This will grow to have all the supported prefixes (then will turn
* into xattr_handlers with prefixes upstream).
*/
static int unknown_prefix(const char *name)
{
return strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
}
ssize_t scoutfs_getxattr(struct dentry *dentry, const char *name, void *buffer,
size_t size)
{
struct inode *inode = dentry->d_inode;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
size_t name_len = strlen(name);
struct scoutfs_xattr *xat;
int ret;
if (unknown_prefix(name))
return -EOPNOTSUPP;
down_read(&si->xattr_rwsem);
ret = lookup_xattr(inode, name, name_len, &curs);
if (ret == 0) {
ret = -ENODATA;
} else if (ret > 0) {
xat = curs.val;
ret = xat->value_len;
if (buffer) {
if (ret <= size)
memcpy(buffer, xat_value(xat), ret);
else
ret = -ERANGE;
}
scoutfs_btree_release(&curs);
}
up_read(&si->xattr_rwsem);
return ret;
}
/*
* Set the xattr with the given name to the given value. The value can
* have a size of 0. A null value pointer indicates that we should
* delete the xattr.
*/
static int scoutfs_xattr_set(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
struct inode *inode = dentry->d_inode;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
size_t name_len = strlen(name);
struct scoutfs_key old_key;
struct scoutfs_key new_key;
bool old;
int ret;
scoutfs_trace(sb, "name %llx value %llx size %llu flags %lld",
name, value, size, flags);
if (unknown_prefix(name))
return -EOPNOTSUPP;
ret = scoutfs_hold_trans(sb);
if (ret)
return ret;
ret = scoutfs_dirty_inode_item(inode);
if (ret)
goto out;
down_write(&si->xattr_rwsem);
ret = lookup_xattr(inode, name, name_len, &curs);
if (ret > 0) {
old = true;
old_key = *curs.key;
scoutfs_btree_release(&curs);
} else if (ret == 0) {
old = false;
} else {
goto out;
}
if (old && (flags & XATTR_CREATE)) {
ret = -EEXIST;
goto out;
}
if (!old && (flags & XATTR_REPLACE)) {
ret = -ENODATA;
goto out;
}
if (value) {
ret = insert_xattr(inode, name, name_len, value, size,
&new_key);
if (ret)
goto out;
}
if (old) {
ret = scoutfs_btree_delete(sb, &old_key);
if (ret) {
scoutfs_btree_delete(sb, &new_key);
goto out;
}
}
inode_inc_iversion(inode);
inode->i_ctime = CURRENT_TIME;
scoutfs_update_inode_item(inode);
ret = 0;
out:
up_write(&si->xattr_rwsem);
scoutfs_release_trans(sb);
return ret;
}
int scoutfs_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
if (size == 0)
value = ""; /* set empty value */
return scoutfs_xattr_set(dentry, name, value, size, 0);
}
int scoutfs_removexattr(struct dentry *dentry, const char *name)
{
return scoutfs_xattr_set(dentry, name, NULL, 0, XATTR_REPLACE);
}
ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
struct inode *inode = dentry->d_inode;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
DECLARE_SCOUTFS_BTREE_CURSOR(curs);
struct scoutfs_xattr *xat;
struct scoutfs_key first;
struct scoutfs_key last;
ssize_t total;
int ret;
scoutfs_set_key(&first, scoutfs_ino(inode), SCOUTFS_XATTR_KEY, 0);
scoutfs_set_key(&last, scoutfs_ino(inode), SCOUTFS_XATTR_KEY, ~0ULL);
down_read(&si->xattr_rwsem);
total = 0;
while ((ret = scoutfs_btree_next(sb, &first, &last, &curs)) > 0) {
xat = curs.val;
total += xat->name_len + 1;
if (!size)
continue;
if (!buffer || total > size) {
ret = -ERANGE;
break;
}
memcpy(buffer, xat->name, xat->name_len);
buffer += xat->name_len;
*(buffer++) = '\0';
}
scoutfs_btree_release(&curs);
up_read(&si->xattr_rwsem);
return ret < 0 ? ret : total;
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef _SCOUTFS_XATTR_H_
#define _SCOUTFS_XATTR_H_
ssize_t scoutfs_getxattr(struct dentry *dentry, const char *name, void *buffer,
size_t size);
int scoutfs_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags);
int scoutfs_removexattr(struct dentry *dentry, const char *name);
ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
#endif