diff options
Diffstat (limited to 'fs/jffs2')
-rw-r--r-- | fs/jffs2/Makefile | 3 | ||||
-rw-r--r-- | fs/jffs2/acl.c | 483 | ||||
-rw-r--r-- | fs/jffs2/acl.h | 46 | ||||
-rw-r--r-- | fs/jffs2/build.c | 2 | ||||
-rw-r--r-- | fs/jffs2/debug.h | 6 | ||||
-rw-r--r-- | fs/jffs2/dir.c | 62 | ||||
-rw-r--r-- | fs/jffs2/file.c | 7 | ||||
-rw-r--r-- | fs/jffs2/fs.c | 11 | ||||
-rw-r--r-- | fs/jffs2/gc.c | 16 | ||||
-rw-r--r-- | fs/jffs2/jffs2_fs_i.h | 5 | ||||
-rw-r--r-- | fs/jffs2/jffs2_fs_sb.h | 10 | ||||
-rw-r--r-- | fs/jffs2/malloc.c | 68 | ||||
-rw-r--r-- | fs/jffs2/nodelist.c | 1 | ||||
-rw-r--r-- | fs/jffs2/nodelist.h | 21 | ||||
-rw-r--r-- | fs/jffs2/os-linux.h | 4 | ||||
-rw-r--r-- | fs/jffs2/readinode.c | 1 | ||||
-rw-r--r-- | fs/jffs2/scan.c | 168 | ||||
-rw-r--r-- | fs/jffs2/security.c | 82 | ||||
-rw-r--r-- | fs/jffs2/summary.c | 191 | ||||
-rw-r--r-- | fs/jffs2/summary.h | 42 | ||||
-rw-r--r-- | fs/jffs2/super.c | 6 | ||||
-rw-r--r-- | fs/jffs2/symlink.c | 7 | ||||
-rw-r--r-- | fs/jffs2/write.c | 2 | ||||
-rw-r--r-- | fs/jffs2/xattr.c | 1271 | ||||
-rw-r--r-- | fs/jffs2/xattr.h | 120 | ||||
-rw-r--r-- | fs/jffs2/xattr_trusted.c | 51 | ||||
-rw-r--r-- | fs/jffs2/xattr_user.c | 51 |
27 files changed, 2722 insertions, 15 deletions
diff --git a/fs/jffs2/Makefile b/fs/jffs2/Makefile index 77dc5561a04..7f28ee0bd13 100644 --- a/fs/jffs2/Makefile +++ b/fs/jffs2/Makefile @@ -12,6 +12,9 @@ jffs2-y += symlink.o build.o erase.o background.o fs.o writev.o jffs2-y += super.o debug.o jffs2-$(CONFIG_JFFS2_FS_WRITEBUFFER) += wbuf.o +jffs2-$(CONFIG_JFFS2_FS_XATTR) += xattr.o xattr_trusted.o xattr_user.o +jffs2-$(CONFIG_JFFS2_FS_SECURITY) += security.o +jffs2-$(CONFIG_JFFS2_FS_POSIX_ACL) += acl.o jffs2-$(CONFIG_JFFS2_RUBIN) += compr_rubin.o jffs2-$(CONFIG_JFFS2_RTIME) += compr_rtime.o jffs2-$(CONFIG_JFFS2_ZLIB) += compr_zlib.o diff --git a/fs/jffs2/acl.c b/fs/jffs2/acl.c new file mode 100644 index 00000000000..080bb51e4b6 --- /dev/null +++ b/fs/jffs2/acl.c @@ -0,0 +1,483 @@ +/*-------------------------------------------------------------------------* + * File: fs/jffs2/acl.c + * POSIX ACL support on JFFS2 FileSystem + * + * Implemented by KaiGai Kohei <kaigai@ak.jp.nec.com> + * Copyright (C) 2006 NEC Corporation + * + * For licensing information, see the file 'LICENCE' in the jffs2 directory. + *-------------------------------------------------------------------------*/ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/time.h> +#include <linux/crc32.h> +#include <linux/jffs2.h> +#include <linux/xattr.h> +#include <linux/posix_acl_xattr.h> +#include <linux/mtd/mtd.h> +#include "nodelist.h" + +static size_t jffs2_acl_size(int count) +{ + if (count <= 4) { + return sizeof(jffs2_acl_header) + + count * sizeof(jffs2_acl_entry_short); + } else { + return sizeof(jffs2_acl_header) + + 4 * sizeof(jffs2_acl_entry_short) + + (count - 4) * sizeof(jffs2_acl_entry); + } +} + +static int jffs2_acl_count(size_t size) +{ + size_t s; + + size -= sizeof(jffs2_acl_header); + s = size - 4 * sizeof(jffs2_acl_entry_short); + if (s < 0) { + if (size % sizeof(jffs2_acl_entry_short)) + return -1; + return size / sizeof(jffs2_acl_entry_short); + } else { + if (s % sizeof(jffs2_acl_entry)) + return -1; + return s / sizeof(jffs2_acl_entry) + 4; + } +} + +static struct posix_acl *jffs2_acl_from_medium(const void *value, size_t size) +{ + const char *end = (char *)value + size; + struct posix_acl *acl; + uint32_t ver; + int i, count; + + if (!value) + return NULL; + if (size < sizeof(jffs2_acl_header)) + return ERR_PTR(-EINVAL); + ver = je32_to_cpu(((jffs2_acl_header *)value)->a_version); + if (ver != JFFS2_ACL_VERSION) { + JFFS2_WARNING("Invalid ACL version. (=%u)\n", ver); + return ERR_PTR(-EINVAL); + } + + value = (char *)value + sizeof(jffs2_acl_header); + count = jffs2_acl_count(size); + if (count < 0) + return ERR_PTR(-EINVAL); + if (count == 0) + return NULL; + + acl = posix_acl_alloc(count, GFP_KERNEL); + if (!acl) + return ERR_PTR(-ENOMEM); + + for (i=0; i < count; i++) { + jffs2_acl_entry *entry = (jffs2_acl_entry *)value; + if ((char *)value + sizeof(jffs2_acl_entry_short) > end) + goto fail; + acl->a_entries[i].e_tag = je16_to_cpu(entry->e_tag); + acl->a_entries[i].e_perm = je16_to_cpu(entry->e_perm); + switch (acl->a_entries[i].e_tag) { + case ACL_USER_OBJ: + case ACL_GROUP_OBJ: + case ACL_MASK: + case ACL_OTHER: + value = (char *)value + sizeof(jffs2_acl_entry_short); + acl->a_entries[i].e_id = ACL_UNDEFINED_ID; + break; + + case ACL_USER: + case ACL_GROUP: + value = (char *)value + sizeof(jffs2_acl_entry); + if ((char *)value > end) + goto fail; + acl->a_entries[i].e_id = je32_to_cpu(entry->e_id); + break; + + default: + goto fail; + } + } + if (value != end) + goto fail; + return acl; + fail: + posix_acl_release(acl); + return ERR_PTR(-EINVAL); +} + +static void *jffs2_acl_to_medium(const struct posix_acl *acl, size_t *size) +{ + jffs2_acl_header *jffs2_acl; + char *e; + size_t i; + + *size = jffs2_acl_size(acl->a_count); + jffs2_acl = (jffs2_acl_header *)kmalloc(sizeof(jffs2_acl_header) + + acl->a_count * sizeof(jffs2_acl_entry), + GFP_KERNEL); + if (!jffs2_acl) + return ERR_PTR(-ENOMEM); + jffs2_acl->a_version = cpu_to_je32(JFFS2_ACL_VERSION); + e = (char *)jffs2_acl + sizeof(jffs2_acl_header); + for (i=0; i < acl->a_count; i++) { + jffs2_acl_entry *entry = (jffs2_acl_entry *)e; + entry->e_tag = cpu_to_je16(acl->a_entries[i].e_tag); + entry->e_perm = cpu_to_je16(acl->a_entries[i].e_perm); + switch(acl->a_entries[i].e_tag) { + case ACL_USER: + case ACL_GROUP: + entry->e_id = cpu_to_je32(acl->a_entries[i].e_id); + e += sizeof(jffs2_acl_entry); + break; + + case ACL_USER_OBJ: + case ACL_GROUP_OBJ: + case ACL_MASK: + case ACL_OTHER: + e += sizeof(jffs2_acl_entry_short); + break; + + default: + goto fail; + } + } + return (char *)jffs2_acl; + fail: + kfree(jffs2_acl); + return ERR_PTR(-EINVAL); +} + +static struct posix_acl *jffs2_iget_acl(struct inode *inode, struct posix_acl **i_acl) +{ + struct posix_acl *acl = JFFS2_ACL_NOT_CACHED; + + spin_lock(&inode->i_lock); + if (*i_acl != JFFS2_ACL_NOT_CACHED) + acl = posix_acl_dup(*i_acl); + spin_unlock(&inode->i_lock); + return acl; +} + +static void jffs2_iset_acl(struct inode *inode, struct posix_acl **i_acl, struct posix_acl *acl) +{ + spin_lock(&inode->i_lock); + if (*i_acl != JFFS2_ACL_NOT_CACHED) + posix_acl_release(*i_acl); + *i_acl = posix_acl_dup(acl); + spin_unlock(&inode->i_lock); +} + +static struct posix_acl *jffs2_get_acl(struct inode *inode, int type) +{ + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); + struct posix_acl *acl; + char *value = NULL; + int rc, xprefix; + + switch (type) { + case ACL_TYPE_ACCESS: + acl = jffs2_iget_acl(inode, &f->i_acl_access); + if (acl != JFFS2_ACL_NOT_CACHED) + return acl; + xprefix = JFFS2_XPREFIX_ACL_ACCESS; + break; + case ACL_TYPE_DEFAULT: + acl = jffs2_iget_acl(inode, &f->i_acl_default); + if (acl != JFFS2_ACL_NOT_CACHED) + return acl; + xprefix = JFFS2_XPREFIX_ACL_DEFAULT; + break; + default: + return ERR_PTR(-EINVAL); + } + rc = do_jffs2_getxattr(inode, xprefix, "", NULL, 0); + if (rc > 0) { + value = kmalloc(rc, GFP_KERNEL); + if (!value) + return ERR_PTR(-ENOMEM); + rc = do_jffs2_getxattr(inode, xprefix, "", value, rc); + } + if (rc > 0) { + acl = jffs2_acl_from_medium(value, rc); + } else if (rc == -ENODATA || rc == -ENOSYS) { + acl = NULL; + } else { + acl = ERR_PTR(rc); + } + if (value) + kfree(value); + if (!IS_ERR(acl)) { + switch (type) { + case ACL_TYPE_ACCESS: + jffs2_iset_acl(inode, &f->i_acl_access, acl); + break; + case ACL_TYPE_DEFAULT: + jffs2_iset_acl(inode, &f->i_acl_default, acl); + break; + } + } + return acl; +} + +static int jffs2_set_acl(struct inode *inode, int type, struct posix_acl *acl) +{ + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); + size_t size = 0; + char *value = NULL; + int rc, xprefix; + + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + + switch (type) { + case ACL_TYPE_ACCESS: + xprefix = JFFS2_XPREFIX_ACL_ACCESS; + if (acl) { + mode_t mode = inode->i_mode; + rc = posix_acl_equiv_mode(acl, &mode); + if (rc < 0) + return rc; + if (inode->i_mode != mode) { + inode->i_mode = mode; + jffs2_dirty_inode(inode); + } + if (rc == 0) + acl = NULL; + } + break; + case ACL_TYPE_DEFAULT: + xprefix = JFFS2_XPREFIX_ACL_DEFAULT; + if (!S_ISDIR(inode->i_mode)) + return acl ? -EACCES : 0; + break; + default: + return -EINVAL; + } + if (acl) { + value = jffs2_acl_to_medium(acl, &size); + if (IS_ERR(value)) + return PTR_ERR(value); + } + + rc = do_jffs2_setxattr(inode, xprefix, "", value, size, 0); + if (value) + kfree(value); + if (!rc) { + switch(type) { + case ACL_TYPE_ACCESS: + jffs2_iset_acl(inode, &f->i_acl_access, acl); + break; + case ACL_TYPE_DEFAULT: + jffs2_iset_acl(inode, &f->i_acl_default, acl); + break; + } + } + return rc; +} + +static int jffs2_check_acl(struct inode *inode, int mask) +{ + struct posix_acl *acl; + int rc; + + acl = jffs2_get_acl(inode, ACL_TYPE_ACCESS); + if (IS_ERR(acl)) + return PTR_ERR(acl); + if (acl) { + rc = posix_acl_permission(inode, acl, mask); + posix_acl_release(acl); + return rc; + } + return -EAGAIN; +} + +int jffs2_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + return generic_permission(inode, mask, jffs2_check_acl); +} + +int jffs2_init_acl(struct inode *inode, struct inode *dir) +{ + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); + struct posix_acl *acl = NULL, *clone; + mode_t mode; + int rc = 0; + + f->i_acl_access = JFFS2_ACL_NOT_CACHED; + f->i_acl_default = JFFS2_ACL_NOT_CACHED; + if (!S_ISLNK(inode->i_mode)) { + acl = jffs2_get_acl(dir, ACL_TYPE_DEFAULT); + if (IS_ERR(acl)) + return PTR_ERR(acl); + if (!acl) + inode->i_mode &= ~current->fs->umask; + } + if (acl) { + if (S_ISDIR(inode->i_mode)) { + rc = jffs2_set_acl(inode, ACL_TYPE_DEFAULT, acl); + if (rc) + goto cleanup; + } + clone = posix_acl_clone(acl, GFP_KERNEL); + rc = -ENOMEM; + if (!clone) + goto cleanup; + mode = inode->i_mode; + rc = posix_acl_create_masq(clone, &mode); + if (rc >= 0) { + inode->i_mode = mode; + if (rc > 0) + rc = jffs2_set_acl(inode, ACL_TYPE_ACCESS, clone); + } + posix_acl_release(clone); + } + cleanup: + posix_acl_release(acl); + return rc; +} + +void jffs2_clear_acl(struct inode *inode) +{ + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); + + if (f->i_acl_access && f->i_acl_access != JFFS2_ACL_NOT_CACHED) { + posix_acl_release(f->i_acl_access); + f->i_acl_access = JFFS2_ACL_NOT_CACHED; + } + if (f->i_acl_default && f->i_acl_default != JFFS2_ACL_NOT_CACHED) { + posix_acl_release(f->i_acl_default); + f->i_acl_default = JFFS2_ACL_NOT_CACHED; + } +} + +int jffs2_acl_chmod(struct inode *inode) +{ + struct posix_acl *acl, *clone; + int rc; + + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + acl = jffs2_get_acl(inode, ACL_TYPE_ACCESS); + if (IS_ERR(acl) || !acl) + return PTR_ERR(acl); + clone = posix_acl_clone(acl, GFP_KERNEL); + posix_acl_release(acl); + if (!clone) + return -ENOMEM; + rc = posix_acl_chmod_masq(clone, inode->i_mode); + if (!rc) + rc = jffs2_set_acl(inode, ACL_TYPE_ACCESS, clone); + posix_acl_release(clone); + return rc; +} + +static size_t jffs2_acl_access_listxattr(struct inode *inode, char *list, size_t list_size, + const char *name, size_t name_len) +{ + const int retlen = sizeof(POSIX_ACL_XATTR_ACCESS); + + if (list && retlen <= list_size) + strcpy(list, POSIX_ACL_XATTR_ACCESS); + return retlen; +} + +static size_t jffs2_acl_default_listxattr(struct inode *inode, char *list, size_t list_size, + const char *name, size_t name_len) +{ + const int retlen = sizeof(POSIX_ACL_XATTR_DEFAULT); + + if (list && retlen <= list_size) + strcpy(list, POSIX_ACL_XATTR_DEFAULT); + return retlen; +} + +static int jffs2_acl_getxattr(struct inode *inode, int type, void *buffer, size_t size) +{ + struct posix_acl *acl; + int rc; + + acl = jffs2_get_acl(inode, type); + if (IS_ERR(acl)) + return PTR_ERR(acl); + if (!acl) + return -ENODATA; + rc = posix_acl_to_xattr(acl, buffer, size); + posix_acl_release(acl); + + return rc; +} + +static int jffs2_acl_access_getxattr(struct inode *inode, const char *name, void *buffer, size_t size) +{ + if (name[0] != '\0') + return -EINVAL; + return jffs2_acl_getxattr(inode, ACL_TYPE_ACCESS, buffer, size); +} + +static int jffs2_acl_default_getxattr(struct inode *inode, const char *name, void *buffer, size_t size) +{ + if (name[0] != '\0') + return -EINVAL; + return jffs2_acl_getxattr(inode, ACL_TYPE_DEFAULT, buffer, size); +} + +static int jffs2_acl_setxattr(struct inode *inode, int type, const void *value, size_t size) +{ + struct posix_acl *acl; + int rc; + + if ((current->fsuid != inode->i_uid) && !capable(CAP_FOWNER)) + return -EPERM; + + if (value) { + acl = posix_acl_from_xattr(value, size); + if (IS_ERR(acl)) + return PTR_ERR(acl); + if (acl) { + rc = posix_acl_valid(acl); + if (rc) + goto out; + } + } else { + acl = NULL; + } + rc = jffs2_set_acl(inode, type, acl); + out: + posix_acl_release(acl); + return rc; +} + +static int jffs2_acl_access_setxattr(struct inode *inode, const char *name, + const void *buffer, size_t size, int flags) +{ + if (name[0] != '\0') + return -EINVAL; + return jffs2_acl_setxattr(inode, ACL_TYPE_ACCESS, buffer, size); +} + +static int jffs2_acl_default_setxattr(struct inode *inode, const char *name, + const void *buffer, size_t size, int flags) +{ + if (name[0] != '\0') + return -EINVAL; + return jffs2_acl_setxattr(inode, ACL_TYPE_DEFAULT, buffer, size); +} + +struct xattr_handler jffs2_acl_access_xattr_handler = { + .prefix = POSIX_ACL_XATTR_ACCESS, + .list = jffs2_acl_access_listxattr, + .get = jffs2_acl_access_getxattr, + .set = jffs2_acl_access_setxattr, +}; + +struct xattr_handler jffs2_acl_default_xattr_handler = { + .prefix = POSIX_ACL_XATTR_DEFAULT, + .list = jffs2_acl_default_listxattr, + .get = jffs2_acl_default_getxattr, + .set = jffs2_acl_default_setxattr, +}; diff --git a/fs/jffs2/acl.h b/fs/jffs2/acl.h new file mode 100644 index 00000000000..c98610b4e81 --- /dev/null +++ b/fs/jffs2/acl.h @@ -0,0 +1,46 @@ +/*-------------------------------------------------------------------------* + * File: fs/jffs2/acl.h + * POSIX ACL support on JFFS2 FileSystem + * + * Implemented by KaiGai Kohei <kaigai@ak.jp.nec.com> + * Copyright (C) 2006 NEC Corporation + * + * For licensing information, see the file 'LICENCE' in the jffs2 directory. + *-------------------------------------------------------------------------*/ +typedef struct { + jint16_t e_tag; + jint16_t e_perm; + jint32_t e_id; +} jffs2_acl_entry; + +typedef struct { + jint16_t e_tag; + jint16_t e_perm; +} jffs2_acl_entry_short; + +typedef struct { + jint32_t a_version; +} jffs2_acl_header; + +#ifdef __KERNEL__ +#ifdef CONFIG_JFFS2_FS_POSIX_ACL + +#define JFFS2_ACL_NOT_CACHED ((void *)-1) + +extern int jffs2_permission(struct inode *, int, struct nameidata *); +extern int jffs2_acl_chmod(struct inode *); +extern int jffs2_init_acl(struct inode *, struct inode *); +extern void jffs2_clear_acl(struct inode *); + +extern struct xattr_handler jffs2_acl_access_xattr_handler; +extern struct xattr_handler jffs2_acl_default_xattr_handler; + +#else + +#define jffs2_permission NULL +#define jffs2_acl_chmod(inode) (0) +#define jffs2_init_acl(inode,dir) (0) +#define jffs2_clear_acl(inode) + +#endif /* CONFIG_JFFS2_FS_POSIX_ACL */ +#endif /* __KERNEL__ */ diff --git a/fs/jffs2/build.c b/fs/jffs2/build.c index 70f7a896c04..02826967ab5 100644 --- a/fs/jffs2/build.c +++ b/fs/jffs2/build.c @@ -160,6 +160,7 @@ static int jffs2_build_filesystem(struct jffs2_sb_info *c) ic->scan_dents = NULL; cond_resched(); } + jffs2_build_xattr_subsystem(c); c->flags &= ~JFFS2_SB_FLAG_BUILDING; dbg_fsbuild("FS build complete\n"); @@ -178,6 +179,7 @@ exit: jffs2_free_full_dirent(fd); } } + jffs2_clear_xattr_subsystem(c); } return ret; diff --git a/fs/jffs2/debug.h b/fs/jffs2/debug.h index 162af6dfe29..5fa494a792b 100644 --- a/fs/jffs2/debug.h +++ b/fs/jffs2/debug.h @@ -171,6 +171,12 @@ #define dbg_memalloc(fmt, ...) #endif +/* Watch the XATTR subsystem */ +#ifdef JFFS2_DBG_XATTR_MESSAGES +#define dbg_xattr(fmt, ...) JFFS2_DEBUG(fmt, ##__VA_ARGS__) +#else +#define dbg_xattr(fmt, ...) +#endif /* "Sanity" checks */ void diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index 1c8e8c0f6ce..f1b18b99a3c 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -57,7 +57,12 @@ struct inode_operations jffs2_dir_inode_operations = .rmdir = jffs2_rmdir, .mknod = jffs2_mknod, .rename = jffs2_rename, + .permission = jffs2_permission, .setattr = jffs2_setattr, + .setxattr = jffs2_setxattr, + .getxattr = jffs2_getxattr, + .listxattr = jffs2_listxattr, + .removexattr = jffs2_removexattr }; /***********************************************************************/ @@ -209,12 +214,15 @@ static int jffs2_create(struct inode *dir_i, struct dentry *dentry, int mode, ret = jffs2_do_create(c, dir_f, f, ri, dentry->d_name.name, dentry->d_name.len); - if (ret) { - make_bad_inode(inode); - iput(inode); - jffs2_free_raw_inode(ri); - return ret; - } + if (ret) + goto fail; + + ret = jffs2_init_security(inode, dir_i); + if (ret) + goto fail; + ret = jffs2_init_acl(inode, dir_i); + if (ret) + goto fail; dir_i->i_mtime = dir_i->i_ctime = ITIME(je32_to_cpu(ri->ctime)); @@ -224,6 +232,12 @@ static int jffs2_create(struct inode *dir_i, struct dentry *dentry, int mode, D1(printk(KERN_DEBUG "jffs2_create: Created ino #%lu with mode %o, nlink %d(%d). nrpages %ld\n", inode->i_ino, inode->i_mode, inode->i_nlink, f->inocache->nlink, inode->i_mapping->nrpages)); return 0; + + fail: + make_bad_inode(inode); + iput(inode); + jffs2_free_raw_inode(ri); + return ret; } /***********************************************************************/ @@ -374,6 +388,18 @@ static int jffs2_symlink (struct inode *dir_i, struct dentry *dentry, const char up(&f->sem); jffs2_complete_reservation(c); + + ret = jffs2_init_security(inode, dir_i); + if (ret) { + jffs2_clear_inode(inode); + return ret; + } + ret = jffs2_init_acl(inode, dir_i); + if (ret) { + jffs2_clear_inode(inode); + return ret; + } + ret = jffs2_reserve_space(c, sizeof(*rd)+namelen, &phys_ofs, &alloclen, ALLOC_NORMAL, JFFS2_SUMMARY_DIRENT_SIZE(namelen)); if (ret) { @@ -504,6 +530,18 @@ static int jffs2_mkdir (struct inode *dir_i, struct dentry *dentry, int mode) up(&f->sem); jffs2_complete_reservation(c); + + ret = jffs2_init_security(inode, dir_i); + if (ret) { + jffs2_clear_inode(inode); + return ret; + } + ret = jffs2_init_acl(inode, dir_i); + if (ret) { + jffs2_clear_inode(inode); + return ret; + } + ret = jffs2_reserve_space(c, sizeof(*rd)+namelen, &phys_ofs, &alloclen, ALLOC_NORMAL, JFFS2_SUMMARY_DIRENT_SIZE(namelen)); if (ret) { @@ -660,6 +698,18 @@ static int jffs2_mknod (struct inode *dir_i, struct dentry *dentry, int mode, de up(&f->sem); jffs2_complete_reservation(c); + + ret = jffs2_init_security(inode, dir_i); + if (ret) { + jffs2_clear_inode(inode); + return ret; + } + ret = jffs2_init_acl(inode, dir_i); + if (ret) { + jffs2_clear_inode(inode); + return ret; + } + ret = jffs2_reserve_space(c, sizeof(*rd)+namelen, &phys_ofs, &alloclen, ALLOC_NORMAL, JFFS2_SUMMARY_DIRENT_SIZE(namelen)); if (ret) { diff --git a/fs/jffs2/file.c b/fs/jffs2/file.c index 9f4171213e5..e92187f34d5 100644 --- a/fs/jffs2/file.c +++ b/fs/jffs2/file.c @@ -54,7 +54,12 @@ const struct file_operations jffs2_file_operations = struct inode_operations jffs2_file_inode_operations = { - .setattr = jffs2_setattr + .permission = jffs2_permission, + .setattr = jffs2_setattr, + .setxattr = jffs2_setxattr, + .getxattr = jffs2_getxattr, + .listxattr = jffs2_listxattr, + .removexattr = jffs2_removexattr }; struct address_space_operations jffs2_file_address_operations = diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c index ea1f37d4fc5..4607cdc4c46 100644 --- a/fs/jffs2/fs.c +++ b/fs/jffs2/fs.c @@ -185,7 +185,12 @@ static int jffs2_do_setattr (struct inode *inode, struct iattr *iattr) int jffs2_setattr(struct dentry *dentry, struct iattr *iattr) { - return jffs2_do_setattr(dentry->d_inode, iattr); + int rc; + + rc = jffs2_do_setattr(dentry->d_inode, iattr); + if (!rc && (iattr->ia_valid & ATTR_MODE)) + rc = jffs2_acl_chmod(dentry->d_inode); + return rc; } int jffs2_statfs(struct super_block *sb, struct kstatfs *buf) @@ -224,6 +229,7 @@ void jffs2_clear_inode (struct inode *inode) D1(printk(KERN_DEBUG "jffs2_clear_inode(): ino #%lu mode %o\n", inode->i_ino, inode->i_mode)); + jffs2_xattr_delete_inode(c, f->inocache); jffs2_do_clear_inode(c, f); } @@ -497,6 +503,8 @@ int jffs2_do_fill_super(struct super_block *sb, void *data, int silent) } memset(c->inocache_list, 0, INOCACHE_HASHSIZE * sizeof(struct jffs2_inode_cache *)); + jffs2_init_xattr_subsystem(c); + if ((ret = jffs2_do_mount_fs(c))) goto out_inohash; @@ -531,6 +539,7 @@ int jffs2_do_fill_super(struct super_block *sb, void *data, int silent) else kfree(c->blocks); out_inohash: + jffs2_clear_xattr_subsystem(c); kfree(c->inocache_list); out_wbuf: jffs2_flash_cleanup(c); diff --git a/fs/jffs2/gc.c b/fs/jffs2/gc.c index 967fb2cf8e2..4ea1b7f0ae7 100644 --- a/fs/jffs2/gc.c +++ b/fs/jffs2/gc.c @@ -125,6 +125,7 @@ int jffs2_garbage_collect_pass(struct jffs2_sb_info *c) struct jffs2_eraseblock *jeb; struct jffs2_raw_node_ref *raw; int ret = 0, inum, nlink; + int xattr = 0; if (down_interruptible(&c->alloc_sem)) return -EINTR; @@ -138,7 +139,7 @@ int jffs2_garbage_collect_pass(struct jffs2_sb_info *c) the node CRCs etc. Do it now. */ /* checked_ino is protected by the alloc_sem */ - if (c->checked_ino > c->highest_ino) { + if (c->checked_ino > c->highest_ino && xattr) { printk(KERN_CRIT "Checked all inodes but still 0x%x bytes of unchecked space?\n", c->unchecked_size); jffs2_dbg_dump_block_lists_nolock(c); @@ -148,6 +149,9 @@ int jffs2_garbage_collect_pass(struct jffs2_sb_info *c) spin_unlock(&c->erase_completion_lock); + if (!xattr) + xattr = jffs2_verify_xattr(c); + spin_lock(&c->inocache_lock); ic = jffs2_get_ino_cache(c, c->checked_ino++); @@ -262,6 +266,16 @@ int jffs2_garbage_collect_pass(struct jffs2_sb_info *c) ic = jffs2_raw_ref_to_ic(raw); + /* When 'ic' refers xattr_datum/xattr_ref, this node is GCed as xattr. + We can decide whether this node is inode or xattr by ic->class. + ret = 0 : ic is xattr_datum/xattr_ref, and GC was SUCCESSED. + ret < 0 : ic is xattr_datum/xattr_ref, but GC was FAILED. + ret > 0 : ic is NOT xattr_datum/xattr_ref. + */ + ret = jffs2_garbage_collect_xattr(c, ic); + if (ret <= 0) + goto release_sem; + /* We need to hold the inocache. Either the erase_completion_lock or the inocache_lock are sufficient; we trade down since the inocache_lock causes less contention. */ diff --git a/fs/jffs2/jffs2_fs_i.h b/fs/jffs2/jffs2_fs_i.h index ad565bf9dcc..2e0cc8e00b8 100644 --- a/fs/jffs2/jffs2_fs_i.h +++ b/fs/jffs2/jffs2_fs_i.h @@ -5,6 +5,7 @@ #include <linux/version.h> #include <linux/rbtree.h> +#include <linux/posix_acl.h> #include <asm/semaphore.h> struct jffs2_inode_info { @@ -45,6 +46,10 @@ struct jffs2_inode_info { struct inode vfs_inode; #endif #endif +#ifdef CONFIG_JFFS2_FS_POSIX_ACL + struct posix_acl *i_acl_access; + struct posix_acl *i_acl_default; +#endif }; #endif /* _JFFS2_FS_I */ diff --git a/fs/jffs2/jffs2_fs_sb.h b/fs/jffs2/jffs2_fs_sb.h index 4bcfb557022..3b4e0edd6db 100644 --- a/fs/jffs2/jffs2_fs_sb.h +++ b/fs/jffs2/jffs2_fs_sb.h @@ -115,6 +115,16 @@ struct jffs2_sb_info { struct jffs2_summary *summary; /* Summary information */ +#ifdef CONFIG_JFFS2_FS_XATTR +#define XATTRINDEX_HASHSIZE (57) + uint32_t highest_xid; + struct list_head xattrindex[XATTRINDEX_HASHSIZE]; + struct list_head xattr_temp; + struct list_head xattr_unchecked; + struct rw_semaphore xattr_sem; + uint32_t xdatum_mem_usage; + uint32_t xdatum_mem_threshold; +#endif /* OS-private pointer for getting back to master superblock info */ void *os_priv; }; diff --git a/fs/jffs2/malloc.c b/fs/jffs2/malloc.c index 036cbd11c00..3d5b7ecfbf8 100644 --- a/fs/jffs2/malloc.c +++ b/fs/jffs2/malloc.c @@ -26,6 +26,10 @@ static kmem_cache_t *tmp_dnode_info_slab; static kmem_cache_t *raw_node_ref_slab; static kmem_cache_t *node_frag_slab; static kmem_cache_t *inode_cache_slab; +#ifdef CONFIG_JFFS2_FS_XATTR +static kmem_cache_t *xattr_datum_cache; +static kmem_cache_t *xattr_ref_cache; +#endif int __init jffs2_create_slab_caches(void) { @@ -68,8 +72,24 @@ int __init jffs2_create_slab_caches(void) inode_cache_slab = kmem_cache_create("jffs2_inode_cache", sizeof(struct jffs2_inode_cache), 0, 0, NULL, NULL); - if (inode_cache_slab) - return 0; + if (!inode_cache_slab) + goto err; + +#ifdef CONFIG_JFFS2_FS_XATTR + xattr_datum_cache = kmem_cache_create("jffs2_xattr_datum", + sizeof(struct jffs2_xattr_datum), + 0, 0, NULL, NULL); + if (!xattr_datum_cache) + goto err; + + xattr_ref_cache = kmem_cache_create("jffs2_xattr_ref", + sizeof(struct jffs2_xattr_ref), + 0, 0, NULL, NULL); + if (!xattr_ref_cache) + goto err; +#endif + + return 0; err: jffs2_destroy_slab_caches(); return -ENOMEM; @@ -91,6 +111,12 @@ void jffs2_destroy_slab_caches(void) kmem_cache_destroy(node_frag_slab); if(inode_cache_slab) kmem_cache_destroy(inode_cache_slab); +#ifdef CONFIG_JFFS2_FS_XATTR + if (xattr_datum_cache) + kmem_cache_destroy(xattr_datum_cache); + if (xattr_ref_cache) + kmem_cache_destroy(xattr_ref_cache); +#endif } struct jffs2_full_dirent *jffs2_alloc_full_dirent(int namesize) @@ -205,3 +231,41 @@ void jffs2_free_inode_cache(struct jffs2_inode_cache *x) dbg_memalloc("%p\n", x); kmem_cache_free(inode_cache_slab, x); } + +#ifdef CONFIG_JFFS2_FS_XATTR +struct jffs2_xattr_datum *jffs2_alloc_xattr_datum(void) +{ + struct jffs2_xattr_datum *xd; + xd = kmem_cache_alloc(xattr_datum_cache, GFP_KERNEL); + dbg_memalloc("%p\n", xd); + + memset(xd, 0, sizeof(struct jffs2_xattr_datum)); + xd->class = RAWNODE_CLASS_XATTR_DATUM; + INIT_LIST_HEAD(&xd->xindex); + return xd; +} + +void jffs2_free_xattr_datum(struct jffs2_xattr_datum *xd) +{ + dbg_memalloc("%p\n", xd); + kmem_cache_free(xattr_datum_cache, xd); +} + +struct jffs2_xattr_ref *jffs2_alloc_xattr_ref(void) +{ + struct jffs2_xattr_ref *ref; + ref = kmem_cache_alloc(xattr_ref_cache, GFP_KERNEL); + dbg_memalloc("%p\n", ref); + + memset(ref, 0, sizeof(struct jffs2_xattr_ref)); + ref->class = RAWNODE_CLASS_XATTR_REF; + INIT_LIST_HEAD(&ref->ilist); + return ref; +} + +void jffs2_free_xattr_ref(struct jffs2_xattr_ref *ref) +{ + dbg_memalloc("%p\n", ref); + kmem_cache_free(xattr_ref_cache, ref); +} +#endif diff --git a/fs/jffs2/nodelist.c b/fs/jffs2/nodelist.c index d4d0c41490c..9c575733659 100644 --- a/fs/jffs2/nodelist.c +++ b/fs/jffs2/nodelist.c @@ -938,6 +938,7 @@ void jffs2_free_ino_caches(struct jffs2_sb_info *c) this = c->inocache_list[i]; while (this) { next = this->next; + jffs2_xattr_free_inode(c, this); jffs2_free_inode_cache(this); this = next; } diff --git a/fs/jffs2/nodelist.h b/fs/jffs2/nodelist.h index f6645afe88e..6f6279cf490 100644 --- a/fs/jffs2/nodelist.h +++ b/fs/jffs2/nodelist.h @@ -20,6 +20,8 @@ #include <linux/jffs2.h> #include "jffs2_fs_sb.h" #include "jffs2_fs_i.h" +#include "xattr.h" +#include "acl.h" #include "summary.h" #ifdef __ECOS @@ -107,11 +109,16 @@ struct jffs2_inode_cache { temporary lists of dirents, and later must be set to NULL to mark the end of the raw_node_ref->next_in_ino chain. */ + u8 class; /* It's used for identification */ + u8 flags; + uint16_t state; struct jffs2_inode_cache *next; struct jffs2_raw_node_ref *nodes; uint32_t ino; int nlink; - int state; +#ifdef CONFIG_JFFS2_FS_XATTR + struct list_head ilist; +#endif }; /* Inode states for 'state' above. We need the 'GC' state to prevent @@ -125,6 +132,12 @@ struct jffs2_inode_cache { #define INO_STATE_READING 5 /* In read_inode() */ #define INO_STATE_CLEARING 6 /* In clear_inode() */ +#define INO_FLAGS_XATTR_CHECKED 0x01 /* has no duplicate xattr_ref */ + +#define RAWNODE_CLASS_INODE_CACHE 0 +#define RAWNODE_CLASS_XATTR_DATUM 1 +#define RAWNODE_CLASS_XATTR_REF 2 + #define INOCACHE_HASHSIZE 128 /* @@ -374,6 +387,12 @@ struct jffs2_node_frag *jffs2_alloc_node_frag(void); void jffs2_free_node_frag(struct jffs2_node_frag *); struct jffs2_inode_cache *jffs2_alloc_inode_cache(void); void jffs2_free_inode_cache(struct jffs2_inode_cache *); +#ifdef CONFIG_JFFS2_FS_XATTR +struct jffs2_xattr_datum *jffs2_alloc_xattr_datum(void); +void jffs2_free_xattr_datum(struct jffs2_xattr_datum *); +struct jffs2_xattr_ref *jffs2_alloc_xattr_ref(void); +void jffs2_free_xattr_ref(struct jffs2_xattr_ref *); +#endif /* gc.c */ int jffs2_garbage_collect_pass(struct jffs2_sb_info *c); diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h index d307cf54862..9936ae23f8d 100644 --- a/fs/jffs2/os-linux.h +++ b/fs/jffs2/os-linux.h @@ -60,6 +60,10 @@ static inline void jffs2_init_inode_info(struct jffs2_inode_info *f) f->target = NULL; f->flags = 0; f->usercompr = 0; +#ifdef CONFIG_JFFS2_FS_POSIX_ACL + f->i_acl_access = JFFS2_ACL_NOT_CACHED; + f->i_acl_default = JFFS2_ACL_NOT_CACHED; +#endif } diff --git a/fs/jffs2/readinode.c b/fs/jffs2/readinode.c index e1acce8fb2b..61ccdf4f104 100644 --- a/fs/jffs2/readinode.c +++ b/fs/jffs2/readinode.c @@ -902,6 +902,7 @@ int jffs2_do_read_inode(struct jffs2_sb_info *c, struct jffs2_inode_info *f, f->inocache->ino = f->inocache->nlink = 1; f->inocache->nodes = (struct jffs2_raw_node_ref *)f->inocache; f->inocache->state = INO_STATE_READING; + init_xattr_inode_cache(f->inocache); jffs2_add_ino_cache(c, f->inocache); } if (!f->inocache) { diff --git a/fs/jffs2/scan.c b/fs/jffs2/scan.c index cf55b221fc2..f09689e320f 100644 --- a/fs/jffs2/scan.c +++ b/fs/jffs2/scan.c @@ -306,6 +306,136 @@ int jffs2_scan_classify_jeb(struct jffs2_sb_info *c, struct jffs2_eraseblock *je return BLK_STATE_ALLDIRTY; } +#ifdef CONFIG_JFFS2_FS_XATTR +static int jffs2_scan_xattr_node(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb, + struct jffs2_raw_xattr *rx, uint32_t ofs, + struct jffs2_summary *s) +{ + struct jffs2_xattr_datum *xd; + struct jffs2_raw_node_ref *raw; + uint32_t totlen, crc; + + crc = crc32(0, rx, sizeof(struct jffs2_raw_xattr) - 4); + if (crc != je32_to_cpu(rx->node_crc)) { + if (je32_to_cpu(rx->node_crc) != 0xffffffff) + JFFS2_WARNING("node CRC failed at %#08x, read=%#08x, calc=%#08x\n", + ofs, je32_to_cpu(rx->node_crc), crc); + DIRTY_SPACE(je32_to_cpu(rx->totlen)); + return 0; + } + + totlen = PAD(sizeof(*rx) + rx->name_len + 1 + je16_to_cpu(rx->value_len)); + if (totlen != je32_to_cpu(rx->totlen)) { + JFFS2_WARNING("node length mismatch at %#08x, read=%u, calc=%u\n", + ofs, je32_to_cpu(rx->totlen), totlen); + DIRTY_SPACE(je32_to_cpu(rx->totlen)); + return 0; + } + + raw = jffs2_alloc_raw_node_ref(); + if (!raw) + return -ENOMEM; + + xd = jffs2_setup_xattr_datum(c, je32_to_cpu(rx->xid), je32_to_cpu(rx->version)); + if (IS_ERR(xd)) { + jffs2_free_raw_node_ref(raw); + if (PTR_ERR(xd) == -EEXIST) { + DIRTY_SPACE(PAD(je32_to_cpu(rx->totlen))); + return 0; + } + return PTR_ERR(xd); + } + xd->xprefix = rx->xprefix; + xd->name_len = rx->name_len; + xd->value_len = je16_to_cpu(rx->value_len); + xd->data_crc = je32_to_cpu(rx->data_crc); + xd->node = raw; + + raw->__totlen = totlen; + raw->flash_offset = ofs | REF_PRISTINE; + raw->next_phys = NULL; + raw->next_in_ino = (void *)xd; + if (!jeb->first_node) + jeb->first_node = raw; + if (jeb->last_node) + jeb->last_node->next_phys = raw; + jeb->last_node = raw; + + USED_SPACE(PAD(je32_to_cpu(rx->totlen))); + if (jffs2_sum_active()) + jffs2_sum_add_xattr_mem(s, rx, ofs - jeb->offset); + dbg_xattr("scaning xdatum at %#08x (xid=%u, version=%u)\n", + ofs, xd->xid, xd->version); + return 0; +} + +static int jffs2_scan_xref_node(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb, + struct jffs2_raw_xref *rr, uint32_t ofs, + struct jffs2_summary *s) +{ + struct jffs2_xattr_ref *ref; + struct jffs2_raw_node_ref *raw; + uint32_t crc; + + crc = crc32(0, rr, sizeof(*rr) - 4); + if (crc != je32_to_cpu(rr->node_crc)) { + if (je32_to_cpu(rr->node_crc) != 0xffffffff) + JFFS2_WARNING("node CRC failed at %#08x, read=%#08x, calc=%#08x\n", + ofs, je32_to_cpu(rr->node_crc), crc); + DIRTY_SPACE(PAD(je32_to_cpu(rr->totlen))); + return 0; + } + + if (PAD(sizeof(struct jffs2_raw_xref)) != je32_to_cpu(rr->totlen)) { + JFFS2_WARNING("node length mismatch at %#08x, read=%u, calc=%u\n", + ofs, je32_to_cpu(rr->totlen), + PAD(sizeof(struct jffs2_raw_xref))); + DIRTY_SPACE(je32_to_cpu(rr->totlen)); + return 0; + } + + ref = jffs2_alloc_xattr_ref(); + if (!ref) + return -ENOMEM; + + raw = jffs2_alloc_raw_node_ref(); + if (!raw) { + jffs2_free_xattr_ref(ref); + return -ENOMEM; + } + + /* BEFORE jffs2_build_xattr_subsystem() called, + * ref->xid is used to store 32bit xid, xd is not used + * ref->ino is used to store 32bit inode-number, ic is not used + * Thoes variables are declared as union, thus using those + * are exclusive. In a similar way, ref->ilist is temporarily + * used to chain all xattr_ref object. It's re-chained to + * jffs2_inode_cache in jffs2_build_xattr_subsystem() correctly. + */ + ref->node = raw; + ref->ino = je32_to_cpu(rr->ino); + ref->xid = je32_to_cpu(rr->xid); + list_add_tail(&ref->ilist, &c->xattr_temp); + + raw->__totlen = PAD(je32_to_cpu(rr->totlen)); + raw->flash_offset = ofs | REF_PRISTINE; + raw->next_phys = NULL; + raw->next_in_ino = (void *)ref; + if (!jeb->first_node) + jeb->first_node = raw; + if (jeb->last_node) + jeb->last_node->next_phys = raw; + jeb->last_node = raw; + + USED_SPACE(PAD(je32_to_cpu(rr->totlen))); + if (jffs2_sum_active()) + jffs2_sum_add_xref_mem(s, rr, ofs - jeb->offset); + dbg_xattr("scan xref at %#08x (xid=%u, ino=%u)\n", + ofs, ref->xid, ref->ino); + return 0; +} +#endif + static int jffs2_scan_eraseblock (struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb, unsigned char *buf, uint32_t buf_size, struct jffs2_summary *s) { struct jffs2_unknown_node *node; @@ -614,6 +744,43 @@ scan_more: ofs += PAD(je32_to_cpu(node->totlen)); break; +#ifdef CONFIG_JFFS2_FS_XATTR + case JFFS2_NODETYPE_XATTR: + if (buf_ofs + buf_len < ofs + je32_to_cpu(node->totlen)) { + buf_len = min_t(uint32_t, buf_size, jeb->offset + c->sector_size - ofs); + D1(printk(KERN_DEBUG "Fewer than %d bytes (xattr node)" + " left to end of buf. Reading 0x%x at 0x%08x\n", + je32_to_cpu(node->totlen), buf_len, ofs)); + err = jffs2_fill_scan_buf(c, buf, ofs, buf_len); + if (err) + return err; + buf_ofs = ofs; + node = (void *)buf; + } + err = jffs2_scan_xattr_node(c, jeb, (void *)node, ofs, s); + if (err) + return err; + ofs += PAD(je32_to_cpu(node->totlen)); + break; + case JFFS2_NODETYPE_XREF: + if (buf_ofs + buf_len < ofs + je32_to_cpu(node->totlen)) { + buf_len = min_t(uint32_t, buf_size, jeb->offset + c->sector_size - ofs); + D1(printk(KERN_DEBUG "Fewer than %d bytes (xref node)" + " left to end of buf. Reading 0x%x at 0x%08x\n", + je32_to_cpu(node->totlen), buf_len, ofs)); + err = jffs2_fill_scan_buf(c, buf, ofs, buf_len); + if (err) + return err; + buf_ofs = ofs; + node = (void *)buf; + } + err = jffs2_scan_xref_node(c, jeb, (void *)node, ofs, s); + if (err) + return err; + ofs += PAD(je32_to_cpu(node->totlen)); + break; +#endif /* CONFIG_JFFS2_FS_XATTR */ + case JFFS2_NODETYPE_CLEANMARKER: D1(printk(KERN_DEBUG "CLEANMARKER node found at 0x%08x\n", ofs)); if (je32_to_cpu(node->totlen) != c->cleanmarker_size) { @@ -721,6 +888,7 @@ struct jffs2_inode_cache *jffs2_scan_make_ino_cache(struct jffs2_sb_info *c, uin ic->ino = ino; ic->nodes = (void *)ic; + init_xattr_inode_cache(ic); jffs2_add_ino_cache(c, ic); if (ino == 1) ic->nlink = 1; diff --git a/fs/jffs2/security.c b/fs/jffs2/security.c new file mode 100644 index 00000000000..4b6c3b22524 --- /dev/null +++ b/fs/jffs2/security.c @@ -0,0 +1,82 @@ +/*-------------------------------------------------------------------------* + * File: fs/jffs2/security.c + * Security Labels support on JFFS2 FileSystem + * + * Implemented by KaiGai Kohei <kaigai@ak.jp.nec.com> + * Copyright (C) 2006 NEC Corporation + * + * For licensing information, see the file 'LICENCE' in the jffs2 directory. + *-------------------------------------------------------------------------*/ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/time.h> +#include <linux/pagemap.h> +#include <linux/highmem.h> +#include <linux/crc32.h> +#include <linux/jffs2.h> +#include <linux/xattr.h> +#include <linux/mtd/mtd.h> +#include <linux/security.h> +#include "nodelist.h" + +/* ---- Initial Security Label Attachment -------------- */ +int jffs2_init_security(struct inode *inode, struct inode *dir) +{ + int rc; + size_t len; + void *value; + char *name; + + rc = security_inode_init_security(inode, dir, &name, &value, &len); + if (rc) { + if (rc == -EOPNOTSUPP) + return 0; + return rc; + } + rc = do_jffs2_setxattr(inode, JFFS2_XPREFIX_SECURITY, name, value, len, 0); + + kfree(name); + kfree(value); + return rc; +} + +/* ---- XATTR Handler for "security.*" ----------------- */ +static int jffs2_security_getxattr(struct inode *inode, const char *name, + void *buffer, size_t size) +{ + if (!strcmp(name, "")) + return -EINVAL; + + return do_jffs2_getxattr(inode, JFFS2_XPREFIX_SECURITY, name, buffer, size); +} + +static int jffs2_security_setxattr(struct inode *inode, const char *name, const void *buffer, + size_t size, int flags) +{ + if (!strcmp(name, "")) + return -EINVAL; + + return do_jffs2_setxattr(inode, JFFS2_XPREFIX_SECURITY, name, buffer, size, flags); +} + +static size_t jffs2_security_listxattr(struct inode *inode, char *list, size_t list_size, + const char *name, size_t name_len) +{ + size_t retlen = XATTR_SECURITY_PREFIX_LEN + name_len + 1; + + if (list && retlen <= list_size) { + strcpy(list, XATTR_SECURITY_PREFIX); + strcpy(list + XATTR_SECURITY_PREFIX_LEN, name); + } + + return retlen; +} + +struct xattr_handler jffs2_security_xattr_handler = { + .prefix = XATTR_SECURITY_PREFIX, + .list = jffs2_security_listxattr, + .set = jffs2_security_setxattr, + .get = jffs2_security_getxattr +}; diff --git a/fs/jffs2/summary.c b/fs/jffs2/summary.c index 7b0ed77a4c3..5d9ec8e3652 100644 --- a/fs/jffs2/summary.c +++ b/fs/jffs2/summary.c @@ -5,6 +5,7 @@ * Zoltan Sogor <weth@inf.u-szeged.hu>, * Patrik Kluba <pajko@halom.u-szeged.hu>, * University of Szeged, Hungary + * 2005 KaiGai Kohei <kaigai@ak.jp.nec.com> * * For licensing information, see the file 'LICENCE' in this directory. * @@ -81,6 +82,19 @@ static int jffs2_sum_add_mem(struct jffs2_summary *s, union jffs2_sum_mem *item) dbg_summary("dirent (%u) added to summary\n", je32_to_cpu(item->d.ino)); break; +#ifdef CONFIG_JFFS2_FS_XATTR + case JFFS2_NODETYPE_XATTR: + s->sum_size += JFFS2_SUMMARY_XATTR_SIZE; + s->sum_num++; + dbg_summary("xattr (xid=%u, version=%u) added to summary\n", + je32_to_cpu(item->x.xid), je32_to_cpu(item->x.version)); + break; + case JFFS2_NODETYPE_XREF: + s->sum_size += JFFS2_SUMMARY_XREF_SIZE; + s->sum_num++; + dbg_summary("xref added to summary\n"); + break; +#endif default: JFFS2_WARNING("UNKNOWN node type %u\n", je16_to_cpu(item->u.nodetype)); @@ -141,6 +155,40 @@ int jffs2_sum_add_dirent_mem(struct jffs2_summary *s, struct jffs2_raw_dirent *r return jffs2_sum_add_mem(s, (union jffs2_sum_mem *)temp); } +#ifdef CONFIG_JFFS2_FS_XATTR +int jffs2_sum_add_xattr_mem(struct jffs2_summary *s, struct jffs2_raw_xattr *rx, uint32_t ofs) +{ + struct jffs2_sum_xattr_mem *temp; + + temp = kmalloc(sizeof(struct jffs2_sum_xattr_mem), GFP_KERNEL); + if (!temp) + return -ENOMEM; + + temp->nodetype = rx->nodetype; + temp->xid = rx->xid; + temp->version = rx->version; + temp->offset = cpu_to_je32(ofs); + temp->totlen = rx->totlen; + temp->next = NULL; + + return jffs2_sum_add_mem(s, (union jffs2_sum_mem *)temp); +} + +int jffs2_sum_add_xref_mem(struct jffs2_summary *s, struct jffs2_raw_xref *rr, uint32_t ofs) +{ + struct jffs2_sum_xref_mem *temp; + + temp = kmalloc(sizeof(struct jffs2_sum_xref_mem), GFP_KERNEL); + if (!temp) + return -ENOMEM; + + temp->nodetype = rr->nodetype; + temp->offset = cpu_to_je32(ofs); + temp->next = NULL; + + return jffs2_sum_add_mem(s, (union jffs2_sum_mem *)temp); +} +#endif /* Cleanup every collected summary information */ static void jffs2_sum_clean_collected(struct jffs2_summary *s) @@ -259,7 +307,40 @@ int jffs2_sum_add_kvec(struct jffs2_sb_info *c, const struct kvec *invecs, return jffs2_sum_add_mem(c->summary, (union jffs2_sum_mem *)temp); } +#ifdef CONFIG_JFFS2_FS_XATTR + case JFFS2_NODETYPE_XATTR: { + struct jffs2_sum_xattr_mem *temp; + if (je32_to_cpu(node->x.version) == 0xffffffff) + return 0; + temp = kmalloc(sizeof(struct jffs2_sum_xattr_mem), GFP_KERNEL); + if (!temp) + goto no_mem; + temp->nodetype = node->x.nodetype; + temp->xid = node->x.xid; + temp->version = node->x.version; + temp->totlen = node->x.totlen; + temp->offset = cpu_to_je32(ofs); + temp->next = NULL; + + return jffs2_sum_add_mem(c->summary, (union jffs2_sum_mem *)temp); + } + case JFFS2_NODETYPE_XREF: { + struct jffs2_sum_xref_mem *temp; + + if (je32_to_cpu(node->r.ino) == 0xffffffff + && je32_to_cpu(node->r.xid) == 0xffffffff) + return 0; + temp = kmalloc(sizeof(struct jffs2_sum_xref_mem), GFP_KERNEL); + if (!temp) + goto no_mem; + temp->nodetype = node->r.nodetype; + temp->offset = cpu_to_je32(ofs); + temp->next = NULL; + + return jffs2_sum_add_mem(c->summary, (union jffs2_sum_mem *)temp); + } +#endif case JFFS2_NODETYPE_PADDING: dbg_summary("node PADDING\n"); c->summary->sum_padded += je32_to_cpu(node->u.totlen); @@ -408,8 +489,94 @@ static int jffs2_sum_process_sum_data(struct jffs2_sb_info *c, struct jffs2_eras break; } +#ifdef CONFIG_JFFS2_FS_XATTR + case JFFS2_NODETYPE_XATTR: { + struct jffs2_xattr_datum *xd; + struct jffs2_sum_xattr_flash *spx; + uint32_t ofs; + + spx = (struct jffs2_sum_xattr_flash *)sp; + ofs = jeb->offset + je32_to_cpu(spx->offset); + dbg_summary("xattr at %#08x (xid=%u, version=%u)\n", ofs, + je32_to_cpu(spx->xid), je32_to_cpu(spx->version)); + raw = jffs2_alloc_raw_node_ref(); + if (!raw) { + JFFS2_NOTICE("allocation of node reference failed\n"); + kfree(summary); + return -ENOMEM; + } + xd = jffs2_setup_xattr_datum(c, je32_to_cpu(spx->xid), + je32_to_cpu(spx->version)); + if (IS_ERR(xd)) { + JFFS2_NOTICE("allocation of xattr_datum failed\n"); + jffs2_free_raw_node_ref(raw); + kfree(summary); + return PTR_ERR(xd); + } + xd->node = raw; + raw->flash_offset = ofs | REF_UNCHECKED; + raw->__totlen = PAD(je32_to_cpu(spx->totlen)); + raw->next_phys = NULL; + raw->next_in_ino = (void *)xd; + if (!jeb->first_node) + jeb->first_node = raw; + if (jeb->last_node) + jeb->last_node->next_phys = raw; + jeb->last_node = raw; + + *pseudo_random += je32_to_cpu(spx->xid); + UNCHECKED_SPACE(je32_to_cpu(spx->totlen)); + sp += JFFS2_SUMMARY_XATTR_SIZE; + + break; + } + case JFFS2_NODETYPE_XREF: { + struct jffs2_xattr_ref *ref; + struct jffs2_sum_xref_flash *spr; + uint32_t ofs; + + spr = (struct jffs2_sum_xref_flash *)sp; + ofs = jeb->offset + je32_to_cpu(spr->offset); + dbg_summary("xref at %#08x (xid=%u, ino=%u)\n", ofs, + je32_to_cpu(spr->xid), je32_to_cpu(spr->ino)); + raw = jffs2_alloc_raw_node_ref(); + if (!raw) { + JFFS2_NOTICE("allocation of node reference failed\n"); + kfree(summary); + return -ENOMEM; + } + ref = jffs2_alloc_xattr_ref(); + if (!ref) { + JFFS2_NOTICE("allocation of xattr_datum failed\n"); + jffs2_free_raw_node_ref(raw); + kfree(summary); + return -ENOMEM; + } + ref->ino = 0xfffffffe; + ref->xid = 0xfffffffd; + ref->node = raw; + list_add_tail(&ref->ilist, &c->xattr_temp); + + raw->__totlen = PAD(sizeof(struct jffs2_raw_xref)); + raw->flash_offset = ofs | REF_UNCHECKED; + raw->next_phys = NULL; + raw->next_in_ino = (void *)ref; + if (!jeb->first_node) + jeb->first_node = raw; + if (jeb->last_node) + jeb->last_node->next_phys = raw; + jeb->last_node = raw; + + UNCHECKED_SPACE(PAD(sizeof(struct jffs2_raw_xref))); + *pseudo_random += ofs; + sp += JFFS2_SUMMARY_XREF_SIZE; + + break; + } +#endif default : { +printk("nodetype = %#04x\n",je16_to_cpu(((struct jffs2_sum_unknown_flash *)sp)->nodetype)); JFFS2_WARNING("Unsupported node type found in summary! Exiting..."); kfree(summary); return -EIO; @@ -617,7 +784,31 @@ static int jffs2_sum_write_data(struct jffs2_sb_info *c, struct jffs2_eraseblock break; } +#ifdef CONFIG_JFFS2_FS_XATTR + case JFFS2_NODETYPE_XATTR: { + struct jffs2_sum_xattr_flash *sxattr_ptr = wpage; + + temp = c->summary->sum_list_head; + sxattr_ptr->nodetype = temp->x.nodetype; + sxattr_ptr->xid = temp->x.xid; + sxattr_ptr->version = temp->x.version; + sxattr_ptr->offset = temp->x.offset; + sxattr_ptr->totlen = temp->x.totlen; + + wpage += JFFS2_SUMMARY_XATTR_SIZE; + break; + } + case JFFS2_NODETYPE_XREF: { + struct jffs2_sum_xref_flash *sxref_ptr = wpage; + + temp = c->summary->sum_list_head; + sxref_ptr->nodetype = temp->r.nodetype; + sxref_ptr->offset = temp->r.offset; + wpage += JFFS2_SUMMARY_XREF_SIZE; + break; + } +#endif default : { BUG(); /* unknown node in summary information */ } diff --git a/fs/jffs2/summary.h b/fs/jffs2/summary.h index b7a678be170..a3b66c18aae 100644 --- a/fs/jffs2/summary.h +++ b/fs/jffs2/summary.h @@ -45,6 +45,8 @@ #define JFFS2_SUMMARY_NOSUM_SIZE 0xffffffff #define JFFS2_SUMMARY_INODE_SIZE (sizeof(struct jffs2_sum_inode_flash)) #define JFFS2_SUMMARY_DIRENT_SIZE(x) (sizeof(struct jffs2_sum_dirent_flash) + (x)) +#define JFFS2_SUMMARY_XATTR_SIZE (sizeof(struct jffs2_sum_xattr_flash)) +#define JFFS2_SUMMARY_XREF_SIZE (sizeof(struct jffs2_sum_xref_flash)) /* Summary structures used on flash */ @@ -75,11 +77,28 @@ struct jffs2_sum_dirent_flash uint8_t name[0]; /* dirent name */ } __attribute__((packed)); +struct jffs2_sum_xattr_flash +{ + jint16_t nodetype; /* == JFFS2_NODETYPE_XATR */ + jint32_t xid; /* xattr identifier */ + jint32_t version; /* version number */ + jint32_t offset; /* offset on jeb */ + jint32_t totlen; /* node length */ +} __attribute__((packed)); + +struct jffs2_sum_xref_flash +{ + jint16_t nodetype; /* == JFFS2_NODETYPE_XREF */ + jint32_t offset; /* offset on jeb */ +} __attribute__((packed)); + union jffs2_sum_flash { struct jffs2_sum_unknown_flash u; struct jffs2_sum_inode_flash i; struct jffs2_sum_dirent_flash d; + struct jffs2_sum_xattr_flash x; + struct jffs2_sum_xref_flash r; }; /* Summary structures used in the memory */ @@ -114,11 +133,30 @@ struct jffs2_sum_dirent_mem uint8_t name[0]; /* dirent name */ } __attribute__((packed)); +struct jffs2_sum_xattr_mem +{ + union jffs2_sum_mem *next; + jint16_t nodetype; + jint32_t xid; + jint32_t version; + jint32_t offset; + jint32_t totlen; +} __attribute__((packed)); + +struct jffs2_sum_xref_mem +{ + union jffs2_sum_mem *next; + jint16_t nodetype; + jint32_t offset; +} __attribute__((packed)); + union jffs2_sum_mem { struct jffs2_sum_unknown_mem u; struct jffs2_sum_inode_mem i; struct jffs2_sum_dirent_mem d; + struct jffs2_sum_xattr_mem x; + struct jffs2_sum_xref_mem r; }; /* Summary related information stored in superblock */ @@ -159,6 +197,8 @@ int jffs2_sum_write_sumnode(struct jffs2_sb_info *c); int jffs2_sum_add_padding_mem(struct jffs2_summary *s, uint32_t size); int jffs2_sum_add_inode_mem(struct jffs2_summary *s, struct jffs2_raw_inode *ri, uint32_t ofs); int jffs2_sum_add_dirent_mem(struct jffs2_summary *s, struct jffs2_raw_dirent *rd, uint32_t ofs); +int jffs2_sum_add_xattr_mem(struct jffs2_summary *s, struct jffs2_raw_xattr *rx, uint32_t ofs); +int jffs2_sum_add_xref_mem(struct jffs2_summary *s, struct jffs2_raw_xref *rr, uint32_t ofs); int jffs2_sum_scan_sumnode(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb, uint32_t ofs, uint32_t *pseudo_random); @@ -176,6 +216,8 @@ int jffs2_sum_scan_sumnode(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb #define jffs2_sum_add_padding_mem(a,b) #define jffs2_sum_add_inode_mem(a,b,c) #define jffs2_sum_add_dirent_mem(a,b,c) +#define jffs2_sum_add_xattr_mem(a,b,c) +#define jffs2_sum_add_xref_mem(a,b,c) #define jffs2_sum_scan_sumnode(a,b,c,d) (0) #endif /* CONFIG_JFFS2_SUMMARY */ diff --git a/fs/jffs2/super.c b/fs/jffs2/super.c index ffd8e84b22c..c8b539ee7d8 100644 --- a/fs/jffs2/super.c +++ b/fs/jffs2/super.c @@ -151,7 +151,10 @@ static struct super_block *jffs2_get_sb_mtd(struct file_system_type *fs_type, sb->s_op = &jffs2_super_operations; sb->s_flags = flags | MS_NOATIME; - + sb->s_xattr = jffs2_xattr_handlers; +#ifdef CONFIG_JFFS2_FS_POSIX_ACL + sb->s_flags |= MS_POSIXACL; +#endif ret = jffs2_do_fill_super(sb, data, flags & MS_SILENT ? 1 : 0); if (ret) { @@ -293,6 +296,7 @@ static void jffs2_put_super (struct super_block *sb) kfree(c->blocks); jffs2_flash_cleanup(c); kfree(c->inocache_list); + jffs2_clear_xattr_subsystem(c); if (c->mtd->sync) c->mtd->sync(c->mtd); diff --git a/fs/jffs2/symlink.c b/fs/jffs2/symlink.c index d55754fe892..fc211b6e9b0 100644 --- a/fs/jffs2/symlink.c +++ b/fs/jffs2/symlink.c @@ -24,7 +24,12 @@ struct inode_operations jffs2_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = jffs2_follow_link, - .setattr = jffs2_setattr + .permission = jffs2_permission, + .setattr = jffs2_setattr, + .setxattr = jffs2_setxattr, + .getxattr = jffs2_getxattr, + .listxattr = jffs2_listxattr, + .removexattr = jffs2_removexattr }; static void *jffs2_follow_link(struct dentry *dentry, struct nameidata *nd) diff --git a/fs/jffs2/write.c b/fs/jffs2/write.c index 1342f0158e9..d5c78195f3b 100644 --- a/fs/jffs2/write.c +++ b/fs/jffs2/write.c @@ -36,7 +36,7 @@ int jffs2_do_new_inode(struct jffs2_sb_info *c, struct jffs2_inode_info *f, uint f->inocache->nlink = 1; f->inocache->nodes = (struct jffs2_raw_node_ref *)f->inocache; f->inocache->state = INO_STATE_PRESENT; - + init_xattr_inode_cache(f->inocache); jffs2_add_ino_cache(c, f->inocache); D1(printk(KERN_DEBUG "jffs2_do_new_inode(): Assigned ino# %d\n", f->inocache->ino)); diff --git a/fs/jffs2/xattr.c b/fs/jffs2/xattr.c new file mode 100644 index 00000000000..c9a185c54ce --- /dev/null +++ b/fs/jffs2/xattr.c @@ -0,0 +1,1271 @@ +/* ------------------------------------------------------------------------- + * File: fs/jffs2/xattr.c + * XATTR support on JFFS2 FileSystem + * + * Implemented by KaiGai Kohei <kaigai@ak.jp.nec.com> + * Copyright (C) 2006 NEC Corporation + * + * For licensing information, see the file 'LICENCE' in the jffs2 directory. + * ------------------------------------------------------------------------- */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/time.h> +#include <linux/pagemap.h> +#include <linux/highmem.h> +#include <linux/crc32.h> +#include <linux/jffs2.h> +#include <linux/xattr.h> +#include <linux/mtd/mtd.h> +#include "nodelist.h" +/* -------- xdatum related functions ---------------- + * xattr_datum_hashkey(xprefix, xname, xvalue, xsize) + * is used to calcurate xdatum hashkey. The reminder of hashkey into XATTRINDEX_HASHSIZE is + * the index of the xattr name/value pair cache (c->xattrindex). + * unload_xattr_datum(c, xd) + * is used to release xattr name/value pair and detach from c->xattrindex. + * reclaim_xattr_datum(c) + * is used to reclaim xattr name/value pairs on the xattr name/value pair cache when + * memory usage by cache is over c->xdatum_mem_threshold. Currentry, this threshold + * is hard coded as 32KiB. + * delete_xattr_datum_node(c, xd) + * is used to delete a jffs2 node is dominated by xdatum. When EBS(Erase Block Summary) is + * enabled, it overwrites the obsolete node by myself. + * delete_xattr_datum(c, xd) + * is used to delete jffs2_xattr_datum object. It must be called with 0-value of reference + * counter. (It means how many jffs2_xattr_ref object refers this xdatum.) + * do_verify_xattr_datum(c, xd) + * is used to load the xdatum informations without name/value pair from the medium. + * It's necessary once, because those informations are not collected during mounting + * process when EBS is enabled. + * 0 will be returned, if success. An negative return value means recoverable error, and + * positive return value means unrecoverable error. Thus, caller must remove this xdatum + * and xref when it returned positive value. + * do_load_xattr_datum(c, xd) + * is used to load name/value pair from the medium. + * The meanings of return value is same as do_verify_xattr_datum(). + * load_xattr_datum(c, xd) + * is used to be as a wrapper of do_verify_xattr_datum() and do_load_xattr_datum(). + * If xd need to call do_verify_xattr_datum() at first, it's called before calling + * do_load_xattr_datum(). The meanings of return value is same as do_verify_xattr_datum(). + * save_xattr_datum(c, xd, phys_ofs) + * is used to write xdatum to medium. xd->version will be incremented. + * create_xattr_datum(c, xprefix, xname, xvalue, xsize, phys_ofs) + * is used to create new xdatum and write to medium. + * -------------------------------------------------- */ + +static uint32_t xattr_datum_hashkey(int xprefix, const char *xname, const char *xvalue, int xsize) +{ + int name_len = strlen(xname); + + return crc32(xprefix, xname, name_len) ^ crc32(xprefix, xvalue, xsize); +} + +static void unload_xattr_datum(struct jffs2_sb_info *c, struct jffs2_xattr_datum *xd) +{ + /* must be called under down_write(xattr_sem) */ + D1(dbg_xattr("%s: xid=%u, version=%u\n", __FUNCTION__, xd->xid, xd->version)); + if (xd->xname) { + c->xdatum_mem_usage -= (xd->name_len + 1 + xd->value_len); + kfree(xd->xname); + } + + list_del_init(&xd->xindex); + xd->hashkey = 0; + xd->xname = NULL; + xd->xvalue = NULL; +} + +static void reclaim_xattr_datum(struct jffs2_sb_info *c) +{ + /* must be called under down_write(xattr_sem) */ + struct jffs2_xattr_datum *xd, *_xd; + uint32_t target, before; + static int index = 0; + int count; + + if (c->xdatum_mem_threshold > c->xdatum_mem_usage) + return; + + before = c->xdatum_mem_usage; + target = c->xdatum_mem_usage * 4 / 5; /* 20% reduction */ + for (count = 0; count < XATTRINDEX_HASHSIZE; count++) { + list_for_each_entry_safe(xd, _xd, &c->xattrindex[index], xindex) { + if (xd->flags & JFFS2_XFLAGS_HOT) { + xd->flags &= ~JFFS2_XFLAGS_HOT; + } else if (!(xd->flags & JFFS2_XFLAGS_BIND)) { + unload_xattr_datum(c, xd); + } + if (c->xdatum_mem_usage <= target) + goto out; + } + index = (index+1) % XATTRINDEX_HASHSIZE; + } + out: + JFFS2_NOTICE("xdatum_mem_usage from %u byte to %u byte (%u byte reclaimed)\n", + before, c->xdatum_mem_usage, before - c->xdatum_mem_usage); +} + +static void delete_xattr_datum_node(struct jffs2_sb_info *c, struct jffs2_xattr_datum *xd) +{ + /* must be called under down_write(xattr_sem) */ + struct jffs2_raw_xattr rx; + uint32_t length; + int rc; + + if (!xd->node) { + JFFS2_WARNING("xdatum (xid=%u) is removed twice.\n", xd->xid); + return; + } + if (jffs2_sum_active()) { + memset(&rx, 0xff, sizeof(struct jffs2_raw_xattr)); + rc = jffs2_flash_read(c, ref_offset(xd->node), + sizeof(struct jffs2_unknown_node), + &length, (char *)&rx); + if (rc || length != sizeof(struct jffs2_unknown_node)) { + JFFS2_ERROR("jffs2_flash_read()=%d, req=%u, read=%u at %#08x\n", + rc, sizeof(struct jffs2_unknown_node), + length, ref_offset(xd->node)); + } + rc = jffs2_flash_write(c, ref_offset(xd->node), sizeof(rx), + &length, (char *)&rx); + if (rc || length != sizeof(struct jffs2_raw_xattr)) { + JFFS2_ERROR("jffs2_flash_write()=%d, req=%u, wrote=%u ar %#08x\n", + rc, sizeof(rx), length, ref_offset(xd->node)); + } + } + spin_lock(&c->erase_completion_lock); + xd->node->next_in_ino = NULL; + spin_unlock(&c->erase_completion_lock); + jffs2_mark_node_obsolete(c, xd->node); + xd->node = NULL; +} + +static void delete_xattr_datum(struct jffs2_sb_info *c, struct jffs2_xattr_datum *xd) +{ + /* must be called under down_write(xattr_sem) */ + BUG_ON(xd->refcnt); + + unload_xattr_datum(c, xd); + if (xd->node) { + delete_xattr_datum_node(c, xd); + xd->node = NULL; + } + jffs2_free_xattr_datum(xd); +} + +static int do_verify_xattr_datum(struct jffs2_sb_info *c, struct jffs2_xattr_datum *xd) +{ + /* must be called under down_write(xattr_sem) */ + struct jffs2_eraseblock *jeb; + struct jffs2_raw_xattr rx; + size_t readlen; + uint32_t crc, totlen; + int rc; + + BUG_ON(!xd->node); + BUG_ON(ref_flags(xd->node) != REF_UNCHECKED); + + rc = jffs2_flash_read(c, ref_offset(xd->node), sizeof(rx), &readlen, (char *)&rx); + if (rc || readlen != sizeof(rx)) { + JFFS2_WARNING("jffs2_flash_read()=%d, req=%u, read=%u at %#08x\n", + rc, sizeof(rx), readlen, ref_offset(xd->node)); + return rc ? rc : -EIO; + } + crc = crc32(0, &rx, sizeof(rx) - 4); + if (crc != je32_to_cpu(rx.node_crc)) { + if (je32_to_cpu(rx.node_crc) != 0xffffffff) + JFFS2_ERROR("node CRC failed at %#08x, read=%#08x, calc=%#08x\n", + ref_offset(xd->node), je32_to_cpu(rx.hdr_crc), crc); + return EIO; + } + totlen = PAD(sizeof(rx) + rx.name_len + 1 + je16_to_cpu(rx.value_len)); + if (je16_to_cpu(rx.magic) != JFFS2_MAGIC_BITMASK + || je16_to_cpu(rx.nodetype) != JFFS2_NODETYPE_XATTR + || je32_to_cpu(rx.totlen) != totlen + || je32_to_cpu(rx.xid) != xd->xid + || je32_to_cpu(rx.version) != xd->version) { + JFFS2_ERROR("inconsistent xdatum at %#08x, magic=%#04x/%#04x, " + "nodetype=%#04x/%#04x, totlen=%u/%u, xid=%u/%u, version=%u/%u\n", + ref_offset(xd->node), je16_to_cpu(rx.magic), JFFS2_MAGIC_BITMASK, + je16_to_cpu(rx.nodetype), JFFS2_NODETYPE_XATTR, + je32_to_cpu(rx.totlen), totlen, + je32_to_cpu(rx.xid), xd->xid, + je32_to_cpu(rx.version), xd->version); + return EIO; + } + xd->xprefix = rx.xprefix; + xd->name_len = rx.name_len; + xd->value_len = je16_to_cpu(rx.value_len); + xd->data_crc = je32_to_cpu(rx.data_crc); + + /* This JFFS2_NODETYPE_XATTR node is checked */ + jeb = &c->blocks[ref_offset(xd->node) / c->sector_size]; + totlen = PAD(je32_to_cpu(rx.totlen)); + + spin_lock(&c->erase_completion_lock); + c->unchecked_size -= totlen; c->used_size += totlen; + jeb->unchecked_size -= totlen; jeb->used_size += totlen; + xd->node->flash_offset = ref_offset(xd->node) | REF_PRISTINE; + spin_unlock(&c->erase_completion_lock); + + /* unchecked xdatum is chained with c->xattr_unchecked */ + list_del_init(&xd->xindex); + + dbg_xattr("success on verfying xdatum (xid=%u, version=%u)\n", + xd->xid, xd->version); + + return 0; +} + +static int do_load_xattr_datum(struct jffs2_sb_info *c, struct jffs2_xattr_datum *xd) +{ + /* must be called under down_write(xattr_sem) */ + char *data; + size_t readlen; + uint32_t crc, length; + int i, ret, retry = 0; + + BUG_ON(!xd->node); + BUG_ON(ref_flags(xd->node) != REF_PRISTINE); + BUG_ON(!list_empty(&xd->xindex)); + retry: + length = xd->name_len + 1 + xd->value_len; + data = kmalloc(length, GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = jffs2_flash_read(c, ref_offset(xd->node)+sizeof(struct jffs2_raw_xattr), + length, &readlen, data); + + if (ret || length!=readlen) { + JFFS2_WARNING("jffs2_flash_read() returned %d, request=%d, readlen=%d, at %#08x\n", + ret, length, readlen, ref_offset(xd->node)); + kfree(data); + return ret ? ret : -EIO; + } + + data[xd->name_len] = '\0'; + crc = crc32(0, data, length); + if (crc != xd->data_crc) { + JFFS2_WARNING("node CRC failed (JFFS2_NODETYPE_XREF)" + " at %#08x, read: 0x%08x calculated: 0x%08x\n", + ref_offset(xd->node), xd->data_crc, crc); + kfree(data); + return EIO; + } + + xd->flags |= JFFS2_XFLAGS_HOT; + xd->xname = data; + xd->xvalue = data + xd->name_len+1; + + c->xdatum_mem_usage += length; + + xd->hashkey = xattr_datum_hashkey(xd->xprefix, xd->xname, xd->xvalue, xd->value_len); + i = xd->hashkey % XATTRINDEX_HASHSIZE; + list_add(&xd->xindex, &c->xattrindex[i]); + if (!retry) { + retry = 1; + reclaim_xattr_datum(c); + if (!xd->xname) + goto retry; + } + + dbg_xattr("success on loading xdatum (xid=%u, xprefix=%u, xname='%s')\n", + xd->xid, xd->xprefix, xd->xname); + + return 0; +} + +static int load_xattr_datum(struct jffs2_sb_info *c, struct jffs2_xattr_datum *xd) +{ + /* must be called under down_write(xattr_sem); + * rc < 0 : recoverable error, try again + * rc = 0 : success + * rc > 0 : Unrecoverable error, this node should be deleted. + */ + int rc = 0; + BUG_ON(xd->xname); + if (!xd->node) + return EIO; + if (unlikely(ref_flags(xd->node) != REF_PRISTINE)) { + rc = do_verify_xattr_datum(c, xd); + if (rc > 0) { + list_del_init(&xd->xindex); + delete_xattr_datum_node(c, xd); + } + } + if (!rc) + rc = do_load_xattr_datum(c, xd); + return rc; +} + +static int save_xattr_datum(struct jffs2_sb_info *c, struct jffs2_xattr_datum *xd, uint32_t phys_ofs) +{ + /* must be called under down_write(xattr_sem) */ + struct jffs2_raw_xattr rx; + struct jffs2_raw_node_ref *raw; + struct kvec vecs[2]; + uint32_t length; + int rc, totlen; + + BUG_ON(!xd->xname); + + vecs[0].iov_base = ℞ + vecs[0].iov_len = PAD(sizeof(rx)); + vecs[1].iov_base = xd->xname; + vecs[1].iov_len = xd->name_len + 1 + xd->value_len; + totlen = vecs[0].iov_len + vecs[1].iov_len; + + raw = jffs2_alloc_raw_node_ref(); + if (!raw) + return -ENOMEM; + raw->flash_offset = phys_ofs; + raw->__totlen = PAD(totlen); + raw->next_phys = NULL; + raw->next_in_ino = (void *)xd; + + /* Setup raw-xattr */ + rx.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); + rx.nodetype = cpu_to_je16(JFFS2_NODETYPE_XATTR); + rx.totlen = cpu_to_je32(PAD(totlen)); + rx.hdr_crc = cpu_to_je32(crc32(0, &rx, sizeof(struct jffs2_unknown_node) - 4)); + + rx.xid = cpu_to_je32(xd->xid); + rx.version = cpu_to_je32(++xd->version); + rx.xprefix = xd->xprefix; + rx.name_len = xd->name_len; + rx.value_len = cpu_to_je16(xd->value_len); + rx.data_crc = cpu_to_je32(crc32(0, vecs[1].iov_base, vecs[1].iov_len)); + rx.node_crc = cpu_to_je32(crc32(0, &rx, sizeof(struct jffs2_raw_xattr) - 4)); + + rc = jffs2_flash_writev(c, vecs, 2, phys_ofs, &length, 0); + if (rc || totlen != length) { + JFFS2_WARNING("jffs2_flash_writev()=%d, req=%u, wrote=%u, at %#08x\n", + rc, totlen, length, phys_ofs); + rc = rc ? rc : -EIO; + if (length) { + raw->flash_offset |= REF_OBSOLETE; + raw->next_in_ino = NULL; + jffs2_add_physical_node_ref(c, raw); + jffs2_mark_node_obsolete(c, raw); + } else { + jffs2_free_raw_node_ref(raw); + } + return rc; + } + BUG_ON(raw->__totlen < sizeof(struct jffs2_raw_xattr)); + /* success */ + raw->flash_offset |= REF_PRISTINE; + jffs2_add_physical_node_ref(c, raw); + if (xd->node) + delete_xattr_datum_node(c, xd); + xd->node = raw; + + dbg_xattr("success on saving xdatum (xid=%u, version=%u, xprefix=%u, xname='%s')\n", + xd->xid, xd->version, xd->xprefix, xd->xname); + + return 0; +} + +static struct jffs2_xattr_datum *create_xattr_datum(struct jffs2_sb_info *c, + int xprefix, const char *xname, + const char *xvalue, int xsize, + uint32_t phys_ofs) +{ + /* must be called under down_write(xattr_sem) */ + struct jffs2_xattr_datum *xd; + uint32_t hashkey, name_len; + char *data; + int i, rc; + + /* Search xattr_datum has same xname/xvalue by index */ + hashkey = xattr_datum_hashkey(xprefix, xname, xvalue, xsize); + i = hashkey % XATTRINDEX_HASHSIZE; + list_for_each_entry(xd, &c->xattrindex[i], xindex) { + if (xd->hashkey==hashkey + && xd->xprefix==xprefix + && xd->value_len==xsize + && !strcmp(xd->xname, xname) + && !memcmp(xd->xvalue, xvalue, xsize)) { + xd->refcnt++; + return xd; + } + } + + /* Not found, Create NEW XATTR-Cache */ + name_len = strlen(xname); + + xd = jffs2_alloc_xattr_datum(); + if (!xd) + return ERR_PTR(-ENOMEM); + + data = kmalloc(name_len + 1 + xsize, GFP_KERNEL); + if (!data) { + jffs2_free_xattr_datum(xd); + return ERR_PTR(-ENOMEM); + } + strcpy(data, xname); + memcpy(data + name_len + 1, xvalue, xsize); + + xd->refcnt = 1; + xd->xid = ++c->highest_xid; + xd->flags |= JFFS2_XFLAGS_HOT; + xd->xprefix = xprefix; + + xd->hashkey = hashkey; + xd->xname = data; + xd->xvalue = data + name_len + 1; + xd->name_len = name_len; + xd->value_len = xsize; + xd->data_crc = crc32(0, data, xd->name_len + 1 + xd->value_len); + + rc = save_xattr_datum(c, xd, phys_ofs); + if (rc) { + kfree(xd->xname); + jffs2_free_xattr_datum(xd); + return ERR_PTR(rc); + } + + /* Insert Hash Index */ + i = hashkey % XATTRINDEX_HASHSIZE; + list_add(&xd->xindex, &c->xattrindex[i]); + + c->xdatum_mem_usage += (xd->name_len + 1 + xd->value_len); + reclaim_xattr_datum(c); + + return xd; +} + +/* -------- xdatum related functions ---------------- + * verify_xattr_ref(c, ref) + * is used to load xref information from medium. Because summary data does not + * contain xid/ino, it's necessary to verify once while mounting process. + * delete_xattr_ref_node(c, ref) + * is used to delete a jffs2 node is dominated by xref. When EBS is enabled, + * it overwrites the obsolete node by myself. + * delete_xattr_ref(c, ref) + * is used to delete jffs2_xattr_ref object. If the reference counter of xdatum + * is refered by this xref become 0, delete_xattr_datum() is called later. + * save_xattr_ref(c, ref, phys_ofs) + * is used to write xref to medium. + * create_xattr_ref(c, ic, xd, phys_ofs) + * is used to create a new xref and write to medium. + * jffs2_xattr_delete_inode(c, ic) + * is called to remove xrefs related to obsolete inode when inode is unlinked. + * jffs2_xattr_free_inode(c, ic) + * is called to release xattr related objects when unmounting. + * check_xattr_ref_ilist(c, ic) + * is used to confirm inode does not have duplicate xattr name/value pair. + * -------------------------------------------------- */ +static int verify_xattr_ref(struct jffs2_sb_info *c, struct jffs2_xattr_ref *ref) +{ + struct jffs2_eraseblock *jeb; + struct jffs2_raw_xref rr; + size_t readlen; + uint32_t crc, totlen; + int rc; + + BUG_ON(ref_flags(ref->node) != REF_UNCHECKED); + + rc = jffs2_flash_read(c, ref_offset(ref->node), sizeof(rr), &readlen, (char *)&rr); + if (rc || sizeof(rr) != readlen) { + JFFS2_WARNING("jffs2_flash_read()=%d, req=%u, read=%u, at %#08x\n", + rc, sizeof(rr), readlen, ref_offset(ref->node)); + return rc ? rc : -EIO; + } + /* obsolete node */ + crc = crc32(0, &rr, sizeof(rr) - 4); + if (crc != je32_to_cpu(rr.node_crc)) { + if (je32_to_cpu(rr.node_crc) != 0xffffffff) + JFFS2_ERROR("node CRC failed at %#08x, read=%#08x, calc=%#08x\n", + ref_offset(ref->node), je32_to_cpu(rr.node_crc), crc); + return EIO; + } + if (je16_to_cpu(rr.magic) != JFFS2_MAGIC_BITMASK + || je16_to_cpu(rr.nodetype) != JFFS2_NODETYPE_XREF + || je32_to_cpu(rr.totlen) != PAD(sizeof(rr))) { + JFFS2_ERROR("inconsistent xref at %#08x, magic=%#04x/%#04x, " + "nodetype=%#04x/%#04x, totlen=%u/%u\n", + ref_offset(ref->node), je16_to_cpu(rr.magic), JFFS2_MAGIC_BITMASK, + je16_to_cpu(rr.nodetype), JFFS2_NODETYPE_XREF, + je32_to_cpu(rr.totlen), PAD(sizeof(rr))); + return EIO; + } + ref->ino = je32_to_cpu(rr.ino); + ref->xid = je32_to_cpu(rr.xid); + + /* fixup superblock/eraseblock info */ + jeb = &c->blocks[ref_offset(ref->node) / c->sector_size]; + totlen = PAD(sizeof(rr)); + + spin_lock(&c->erase_completion_lock); + c->unchecked_size -= totlen; c->used_size += totlen; + jeb->unchecked_size -= totlen; jeb->used_size += totlen; + ref->node->flash_offset = ref_offset(ref->node) | REF_PRISTINE; + spin_unlock(&c->erase_completion_lock); + + dbg_xattr("success on verifying xref (ino=%u, xid=%u) at %#08x\n", + ref->ino, ref->xid, ref_offset(ref->node)); + return 0; +} + +static void delete_xattr_ref_node(struct jffs2_sb_info *c, struct jffs2_xattr_ref *ref) +{ + struct jffs2_raw_xref rr; + uint32_t length; + int rc; + + if (jffs2_sum_active()) { + memset(&rr, 0xff, sizeof(rr)); + rc = jffs2_flash_read(c, ref_offset(ref->node), + sizeof(struct jffs2_unknown_node), + &length, (char *)&rr); + if (rc || length != sizeof(struct jffs2_unknown_node)) { + JFFS2_ERROR("jffs2_flash_read()=%d, req=%u, read=%u at %#08x\n", + rc, sizeof(struct jffs2_unknown_node), + length, ref_offset(ref->node)); + } + rc = jffs2_flash_write(c, ref_offset(ref->node), sizeof(rr), + &length, (char *)&rr); + if (rc || length != sizeof(struct jffs2_raw_xref)) { + JFFS2_ERROR("jffs2_flash_write()=%d, req=%u, wrote=%u at %#08x\n", + rc, sizeof(rr), length, ref_offset(ref->node)); + } + } + spin_lock(&c->erase_completion_lock); + ref->node->next_in_ino = NULL; + spin_unlock(&c->erase_completion_lock); + jffs2_mark_node_obsolete(c, ref->node); + ref->node = NULL; +} + +static void delete_xattr_ref(struct jffs2_sb_info *c, struct jffs2_xattr_ref *ref) +{ + /* must be called under down_write(xattr_sem) */ + struct jffs2_xattr_datum *xd; + + BUG_ON(!ref->node); + delete_xattr_ref_node(c, ref); + + list_del(&ref->ilist); + xd = ref->xd; + xd->refcnt--; + if (!xd->refcnt) + delete_xattr_datum(c, xd); + jffs2_free_xattr_ref(ref); +} + +static int save_xattr_ref(struct jffs2_sb_info *c, struct jffs2_xattr_ref *ref, uint32_t phys_ofs) +{ + /* must be called under down_write(xattr_sem) */ + struct jffs2_raw_node_ref *raw; + struct jffs2_raw_xref rr; + uint32_t length; + int ret; + + raw = jffs2_alloc_raw_node_ref(); + if (!raw) + return -ENOMEM; + raw->flash_offset = phys_ofs; + raw->__totlen = PAD(sizeof(rr)); + raw->next_phys = NULL; + raw->next_in_ino = (void *)ref; + + rr.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); + rr.nodetype = cpu_to_je16(JFFS2_NODETYPE_XREF); + rr.totlen = cpu_to_je32(PAD(sizeof(rr))); + rr.hdr_crc = cpu_to_je32(crc32(0, &rr, sizeof(struct jffs2_unknown_node) - 4)); + + rr.ino = cpu_to_je32(ref->ic->ino); + rr.xid = cpu_to_je32(ref->xd->xid); + rr.node_crc = cpu_to_je32(crc32(0, &rr, sizeof(rr) - 4)); + + ret = jffs2_flash_write(c, phys_ofs, sizeof(rr), &length, (char *)&rr); + if (ret || sizeof(rr) != length) { + JFFS2_WARNING("jffs2_flash_write() returned %d, request=%u, retlen=%u, at %#08x\n", + ret, sizeof(rr), length, phys_ofs); + ret = ret ? ret : -EIO; + if (length) { + raw->flash_offset |= REF_OBSOLETE; + raw->next_in_ino = NULL; + jffs2_add_physical_node_ref(c, raw); + jffs2_mark_node_obsolete(c, raw); + } else { + jffs2_free_raw_node_ref(raw); + } + return ret; + } + raw->flash_offset |= REF_PRISTINE; + + jffs2_add_physical_node_ref(c, raw); + if (ref->node) + delete_xattr_ref_node(c, ref); + ref->node = raw; + + dbg_xattr("success on saving xref (ino=%u, xid=%u)\n", ref->ic->ino, ref->xd->xid); + + return 0; +} + +static struct jffs2_xattr_ref *create_xattr_ref(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic, + struct jffs2_xattr_datum *xd, uint32_t phys_ofs) +{ + /* must be called under down_write(xattr_sem) */ + struct jffs2_xattr_ref *ref; + int ret; + + ref = jffs2_alloc_xattr_ref(); + if (!ref) + return ERR_PTR(-ENOMEM); + ref->ic = ic; + ref->xd = xd; + + ret = save_xattr_ref(c, ref, phys_ofs); + if (ret) { + jffs2_free_xattr_ref(ref); + return ERR_PTR(ret); + } + + /* Chain to inode */ + list_add(&ref->ilist, &ic->ilist); + + return ref; /* success */ +} + +void jffs2_xattr_delete_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic) +{ + /* It's called from jffs2_clear_inode() on inode removing. + When an inode with XATTR is removed, those XATTRs must be removed. */ + struct jffs2_xattr_ref *ref, *_ref; + + if (!ic || ic->nlink > 0) + return; + + down_write(&c->xattr_sem); + list_for_each_entry_safe(ref, _ref, &ic->ilist, ilist) + delete_xattr_ref(c, ref); + up_write(&c->xattr_sem); +} + +void jffs2_xattr_free_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic) +{ + /* It's called from jffs2_free_ino_caches() until unmounting FS. */ + struct jffs2_xattr_datum *xd; + struct jffs2_xattr_ref *ref, *_ref; + + down_write(&c->xattr_sem); + list_for_each_entry_safe(ref, _ref, &ic->ilist, ilist) { + list_del(&ref->ilist); + xd = ref->xd; + xd->refcnt--; + if (!xd->refcnt) { + unload_xattr_datum(c, xd); + jffs2_free_xattr_datum(xd); + } + jffs2_free_xattr_ref(ref); + } + up_write(&c->xattr_sem); +} + +static int check_xattr_ref_ilist(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic) +{ + /* success of check_xattr_ref_ilist() means taht inode (ic) dose not have + * duplicate name/value pairs. If duplicate name/value pair would be found, + * one will be removed. + */ + struct jffs2_xattr_ref *ref, *cmp; + int rc = 0; + + if (likely(ic->flags & INO_FLAGS_XATTR_CHECKED)) + return 0; + down_write(&c->xattr_sem); + retry: + rc = 0; + list_for_each_entry(ref, &ic->ilist, ilist) { + if (!ref->xd->xname) { + rc = load_xattr_datum(c, ref->xd); + if (unlikely(rc > 0)) { + delete_xattr_ref(c, ref); + goto retry; + } else if (unlikely(rc < 0)) + goto out; + } + cmp = ref; + list_for_each_entry_continue(cmp, &ic->ilist, ilist) { + if (!cmp->xd->xname) { + ref->xd->flags |= JFFS2_XFLAGS_BIND; + rc = load_xattr_datum(c, cmp->xd); + ref->xd->flags &= ~JFFS2_XFLAGS_BIND; + if (unlikely(rc > 0)) { + delete_xattr_ref(c, cmp); + goto retry; + } else if (unlikely(rc < 0)) + goto out; + } + if (ref->xd->xprefix == cmp->xd->xprefix + && !strcmp(ref->xd->xname, cmp->xd->xname)) { + delete_xattr_ref(c, cmp); + goto retry; + } + } + } + ic->flags |= INO_FLAGS_XATTR_CHECKED; + out: + up_write(&c->xattr_sem); + + return rc; +} + +/* -------- xattr subsystem functions --------------- + * jffs2_init_xattr_subsystem(c) + * is used to initialize semaphore and list_head, and some variables. + * jffs2_find_xattr_datum(c, xid) + * is used to lookup xdatum while scanning process. + * jffs2_clear_xattr_subsystem(c) + * is used to release any xattr related objects. + * jffs2_build_xattr_subsystem(c) + * is used to associate xdatum and xref while super block building process. + * jffs2_setup_xattr_datum(c, xid, version) + * is used to insert xdatum while scanning process. + * -------------------------------------------------- */ +void jffs2_init_xattr_subsystem(struct jffs2_sb_info *c) +{ + int i; + + for (i=0; i < XATTRINDEX_HASHSIZE; i++) + INIT_LIST_HEAD(&c->xattrindex[i]); + INIT_LIST_HEAD(&c->xattr_temp); + INIT_LIST_HEAD(&c->xattr_unchecked); + + init_rwsem(&c->xattr_sem); + c->xdatum_mem_usage = 0; + c->xdatum_mem_threshold = 32 * 1024; /* Default 32KB */ +} + +static struct jffs2_xattr_datum *jffs2_find_xattr_datum(struct jffs2_sb_info *c, uint32_t xid) +{ + struct jffs2_xattr_datum *xd; + int i = xid % XATTRINDEX_HASHSIZE; + + /* It's only used in scanning/building process. */ + BUG_ON(!(c->flags & (JFFS2_SB_FLAG_SCANNING|JFFS2_SB_FLAG_BUILDING))); + + list_for_each_entry(xd, &c->xattrindex[i], xindex) { + if (xd->xid==xid) + return xd; + } + return NULL; +} + +void jffs2_clear_xattr_subsystem(struct jffs2_sb_info *c) +{ + struct jffs2_xattr_datum *xd, *_xd; + struct jffs2_xattr_ref *ref, *_ref; + int i; + + list_for_each_entry_safe(ref, _ref, &c->xattr_temp, ilist) + jffs2_free_xattr_ref(ref); + + for (i=0; i < XATTRINDEX_HASHSIZE; i++) { + list_for_each_entry_safe(xd, _xd, &c->xattrindex[i], xindex) { + list_del(&xd->xindex); + if (xd->xname) + kfree(xd->xname); + jffs2_free_xattr_datum(xd); + } + } +} + +void jffs2_build_xattr_subsystem(struct jffs2_sb_info *c) +{ + struct jffs2_xattr_ref *ref, *_ref; + struct jffs2_xattr_datum *xd, *_xd; + struct jffs2_inode_cache *ic; + int i, xdatum_count =0, xdatum_unchecked_count = 0, xref_count = 0; + + BUG_ON(!(c->flags & JFFS2_SB_FLAG_BUILDING)); + + /* Phase.1 */ + list_for_each_entry_safe(ref, _ref, &c->xattr_temp, ilist) { + list_del_init(&ref->ilist); + /* checking REF_UNCHECKED nodes */ + if (ref_flags(ref->node) != REF_PRISTINE) { + if (verify_xattr_ref(c, ref)) { + delete_xattr_ref_node(c, ref); + jffs2_free_xattr_ref(ref); + continue; + } + } + /* At this point, ref->xid and ref->ino contain XID and inode number. + ref->xd and ref->ic are not valid yet. */ + xd = jffs2_find_xattr_datum(c, ref->xid); + ic = jffs2_get_ino_cache(c, ref->ino); + if (!xd || !ic) { + if (ref_flags(ref->node) != REF_UNCHECKED) + JFFS2_WARNING("xref(ino=%u, xid=%u) is orphan. \n", + ref->ino, ref->xid); + delete_xattr_ref_node(c, ref); + jffs2_free_xattr_ref(ref); + continue; + } + ref->xd = xd; + ref->ic = ic; + xd->refcnt++; + list_add_tail(&ref->ilist, &ic->ilist); + xref_count++; + } + /* After this, ref->xid/ino are NEVER used. */ + + /* Phase.2 */ + for (i=0; i < XATTRINDEX_HASHSIZE; i++) { + list_for_each_entry_safe(xd, _xd, &c->xattrindex[i], xindex) { + list_del_init(&xd->xindex); + if (!xd->refcnt) { + if (ref_flags(xd->node) != REF_UNCHECKED) + JFFS2_WARNING("orphan xdatum(xid=%u, version=%u) at %#08x\n", + xd->xid, xd->version, ref_offset(xd->node)); + delete_xattr_datum(c, xd); + continue; + } + if (ref_flags(xd->node) != REF_PRISTINE) { + dbg_xattr("unchecked xdatum(xid=%u) at %#08x\n", + xd->xid, ref_offset(xd->node)); + list_add(&xd->xindex, &c->xattr_unchecked); + xdatum_unchecked_count++; + } + xdatum_count++; + } + } + /* build complete */ + JFFS2_NOTICE("complete building xattr subsystem, %u of xdatum (%u unchecked) and " + "%u of xref found.\n", xdatum_count, xdatum_unchecked_count, xref_count); +} + +struct jffs2_xattr_datum *jffs2_setup_xattr_datum(struct jffs2_sb_info *c, + uint32_t xid, uint32_t version) +{ + struct jffs2_xattr_datum *xd, *_xd; + + _xd = jffs2_find_xattr_datum(c, xid); + if (_xd) { + dbg_xattr("duplicate xdatum (xid=%u, version=%u/%u) at %#08x\n", + xid, version, _xd->version, ref_offset(_xd->node)); + if (version < _xd->version) + return ERR_PTR(-EEXIST); + } + xd = jffs2_alloc_xattr_datum(); + if (!xd) + return ERR_PTR(-ENOMEM); + xd->xid = xid; + xd->version = version; + if (xd->xid > c->highest_xid) + c->highest_xid = xd->xid; + list_add_tail(&xd->xindex, &c->xattrindex[xid % XATTRINDEX_HASHSIZE]); + + if (_xd) { + list_del_init(&_xd->xindex); + delete_xattr_datum_node(c, _xd); + jffs2_free_xattr_datum(_xd); + } + return xd; +} + +/* -------- xattr subsystem functions --------------- + * xprefix_to_handler(xprefix) + * is used to translate xprefix into xattr_handler. + * jffs2_listxattr(dentry, buffer, size) + * is an implementation of listxattr handler on jffs2. + * do_jffs2_getxattr(inode, xprefix, xname, buffer, size) + * is an implementation of getxattr handler on jffs2. + * do_jffs2_setxattr(inode, xprefix, xname, buffer, size, flags) + * is an implementation of setxattr handler on jffs2. + * -------------------------------------------------- */ +struct xattr_handler *jffs2_xattr_handlers[] = { + &jffs2_user_xattr_handler, +#ifdef CONFIG_JFFS2_FS_SECURITY + &jffs2_security_xattr_handler, +#endif +#ifdef CONFIG_JFFS2_FS_POSIX_ACL + &jffs2_acl_access_xattr_handler, + &jffs2_acl_default_xattr_handler, +#endif + &jffs2_trusted_xattr_handler, + NULL +}; + +static struct xattr_handler *xprefix_to_handler(int xprefix) { + struct xattr_handler *ret; + + switch (xprefix) { + case JFFS2_XPREFIX_USER: + ret = &jffs2_user_xattr_handler; + break; +#ifdef CONFIG_JFFS2_FS_SECURITY + case JFFS2_XPREFIX_SECURITY: + ret = &jffs2_security_xattr_handler; + break; +#endif +#ifdef CONFIG_JFFS2_FS_POSIX_ACL + case JFFS2_XPREFIX_ACL_ACCESS: + ret = &jffs2_acl_access_xattr_handler; + break; + case JFFS2_XPREFIX_ACL_DEFAULT: + ret = &jffs2_acl_default_xattr_handler; + break; +#endif + case JFFS2_XPREFIX_TRUSTED: + ret = &jffs2_trusted_xattr_handler; + break; + default: + ret = NULL; + break; + } + return ret; +} + +ssize_t jffs2_listxattr(struct dentry *dentry, char *buffer, size_t size) +{ + struct inode *inode = dentry->d_inode; + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); + struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb); + struct jffs2_inode_cache *ic = f->inocache; + struct jffs2_xattr_ref *ref; + struct jffs2_xattr_datum *xd; + struct xattr_handler *xhandle; + ssize_t len, rc; + int retry = 0; + + rc = check_xattr_ref_ilist(c, ic); + if (unlikely(rc)) + return rc; + + down_read(&c->xattr_sem); + retry: + len = 0; + list_for_each_entry(ref, &ic->ilist, ilist) { + BUG_ON(ref->ic != ic); + xd = ref->xd; + if (!xd->xname) { + /* xdatum is unchached */ + if (!retry) { + retry = 1; + up_read(&c->xattr_sem); + down_write(&c->xattr_sem); + goto retry; + } else { + rc = load_xattr_datum(c, xd); + if (unlikely(rc > 0)) { + delete_xattr_ref(c, ref); + goto retry; + } else if (unlikely(rc < 0)) + goto out; + } + } + xhandle = xprefix_to_handler(xd->xprefix); + if (!xhandle) + continue; + if (buffer) { + rc = xhandle->list(inode, buffer+len, size-len, xd->xname, xd->name_len); + } else { + rc = xhandle->list(inode, NULL, 0, xd->xname, xd->name_len); + } + if (rc < 0) + goto out; + len += rc; + } + rc = len; + out: + if (!retry) { + up_read(&c->xattr_sem); + } else { + up_write(&c->xattr_sem); + } + return rc; +} + +int do_jffs2_getxattr(struct inode *inode, int xprefix, const char *xname, + char *buffer, size_t size) +{ + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); + struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb); + struct jffs2_inode_cache *ic = f->inocache; + struct jffs2_xattr_datum *xd; + struct jffs2_xattr_ref *ref; + int rc, retry = 0; + + rc = check_xattr_ref_ilist(c, ic); + if (unlikely(rc)) + return rc; + + down_read(&c->xattr_sem); + retry: + list_for_each_entry(ref, &ic->ilist, ilist) { + BUG_ON(ref->ic!=ic); + + xd = ref->xd; + if (xd->xprefix != xprefix) + continue; + if (!xd->xname) { + /* xdatum is unchached */ + if (!retry) { + retry = 1; + up_read(&c->xattr_sem); + down_write(&c->xattr_sem); + goto retry; + } else { + rc = load_xattr_datum(c, xd); + if (unlikely(rc > 0)) { + delete_xattr_ref(c, ref); + goto retry; + } else if (unlikely(rc < 0)) { + goto out; + } + } + } + if (!strcmp(xname, xd->xname)) { + rc = xd->value_len; + if (buffer) { + if (size < rc) { + rc = -ERANGE; + } else { + memcpy(buffer, xd->xvalue, rc); + } + } + goto out; + } + } + rc = -ENODATA; + out: + if (!retry) { + up_read(&c->xattr_sem); + } else { + up_write(&c->xattr_sem); + } + return rc; +} + +int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname, + const char *buffer, size_t size, int flags) +{ + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); + struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb); + struct jffs2_inode_cache *ic = f->inocache; + struct jffs2_xattr_datum *xd; + struct jffs2_xattr_ref *ref, *newref; + uint32_t phys_ofs, length, request; + int rc; + + rc = check_xattr_ref_ilist(c, ic); + if (unlikely(rc)) + return rc; + + request = PAD(sizeof(struct jffs2_raw_xattr) + strlen(xname) + 1 + size); + rc = jffs2_reserve_space(c, request, &phys_ofs, &length, + ALLOC_NORMAL, JFFS2_SUMMARY_XATTR_SIZE); + if (rc) { + JFFS2_WARNING("jffs2_reserve_space()=%d, request=%u\n", rc, request); + return rc; + } + + /* Find existing xattr */ + down_write(&c->xattr_sem); + retry: + list_for_each_entry(ref, &ic->ilist, ilist) { + xd = ref->xd; + if (xd->xprefix != xprefix) + continue; + if (!xd->xname) { + rc = load_xattr_datum(c, xd); + if (unlikely(rc > 0)) { + delete_xattr_ref(c, ref); + goto retry; + } else if (unlikely(rc < 0)) + goto out; + } + if (!strcmp(xd->xname, xname)) { + if (flags & XATTR_CREATE) { + rc = -EEXIST; + goto out; + } + if (!buffer) { + delete_xattr_ref(c, ref); + rc = 0; + goto out; + } + goto found; + } + } + /* not found */ + ref = NULL; + if (flags & XATTR_REPLACE) { + rc = -ENODATA; + goto out; + } + if (!buffer) { + rc = -EINVAL; + goto out; + } + found: + xd = create_xattr_datum(c, xprefix, xname, buffer, size, phys_ofs); + if (IS_ERR(xd)) { + rc = PTR_ERR(xd); + goto out; + } + up_write(&c->xattr_sem); + jffs2_complete_reservation(c); + + /* create xattr_ref */ + request = PAD(sizeof(struct jffs2_raw_xref)); + rc = jffs2_reserve_space(c, request, &phys_ofs, &length, + ALLOC_NORMAL, JFFS2_SUMMARY_XREF_SIZE); + if (rc) { + JFFS2_WARNING("jffs2_reserve_space()=%d, request=%u\n", rc, request); + down_write(&c->xattr_sem); + xd->refcnt--; + if (!xd->refcnt) + delete_xattr_datum(c, xd); + up_write(&c->xattr_sem); + return rc; + } + down_write(&c->xattr_sem); + newref = create_xattr_ref(c, ic, xd, phys_ofs); + if (IS_ERR(newref)) { + rc = PTR_ERR(newref); + xd->refcnt--; + if (!xd->refcnt) + delete_xattr_datum(c, xd); + } else if (ref) { + /* If replaced xattr_ref exists */ + delete_xattr_ref(c, ref); + } + out: + up_write(&c->xattr_sem); + jffs2_complete_reservation(c); + return rc; +} + +/* -------- garbage collector functions ------------- + * jffs2_garbage_collect_xattr_datum(c, xd) + * is used to move xdatum into new node. + * jffs2_garbage_collect_xattr_ref(c, ref) + * is used to move xref into new node. + * jffs2_garbage_collect_xattr(c, ic) + * is used to call appropriate garbage collector function, if argument + * pointer (ic) is the reference of xdatum/xref. + * jffs2_verify_xattr(c) + * is used to call do_verify_xattr_datum() before garbage collecting. + * -------------------------------------------------- */ +static int jffs2_garbage_collect_xattr_datum(struct jffs2_sb_info *c, + struct jffs2_xattr_datum *xd) +{ + /* must be called under down_write(xattr_sem), and called from GC thread */ + uint32_t phys_ofs, totlen, length, old_ofs; + int rc; + + BUG_ON(!xd->node); + + old_ofs = ref_offset(xd->node); + totlen = ref_totlen(c, c->gcblock, xd->node); + if (totlen < sizeof(struct jffs2_raw_xattr)) + return -EINVAL; + + if (!xd->xname) { + rc = load_xattr_datum(c, xd); + if (unlikely(rc > 0)) { + delete_xattr_datum_node(c, xd); + return 0; + } else if (unlikely(rc < 0)) + return -EINVAL; + } + rc = jffs2_reserve_space_gc(c, totlen, &phys_ofs, &length, JFFS2_SUMMARY_XATTR_SIZE); + if (rc || length < totlen) { + JFFS2_WARNING("jffs2_reserve_space()=%d, request=%u\n", rc, totlen); + return rc ? rc : -EBADFD; + } + rc = save_xattr_datum(c, xd, phys_ofs); + if (!rc) + dbg_xattr("xdatum (xid=%u, version=%u) GC'ed from %#08x to %08x\n", + xd->xid, xd->version, old_ofs, ref_offset(xd->node)); + return rc; +} + + +static int jffs2_garbage_collect_xattr_ref(struct jffs2_sb_info *c, + struct jffs2_xattr_ref *ref) +{ + /* must be called under down(alloc_sem) */ + uint32_t phys_ofs, totlen, length, old_ofs; + int rc; + + BUG_ON(!ref->node); + + old_ofs = ref_offset(ref->node); + totlen = ref_totlen(c, c->gcblock, ref->node); + if (totlen != sizeof(struct jffs2_raw_xref)) + return -EINVAL; + rc = jffs2_reserve_space_gc(c, totlen, &phys_ofs, &length, JFFS2_SUMMARY_XREF_SIZE); + if (rc || length < totlen) { + JFFS2_WARNING("%s: jffs2_reserve_space() = %d, request = %u\n", + __FUNCTION__, rc, totlen); + return rc ? rc : -EBADFD; + } + rc = save_xattr_ref(c, ref, phys_ofs); + if (!rc) + dbg_xattr("xref (ino=%u, xid=%u) GC'ed from %#08x to %08x\n", + ref->ic->ino, ref->xd->xid, old_ofs, ref_offset(ref->node)); + return rc; +} + +int jffs2_garbage_collect_xattr(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic) +{ + struct jffs2_xattr_datum *xd; + struct jffs2_xattr_ref *ref; + int ret; + + switch (ic->class) { + case RAWNODE_CLASS_XATTR_DATUM: + spin_unlock(&c->erase_completion_lock); + + down_write(&c->xattr_sem); + xd = (struct jffs2_xattr_datum *)ic; + ret = xd ? jffs2_garbage_collect_xattr_datum(c, xd) : 0; + up_write(&c->xattr_sem); + break; + case RAWNODE_CLASS_XATTR_REF: + spin_unlock(&c->erase_completion_lock); + + down_write(&c->xattr_sem); + ref = (struct jffs2_xattr_ref *)ic; + ret = ref ? jffs2_garbage_collect_xattr_ref(c, ref) : 0; + up_write(&c->xattr_sem); + break; + default: + /* This node is not xattr_datum/xattr_ref */ + ret = 1; + break; + } + return ret; +} + +int jffs2_verify_xattr(struct jffs2_sb_info *c) +{ + struct jffs2_xattr_datum *xd, *_xd; + int rc; + + down_write(&c->xattr_sem); + list_for_each_entry_safe(xd, _xd, &c->xattr_unchecked, xindex) { + rc = do_verify_xattr_datum(c, xd); + if (rc == 0) { + list_del_init(&xd->xindex); + break; + } else if (rc > 0) { + list_del_init(&xd->xindex); + delete_xattr_datum_node(c, xd); + } + } + up_write(&c->xattr_sem); + + return list_empty(&c->xattr_unchecked) ? 1 : 0; +} diff --git a/fs/jffs2/xattr.h b/fs/jffs2/xattr.h new file mode 100644 index 00000000000..d157ad641ed --- /dev/null +++ b/fs/jffs2/xattr.h @@ -0,0 +1,120 @@ +/*-------------------------------------------------------------------------* + * File: fs/jffs2/xattr.c + * XATTR support on JFFS2 FileSystem + * + * Implemented by KaiGai Kohei <kaigai@ak.jp.nec.com> + * Copyright (C) 2006 NEC Corporation + * + * For licensing information, see the file 'LICENCE' in the jffs2 directory. + *-------------------------------------------------------------------------*/ + +#ifndef _JFFS2_FS_XATTR_H_ +#define _JFFS2_FS_XATTR_H_ + +#include <linux/xattr.h> + +#define JFFS2_XFLAGS_HOT (0x01) /* This datum is HOT */ +#define JFFS2_XFLAGS_BIND (0x02) /* This datum is not reclaimed */ + +struct jffs2_xattr_datum +{ + void *always_null; + u8 class; + u8 flags; + u16 xprefix; /* see JFFS2_XATTR_PREFIX_* */ + + struct jffs2_raw_node_ref *node; + struct list_head xindex; /* chained from c->xattrindex[n] */ + uint32_t refcnt; /* # of xattr_ref refers this */ + uint32_t xid; + uint32_t version; + + uint32_t data_crc; + uint32_t hashkey; + char *xname; /* XATTR name without prefix */ + uint32_t name_len; /* length of xname */ + char *xvalue; /* XATTR value */ + uint32_t value_len; /* length of xvalue */ +}; + +struct jffs2_inode_cache; /* forward refence */ +struct jffs2_xattr_ref +{ + void *always_null; + u8 class; + u8 flags; /* Currently unused */ + u16 unused; + + struct jffs2_raw_node_ref *node; + union { + struct jffs2_inode_cache *ic; /* reference to jffs2_inode_cache */ + uint32_t ino; /* only used in scanning/building */ + }; + union { + struct jffs2_xattr_datum *xd; /* reference to jffs2_xattr_datum */ + uint32_t xid; /* only used in sccanning/building */ + }; + struct list_head ilist; /* chained from ic->ilist */ +}; + +#ifdef CONFIG_JFFS2_FS_XATTR + +extern void jffs2_init_xattr_subsystem(struct jffs2_sb_info *c); +extern void jffs2_build_xattr_subsystem(struct jffs2_sb_info *c); +extern void jffs2_clear_xattr_subsystem(struct jffs2_sb_info *c); + +extern struct jffs2_xattr_datum *jffs2_setup_xattr_datum(struct jffs2_sb_info *c, + uint32_t xid, uint32_t version); + +extern void jffs2_xattr_delete_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic); +extern void jffs2_xattr_free_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic); + +extern int jffs2_garbage_collect_xattr(struct jffs2_sb_info *c, struct jffs2_inode_cache *ic); +extern int jffs2_verify_xattr(struct jffs2_sb_info *c); + +extern int do_jffs2_getxattr(struct inode *inode, int xprefix, const char *xname, + char *buffer, size_t size); +extern int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname, + const char *buffer, size_t size, int flags); + +extern struct xattr_handler *jffs2_xattr_handlers[]; +extern struct xattr_handler jffs2_user_xattr_handler; +extern struct xattr_handler jffs2_trusted_xattr_handler; + +extern ssize_t jffs2_listxattr(struct dentry *, char *, size_t); +#define jffs2_getxattr generic_getxattr +#define jffs2_setxattr generic_setxattr +#define jffs2_removexattr generic_removexattr + +/*---- Any inline initialize functions ----*/ +#define init_xattr_inode_cache(x) INIT_LIST_HEAD(&((x)->ilist)) + +#else + +#define jffs2_init_xattr_subsystem(c) +#define jffs2_build_xattr_subsystem(c) +#define jffs2_clear_xattr_subsystem(c) + +#define jffs2_xattr_delete_inode(c, ic) +#define jffs2_xattr_free_inode(c, ic) +#define jffs2_garbage_collect_xattr(c, ic) (1) +#define jffs2_verify_xattr(c) (1) + +#define jffs2_xattr_handlers NULL +#define jffs2_listxattr NULL +#define jffs2_getxattr NULL +#define jffs2_setxattr NULL +#define jffs2_removexattr NULL + +#define init_xattr_inode_cache(x) + +#endif /* CONFIG_JFFS2_FS_XATTR */ + +#ifdef CONFIG_JFFS2_FS_SECURITY +extern int jffs2_init_security(struct inode *inode, struct inode *dir); +extern struct xattr_handler jffs2_security_xattr_handler; +#else +#define jffs2_init_security(inode,dir) (0) +#endif /* CONFIG_JFFS2_FS_SECURITY */ + +#endif /* _JFFS2_FS_XATTR_H_ */ diff --git a/fs/jffs2/xattr_trusted.c b/fs/jffs2/xattr_trusted.c new file mode 100644 index 00000000000..a018c9c31a6 --- /dev/null +++ b/fs/jffs2/xattr_trusted.c @@ -0,0 +1,51 @@ +/*-------------------------------------------------------------------------* + * File: fs/jffs2/xattr_trusted.c + * XATTR support on JFFS2 FileSystem + * + * Implemented by KaiGai Kohei <kaigai@ak.jp.nec.com> + * Copyright (C) 2006 NEC Corporation + * + * For licensing information, see the file 'LICENCE' in the jffs2 directory. + *-------------------------------------------------------------------------*/ +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/jffs2.h> +#include <linux/xattr.h> +#include <linux/mtd/mtd.h> +#include "nodelist.h" + +static int jffs2_trusted_getxattr(struct inode *inode, const char *name, + void *buffer, size_t size) +{ + if (!strcmp(name, "")) + return -EINVAL; + return do_jffs2_getxattr(inode, JFFS2_XPREFIX_TRUSTED, name, buffer, size); +} + +static int jffs2_trusted_setxattr(struct inode *inode, const char *name, const void *buffer, + size_t size, int flags) +{ + if (!strcmp(name, "")) + return -EINVAL; + return do_jffs2_setxattr(inode, JFFS2_XPREFIX_TRUSTED, name, buffer, size, flags); +} + +static size_t jffs2_trusted_listxattr(struct inode *inode, char *list, size_t list_size, + const char *name, size_t name_len) +{ + size_t retlen = XATTR_TRUSTED_PREFIX_LEN + name_len + 1; + + if (list && retlen<=list_size) { + strcpy(list, XATTR_TRUSTED_PREFIX); + strcpy(list + XATTR_TRUSTED_PREFIX_LEN, name); + } + + return retlen; +} + +struct xattr_handler jffs2_trusted_xattr_handler = { + .prefix = XATTR_TRUSTED_PREFIX, + .list = jffs2_trusted_listxattr, + .set = jffs2_trusted_setxattr, + .get = jffs2_trusted_getxattr +}; diff --git a/fs/jffs2/xattr_user.c b/fs/jffs2/xattr_user.c new file mode 100644 index 00000000000..d8c13636ea4 --- /dev/null +++ b/fs/jffs2/xattr_user.c @@ -0,0 +1,51 @@ +/*-------------------------------------------------------------------------* + * File: fs/jffs2/xattr_user.c + * XATTR support on JFFS2 FileSystem + * + * Implemented by KaiGai Kohei <kaigai@ak.jp.nec.com> + * Copyright (C) 2006 NEC Corporation + * + * For licensing information, see the file 'LICENCE' in the jffs2 directory. + *-------------------------------------------------------------------------*/ +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/jffs2.h> +#include <linux/xattr.h> +#include <linux/mtd/mtd.h> +#include "nodelist.h" + +static int jffs2_user_getxattr(struct inode *inode, const char *name, + void *buffer, size_t size) +{ + if (!strcmp(name, "")) + return -EINVAL; + return do_jffs2_getxattr(inode, JFFS2_XPREFIX_USER, name, buffer, size); +} + +static int jffs2_user_setxattr(struct inode *inode, const char *name, const void *buffer, + size_t size, int flags) +{ + if (!strcmp(name, "")) + return -EINVAL; + return do_jffs2_setxattr(inode, JFFS2_XPREFIX_USER, name, buffer, size, flags); +} + +static size_t jffs2_user_listxattr(struct inode *inode, char *list, size_t list_size, + const char *name, size_t name_len) +{ + size_t retlen = XATTR_USER_PREFIX_LEN + name_len + 1; + + if (list && retlen <= list_size) { + strcpy(list, XATTR_USER_PREFIX); + strcpy(list + XATTR_USER_PREFIX_LEN, name); + } + + return retlen; +} + +struct xattr_handler jffs2_user_xattr_handler = { + .prefix = XATTR_USER_PREFIX, + .list = jffs2_user_listxattr, + .set = jffs2_user_setxattr, + .get = jffs2_user_getxattr +}; |