何の話かというと
RHEL6では、tmpfsに対して、xattr(拡張ファイル属性)が利用できません。正確に言うと、security.selinuxなど特定の属性だけが使えて、user.*やtrusted.*などの一般的な属性が使えません。アップストリームでは、3.0カーネルで、一般的なxattrに対応する公式パッチ(下記)が取り込まれているのですが、RHEL6にはバックポートされていないためです。
・tmpfs: implement generic xattr support
最初、このパッチに気づかないで、(別ブランチからバックポートしていた)Fedoraのソースを見ながらやっつけで、俺々パッチを書いたんですが、公式パッチの下記コメントにもあるように、xattrの取り扱いがちょっと特殊で面白かったので、そのあたりをメモしておこうかなと。
The xattr interface is a bit odd. If a filesystem does not implement any {get,set,list}xattr functions the VFS will call into some random LSM hooks and the running LSM can then implement some method for handling xattrs. SELinux for example provides a method to support security.selinux but no other security.* xattrs.
RHEL6のカーネルソース
まず、現行のRHEL6カーネル(linux-2.6.32-358.23.2.el6)のソースを見ます。
mm/shmem.c
static const struct inode_operations shmem_inode_operations = { .truncate = shmem_truncate, .setattr = shmem_setattr, .truncate_range = shmem_truncate_range, #ifdef CONFIG_TMPFS_POSIX_ACL .setxattr = generic_setxattr, .getxattr = generic_getxattr, .listxattr = generic_listxattr, .removexattr = generic_removexattr, .check_acl = shmem_check_acl, #endif };
inode_operationテーブルのxattr操作関数として、generic_*xattrが使用されています。たとえば、generic_getxattrを見るとこんな感じです。
fs/xattr.c
ssize_t generic_getxattr(struct dentry *dentry, const char *name, void *buffer, size_t size) { struct xattr_handler *handler; struct inode *inode = dentry->d_inode; handler = xattr_resolve_name(inode->i_sb->s_xattr, &name); if (!handler) return -EOPNOTSUPP; return handler->get(inode, name, buffer, size); }
super blockのテーブル「inode->i_sb->s_xattr」から、属性名に対応するハンドラをさがして、見つかったハンドラに処理を移譲しています。このテーブル「inode->i_sb->s_xattr」は、次のように用意されています。
mm/shmem.c
int shmem_fill_super(struct super_block *sb, void *data, int silent) { ... #ifdef CONFIG_TMPFS_POSIX_ACL sb->s_xattr = shmem_xattr_handlers; sb->s_flags |= MS_POSIXACL; #endif ... static struct xattr_handler *shmem_xattr_handlers[] = { &shmem_xattr_acl_access_handler, &shmem_xattr_acl_default_handler, &shmem_xattr_security_handler, NULL };
テーブルに登録しているハンドラの中身はこんな感じ。
mm/shmem.c
static struct xattr_handler shmem_xattr_security_handler = { .prefix = XATTR_SECURITY_PREFIX, .list = shmem_xattr_security_list, .get = shmem_xattr_security_get, .set = shmem_xattr_security_set, };
mm/shmem_acl.c
struct xattr_handler shmem_xattr_acl_access_handler = { .prefix = POSIX_ACL_XATTR_ACCESS, .list = shmem_list_acl_access, .get = shmem_get_acl_access, .set = shmem_set_acl_access, }; ... struct xattr_handler shmem_xattr_acl_default_handler = { .prefix = POSIX_ACL_XATTR_DEFAULT, .list = shmem_list_acl_default, .get = shmem_get_acl_default, .set = shmem_set_acl_default, };
これらのハンドラで、拡張属性の種類ごとに特化した取り扱い処理を実装しています。上記のテーブルにある.prefixの内容は、次の通り。
include/linux/posix_acl_xattr.h:
#define POSIX_ACL_XATTR_ACCESS "system.posix_acl_access" #define POSIX_ACL_XATTR_DEFAULT "system.posix_acl_default" ... #define XATTR_SECURITY_PREFIX "security."
属性名からハンドラを検索する部分を確認すると、属性名のprefixでマッチングしていることが分かります。したがって、現在の実装では、上記の3種類のprefixだけに対応していることになります。
fs/xattr.c
static struct xattr_handler * xattr_resolve_name(struct xattr_handler **handlers, const char **name) { struct xattr_handler *handler; if (!*name) return NULL; for_each_xattr_handler(handlers, handler) { const char *n = strcmp_prefix(*name, handler->prefix); if (n) { *name = n; break; } } return handler; }
俺々パッチ
そこで、俺々パッチでは、generic_*xattrのラッパーを用意して、prefixが「system」と「security」の場合は、generic_*xattrに投げて、それ以外の場合は、独自実装で拡張属性を保存するようにしています。
まず、こいつで、inodeに拡張属性のフィールドを追加しています。
include/linux/shmem_fs.h
struct simple_xattrs { struct list_head head; spinlock_t lock; }; struct simple_xattr { struct list_head list; char *name; size_t size; char value[0]; }; static inline void simple_xattrs_init(struct simple_xattrs *xattrs) { INIT_LIST_HEAD(&xattrs->head); spin_lock_init(&xattrs->lock); } struct shmem_inode_info { ... struct simple_xattrs xattrs; /* list of xattrs */ ... };
続いて、こちらで、ラッパーを追加しています。
inode_operationテーブルに、下記のラッパー「shmem_*xattr」を登録します。
mm/shmem.c
static const struct inode_operations shmem_inode_operations = { .truncate = shmem_truncate, .setattr = shmem_setattr, .truncate_range = shmem_truncate_range, #ifdef CONFIG_TMPFS_POSIX_ACL .check_acl = shmem_check_acl, #endif .setxattr = shmem_setxattr, .getxattr = shmem_getxattr, .listxattr = shmem_listxattr, .removexattr = shmem_removexattr, };
ラッパーの中身は、たとえばこんな感じ。prefixを見て処理を分けています。
mm/shmem.c
static ssize_t shmem_getxattr(struct dentry *dentry, const char *name, void *buffer, size_t size) { struct shmem_inode_info *info = SHMEM_I(dentry->d_inode); if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) || !strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) ) return generic_getxattr(dentry, name, buffer, size); return simple_xattr_get(&info->xattrs, name, buffer, size); }
linus treeのパッチ
最後にlinus treeの公式パッチです。これもgeneric_*xattrのラッパーを作っている点では同じですが、security prefixについては、super blockのハンドラに移譲することをやめて、独自実装でまかなうようになっています。
static const struct xattr_handler *shmem_xattr_handlers[] = { +#ifdef CONFIG_TMPFS_POSIX_ACL &generic_acl_access_handler, &generic_acl_default_handler, - &shmem_xattr_security_handler, +#endif NULL };
というのは、「shmem_xattr_security_handler」では、SELinuxなどのセキュリティモジュールに特化した属性だけを扱っており、security prefixを持つ、一般的な属性を扱えないためです。この点が、下記のコメントに対応しているものと思われます。
This new patch does not use the LSM fallback functions and instead just implements a native get/set/list xattr feature for the full security.* and trusted.* namespace like a normal filesystem. This means that tmpfs can now support both security.selinux and security.capability, which was not previously possible.
すっきり!