Linux Extended Attributes -- Kernel Patch
24 April 2002, 11:29:40
This patch is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This patch is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this patch; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
After extracting the linux-2.2.20.tar.gz package, apply this patch as follows:
cd linux
patch -p1 < ../linux-2.2.20ea-0.8.26.patch
diff -Nur linux-2.2.20/Documentation/Configure.help linux-2.2.20ea/Documentation/Configure.help
--- linux-2.2.20/Documentation/Configure.help Fri Nov 2 17:39:05 2001
+++ linux-2.2.20ea/Documentation/Configure.help Sun Mar 24 21:33:25 2002
@@ -9057,6 +9057,32 @@
compiled as a module, and so this could be dangerous. Most everyone
wants to say Y here.
+Ext2 extended attributes
+CONFIG_EXT2_FS_XATTR
+ Extended attributes are name:value pairs associated with inodes by
+ the kernel or by users (see the attr(5) manual page, or visit
+ for details).
+
+ You need this for POSIX ACL support on ext2.
+
+ If unsure, say N.
+
+Ext2 extended attribute block sharing
+CONFIG_EXT2_FS_XATTR_SHARING
+ This options enables code for sharing identical extended attribute
+ blocks among multiple inodes.
+
+ Usually, say Y.
+
+Ext2 extended user attributes
+CONFIG_EXT2_FS_XATTR_USER
+ This option enables extended user attributes on ext2. Processes can
+ associate extended user attributes with inodes to store additional
+ information such as the character encoding of files, etc. (see the
+ attr(5) manual page, or visit for details).
+
+ If unsure, say N.
+
ISO 9660 CDROM filesystem support
CONFIG_ISO9660_FS
This is the standard filesystem used on CDROMs. It was previously
diff -Nur linux-2.2.20/arch/i386/kernel/entry.S linux-2.2.20ea/arch/i386/kernel/entry.S
--- linux-2.2.20/arch/i386/kernel/entry.S Fri Nov 2 17:39:05 2001
+++ linux-2.2.20ea/arch/i386/kernel/entry.S Thu Feb 28 22:44:41 2002
@@ -567,13 +567,54 @@
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork) /* 190 */
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall) /* 195 */
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall) /* 200 */
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall) /* 205 */
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall) /* 210 */
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall) /* 215 */
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall) /* 220 */
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall)
+ .long SYMBOL_NAME(sys_ni_syscall) /* 225 */
+ .long SYMBOL_NAME(sys_setxattr)
+ .long SYMBOL_NAME(sys_lsetxattr)
+ .long SYMBOL_NAME(sys_fsetxattr)
+ .long SYMBOL_NAME(sys_getxattr)
+ .long SYMBOL_NAME(sys_lgetxattr) /* 230 */
+ .long SYMBOL_NAME(sys_fgetxattr)
+ .long SYMBOL_NAME(sys_listxattr)
+ .long SYMBOL_NAME(sys_llistxattr)
+ .long SYMBOL_NAME(sys_flistxattr)
+ .long SYMBOL_NAME(sys_removexattr) /* 235 */
+ .long SYMBOL_NAME(sys_lremovexattr)
+ .long SYMBOL_NAME(sys_fremovexattr)
- /*
- * NOTE!! This doesn't have to be exact - we just have
- * to make sure we have _enough_ of the "sys_ni_syscall"
- * entries. Don't panic if you notice that this hasn't
- * been shrunk every time we add a new system call.
- */
- .rept NR_syscalls-190
+ .rept NR_syscalls-(.-sys_call_table)/4
.long SYMBOL_NAME(sys_ni_syscall)
.endr
diff -Nur linux-2.2.20/arch/sparc64/kernel/systbls.S linux-2.2.20ea/arch/sparc64/kernel/systbls.S
--- linux-2.2.20/arch/sparc64/kernel/systbls.S Sun Mar 25 18:37:30 2001
+++ linux-2.2.20ea/arch/sparc64/kernel/systbls.S Wed Apr 24 11:28:10 2002
@@ -52,11 +52,11 @@
/*150*/ .word sys_nis_syscall, sys_nis_syscall, sys_nis_syscall, sys_poll, sys_lfs_syscall
.word sys_lfs_syscall, sys_nis_syscall, sys32_statfs, sys32_fstatfs, sys_oldumount
/*160*/ .word sys_nis_syscall, sys_nis_syscall, sys_getdomainname, sys_setdomainname, sys_nis_syscall
- .word sys32_quotactl, sys_nis_syscall, sys32_mount, sys_ustat, sys_nis_syscall
-/*170*/ .word sys_nis_syscall, sys_nis_syscall, sys_nis_syscall, sys_nis_syscall, sys32_getdents
- .word sys_setsid, sys_fchdir, sys_nis_syscall, sys_nis_syscall, sys_nis_syscall
-/*180*/ .word sys_nis_syscall, sys_nis_syscall, sys_nis_syscall, sys32_sigpending, sys32_query_module
- .word sys_setpgid, sys_nis_syscall, sys_nis_syscall, sys_nis_syscall, sys32_newuname
+ .word sys32_quotactl, sys_nis_syscall, sys32_mount, sys_ustat, sys_setxattr
+/*170*/ .word sys_lsetxattr, sys_fsetxattr, sys_getxattr, sys_lgetxattr, sys32_getdents
+ .word sys_setsid, sys_fchdir, sys_fgetxattr, sys_listxattr, sys_llistxattr
+/*180*/ .word sys_flistxattr, sys_removexattr, sys_lremovexattr, sys32_sigpending, sys32_query_module
+ .word sys_setpgid, sys_fremovexattr, sys_nis_syscall, sys_nis_syscall, sys32_newuname
/*190*/ .word sys32_init_module, sys32_personality, sys_nis_syscall, sys_nis_syscall, sys_nis_syscall
.word sys_nis_syscall, sys_nis_syscall, sys_getppid, sys32_sigaction, sys_sgetmask
/*200*/ .word sys_ssetmask, sys_sigsuspend, sys32_newlstat, sys_uselib, old32_readdir
diff -Nur linux-2.2.20/fs/Config.in linux-2.2.20ea/fs/Config.in
--- linux-2.2.20/fs/Config.in Sun Mar 25 18:37:38 2001
+++ linux-2.2.20ea/fs/Config.in Tue Mar 26 00:05:13 2002
@@ -49,6 +49,14 @@
fi
tristate 'ROM filesystem support' CONFIG_ROMFS_FS
tristate 'Second extended fs support' CONFIG_EXT2_FS
+dep_mbool ' Ext2 extended attributes' CONFIG_EXT2_FS_XATTR $CONFIG_EXT2_FS
+dep_bool ' Ext2 extended attribute block sharing' \
+ CONFIG_EXT2_FS_XATTR_SHARING $CONFIG_EXT2_FS_XATTR
+dep_bool ' Ext2 extended user attributes' \
+ CONFIG_EXT2_FS_XATTR_USER $CONFIG_EXT2_FS_XATTR
+if [ "$CONFIG_EXT2_FS_XATTR_SHARING" = "y" ]; then
+ define_tristate CONFIG_FS_MBCACHE $CONFIG_EXT2_FS
+fi
tristate 'System V and Coherent filesystem support' CONFIG_SYSV_FS
tristate 'UFS filesystem support' CONFIG_UFS_FS
if [ "$CONFIG_UFS_FS" != "n" ]; then
diff -Nur linux-2.2.20/fs/Makefile linux-2.2.20ea/fs/Makefile
--- linux-2.2.20/fs/Makefile Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/Makefile Sun Mar 24 21:41:25 2002
@@ -13,7 +13,7 @@
O_OBJS = open.o read_write.o devices.o file_table.o buffer.o \
super.o block_dev.o stat.o exec.o pipe.o namei.o fcntl.o \
ioctl.o readdir.o select.o fifo.o locks.o filesystems.o \
- dcache.o inode.o attr.o bad_inode.o file.o iobuf.o $(BINFMTS)
+ dcache.o inode.o attr.o bad_inode.o file.o iobuf.o xattr.o $(BINFMTS)
MOD_LIST_NAME := FS_MODULES
ALL_SUB_DIRS = coda minix ext2 fat msdos vfat proc isofs nfs umsdos ntfs \
@@ -21,6 +21,13 @@
hpfs jfs sysv smbfs ncpfs ufs affs romfs autofs hfs lockd \
nfsd nls devpts adfs qnx4 udf efs
+ifeq ($(CONFIG_FS_MBCACHE),y)
+OX_OBJS += mbcache.o
+endif
+ifeq ($(CONFIG_FS_MBCACHE),m)
+MX_OBJS += mbcache.o
+endif
+
ifeq ($(CONFIG_QUOTA),y)
O_OBJS += dquot.o
else
diff -Nur linux-2.2.20/fs/ext2/Makefile linux-2.2.20ea/fs/ext2/Makefile
--- linux-2.2.20/fs/ext2/Makefile Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/Makefile Tue Mar 26 00:04:03 2002
@@ -12,4 +12,12 @@
ioctl.o namei.o super.o symlink.o truncate.o
M_OBJS := $(O_TARGET)
+ifeq ($(CONFIG_EXT2_FS_XATTR),y)
+OX_OBJS += xattr.o
+endif
+
+ifeq ($(CONFIG_EXT2_FS_XATTR_USER),y)
+O_OBJS += xattr_user.o
+endif
+
include $(TOPDIR)/Rules.make
diff -Nur linux-2.2.20/fs/ext2/dir.c linux-2.2.20ea/fs/ext2/dir.c
--- linux-2.2.20/fs/ext2/dir.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/dir.c Thu Feb 28 23:47:17 2002
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
@@ -72,7 +73,15 @@
NULL, /* bmap */
NULL, /* truncate */
ext2_permission, /* permission */
- NULL /* smap */
+ NULL, /* smap */
+ NULL, /* updatepage */
+ NULL, /* revalidate */
+ NULL, /* prepare_write */
+ NULL, /* sync_page */
+ ext2_setxattr, /* setxattr */
+ ext2_getxattr, /* getxattr */
+ ext2_listxattr, /* listxattr */
+ ext2_removexattr, /* removexattr */
};
int ext2_check_dir_entry (const char * function, struct inode * dir,
diff -Nur linux-2.2.20/fs/ext2/file.c linux-2.2.20ea/fs/ext2/file.c
--- linux-2.2.20/fs/ext2/file.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/file.c Thu Feb 28 23:46:43 2002
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -100,7 +101,15 @@
ext2_bmap, /* bmap */
ext2_truncate, /* truncate */
ext2_permission, /* permission */
- NULL /* smap */
+ NULL, /* smap */
+ NULL, /* updatepage */
+ NULL, /* revalidate */
+ NULL, /* prepare_write */
+ NULL, /* sync_page */
+ ext2_setxattr, /* setxattr */
+ ext2_getxattr, /* getxattr */
+ ext2_listxattr, /* listxattr */
+ ext2_removexattr, /* removexattr */
};
/*
diff -Nur linux-2.2.20/fs/ext2/ialloc.c linux-2.2.20ea/fs/ext2/ialloc.c
--- linux-2.2.20/fs/ext2/ialloc.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/ialloc.c Fri Mar 1 00:21:04 2002
@@ -29,6 +29,7 @@
#include
#include
+#include
#include
#include
#include
@@ -211,6 +212,11 @@
ino = inode->i_ino;
ext2_debug ("freeing inode %lu\n", ino);
+ if (inode->u.ext2_i.i_file_acl) {
+ DQUOT_INIT(inode);
+ ext2_xattr_drop_inode(inode);
+ }
+
/*
* Note: we must free any quota before locking the superblock,
* as writing the quota to disk may need the lock as well.
@@ -266,6 +272,122 @@
error_return:
unlock_super (sb);
}
+
+struct inode_operations ext2_chrdev_inode_operations = {
+ &def_chr_fops, /* default file operations */
+ NULL, /* create */
+ NULL, /* lookup */
+ NULL, /* link */
+ NULL, /* unlink */
+ NULL, /* symlink */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL, /* mknod */
+ NULL, /* rename */
+ NULL, /* readlink */
+ NULL, /* follow_link */
+ NULL, /* readpage */
+ NULL, /* writepage */
+ NULL, /* bmap */
+ NULL, /* truncate */
+ NULL, /* permission */
+ NULL, /* smap */
+ NULL, /* updatepage */
+ NULL, /* revalidate */
+ NULL, /* prepare_write */
+ NULL, /* sync_page */
+ ext2_setxattr, /* setxattr */
+ ext2_getxattr, /* getxattr */
+ ext2_listxattr, /* listxattr */
+ ext2_removexattr, /* removexattr */
+};
+
+struct inode_operations ext2_blkdev_inode_operations = {
+ &def_blk_fops, /* default file operations */
+ NULL, /* create */
+ NULL, /* lookup */
+ NULL, /* link */
+ NULL, /* unlink */
+ NULL, /* symlink */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL, /* mknod */
+ NULL, /* rename */
+ NULL, /* readlink */
+ NULL, /* follow_link */
+ NULL, /* readpage */
+ NULL, /* writepage */
+ NULL, /* bmap */
+ NULL, /* truncate */
+ NULL, /* permission */
+ NULL, /* smap */
+ NULL, /* updatepage */
+ NULL, /* revalidate */
+ NULL, /* prepare_write */
+ NULL, /* sync_page */
+ ext2_setxattr, /* setxattr */
+ ext2_getxattr, /* getxattr */
+ ext2_listxattr, /* listxattr */
+ ext2_removexattr, /* removexattr */
+};
+
+struct inode_operations ext2_fifo_inode_operations = {
+ &def_fifo_fops, /* default file operations */
+ NULL, /* create */
+ NULL, /* lookup */
+ NULL, /* link */
+ NULL, /* unlink */
+ NULL, /* symlink */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL, /* mknod */
+ NULL, /* rename */
+ NULL, /* readlink */
+ NULL, /* follow_link */
+ NULL, /* readpage */
+ NULL, /* writepage */
+ NULL, /* bmap */
+ NULL, /* truncate */
+ NULL, /* permission */
+ NULL, /* smap */
+ NULL, /* updatepage */
+ NULL, /* revalidate */
+ NULL, /* prepare_write */
+ NULL, /* sync_page */
+ ext2_setxattr, /* setxattr */
+ ext2_getxattr, /* getxattr */
+ ext2_listxattr, /* listxattr */
+ ext2_removexattr, /* removexattr */
+};
+
+struct inode_operations ext2_sock_inode_operations = {
+ NULL, /* default file operations */
+ NULL, /* create */
+ NULL, /* lookup */
+ NULL, /* link */
+ NULL, /* unlink */
+ NULL, /* symlink */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL, /* mknod */
+ NULL, /* rename */
+ NULL, /* readlink */
+ NULL, /* follow_link */
+ NULL, /* readpage */
+ NULL, /* writepage */
+ NULL, /* bmap */
+ NULL, /* truncate */
+ NULL, /* permission */
+ NULL, /* smap */
+ NULL, /* updatepage */
+ NULL, /* revalidate */
+ NULL, /* prepare_write */
+ NULL, /* sync_page */
+ ext2_setxattr, /* setxattr */
+ ext2_getxattr, /* getxattr */
+ ext2_listxattr, /* listxattr */
+ ext2_removexattr, /* removexattr */
+};
/*
* There are two policies for allocating an inode. If the new inode is
diff -Nur linux-2.2.20/fs/ext2/inode.c linux-2.2.20ea/fs/ext2/inode.c
--- linux-2.2.20/fs/ext2/inode.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/inode.c Thu Feb 28 23:57:53 2002
@@ -1,5 +1,5 @@
/*
- * linux/fs/ext2/inode.c
+ * linux/fs/ext/inode.c
*
* Copyright (C) 1992, 1993, 1994, 1995
* Remy Card (card@masi.ibp.fr)
@@ -47,9 +47,7 @@
*/
void ext2_delete_inode (struct inode * inode)
{
- if (is_bad_inode(inode) ||
- inode->i_ino == EXT2_ACL_IDX_INO ||
- inode->i_ino == EXT2_ACL_DATA_INO)
+ if (is_bad_inode(inode))
return;
inode->u.ext2_i.i_dtime = CURRENT_TIME;
/* When we delete an inode, we increment its i_generation.
@@ -463,8 +461,7 @@
unsigned long offset;
struct ext2_group_desc * gdp;
- if ((inode->i_ino != EXT2_ROOT_INO && inode->i_ino != EXT2_ACL_IDX_INO &&
- inode->i_ino != EXT2_ACL_DATA_INO &&
+ if ((inode->i_ino != EXT2_ROOT_INO &&
inode->i_ino < EXT2_FIRST_INO(inode->i_sb)) ||
inode->i_ino > le32_to_cpu(inode->i_sb->u.ext2_sb.s_es->s_inodes_count)) {
ext2_error (inode->i_sb, "ext2_read_inode",
@@ -559,21 +556,22 @@
inode->u.ext2_i.i_data[block] = raw_inode->i_block[block];
brelse (bh);
inode->i_op = NULL;
- if (inode->i_ino == EXT2_ACL_IDX_INO ||
- inode->i_ino == EXT2_ACL_DATA_INO)
- /* Nothing to do */ ;
- else if (S_ISREG(inode->i_mode))
+ if (S_ISREG(inode->i_mode))
inode->i_op = &ext2_file_inode_operations;
else if (S_ISDIR(inode->i_mode))
inode->i_op = &ext2_dir_inode_operations;
else if (S_ISLNK(inode->i_mode))
inode->i_op = &ext2_symlink_inode_operations;
else if (S_ISCHR(inode->i_mode))
- inode->i_op = &chrdev_inode_operations;
+ inode->i_op = &ext2_chrdev_inode_operations;
else if (S_ISBLK(inode->i_mode))
- inode->i_op = &blkdev_inode_operations;
- else if (S_ISFIFO(inode->i_mode))
- init_fifo(inode);
+ inode->i_op = &ext2_blkdev_inode_operations;
+ else if (S_ISFIFO(inode->i_mode)) {
+ init_fifo(inode); /* sets inode->i_op */
+ inode->i_op = &ext2_fifo_inode_operations;
+ } else if (S_ISSOCK(inode->i_mode))
+ inode->i_op = &ext2_sock_inode_operations;
+
inode->i_attr_flags = 0;
if (inode->u.ext2_i.i_flags & EXT2_SYNC_FL) {
inode->i_attr_flags |= ATTR_FLAG_SYNCRONOUS;
diff -Nur linux-2.2.20/fs/ext2/namei.c linux-2.2.20ea/fs/ext2/namei.c
--- linux-2.2.20/fs/ext2/namei.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/namei.c Fri Mar 1 00:18:29 2002
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -374,6 +375,7 @@
mark_inode_dirty(inode);
bh = ext2_add_entry (dir, dentry->d_name.name, dentry->d_name.len, &de, &err);
if (!bh) {
+ ext2_xattr_drop_inode(inode);
inode->i_nlink--;
mark_inode_dirty(inode);
iput (inode);
@@ -419,21 +421,23 @@
EXT2_FEATURE_INCOMPAT_FILETYPE))
de->file_type = EXT2_FT_REG_FILE;
} else if (S_ISSOCK(inode->i_mode)) {
+ inode->i_op = &ext2_sock_inode_operations;
if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb,
EXT2_FEATURE_INCOMPAT_FILETYPE))
de->file_type = EXT2_FT_SOCK;
} else if (S_ISCHR(inode->i_mode)) {
- inode->i_op = &chrdev_inode_operations;
+ inode->i_op = &ext2_chrdev_inode_operations;
if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb,
EXT2_FEATURE_INCOMPAT_FILETYPE))
de->file_type = EXT2_FT_CHRDEV;
} else if (S_ISBLK(inode->i_mode)) {
- inode->i_op = &blkdev_inode_operations;
+ inode->i_op = &ext2_blkdev_inode_operations;
if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb,
EXT2_FEATURE_INCOMPAT_FILETYPE))
de->file_type = EXT2_FT_BLKDEV;
} else if (S_ISFIFO(inode->i_mode)) {
- init_fifo(inode);
+ init_fifo(inode); /* (sets i_op) */
+ inode->i_op = &ext2_fifo_inode_operations;
if (EXT2_HAS_INCOMPAT_FEATURE(dir->i_sb,
EXT2_FEATURE_INCOMPAT_FILETYPE))
de->file_type = EXT2_FT_FIFO;
@@ -453,6 +457,7 @@
return err;
out_no_entry:
+ ext2_xattr_drop_inode(inode);
inode->i_nlink--;
mark_inode_dirty(inode);
iput(inode);
@@ -480,6 +485,7 @@
inode->i_blocks = 0;
dir_block = ext2_bread (inode, 0, 1, &err);
if (!dir_block) {
+ ext2_xattr_drop_inode(inode);
inode->i_nlink--; /* is this nlink == 0? */
mark_inode_dirty(inode);
iput (inode);
@@ -531,6 +537,7 @@
return err;
out_no_entry:
+ ext2_xattr_drop_inode(inode);
inode->i_nlink = 0;
mark_inode_dirty(inode);
iput (inode);
diff -Nur linux-2.2.20/fs/ext2/super.c linux-2.2.20ea/fs/ext2/super.c
--- linux-2.2.20/fs/ext2/super.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/super.c Tue Mar 26 00:11:54 2002
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -107,6 +108,7 @@
int db_count;
int i;
+ ext2_xattr_put_super(sb);
if (!(sb->s_flags & MS_RDONLY)) {
sb->u.ext2_sb.s_es->s_state = le16_to_cpu(sb->u.ext2_sb.s_mount_state);
mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1);
@@ -146,6 +148,7 @@
*/
static int parse_options (char * options, unsigned long * sb_block,
unsigned short *resuid, unsigned short * resgid,
+ unsigned long *xattr_flags,
unsigned long * mount_options)
{
char * this_char;
@@ -158,6 +161,32 @@
this_char = strtok (NULL, ",")) {
if ((value = strchr (this_char, '=')) != NULL)
*value++ = 0;
+#ifdef CONFIG_EXT2_FS_XATTR
+ if (!strcmp (this_char, "attr") && value) {
+ unsigned long flags = *xattr_flags;
+ char *c;
+
+ do {
+ c = strchr(value, ':');
+ if (c)
+ *c++ = '\0';
+ if (!strcmp(value, "off"))
+ flags = 0;
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+ else if (!strcmp(value, "user"))
+ flags |= XATTR_MNT_FLAG_USER;
+ else if (!strcmp(value, "nouser"))
+ flags &= ~XATTR_MNT_FLAG_USER;
+#endif
+ else {
+ printk("EXT2-fs: Invalid attr option: "
+ "%s\n", value);
+ return 0;
+ }
+ } while (value = c, value);
+ *xattr_flags = flags;
+ } else
+#endif
if (!strcmp (this_char, "bsddf"))
clear_opt (*mount_options, MINIX_DF);
else if (!strcmp (this_char, "check")) {
@@ -407,8 +436,12 @@
sb->u.ext2_sb.s_mount_opt = 0;
set_opt (sb->u.ext2_sb.s_mount_opt, CHECK_NORMAL);
+ sb->s_xattr_flags = 0;
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+ sb->s_xattr_flags |= XATTR_MNT_FLAG_USER;
+#endif
if (!parse_options ((char *) data, &sb_block, &resuid, &resgid,
- &sb->u.ext2_sb.s_mount_opt)) {
+ &sb->s_xattr_flags, &sb->u.ext2_sb.s_mount_opt)) {
sb->s_dev = 0;
return NULL;
}
@@ -696,7 +729,7 @@
*/
new_mount_opt = EXT2_MOUNT_CHECK_NORMAL;
if (!parse_options (data, &tmp, &resuid, &resgid,
- &new_mount_opt))
+ &sb->s_xattr_flags, &new_mount_opt))
return -EINVAL;
sb->u.ext2_sb.s_mount_opt = new_mount_opt;
@@ -739,9 +772,25 @@
NULL
};
+static void exit_ext2_fs(void)
+{
+ unregister_filesystem(&ext2_fs_type);
+ exit_ext2_xattr_user();
+ exit_ext2_xattr();
+}
+
__initfunc(int init_ext2_fs(void))
{
- return register_filesystem(&ext2_fs_type);
+ int error = init_ext2_xattr();
+ if (!error)
+ error = init_ext2_xattr_user();
+ if (!error)
+ error = register_filesystem(&ext2_fs_type);
+ if (!error)
+ return 0;
+
+ exit_ext2_fs();
+ return error;
}
#ifdef MODULE
@@ -754,7 +803,7 @@
void cleanup_module(void)
{
- unregister_filesystem(&ext2_fs_type);
+ exit_ext2_fs();
}
#endif
diff -Nur linux-2.2.20/fs/ext2/symlink.c linux-2.2.20ea/fs/ext2/symlink.c
--- linux-2.2.20/fs/ext2/symlink.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/symlink.c Thu Mar 14 21:58:32 2002
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -48,7 +49,15 @@
NULL, /* bmap */
NULL, /* truncate */
NULL, /* permission */
- NULL /* smap */
+ NULL, /* smap */
+ NULL, /* updatepage */
+ NULL, /* revalidate */
+ NULL, /* prepare_write */
+ NULL, /* sync_page */
+ ext2_setxattr, /* setxattr */
+ ext2_getxattr, /* getxattr */
+ ext2_listxattr, /* listxattr */
+ ext2_removexattr, /* removexattr */
};
static struct dentry * ext2_follow_link(struct dentry * dentry,
@@ -61,7 +70,7 @@
char * link;
link = (char *) inode->u.ext2_i.i_data;
- if (inode->i_blocks) {
+ if (!ext2_inode_is_fast_symlink(inode)) {
if (!(bh = ext2_bread (inode, 0, 0, &error))) {
dput(base);
return ERR_PTR(-EIO);
@@ -86,7 +95,7 @@
buflen = inode->i_sb->s_blocksize - 1;
link = (char *) inode->u.ext2_i.i_data;
- if (inode->i_blocks) {
+ if (!ext2_inode_is_fast_symlink(inode)) {
int err;
bh = ext2_bread (inode, 0, 0, &err);
if (!bh) {
diff -Nur linux-2.2.20/fs/ext2/truncate.c linux-2.2.20ea/fs/ext2/truncate.c
--- linux-2.2.20/fs/ext2/truncate.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/ext2/truncate.c Thu Mar 14 21:56:42 2002
@@ -386,6 +386,8 @@
if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) ||
S_ISLNK(inode->i_mode)))
return;
+ if (ext2_inode_is_fast_symlink(inode))
+ return;
if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
return;
diff -Nur linux-2.2.20/fs/ext2/xattr.c linux-2.2.20ea/fs/ext2/xattr.c
--- linux-2.2.20/fs/ext2/xattr.c Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/fs/ext2/xattr.c Fri Apr 5 09:54:13 2002
@@ -0,0 +1,1227 @@
+/*
+ * linux/fs/ext2/xattr.c
+ *
+ * Copyright (C) 2001 by Andreas Gruenbacher,
+ *
+ * Fix by Harrison Xing .
+ * Extended attributes for symlinks and special files added per
+ * suggestion of Luka Renko .
+ */
+
+/*
+ * Extended attributes are stored on disk blocks allocated outside of
+ * any inode. The i_file_acl field is then made to point to this allocated
+ * block. If all extended attributes of an inode are identical, these
+ * inodes may share the same extended attribute block. Such situations
+ * are automatically detected by keeping a cache of recent attribute block
+ * numbers and hashes over the block's contents in memory.
+ *
+ *
+ * Extended attribute block layout:
+ *
+ * +------------------+
+ * | header |
+ * ¦ entry 1 | |
+ * | entry 2 | | growing downwards
+ * | entry 3 | v
+ * | four null bytes |
+ * | . . . |
+ * | value 1 | ^
+ * | value 3 | | growing upwards
+ * | value 2 | |
+ * +------------------+
+ *
+ * The block header is followed by multiple entry descriptors. These entry
+ * descriptors are variable in size, and alligned to EXT2_XATTR_PAD
+ * byte boundaries. The entry descriptors are sorted by attribute name,
+ * so that two extended attribute blocks can be compared efficiently.
+ *
+ * Attribute values are aligned to the end of the block, stored in
+ * no specific order. They are also padded to EXT2_XATTR_PAD byte
+ * boundaries. No additional gaps are left between them.
+ *
+ * Locking strategy
+ * ----------------
+ * The VFS already holds the BKL and the inode->i_sem semaphore when any of
+ * the xattr inode operations are called, so we are guaranteed that only one
+ * processes accesses extended attributes of an inode at any time.
+ *
+ * For writing we also grab the ext2_xattr_sem semaphore. This ensures that
+ * only a single process is modifying an extended attribute block, even
+ * if the block is shared among inodes.
+ *
+ * Note for porting to 2.5
+ * -----------------------
+ * The BKL will no longer be held in the xattr inode operations.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* These symbols may be needed by a module. */
+EXPORT_SYMBOL(ext2_xattr_register);
+EXPORT_SYMBOL(ext2_xattr_unregister);
+EXPORT_SYMBOL(ext2_xattr_get);
+EXPORT_SYMBOL(ext2_xattr_list);
+EXPORT_SYMBOL(ext2_xattr_set);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
+# define mark_buffer_dirty(bh) mark_buffer_dirty(bh, 1)
+#endif
+
+#define HDR(bh) ((struct ext2_xattr_header *)((bh)->b_data))
+#define ENTRY(ptr) ((struct ext2_xattr_entry *)(ptr))
+#define FIRST_ENTRY(bh) ENTRY(HDR(bh)+1)
+#define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0)
+
+#ifdef EXT2_XATTR_DEBUG
+# define ea_idebug(inode, f...) do { \
+ printk(KERN_DEBUG "inode %s:%ld: ", \
+ kdevname(inode->i_dev), inode->i_ino); \
+ printk(f); \
+ printk("\n"); \
+ } while (0)
+# define ea_bdebug(bh, f...) do { \
+ printk(KERN_DEBUG "block %s:%ld: ", \
+ kdevname(bh->b_dev), bh->b_blocknr); \
+ printk(f); \
+ printk("\n"); \
+ } while (0)
+#else
+# define ea_idebug(f...)
+# define ea_bdebug(f...)
+#endif
+
+static int ext2_xattr_set2(struct inode *, struct buffer_head *,
+ struct ext2_xattr_header *);
+
+#ifdef CONFIG_EXT2_FS_XATTR_SHARING
+
+static int ext2_xattr_cache_insert(struct buffer_head *);
+static struct buffer_head *ext2_xattr_cache_find(struct inode *,
+ struct ext2_xattr_header *);
+static void ext2_xattr_cache_remove(struct buffer_head *);
+static void ext2_xattr_rehash(struct ext2_xattr_header *,
+ struct ext2_xattr_entry *);
+
+static struct mb_cache *ext2_xattr_cache;
+
+#else
+# define ext2_xattr_cache_insert(bh) 0
+# define ext2_xattr_cache_find(inode, header) NULL
+# define ext2_xattr_cache_remove(bh) while(0) {}
+# define ext2_xattr_rehash(header, entry) while(0) {}
+#endif
+
+/*
+ * If a file system does not share extended attributes among inodes,
+ * we should not need the ext2_xattr_sem semaphore. However, the
+ * filesystem may still contain shared blocks, so we always take
+ * the lock.
+ */
+
+DECLARE_MUTEX(ext2_xattr_sem);
+
+static inline void
+ext2_xattr_lock(void)
+{
+ down(&ext2_xattr_sem);
+}
+
+static inline void
+ext2_xattr_unlock(void)
+{
+ up(&ext2_xattr_sem);
+}
+
+static inline int
+ext2_xattr_new_block(struct inode *inode, int * errp, int force)
+{
+ struct super_block *sb = inode->i_sb;
+ int goal = le32_to_cpu(EXT2_SB(sb)->s_es->s_first_data_block) +
+ EXT2_I(inode)->i_block_group * EXT2_BLOCKS_PER_GROUP(sb);
+
+ /* How can we enforce the allocation? */
+ int block = ext2_new_block(inode, goal, 0, 0, errp);
+#ifdef OLD_QUOTAS
+ if (!*errp)
+ inode->i_blocks += inode->i_sb->s_blocksize >> 9;
+#endif
+ return block;
+}
+
+static inline int
+ext2_xattr_quota_alloc(struct inode *inode, int force)
+{
+ /* How can we enforce the allocation? */
+#ifdef OLD_QUOTAS
+ int error = DQUOT_ALLOC_BLOCK(inode->i_sb, inode, 1);
+ if (!error)
+ inode->i_blocks += inode->i_sb->s_blocksize >> 9;
+#else
+ int error = DQUOT_ALLOC_BLOCK(inode, 1);
+#endif
+ return error;
+}
+
+#ifdef OLD_QUOTAS
+
+static inline void
+ext2_xattr_quota_free(struct inode *inode)
+{
+ DQUOT_FREE_BLOCK(inode->i_sb, inode, 1);
+ inode->i_blocks -= inode->i_sb->s_blocksize >> 9;
+}
+
+static inline void
+ext2_xattr_free_block(struct inode * inode, unsigned long block)
+{
+ ext2_free_blocks(inode, block, 1);
+ inode->i_blocks -= inode->i_sb->s_blocksize >> 9;
+}
+
+#else
+# define ext2_xattr_quota_free(inode) \
+ DQUOT_FREE_BLOCK(inode, 1)
+# define ext2_xattr_free_block(inode, block) \
+ ext2_free_blocks(inode, block, 1)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
+
+static inline struct buffer_head *
+sb_bread(struct super_block *sb, int block)
+{
+ return bread(sb->s_dev, block, sb->s_blocksize);
+}
+
+static inline struct buffer_head *
+sb_getblk(struct super_block *sb, int block)
+{
+ return getblk(sb->s_dev, block, sb->s_blocksize);
+}
+
+#endif
+
+struct ext2_xattr_handler *ext2_xattr_handlers[EXT2_XATTR_INDEX_MAX];
+rwlock_t ext2_handler_lock = RW_LOCK_UNLOCKED;
+
+int
+ext2_xattr_register(int name_index, struct ext2_xattr_handler *handler)
+{
+ int error = -EINVAL;
+
+ if (name_index > 0 && name_index <= EXT2_XATTR_INDEX_MAX) {
+ write_lock(&ext2_handler_lock);
+ if (!ext2_xattr_handlers[name_index-1]) {
+ ext2_xattr_handlers[name_index-1] = handler;
+ error = 0;
+ }
+ write_unlock(&ext2_handler_lock);
+ }
+ return error;
+}
+
+void
+ext2_xattr_unregister(int name_index, struct ext2_xattr_handler *handler)
+{
+ if (name_index > 0 || name_index <= EXT2_XATTR_INDEX_MAX) {
+ write_lock(&ext2_handler_lock);
+ ext2_xattr_handlers[name_index-1] = NULL;
+ write_unlock(&ext2_handler_lock);
+ }
+}
+
+static inline const char *
+strcmp_prefix(const char *a, const char *a_prefix)
+{
+ while (*a_prefix && *a == *a_prefix) {
+ a++;
+ a_prefix++;
+ }
+ return *a_prefix ? NULL : a;
+}
+
+/*
+ * Decode the extended attribute name, and translate it into
+ * the name_index and name suffix.
+ */
+static struct ext2_xattr_handler *
+ext2_xattr_resolve_name(const char **name)
+{
+ struct ext2_xattr_handler *handler = NULL;
+ int i;
+
+ if (!*name)
+ return NULL;
+ read_lock(&ext2_handler_lock);
+ for (i=0; iprefix);
+ if (n) {
+ handler = ext2_xattr_handlers[i];
+ *name = n;
+ break;
+ }
+ }
+ }
+ read_unlock(&ext2_handler_lock);
+ return handler;
+}
+
+static inline struct ext2_xattr_handler *
+ext2_xattr_handler(int name_index)
+{
+ struct ext2_xattr_handler *handler = NULL;
+ if (name_index > 0 && name_index <= EXT2_XATTR_INDEX_MAX) {
+ read_lock(&ext2_handler_lock);
+ handler = ext2_xattr_handlers[name_index-1];
+ read_unlock(&ext2_handler_lock);
+ }
+ return handler;
+}
+
+/*
+ * Inode operation getxattr()
+ *
+ * dentry->d_inode->i_sem down
+ * BKL held [before 2.5.x]
+ */
+ssize_t
+ext2_getxattr(struct dentry *dentry, const char *name,
+ void *buffer, size_t size)
+{
+ struct ext2_xattr_handler *handler;
+ struct inode *inode = dentry->d_inode;
+
+ handler = ext2_xattr_resolve_name(&name);
+ if (!handler)
+ return -ENOTSUP;
+ return handler->get(inode, name, buffer, size);
+}
+
+/*
+ * Inode operation listxattr()
+ *
+ * dentry->d_inode->i_sem down
+ * BKL held [before 2.5.x]
+ */
+ssize_t
+ext2_listxattr(struct dentry *dentry, char *buffer, size_t size)
+{
+ return ext2_xattr_list(dentry->d_inode, buffer, size);
+}
+
+/*
+ * Inode operation setxattr()
+ *
+ * dentry->d_inode->i_sem down
+ * BKL held [before 2.5.x]
+ */
+int
+ext2_setxattr(struct dentry *dentry, const char *name,
+ void *value, size_t size, int flags)
+{
+ struct ext2_xattr_handler *handler;
+ struct inode *inode = dentry->d_inode;
+
+ if (size == 0)
+ value = ""; /* empty EA, do not remove */
+ handler = ext2_xattr_resolve_name(&name);
+ if (!handler)
+ return -ENOTSUP;
+ return handler->set(inode, name, value, size, flags);
+}
+
+/*
+ * Inode operation removexattr()
+ *
+ * dentry->d_inode->i_sem down
+ * BKL held [before 2.5.x]
+ */
+int
+ext2_removexattr(struct dentry *dentry, const char *name)
+{
+ struct ext2_xattr_handler *handler;
+ struct inode *inode = dentry->d_inode;
+
+ handler = ext2_xattr_resolve_name(&name);
+ if (!handler)
+ return -ENOTSUP;
+ return handler->set(inode, name, NULL, 0, XATTR_REPLACE);
+}
+
+/*
+ * ext2_xattr_get()
+ *
+ * Copy an extended attribute into the buffer
+ * provided, or compute the buffer size required.
+ * Buffer is NULL to compute the size of the buffer required.
+ *
+ * Returns a negative error number on failure, or the number of bytes
+ * used / required on success.
+ */
+int
+ext2_xattr_get(struct inode *inode, int name_index, const char *name,
+ void *buffer, size_t buffer_size)
+{
+ struct buffer_head *bh = NULL;
+ struct ext2_xattr_entry *entry;
+ unsigned int block, size;
+ char *end;
+ int name_len, error;
+
+ ea_idebug(inode, "name=%d.%s, buffer=%p, buffer_size=%ld",
+ name_index, name, buffer, (long)buffer_size);
+
+ if (name == NULL)
+ return -EINVAL;
+ if (!EXT2_I(inode)->i_file_acl)
+ return -ENOATTR;
+ block = EXT2_I(inode)->i_file_acl;
+ ea_idebug(inode, "reading block %d", block);
+ bh = sb_bread(inode->i_sb, block);
+ if (!bh)
+ return -EIO;
+ ea_bdebug(bh, "b_count=%d, refcount=%d",
+ atomic_read(&(bh->b_count)), le32_to_cpu(HDR(bh)->h_refcount));
+ end = bh->b_data + bh->b_size;
+ if (HDR(bh)->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
+ HDR(bh)->h_blocks != cpu_to_le32(1)) {
+bad_block: ext2_error(inode->i_sb, "ext2_xattr_get",
+ "inode %ld: bad block %d", inode->i_ino, block);
+ error = -EIO;
+ goto cleanup;
+ }
+ /* find named attribute */
+ name_len = strlen(name);
+
+ error = -ERANGE;
+ if (name_len > 255)
+ goto cleanup;
+ entry = FIRST_ENTRY(bh);
+ while (!IS_LAST_ENTRY(entry)) {
+ struct ext2_xattr_entry *next =
+ EXT2_XATTR_NEXT(entry);
+ if ((char *)next >= end)
+ goto bad_block;
+ if (name_index == entry->e_name_index &&
+ name_len == entry->e_name_len &&
+ memcmp(name, entry->e_name, name_len) == 0)
+ goto found;
+ entry = next;
+ }
+ /* Check the remaining name entries */
+ while (!IS_LAST_ENTRY(entry)) {
+ struct ext2_xattr_entry *next =
+ EXT2_XATTR_NEXT(entry);
+ if ((char *)next >= end)
+ goto bad_block;
+ entry = next;
+ }
+ if (ext2_xattr_cache_insert(bh))
+ ea_idebug(inode, "cache insert failed");
+ error = -ENOATTR;
+ goto cleanup;
+found:
+ /* check the buffer size */
+ if (entry->e_value_block != 0)
+ goto bad_block;
+ size = le32_to_cpu(entry->e_value_size);
+ if (size > inode->i_sb->s_blocksize ||
+ le16_to_cpu(entry->e_value_offs) + size > inode->i_sb->s_blocksize)
+ goto bad_block;
+
+ if (ext2_xattr_cache_insert(bh))
+ ea_idebug(inode, "cache insert failed");
+ if (buffer) {
+ error = -ERANGE;
+ if (size > buffer_size)
+ goto cleanup;
+ /* return value of attribute */
+ memcpy(buffer, bh->b_data + le16_to_cpu(entry->e_value_offs),
+ size);
+ }
+ error = size;
+
+cleanup:
+ brelse(bh);
+
+ return error;
+}
+
+/*
+ * ext2_xattr_list()
+ *
+ * Copy a list of attribute names into the buffer
+ * provided, or compute the buffer size required.
+ * Buffer is NULL to compute the size of the buffer required.
+ *
+ * Returns a negative error number on failure, or the number of bytes
+ * used / required on success.
+ */
+int
+ext2_xattr_list(struct inode *inode, char *buffer, size_t buffer_size)
+{
+ struct buffer_head *bh = NULL;
+ struct ext2_xattr_entry *entry;
+ unsigned int block, size = 0;
+ char *buf, *end;
+ int error;
+
+ ea_idebug(inode, "buffer=%p, buffer_size=%ld",
+ buffer, (long)buffer_size);
+
+ if (!EXT2_I(inode)->i_file_acl)
+ return 0;
+ block = EXT2_I(inode)->i_file_acl;
+ ea_idebug(inode, "reading block %d", block);
+ bh = sb_bread(inode->i_sb, block);
+ if (!bh)
+ return -EIO;
+ ea_bdebug(bh, "b_count=%d, refcount=%d",
+ atomic_read(&(bh->b_count)), le32_to_cpu(HDR(bh)->h_refcount));
+ end = bh->b_data + bh->b_size;
+ if (HDR(bh)->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
+ HDR(bh)->h_blocks != cpu_to_le32(1)) {
+bad_block: ext2_error(inode->i_sb, "ext2_xattr_list",
+ "inode %ld: bad block %d", inode->i_ino, block);
+ error = -EIO;
+ goto cleanup;
+ }
+ /* compute the size required for the list of attribute names */
+ for (entry = FIRST_ENTRY(bh); !IS_LAST_ENTRY(entry);
+ entry = EXT2_XATTR_NEXT(entry)) {
+ struct ext2_xattr_handler *handler;
+ struct ext2_xattr_entry *next =
+ EXT2_XATTR_NEXT(entry);
+ if ((char *)next >= end)
+ goto bad_block;
+
+ handler = ext2_xattr_handler(entry->e_name_index);
+ if (handler) {
+ size += handler->list(NULL, inode, entry->e_name,
+ entry->e_name_len) + 1;
+ }
+ }
+
+ if (ext2_xattr_cache_insert(bh))
+ ea_idebug(inode, "cache insert failed");
+ if (!buffer) {
+ error = size;
+ goto cleanup;
+ } else {
+ error = -ERANGE;
+ if (size > buffer_size)
+ goto cleanup;
+ }
+
+ /* list the attribute names */
+ buf = buffer;
+ for (entry = FIRST_ENTRY(bh); !IS_LAST_ENTRY(entry);
+ entry = EXT2_XATTR_NEXT(entry)) {
+ struct ext2_xattr_handler *handler;
+
+ handler = ext2_xattr_handler(entry->e_name_index);
+ if (handler) {
+ buf += handler->list(buf, inode, entry->e_name,
+ entry->e_name_len);
+ *buf++ = '\0';
+ }
+ }
+ error = size;
+
+cleanup:
+ brelse(bh);
+
+ return error;
+}
+
+/*
+ * If the EXT2_FEATURE_COMPAT_EXT_ATTR feature of this file system is
+ * not set, set it.
+ */
+static void ext2_xattr_update_super_block(struct super_block *sb)
+{
+ if (EXT2_HAS_COMPAT_FEATURE(sb, EXT2_FEATURE_COMPAT_EXT_ATTR))
+ return;
+
+ lock_super(sb);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
+ EXT2_SB(sb)->s_feature_compat |= EXT2_FEATURE_COMPAT_EXT_ATTR;
+#endif
+ EXT2_SB(sb)->s_es->s_feature_compat |=
+ cpu_to_le32(EXT2_FEATURE_COMPAT_EXT_ATTR);
+ sb->s_dirt = 1;
+ mark_buffer_dirty(EXT2_SB(sb)->s_sbh);
+ unlock_super(sb);
+}
+
+/*
+ * ext2_xattr_set()
+ *
+ * Create, replace or remove an extended attribute for this inode. Buffer
+ * is NULL to remove an existing extended attribute, and non-NULL to
+ * either replace an existing extended attribute, or create a new extended
+ * attribute. The flags XATTR_REPLACE and XATTR_CREATE
+ * specify that an extended attribute must exist and must not exist
+ * previous to the call, respectively.
+ *
+ * Returns 0, or a negative error number on failure.
+ */
+int
+ext2_xattr_set(struct inode *inode, int name_index, const char *name,
+ void *value, size_t value_len, int flags)
+{
+ struct super_block *sb = inode->i_sb;
+ struct buffer_head *bh = NULL;
+ struct ext2_xattr_header *header = NULL;
+ struct ext2_xattr_entry *here, *last;
+ unsigned int name_len;
+ int min_offs = sb->s_blocksize, not_found = 1, free, error;
+ char *end;
+
+ /*
+ * header -- Points either into bh, or to a temporarily
+ * allocated buffer.
+ * here -- The named entry found, or the place for inserting, within
+ * the block pointed to by header.
+ * last -- Points right after the last named entry within the block
+ * pointed to by header.
+ * min_offs -- The offset of the first value (values are aligned
+ * towards the end of the block).
+ * end -- Points right after the block pointed to by header.
+ */
+
+ ea_idebug(inode, "name=%d.%s, value=%p, value_len=%ld",
+ name_index, name, value, (long)value_len);
+
+ if (IS_RDONLY(inode))
+ return -EROFS;
+ if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
+ return -EPERM;
+ if (value == NULL)
+ value_len = 0;
+ if (name == NULL)
+ return -EINVAL;
+ name_len = strlen(name);
+ if (name_len > 255 || value_len > sb->s_blocksize)
+ return -ERANGE;
+ ext2_xattr_lock();
+
+ if (EXT2_I(inode)->i_file_acl) {
+ /* The inode already has an extended attribute block. */
+ int block = EXT2_I(inode)->i_file_acl;
+
+ bh = sb_bread(sb, block);
+ error = -EIO;
+ if (!bh)
+ goto cleanup;
+ ea_bdebug(bh, "b_count=%d, refcount=%d",
+ atomic_read(&(bh->b_count)),
+ le32_to_cpu(HDR(bh)->h_refcount));
+ header = HDR(bh);
+ end = bh->b_data + bh->b_size;
+ if (header->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
+ header->h_blocks != cpu_to_le32(1)) {
+bad_block: ext2_error(sb, "ext2_xattr_set",
+ "inode %ld: bad block %d", inode->i_ino, block);
+ error = -EIO;
+ goto cleanup;
+ }
+ /* Find the named attribute. */
+ here = FIRST_ENTRY(bh);
+ while (!IS_LAST_ENTRY(here)) {
+ struct ext2_xattr_entry *next = EXT2_XATTR_NEXT(here);
+ if ((char *)next >= end)
+ goto bad_block;
+ if (!here->e_value_block && here->e_value_size) {
+ int offs = le16_to_cpu(here->e_value_offs);
+ if (offs < min_offs)
+ min_offs = offs;
+ }
+ not_found = name_index - here->e_name_index;
+ if (!not_found)
+ not_found = name_len - here->e_name_len;
+ if (!not_found)
+ not_found = memcmp(name, here->e_name,name_len);
+ if (not_found <= 0)
+ break;
+ here = next;
+ }
+ last = here;
+ /* We still need to compute min_offs and last. */
+ while (!IS_LAST_ENTRY(last)) {
+ struct ext2_xattr_entry *next = EXT2_XATTR_NEXT(last);
+ if ((char *)next >= end)
+ goto bad_block;
+ if (!last->e_value_block && last->e_value_size) {
+ int offs = le16_to_cpu(last->e_value_offs);
+ if (offs < min_offs)
+ min_offs = offs;
+ }
+ last = next;
+ }
+
+ /* Check whether we have enough space left. */
+ free = min_offs - ((char*)last - (char*)header) - sizeof(__u32);
+ } else {
+ /* We will use a new extended attribute block. */
+ free = sb->s_blocksize -
+ sizeof(struct ext2_xattr_header) - sizeof(__u32);
+ here = last = NULL; /* avoid gcc uninitialized warning. */
+ }
+
+ if (not_found) {
+ /* Request to remove a nonexistent attribute? */
+ error = -ENOATTR;
+ if (flags & XATTR_REPLACE)
+ goto cleanup;
+ error = 0;
+ if (value == NULL)
+ goto cleanup;
+ else
+ free -= EXT2_XATTR_LEN(name_len);
+ } else {
+ /* Request to create an existing attribute? */
+ error = -EEXIST;
+ if (flags & XATTR_CREATE)
+ goto cleanup;
+ if (!here->e_value_block && here->e_value_size) {
+ unsigned int size = le32_to_cpu(here->e_value_size);
+
+ if (le16_to_cpu(here->e_value_offs) + size >
+ sb->s_blocksize || size > sb->s_blocksize)
+ goto bad_block;
+ free += EXT2_XATTR_SIZE(size);
+ }
+ }
+ free -= EXT2_XATTR_SIZE(value_len);
+ error = -ENOSPC;
+ if (free < 0)
+ goto cleanup;
+
+ /* Here we know that we can set the new attribute. */
+
+ if (header) {
+ if (header->h_refcount == cpu_to_le32(1)) {
+ ea_bdebug(bh, "modifying in-place");
+ ext2_xattr_cache_remove(bh);
+ } else {
+ int offset;
+
+ ea_bdebug(bh, "cloning");
+ header = kmalloc(bh->b_size, GFP_KERNEL);
+ error = -ENOMEM;
+ if (header == NULL)
+ goto cleanup;
+ memcpy(header, HDR(bh), bh->b_size);
+ header->h_refcount = cpu_to_le32(1);
+ offset = (char *)header - bh->b_data;
+ here = ENTRY((char *)here + offset);
+ last = ENTRY((char *)last + offset);
+ }
+ } else {
+ /* Allocate a buffer where we construct the new block. */
+ header = kmalloc(sb->s_blocksize, GFP_KERNEL);
+ error = -ENOMEM;
+ if (header == NULL)
+ goto cleanup;
+ memset(header, 0, sb->s_blocksize);
+ end = (char *)header + sb->s_blocksize;
+ header->h_magic = cpu_to_le32(EXT2_XATTR_MAGIC);
+ header->h_blocks = header->h_refcount = cpu_to_le32(1);
+ last = here = ENTRY(header+1);
+ }
+
+ if (not_found) {
+ /* Insert the new name. */
+ int size = EXT2_XATTR_LEN(name_len);
+ int rest = (char *)last - (char *)here;
+ memmove((char *)here + size, here, rest);
+ memset(here, 0, size);
+ here->e_name_index = name_index;
+ here->e_name_len = name_len;
+ memcpy(here->e_name, name, name_len);
+ } else {
+ /* Remove the old value. */
+ if (!here->e_value_block && here->e_value_size) {
+ char *first_val = (char *)header + min_offs;
+ int offs = le16_to_cpu(here->e_value_offs);
+ char *val = (char *)header + offs;
+ size_t size = EXT2_XATTR_SIZE(
+ le32_to_cpu(here->e_value_size));
+ memmove(first_val + size, first_val, val - first_val);
+ memset(first_val, 0, size);
+ here->e_value_offs = 0;
+ min_offs += size;
+
+ /* Adjust all value offsets. */
+ last = ENTRY(header+1);
+ while (!IS_LAST_ENTRY(last)) {
+ int o = le16_to_cpu(last->e_value_offs);
+ if (!last->e_value_block && o < offs)
+ last->e_value_offs =
+ cpu_to_le16(o + size);
+ last = EXT2_XATTR_NEXT(last);
+ }
+ }
+ if (value == NULL) {
+ /* Remove this attribute. */
+ if (EXT2_XATTR_NEXT(ENTRY(header+1)) == last) {
+ /* This block is now empty. */
+ error = ext2_xattr_set2(inode, bh, NULL);
+ goto cleanup;
+ } else {
+ /* Remove the old name. */
+ int size = EXT2_XATTR_LEN(name_len);
+ last = ENTRY((char *)last - size);
+ memmove(here, (char*)here + size,
+ (char*)last - (char*)here);
+ memset(last, 0, size);
+ }
+ }
+ }
+
+ if (value != NULL) {
+ /* Insert the new value. */
+ here->e_value_size = cpu_to_le32(value_len);
+ if (value_len) {
+ size_t size = EXT2_XATTR_SIZE(value_len);
+ char *val = (char *)header + min_offs - size;
+ here->e_value_offs =
+ cpu_to_le16((char *)val - (char *)header);
+ memset(val + size - EXT2_XATTR_PAD, 0,
+ EXT2_XATTR_PAD); /* Clear the pad bytes. */
+ memcpy(val, value, value_len);
+ }
+ }
+ ext2_xattr_rehash(header, here);
+
+ error = ext2_xattr_set2(inode, bh, header);
+
+cleanup:
+ brelse(bh);
+ if (!(bh && header == HDR(bh)))
+ kfree(header);
+ ext2_xattr_unlock();
+
+ return error;
+}
+
+/*
+ * Second half of ext2_xattr_set(): Update the file system.
+ */
+static int
+ext2_xattr_set2(struct inode *inode, struct buffer_head *old_bh,
+ struct ext2_xattr_header *header)
+{
+ struct super_block *sb = inode->i_sb;
+ struct buffer_head *new_bh = NULL;
+ int error;
+
+ if (header) {
+ new_bh = ext2_xattr_cache_find(inode, header);
+ if (new_bh) {
+ /*
+ * We found an identical block in the cache.
+ * The old block will be released after updating
+ * the inode.
+ */
+ ea_bdebug(old_bh, "reusing block %ld",
+ new_bh->b_blocknr);
+
+ error = -EDQUOT;
+ if (ext2_xattr_quota_alloc(inode, 1))
+ goto cleanup;
+
+ HDR(new_bh)->h_refcount = cpu_to_le32(
+ le32_to_cpu(HDR(new_bh)->h_refcount) + 1);
+ ea_bdebug(new_bh, "refcount now=%d",
+ le32_to_cpu(HDR(new_bh)->h_refcount));
+ } else if (old_bh && header == HDR(old_bh)) {
+ /* Keep this block. */
+ new_bh = old_bh;
+ ext2_xattr_cache_insert(new_bh);
+ } else {
+ /* We need to allocate a new block */
+ int force = EXT2_I(inode)->i_file_acl != 0;
+ int block = ext2_xattr_new_block(inode, &error, force);
+ if (error)
+ goto cleanup;
+ ea_idebug(inode, "creating block %d", block);
+
+ new_bh = sb_getblk(sb, block);
+ if (!new_bh) {
+ ext2_xattr_free_block(inode, block);
+ error = -EIO;
+ goto cleanup;
+ }
+ lock_buffer(new_bh);
+ memcpy(new_bh->b_data, header, new_bh->b_size);
+ mark_buffer_uptodate(new_bh, 1);
+ unlock_buffer(new_bh);
+ ext2_xattr_cache_insert(new_bh);
+
+ ext2_xattr_update_super_block(sb);
+ }
+ mark_buffer_dirty(new_bh);
+ if (IS_SYNC(inode)) {
+ ll_rw_block(WRITE, 1, &new_bh);
+ wait_on_buffer(new_bh);
+ error = -EIO;
+ if (buffer_req(new_bh) && !buffer_uptodate(new_bh))
+ goto cleanup;
+ }
+ }
+
+ /* Update the inode. */
+ EXT2_I(inode)->i_file_acl = new_bh ? new_bh->b_blocknr : 0;
+ inode->i_ctime = CURRENT_TIME;
+ if (IS_SYNC(inode)) {
+ error = ext2_sync_inode (inode);
+ if (error)
+ goto cleanup;
+ } else
+ mark_inode_dirty(inode);
+
+ error = 0;
+ if (old_bh && old_bh != new_bh) {
+ /*
+ * If there was an old block, and we are not still using it,
+ * we now release the old block.
+ */
+ unsigned int refcount = le32_to_cpu(HDR(old_bh)->h_refcount);
+
+ if (refcount == 1) {
+ /* Free the old block. */
+ ea_bdebug(old_bh, "freeing");
+ ext2_xattr_free_block(inode, old_bh->b_blocknr);
+ mark_buffer_clean(old_bh);
+ } else {
+ /* Decrement the refcount only. */
+ refcount--;
+ HDR(old_bh)->h_refcount = cpu_to_le32(refcount);
+ ext2_xattr_quota_free(inode);
+ mark_buffer_dirty(old_bh);
+ ea_bdebug(old_bh, "refcount now=%d", refcount);
+ }
+ }
+
+cleanup:
+ if (old_bh != new_bh)
+ brelse(new_bh);
+
+ return error;
+}
+
+/*
+ * ext2_xattr_drop_inode()
+ *
+ * Free extended attribute resources associated with this inode. This
+ * is called immediately before an inode is freed.
+ */
+void
+ext2_xattr_drop_inode(struct inode *inode)
+{
+ struct buffer_head *bh;
+ unsigned int block = EXT2_I(inode)->i_file_acl;
+
+ if (!block)
+ return;
+ ext2_xattr_lock();
+
+ bh = sb_bread(inode->i_sb, block);
+ if (!bh) {
+ ext2_error(inode->i_sb, "ext2_xattr_drop_inode",
+ "inode %ld: block %d read error", inode->i_ino, block);
+ goto cleanup;
+ }
+ ea_bdebug(bh, "b_count=%d", atomic_read(&(bh->b_count)));
+ if (HDR(bh)->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
+ HDR(bh)->h_blocks != cpu_to_le32(1)) {
+ ext2_error(inode->i_sb, "ext2_xattr_drop_inode",
+ "inode %ld: bad block %d", inode->i_ino, block);
+ goto cleanup;
+ }
+ ea_bdebug(bh, "refcount now=%d", le32_to_cpu(HDR(bh)->h_refcount) - 1);
+ if (HDR(bh)->h_refcount == cpu_to_le32(1)) {
+ ext2_xattr_cache_remove(bh);
+ ext2_xattr_free_block(inode, block);
+ bforget(bh);
+ bh = NULL;
+ } else {
+ HDR(bh)->h_refcount = cpu_to_le32(
+ le32_to_cpu(HDR(bh)->h_refcount) - 1);
+ mark_buffer_dirty(bh);
+ if (IS_SYNC(inode)) {
+ ll_rw_block(WRITE, 1, &bh);
+ wait_on_buffer(bh);
+ }
+ ext2_xattr_quota_free(inode);
+ }
+ EXT2_I(inode)->i_file_acl = 0;
+
+cleanup:
+ brelse(bh);
+ ext2_xattr_unlock();
+}
+
+/*
+ * ext2_xattr_put_super()
+ *
+ * This is called when a file system is unmounted.
+ */
+void
+ext2_xattr_put_super(struct super_block *sb)
+{
+#ifdef CONFIG_EXT2_FS_XATTR_SHARING
+ mb_cache_shrink(ext2_xattr_cache, sb->s_dev);
+#endif
+}
+
+#ifdef CONFIG_EXT2_FS_XATTR_SHARING
+
+/*
+ * ext2_xattr_cache_insert()
+ *
+ * Create a new entry in the extended attribute cache, and insert
+ * it unless such an entry is already in the cache.
+ *
+ * Returns 0, or a negative error number on failure.
+ */
+static int
+ext2_xattr_cache_insert(struct buffer_head *bh)
+{
+ __u32 hash = le32_to_cpu(HDR(bh)->h_hash);
+ struct mb_cache_entry *ce;
+ int error;
+
+ ce = mb_cache_entry_alloc(ext2_xattr_cache);
+ if (!ce)
+ return -ENOMEM;
+ error = mb_cache_entry_insert(ce, bh->b_dev, bh->b_blocknr, &hash);
+ if (error) {
+ mb_cache_entry_free(ce);
+ if (error == -EBUSY) {
+ ea_bdebug(bh, "already in cache (%d cache entries)",
+ atomic_read(&ext2_xattr_cache->c_entry_count));
+ error = 0;
+ }
+ } else {
+ ea_bdebug(bh, "inserting [%x] (%d cache entries)", (int)hash,
+ atomic_read(&ext2_xattr_cache->c_entry_count));
+ mb_cache_entry_release(ce);
+ }
+ return error;
+}
+
+/*
+ * ext2_xattr_cmp()
+ *
+ * Compare two extended attribute blocks for equality.
+ *
+ * Returns 0 if the blocks are equal, 1 if they differ, and
+ * a negative error number on errors.
+ */
+static int
+ext2_xattr_cmp(struct ext2_xattr_header *header1,
+ struct ext2_xattr_header *header2)
+{
+ struct ext2_xattr_entry *entry1, *entry2;
+
+ entry1 = ENTRY(header1+1);
+ entry2 = ENTRY(header2+1);
+ while (!IS_LAST_ENTRY(entry1)) {
+ if (IS_LAST_ENTRY(entry2))
+ return 1;
+ if (entry1->e_hash != entry2->e_hash ||
+ entry1->e_name_len != entry2->e_name_len ||
+ entry1->e_value_size != entry2->e_value_size ||
+ memcmp(entry1->e_name, entry2->e_name, entry1->e_name_len))
+ return 1;
+ if (entry1->e_value_block != 0 || entry2->e_value_block != 0)
+ return -EIO;
+ if (memcmp((char *)header1 + le16_to_cpu(entry1->e_value_offs),
+ (char *)header2 + le16_to_cpu(entry2->e_value_offs),
+ le32_to_cpu(entry1->e_value_size)))
+ return 1;
+
+ entry1 = EXT2_XATTR_NEXT(entry1);
+ entry2 = EXT2_XATTR_NEXT(entry2);
+ }
+ if (!IS_LAST_ENTRY(entry2))
+ return 1;
+ return 0;
+}
+
+/*
+ * ext2_xattr_cache_find()
+ *
+ * Find an identical extended attribute block.
+ *
+ * Returns a pointer to the block found, or NULL if such a block was
+ * not found or an error occurred.
+ */
+static struct buffer_head *
+ext2_xattr_cache_find(struct inode *inode, struct ext2_xattr_header *header)
+{
+ __u32 hash = le32_to_cpu(header->h_hash);
+ struct mb_cache_entry *ce;
+
+ if (!header->h_hash)
+ return NULL; /* never share */
+ ea_idebug(inode, "looking for cached blocks [%x]", (int)hash);
+ ce = mb_cache_entry_find_first(ext2_xattr_cache, 0, inode->i_dev, hash);
+ while (ce) {
+ struct buffer_head *bh = sb_bread(inode->i_sb, ce->e_block);
+
+ if (!bh) {
+ ext2_error(inode->i_sb, "ext2_xattr_cache_find",
+ "inode %ld: block %ld read error",
+ inode->i_ino, ce->e_block);
+ } else if (le32_to_cpu(HDR(bh)->h_refcount) >
+ EXT2_XATTR_REFCOUNT_MAX) {
+ ea_idebug(inode, "block %ld refcount %d>%d",ce->e_block,
+ le32_to_cpu(HDR(bh)->h_refcount),
+ EXT2_XATTR_REFCOUNT_MAX);
+ } else if (!ext2_xattr_cmp(header, HDR(bh))) {
+ ea_bdebug(bh, "b_count=%d",atomic_read(&(bh->b_count)));
+ mb_cache_entry_release(ce);
+ return bh;
+ }
+ brelse(bh);
+ ce = mb_cache_entry_find_next(ce, 0, inode->i_dev, hash);
+ }
+ return NULL;
+}
+
+/*
+ * ext2_xattr_cache_remove()
+ *
+ * Remove the cache entry of a block from the cache. Called when a
+ * block becomes invalid.
+ */
+static void
+ext2_xattr_cache_remove(struct buffer_head *bh)
+{
+ struct mb_cache_entry *ce;
+
+ ce = mb_cache_entry_get(ext2_xattr_cache, bh->b_dev, bh->b_blocknr);
+ if (ce) {
+ ea_bdebug(bh, "removing (%d cache entries remaining)",
+ atomic_read(&ext2_xattr_cache->c_entry_count)-1);
+ mb_cache_entry_free(ce);
+ } else
+ ea_bdebug(bh, "no cache entry");
+}
+
+#define NAME_HASH_SHIFT 5
+#define VALUE_HASH_SHIFT 16
+
+/*
+ * ext2_xattr_hash_entry()
+ *
+ * Compute the hash of an extended attribute.
+ */
+static inline void ext2_xattr_hash_entry(struct ext2_xattr_header *header,
+ struct ext2_xattr_entry *entry)
+{
+ __u32 hash = 0;
+ char *name = entry->e_name;
+ int n;
+
+ for (n=0; n < entry->e_name_len; n++) {
+ hash = (hash << NAME_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - NAME_HASH_SHIFT)) ^
+ *name++;
+ }
+
+ if (entry->e_value_block == 0 && entry->e_value_size != 0) {
+ __u32 *value = (__u32 *)((char *)header +
+ le16_to_cpu(entry->e_value_offs));
+ for (n = (le32_to_cpu(entry->e_value_size) +
+ EXT2_XATTR_ROUND) >> EXT2_XATTR_PAD_BITS; n; n--) {
+ hash = (hash << VALUE_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - VALUE_HASH_SHIFT)) ^
+ le32_to_cpu(*value++);
+ }
+ }
+ entry->e_hash = cpu_to_le32(hash);
+}
+
+#undef NAME_HASH_SHIFT
+#undef VALUE_HASH_SHIFT
+
+#define BLOCK_HASH_SHIFT 16
+
+/*
+ * ext2_xattr_rehash()
+ *
+ * Re-compute the extended attribute hash value after an entry has changed.
+ */
+static void ext2_xattr_rehash(struct ext2_xattr_header *header,
+ struct ext2_xattr_entry *entry)
+{
+ struct ext2_xattr_entry *here;
+ __u32 hash = 0;
+
+ ext2_xattr_hash_entry(header, entry);
+ here = ENTRY(header+1);
+ while (!IS_LAST_ENTRY(here)) {
+ if (!here->e_hash) {
+ /* Block is not shared if an entry's hash value == 0 */
+ hash = 0;
+ break;
+ }
+ hash = (hash << BLOCK_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - BLOCK_HASH_SHIFT)) ^
+ le32_to_cpu(here->e_hash);
+ here = EXT2_XATTR_NEXT(here);
+ }
+ header->h_hash = cpu_to_le32(hash);
+}
+
+#undef BLOCK_HASH_SHIFT
+
+int __init
+init_ext2_xattr(void)
+{
+ ext2_xattr_cache = mb_cache_create("ext2_xattr", NULL,
+ sizeof(struct mb_cache_entry) +
+ sizeof(struct mb_cache_entry_index), 1, 61);
+ if (!ext2_xattr_cache)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void
+exit_ext2_xattr(void)
+{
+ mb_cache_destroy(ext2_xattr_cache);
+}
+
+#else /* CONFIG_EXT2_FS_XATTR_SHARING */
+
+int __init
+init_ext2_xattr(void)
+{
+ return 0;
+}
+
+void
+exit_ext2_xattr(void)
+{
+}
+
+#endif /* CONFIG_EXT2_FS_XATTR_SHARING */
diff -Nur linux-2.2.20/fs/ext2/xattr_user.c linux-2.2.20ea/fs/ext2/xattr_user.c
--- linux-2.2.20/fs/ext2/xattr_user.c Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/fs/ext2/xattr_user.c Mon Apr 8 17:33:29 2002
@@ -0,0 +1,89 @@
+/*
+ * linux/fs/ext2/xattr_user.c
+ * Handler for extended user attributes.
+ *
+ * Copyright (C) 2001 by Andreas Gruenbacher,
+ */
+
+#include
+#include
+#include
+#include
+
+#define XATTR_USER_PREFIX "user."
+
+static size_t
+ext2_xattr_user_list(char *list, struct inode *inode,
+ const char *name, int name_len)
+{
+ const int prefix_len = sizeof(XATTR_USER_PREFIX)-1;
+
+ if (!IS_XATTR_USER(inode))
+ return 0;
+
+ if (list) {
+ memcpy(list, XATTR_USER_PREFIX, prefix_len);
+ memcpy(list+prefix_len, name, name_len);
+ }
+ return prefix_len + name_len;
+}
+
+static int
+ext2_xattr_user_get(struct inode *inode, const char *name,
+ void *buffer, size_t size)
+{
+ int error;
+
+ if (strcmp(name, "") == 0)
+ return -EINVAL;
+ if (!IS_XATTR_USER(inode))
+ return -ENOTSUP;
+ error = permission(inode, MAY_READ);
+ if (error)
+ return error;
+
+ return ext2_xattr_get(inode, EXT2_XATTR_INDEX_USER, name,
+ buffer, size);
+}
+
+static int
+ext2_xattr_user_set(struct inode *inode, const char *name,
+ void *value, size_t size, int flags)
+{
+ int error;
+
+ if (strcmp(name, "") == 0)
+ return -EINVAL;
+ if (!IS_XATTR_USER(inode))
+ return -ENOTSUP;
+ if ( !S_ISREG(inode->i_mode) &&
+ (!S_ISDIR(inode->i_mode) || inode->i_mode & S_ISVTX))
+ return -EPERM;
+ error = permission(inode, MAY_WRITE);
+ if (error)
+ return error;
+
+ return ext2_xattr_set(inode, EXT2_XATTR_INDEX_USER, name,
+ value, size, flags);
+}
+
+struct ext2_xattr_handler ext2_xattr_user_handler = {
+ prefix: XATTR_USER_PREFIX,
+ list: ext2_xattr_user_list,
+ get: ext2_xattr_user_get,
+ set: ext2_xattr_user_set,
+};
+
+int __init
+init_ext2_xattr_user(void)
+{
+ return ext2_xattr_register(EXT2_XATTR_INDEX_USER,
+ &ext2_xattr_user_handler);
+}
+
+void
+exit_ext2_xattr_user(void)
+{
+ ext2_xattr_unregister(EXT2_XATTR_INDEX_USER,
+ &ext2_xattr_user_handler);
+}
diff -Nur linux-2.2.20/fs/fifo.c linux-2.2.20ea/fs/fifo.c
--- linux-2.2.20/fs/fifo.c Sun Mar 25 18:30:58 2001
+++ linux-2.2.20ea/fs/fifo.c Fri Mar 1 00:05:55 2002
@@ -114,7 +114,7 @@
* is contain the open that then fills in the correct operations
* depending on the access mode of the file...
*/
-static struct file_operations def_fifo_fops = {
+struct file_operations def_fifo_fops = {
NULL,
NULL,
NULL,
diff -Nur linux-2.2.20/fs/mbcache.c linux-2.2.20ea/fs/mbcache.c
--- linux-2.2.20/fs/mbcache.c Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/fs/mbcache.c Tue Mar 19 18:07:48 2002
@@ -0,0 +1,727 @@
+/*
+ * linux/fs/mbcache.c
+ * Copyright (C) 2001 by Andreas Gruenbacher,
+ */
+
+/*
+ * Filesystem Meta Information Block Cache (mbcache)
+ *
+ * The mbcache caches blocks of block devices that need to be located
+ * by their device/block number, as well as by other criteria (such
+ * as the block's contents).
+ *
+ * There can only be one cache entry in a cache per device and block number.
+ * Additional indexes need not be unique in this sense. The number of
+ * additional indexes (=other criteria) can be hardwired (at compile time)
+ * or specified at cache create time.
+ *
+ * Each cache entry is of fixed size. An entry may be `valid' or `invalid'
+ * in the cache. A valid entry is in the main hash tables of the cache,
+ * and may also be in the lru list. An invalid entry is not in any hashes
+ * or lists.
+ *
+ * A valid cache entry is only in the lru list if no handles refer to it.
+ * Invalid cache entries will be freed when the last handle to the cache
+ * entry is released.
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#ifdef MB_CACHE_DEBUG
+# define mb_debug(f...) do { \
+ printk(KERN_DEBUG f); \
+ printk("\n"); \
+ } while (0)
+#else
+# define mb_debug(f...)
+#endif
+#define mb_error(f...) do { \
+ printk(KERN_ERR f); \
+ printk("\n"); \
+ } while(0)
+#define mb_assert(c) do { if (!(c)) \
+ printk(KERN_ERR "assertion " ## #c ## " failed\n"); \
+ } while(0)
+
+#if defined(SMP)
+# define MB_CACHE_LOCKS
+#endif
+
+#ifdef MB_CACHE_LOCKS
+# define MB_CACHE_LOCK spin_lock(&mb_cache_root.r_lock)
+# define MB_CACHE_UNLOCK spin_unlock(&mb_cache_root.r_lock)
+#else
+# define MB_CACHE_LOCK
+# define MB_CACHE_UNLOCK
+#endif
+
+#ifdef MB_CACHE_INDEXES_COUNT
+# define C_INDEXES_COUNT(cache) MB_CACHE_INDEXES_COUNT
+#else
+# define C_INDEXES_COUNT(cache) ((cache)->c_indexes_count)
+#endif
+
+
+MODULE_AUTHOR("Andreas Gruenbacher ");
+MODULE_DESCRIPTION("Meta block cache (for extended attributes)");
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
+MODULE_LICENSE("GPL");
+#endif
+
+EXPORT_SYMBOL(mb_cache_create);
+EXPORT_SYMBOL(mb_cache_shrink);
+EXPORT_SYMBOL(mb_cache_destroy);
+EXPORT_SYMBOL(mb_cache_entry_alloc);
+EXPORT_SYMBOL(mb_cache_entry_insert);
+EXPORT_SYMBOL(mb_cache_entry_release);
+EXPORT_SYMBOL(mb_cache_entry_takeout);
+EXPORT_SYMBOL(mb_cache_entry_free);
+EXPORT_SYMBOL(mb_cache_entry_dup);
+EXPORT_SYMBOL(mb_cache_entry_get);
+#if !defined(MB_CACHE_INDEXES_COUNT) || (MB_CACHE_INDEXES_COUNT > 0)
+EXPORT_SYMBOL(mb_cache_entry_find_first);
+EXPORT_SYMBOL(mb_cache_entry_find_next);
+#endif
+
+
+/*
+ * Global data: list of all mbcache's, lru list, and a spinlock for
+ * accessing cache data structures on SMP machines. (The lru list is
+ * global across all mbcache's.)
+ */
+
+struct mb_cache_root {
+ struct list_head r_cache_list;
+ struct list_head r_lru_list;
+#ifdef MB_CACHE_LOCKS
+ spinlock_t r_lock;
+#endif
+};
+
+static struct mb_cache_root mb_cache_root;
+
+
+/*
+ * What the mbcache registers as to get shrunk dynamically.
+ */
+
+static void
+mb_cache_memory_pressure(int priority, unsigned int gfp_mask);
+
+static struct cache_definition mb_cache_definition = {
+ "mb_cache",
+ mb_cache_memory_pressure
+};
+
+
+static inline void
+__mb_cache_entry_takeout_lru(struct mb_cache_entry *ce)
+{
+ if (ce->e_lru_list.prev) {
+ list_del(&ce->e_lru_list);
+ ce->e_lru_list.prev = NULL;
+ }
+}
+
+
+static inline void
+__mb_cache_entry_into_lru(struct mb_cache_entry *ce)
+{
+ list_add(&ce->e_lru_list, &mb_cache_root.r_lru_list);
+}
+
+
+static inline int
+__mb_cache_entry_in_lru(struct mb_cache_entry *ce)
+{
+ return (ce->e_lru_list.prev != NULL);
+}
+
+
+/*
+ * Insert the cache entry into all hashes.
+ */
+static inline void
+__mb_cache_entry_link(struct mb_cache_entry *ce)
+{
+ struct mb_cache *cache = ce->e_cache;
+ unsigned int bucket = (HASHDEV(ce->e_dev) + ce->e_block) %
+ cache->c_bucket_count;
+ int n;
+
+ list_add(&ce->e_block_list, &cache->c_block_hash[bucket]);
+ for (n=0; ne_indexes[n].o_key % cache->c_bucket_count;
+ list_add(&ce->e_indexes[n].o_list,
+ &cache->c_indexes_hash[n][bucket]);
+ }
+}
+
+
+/*
+ * Remove the cache entry from all hashes.
+ */
+static inline void
+__mb_cache_entry_unlink(struct mb_cache_entry *ce)
+{
+ int n;
+
+ list_del(&ce->e_block_list);
+ ce->e_block_list.prev = NULL;
+ for (n=0; ne_cache); n++)
+ list_del(&ce->e_indexes[n].o_list);
+}
+
+
+static inline int
+__mb_cache_entry_is_linked(struct mb_cache_entry *ce)
+{
+ return (ce->e_block_list.prev != NULL);
+}
+
+
+static inline struct mb_cache_entry *
+__mb_cache_entry_read(struct mb_cache_entry *ce)
+{
+ __mb_cache_entry_takeout_lru(ce);
+ ce->e_used++;
+ return ce;
+}
+
+
+static inline void
+__mb_cache_entry_forget(struct mb_cache_entry *ce)
+{
+ struct mb_cache *cache = ce->e_cache;
+
+ mb_assert(ce->e_used == 0);
+ atomic_dec(&cache->c_entry_count);
+ if (cache->c_op.free)
+ cache->c_op.free(ce);
+ kmem_cache_free(cache->c_entry_cache, ce);
+}
+
+
+static inline void
+__mb_cache_entry_release(struct mb_cache_entry *ce)
+{
+ ce->e_used--;
+ if (ce->e_used == 0) {
+ if (__mb_cache_entry_is_linked(ce))
+ __mb_cache_entry_into_lru(ce);
+ else {
+ MB_CACHE_UNLOCK;
+ __mb_cache_entry_forget(ce);
+ MB_CACHE_LOCK;
+ }
+ }
+}
+
+
+/*
+ * mb_cache_memory_pressure() memory pressure callback
+ *
+ * This function is called by the kernel memory management when memory
+ * gets low.
+ *
+ * @priority: Amount by which to shrink the cache (0 = highes priority)
+ * @gfp_mask: (ignored)
+ */
+static void
+mb_cache_memory_pressure(int priority, unsigned int gfp_mask)
+{
+ LIST_HEAD(free_list);
+ struct list_head *l;
+ int count = 0;
+
+ MB_CACHE_LOCK;
+ l = mb_cache_root.r_cache_list.prev;
+ while (l != &mb_cache_root.r_cache_list) {
+ struct mb_cache *cache =
+ list_entry(l, struct mb_cache, c_cache_list);
+ mb_debug("cache %s (%d)", cache->c_name,
+ atomic_read(&cache->c_entry_count));
+ count += atomic_read(&cache->c_entry_count);
+ l = l->prev;
+ }
+ mb_debug("trying to free %d of %d entries",
+ count / (priority ? priority : 1), count);
+ if (priority)
+ count /= priority;
+ while (count && !list_empty(&mb_cache_root.r_lru_list)) {
+ struct mb_cache_entry *ce =
+ list_entry(mb_cache_root.r_lru_list.prev,
+ struct mb_cache_entry, e_lru_list);
+ list_del(&ce->e_lru_list);
+ list_add(&ce->e_lru_list, &free_list);
+ if (__mb_cache_entry_is_linked(ce))
+ __mb_cache_entry_unlink(ce);
+ count--;
+ }
+ MB_CACHE_UNLOCK;
+ l = free_list.prev;
+ while (l != &free_list) {
+ struct mb_cache_entry *ce = list_entry(l,
+ struct mb_cache_entry, e_lru_list);
+ l = l->prev;
+ __mb_cache_entry_forget(ce);
+ }
+ if (count)
+ mb_debug("%d fewer entries freed", count);
+}
+
+
+/*
+ * mb_cache_create() create a new cache
+ *
+ * All entries in one cache are equal size. Cache entries may be from
+ * multiple devices. If this is the first mbcache created, registers
+ * the cache with kernel memory management. Returns NULL if no more
+ * memory was available.
+ *
+ * @name: name of the cache (informal)
+ * @cache_op: contains the callback called when freeing a cache entry
+ * @entry_size: The size of a cache entry, including
+ * struct mb_cache_entry
+ * @indexes_count: number of additional indexes in the cache. Must equal
+ * MB_CACHE_INDEXES_COUNT if the number of indexes is
+ * hardwired.
+ * @bucket_count: number of hash buckets
+ */
+struct mb_cache *
+mb_cache_create(const char *name, struct mb_cache_op *cache_op,
+ size_t entry_size, int indexes_count, int bucket_count)
+{
+ int m=0, n;
+ struct mb_cache *cache = NULL;
+
+ if(entry_size < sizeof(struct mb_cache_entry) +
+ indexes_count * sizeof(struct mb_cache_entry_index))
+ return NULL;
+
+ MOD_INC_USE_COUNT;
+ cache = kmalloc(sizeof(struct mb_cache) +
+ indexes_count * sizeof(struct list_head), GFP_KERNEL);
+ if (!cache)
+ goto fail;
+ cache->c_name = name;
+ if (cache_op)
+ cache->c_op.free = cache_op->free;
+ else
+ cache->c_op.free = NULL;
+ atomic_set(&cache->c_entry_count, 0);
+ cache->c_bucket_count = bucket_count;
+#ifdef MB_CACHE_INDEXES_COUNT
+ mb_assert(indexes_count == MB_CACHE_INDEXES_COUNT);
+#else
+ cache->c_indexes_count = indexes_count;
+#endif
+ cache->c_block_hash = kmalloc(bucket_count * sizeof(struct list_head),
+ GFP_KERNEL);
+ if (!cache->c_block_hash)
+ goto fail;
+ for (n=0; nc_block_hash[n]);
+ for (m=0; mc_indexes_hash[m] = kmalloc(bucket_count *
+ sizeof(struct list_head),
+ GFP_KERNEL);
+ if (!cache->c_indexes_hash[m])
+ goto fail;
+ for (n=0; nc_indexes_hash[m][n]);
+ }
+ cache->c_entry_cache = kmem_cache_create(
+ name, entry_size, 0, 0 /*| SLAB_HWCACHE_ALIGN*/
+ /*| SLAB_DEBUG_FREE*/, NULL, NULL);
+ if (!cache->c_entry_cache)
+ goto fail;
+
+ if (mb_cache_root.r_cache_list.prev == NULL) {
+ /* Initialize global data structures and register */
+ INIT_LIST_HEAD(&mb_cache_root.r_cache_list);
+ INIT_LIST_HEAD(&mb_cache_root.r_lru_list);
+#ifdef MB_CACHE_LOCKS
+ mb_cache_root.r_lock = SPIN_LOCK_UNLOCKED;
+#endif
+ register_cache(&mb_cache_definition);
+ }
+
+ MB_CACHE_LOCK;
+ list_add(&cache->c_cache_list, &mb_cache_root.r_cache_list);
+ MB_CACHE_UNLOCK;
+ return cache;
+
+fail:
+ if (cache) {
+ while (--m >= 0)
+ kfree(cache->c_indexes_hash[m]);
+ if (cache->c_block_hash)
+ kfree(cache->c_block_hash);
+ kfree(cache);
+ }
+ MOD_DEC_USE_COUNT;
+ return NULL;
+}
+
+
+static void
+__mb_cache_shrink(struct mb_cache *cache, kdev_t *dev)
+{
+ LIST_HEAD(free_list);
+ struct list_head *el;
+
+ el = mb_cache_root.r_lru_list.prev;
+ while (el != &mb_cache_root.r_lru_list) {
+ struct mb_cache_entry *ce =
+ list_entry(el, struct mb_cache_entry, e_lru_list);
+ el = el->prev;
+ if (dev && ce->e_dev == *dev) {
+ list_del(&ce->e_lru_list);
+ list_add(&ce->e_lru_list, &free_list);
+ if (__mb_cache_entry_is_linked(ce))
+ __mb_cache_entry_unlink(ce);
+ }
+ }
+ MB_CACHE_UNLOCK;
+ el = free_list.prev;
+ while (el != &free_list) {
+ struct mb_cache_entry *ce =
+ list_entry(el, struct mb_cache_entry, e_lru_list);
+ el = el->prev;
+ __mb_cache_entry_forget(ce);
+ }
+ MB_CACHE_LOCK;
+}
+
+
+/*
+ * mb_cache_shrink()
+ *
+ * Removes all cache entires of a device from the cache. All cache entries
+ * currently in use cannot be freed, and thus remain in the cache. All others
+ * are freed.
+ *
+ * @cache: which cache to shrink
+ * @dev: which device's cache entries to shrink
+ */
+void
+mb_cache_shrink(struct mb_cache *cache, kdev_t dev)
+{
+ MB_CACHE_LOCK;
+ __mb_cache_shrink(cache, &dev);
+ MB_CACHE_UNLOCK;
+}
+
+
+/*
+ * mb_cache_destroy()
+ *
+ * Shrinks the cache to its minimum possible size (hopefully 0 entries),
+ * and then destroys it. If this was the last mbcache, un-registers the
+ * mbcache from kernel memory management.
+ */
+void
+mb_cache_destroy(struct mb_cache *cache)
+{
+ int n;
+
+ MB_CACHE_LOCK;
+ list_del(&cache->c_cache_list);
+ __mb_cache_shrink(cache, NULL);
+ MB_CACHE_UNLOCK;
+
+ if (atomic_read(&cache->c_entry_count) > 0) {
+ mb_error("cache %s: %d orphaned entries",
+ cache->c_name,
+ atomic_read(&cache->c_entry_count));
+ }
+
+ for (n=0; n < C_INDEXES_COUNT(cache); n++)
+ kfree(cache->c_indexes_hash[n]);
+ kfree(cache->c_block_hash);
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0))
+ /* We don't have kmem_cache_destroy() in 2.2.x */
+ kmem_cache_shrink(cache->c_entry_cache);
+#else
+ kmem_cache_destroy(cache->c_entry_cache);
+#endif
+ kfree(cache);
+
+ if (mb_cache_root.r_cache_list.next != NULL) {
+ if (list_empty(&mb_cache_root.r_cache_list)) {
+ unregister_cache(&mb_cache_definition);
+ memset(&mb_cache_root, '\0',
+ sizeof(struct mb_cache_root));
+ }
+ }
+
+ MOD_DEC_USE_COUNT;
+}
+
+
+/*
+ * mb_cache_entry_alloc()
+ *
+ * Allocates a new cache entry. The new entry will not be valid initially,
+ * and thus cannot be looked up yet. It should be filled with data, and
+ * then inserted into the cache using mb_cache_entry_insert(). Returns NULL
+ * if no more memory was available.
+ */
+struct mb_cache_entry *
+mb_cache_entry_alloc(struct mb_cache *cache)
+{
+ struct mb_cache_entry *ce;
+
+ atomic_inc(&cache->c_entry_count);
+ ce = kmem_cache_alloc(cache->c_entry_cache, GFP_KERNEL);
+ if (ce) {
+ ce->e_lru_list.prev = NULL;
+ ce->e_block_list.prev = NULL;
+ ce->e_cache = cache;
+ ce->e_used = 1;
+ }
+ return ce;
+}
+
+
+/*
+ * mb_cache_entry_insert()
+ *
+ * Inserts an entry that was allocated using mb_cache_entry_alloc() into
+ * the cache. After this, the cache entry can be looked up, but is not yet
+ * in the lru list as the caller still holds a handle to it. Returns 0 on
+ * success, or -EBUSY if a cache entry for that device + inode exists
+ * already (this may happen after a failed lookup, but when another process
+ * has inserted the same cache entry in the meantime).
+ *
+ * @dev: device the cache entry belongs to
+ * @block: block number
+ * @keys: array of additional keys. There must be indexes_count entries
+ * in the array (as specified when creating the cache).
+ */
+int
+mb_cache_entry_insert(struct mb_cache_entry *ce, kdev_t dev,
+ unsigned long block, unsigned int keys[])
+{
+ struct mb_cache *cache = ce->e_cache;
+ unsigned int bucket = (HASHDEV(dev) + block) % cache->c_bucket_count;
+ struct list_head *el = cache->c_block_hash[bucket].prev;
+ int n;
+
+ MB_CACHE_LOCK;
+ while (el != &cache->c_block_hash[bucket]) {
+ struct mb_cache_entry *ce =
+ list_entry(el, struct mb_cache_entry, e_block_list);
+ if (ce->e_dev == dev && ce->e_block == block)
+ return -EBUSY;
+ el = el->prev;
+ }
+ mb_assert(!__mb_cache_entry_is_linked(ce));
+ ce->e_dev = dev;
+ ce->e_block = block;
+ for (n=0; ne_indexes[n].o_key = keys[n];
+ __mb_cache_entry_link(ce);
+ MB_CACHE_UNLOCK;
+ return 0;
+}
+
+
+/*
+ * mb_cache_entry_release()
+ *
+ * Release a handle to a cache entry. When the last handle to a cache entry
+ * is released it is either freed (if it is invalid) or otherwise inserted
+ * in to the lru list.
+ */
+void
+mb_cache_entry_release(struct mb_cache_entry *ce)
+{
+ MB_CACHE_LOCK;
+ __mb_cache_entry_release(ce);
+ MB_CACHE_UNLOCK;
+}
+
+
+/*
+ * mb_cache_entry_takeout()
+ *
+ * Take a cache entry out of the cache, making it invalid. The entry can later
+ * be re-inserted using mb_cache_entry_insert(), or released using
+ * mb_cache_entry_release().
+ */
+void
+mb_cache_entry_takeout(struct mb_cache_entry *ce)
+{
+ MB_CACHE_LOCK;
+ mb_assert(!__mb_cache_entry_in_lru(ce));
+ if (__mb_cache_entry_is_linked(ce))
+ __mb_cache_entry_unlink(ce);
+ MB_CACHE_UNLOCK;
+}
+
+
+/*
+ * mb_cache_entry_free()
+ *
+ * This is equivalent to the sequence mb_cache_entry_takeout() --
+ * mb_cache_entry_release().
+ */
+void
+mb_cache_entry_free(struct mb_cache_entry *ce)
+{
+ MB_CACHE_LOCK;
+ mb_assert(!__mb_cache_entry_in_lru(ce));
+ if (__mb_cache_entry_is_linked(ce))
+ __mb_cache_entry_unlink(ce);
+ __mb_cache_entry_release(ce);
+ MB_CACHE_UNLOCK;
+}
+
+
+/*
+ * mb_cache_entry_dup()
+ *
+ * Duplicate a handle to a cache entry (does not duplicate the cache entry
+ * itself). After the call, both the old and the new handle must be released.
+ */
+struct mb_cache_entry *
+mb_cache_entry_dup(struct mb_cache_entry *ce)
+{
+ MB_CACHE_LOCK;
+ ce->e_used++;
+ MB_CACHE_UNLOCK;
+ return ce;
+}
+
+
+/*
+ * mb_cache_entry_get()
+ *
+ * Get a cache entry by device / block number. (There can only be one entry
+ * in the cache per device and block.) Returns NULL if no such cache entry
+ * exists.
+ */
+struct mb_cache_entry *
+mb_cache_entry_get(struct mb_cache *cache, kdev_t dev, unsigned long block)
+{
+ unsigned int bucket = (HASHDEV(dev) + block) % cache->c_bucket_count;
+ struct list_head *el = cache->c_block_hash[bucket].next;
+ struct mb_cache_entry *ce;
+
+ MB_CACHE_LOCK;
+ while (el != &cache->c_block_hash[bucket]) {
+ ce = list_entry(el, struct mb_cache_entry, e_block_list);
+ if (ce->e_dev == dev && ce->e_block == block) {
+ ce = __mb_cache_entry_read(ce);
+ goto cleanup;
+ }
+ el = el->next;
+ }
+ ce = NULL;
+
+cleanup:
+ MB_CACHE_UNLOCK;
+ return ce;
+}
+
+#if !defined(MB_CACHE_INDEXES_COUNT) || (MB_CACHE_INDEXES_COUNT > 0)
+
+static struct mb_cache_entry *
+__mb_cache_entry_find(struct list_head *el, struct list_head *head,
+ int index, kdev_t dev, unsigned int key)
+{
+ while (el != head) {
+ struct mb_cache_entry *ce =
+ list_entry(el, struct mb_cache_entry,
+ e_indexes[index].o_list);
+ if (ce->e_dev == dev && ce->e_indexes[index].o_key == key) {
+ ce = __mb_cache_entry_read(ce);
+ if (ce)
+ return ce;
+ }
+ el = el->next;
+ }
+ return NULL;
+}
+
+
+/*
+ * mb_cache_entry_find_first()
+ *
+ * Find the first cache entry on a given device with a certain key in
+ * an additional index. Additonal matches can be found with
+ * mb_cache_entry_find_next(). Returns NULL if no match was found.
+ *
+ * @cache: the cache to search
+ * @index: the number of the additonal index to search (0<=indexc_bucket_count;
+ struct list_head *el = cache->c_indexes_hash[index][bucket].next;
+ struct mb_cache_entry *ce;
+
+ mb_assert(index < C_INDEXES_COUNT(cache));
+ MB_CACHE_LOCK;
+ ce = __mb_cache_entry_find(el, &cache->c_indexes_hash[index][bucket],
+ index, dev, key);
+ MB_CACHE_UNLOCK;
+ return ce;
+}
+
+
+/*
+ * mb_cache_entry_find_next()
+ *
+ * Find the next cache entry on a given device with a certain key in an
+ * additional index. Returns NULL if no match could be found. The previous
+ * entry is atomatically released, so that mb_cache_entry_find_next() can
+ * be called like this:
+ *
+ * entry = mb_cache_entry_find_first();
+ * while (entry) {
+ * ...
+ * entry = mb_cache_entry_find_next(entry, ...);
+ * }
+ *
+ * @prev: The previous match
+ * @index: the number of the additonal index to search (0<=indexe_cache;
+ unsigned int bucket = key % cache->c_bucket_count;
+ struct list_head *el = prev->e_indexes[index].o_list.next;
+ struct mb_cache_entry *ce;
+
+ mb_assert(index < C_INDEXES_COUNT(cache));
+ MB_CACHE_LOCK;
+ __mb_cache_entry_release(prev);
+ ce = __mb_cache_entry_find(el, &cache->c_indexes_hash[index][bucket],
+ index, dev, key);
+ MB_CACHE_UNLOCK;
+ return ce;
+}
+
+#endif /* !defined(MB_CACHE_INDEXES_COUNT) || (MB_CACHE_INDEXES_COUNT > 0) */
diff -Nur linux-2.2.20/fs/xattr.c linux-2.2.20ea/fs/xattr.c
--- linux-2.2.20/fs/xattr.c Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/fs/xattr.c Mon Mar 18 02:34:34 2002
@@ -0,0 +1,395 @@
+/*
+ File: fs/xattr.c
+
+ Extended attribute handling.
+
+ Copyright (C) 2001 by Andreas Gruenbacher
+ Copyright (C) 2001 SGI - Silicon Graphics, Inc
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * Extended attribute memory allocation wrappers, originally
+ * based on the Intermezzo PRESTO_ALLOC/PRESTO_FREE macros.
+ * The vmalloc use here is very uncommon - extended attributes
+ * are supposed to be small chunks of metadata, and it is quite
+ * unusual to have very many extended attributes, so lists tend
+ * to be quite short as well. The 64K upper limit is derived
+ * from the extended attribute size limit used by XFS.
+ * Intentionally allow zero @size for value/list size requests.
+ */
+static void *
+xattr_alloc(size_t size, size_t limit)
+{
+ void *ptr;
+
+ if (size > limit)
+ return ERR_PTR(-E2BIG);
+
+ if (!size) /* size request, no buffer is needed */
+ return NULL;
+ else if (size <= PAGE_SIZE)
+ ptr = kmalloc((unsigned long) size, GFP_KERNEL);
+ else
+ ptr = vmalloc((unsigned long) size);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+ return ptr;
+}
+
+static void
+xattr_free(void *ptr, size_t size)
+{
+ if (!size) /* size request, no buffer was needed */
+ return;
+ else if (size <= PAGE_SIZE)
+ kfree(ptr);
+ else
+ vfree(ptr);
+}
+
+/*
+ * Extended attribute SET operations
+ */
+static long
+setxattr(struct dentry *d, char *name, void *value, size_t size, int flags)
+{
+ int error;
+ void *kvalue;
+ char kname[XATTR_NAME_MAX + 1];
+
+ if (flags & ~(XATTR_CREATE|XATTR_REPLACE))
+ return -EINVAL;
+
+ error = strncpy_from_user(kname, name, sizeof(kname));
+ if (error == 0 || error == sizeof(kname))
+ error = -ERANGE;
+ if (error < 0)
+ return error;
+
+ kvalue = xattr_alloc(size, XATTR_SIZE_MAX);
+ if (IS_ERR(kvalue))
+ return PTR_ERR(kvalue);
+
+ if (size > 0 && copy_from_user(kvalue, value, size)) {
+ xattr_free(kvalue, size);
+ return -EFAULT;
+ }
+
+ error = -EOPNOTSUPP;
+ if (d->d_inode->i_op && d->d_inode->i_op->setxattr) {
+ down(&d->d_inode->i_sem);
+ error = d->d_inode->i_op->setxattr(d, kname, kvalue, size, flags);
+ up(&d->d_inode->i_sem);
+ }
+
+ xattr_free(kvalue, size);
+ return error;
+}
+
+asmlinkage long
+sys_setxattr(char *path, char *name, void *value, size_t size, int flags)
+{
+ struct dentry *dentry;
+ int error;
+
+ lock_kernel();
+ dentry = namei(path);
+ error = PTR_ERR(dentry);
+ if (!IS_ERR(dentry)) {
+ error = setxattr(dentry, name, value, size, flags);
+ dput(dentry);
+ }
+ unlock_kernel();
+ return error;
+}
+
+asmlinkage long
+sys_lsetxattr(char *path, char *name, void *value, size_t size, int flags)
+{
+ struct dentry *dentry;
+ int error;
+
+ lock_kernel();
+ dentry = lnamei(path);
+ error = PTR_ERR(dentry);
+ if (!IS_ERR(dentry)) {
+ error = setxattr(dentry, name, value, size, flags);
+ dput(dentry);
+ }
+ unlock_kernel();
+ return error;
+}
+
+asmlinkage long
+sys_fsetxattr(int fd, char *name, void *value, size_t size, int flags)
+{
+ struct file *f;
+ int error = -EBADF;
+
+ lock_kernel();
+ f = fget(fd);
+ if (!f)
+ goto out;
+ error = -ENOENT;
+ if (f->f_dentry)
+ error = setxattr(f->f_dentry, name, value, size, flags);
+ fput(f);
+
+out:
+ unlock_kernel();
+ return error;
+}
+
+/*
+ * Extended attribute GET operations
+ */
+static ssize_t
+getxattr(struct dentry *d, char *name, void *value, size_t size)
+{
+ ssize_t error;
+ void *kvalue;
+ char kname[XATTR_NAME_MAX + 1];
+
+ error = strncpy_from_user(kname, name, sizeof(kname));
+ if (error == 0 || error == sizeof(kname))
+ error = -ERANGE;
+ if (error < 0)
+ return error;
+
+ kvalue = xattr_alloc(size, XATTR_SIZE_MAX);
+ if (IS_ERR(kvalue))
+ return PTR_ERR(kvalue);
+
+ error = -EOPNOTSUPP;
+ if (d->d_inode->i_op && d->d_inode->i_op->getxattr) {
+ down(&d->d_inode->i_sem);
+ error = d->d_inode->i_op->getxattr(d, kname, kvalue, size);
+ up(&d->d_inode->i_sem);
+ }
+
+ if (kvalue && error > 0)
+ if (copy_to_user(value, kvalue, error))
+ error = -EFAULT;
+ xattr_free(kvalue, size);
+ return error;
+}
+
+asmlinkage ssize_t
+sys_getxattr(char *path, char *name, void *value, size_t size)
+{
+ struct dentry *dentry;
+ int error;
+
+ lock_kernel();
+ dentry = namei(path);
+ error = PTR_ERR(dentry);
+ if (!IS_ERR(dentry)) {
+ error = getxattr(dentry, name, value, size);
+ dput(dentry);
+ }
+ unlock_kernel();
+ return error;
+}
+
+asmlinkage ssize_t
+sys_lgetxattr(char *path, char *name, void *value, size_t size)
+{
+ struct dentry *dentry;
+ int error;
+
+ lock_kernel();
+ dentry = lnamei(path);
+ error = PTR_ERR(dentry);
+ if (!IS_ERR(dentry)) {
+ error = getxattr(dentry, name, value, size);
+ dput(dentry);
+ }
+ unlock_kernel();
+ return error;
+}
+
+asmlinkage ssize_t
+sys_fgetxattr(int fd, char *name, void *value, size_t size)
+{
+ struct file *f;
+ int error = -EBADF;
+
+ lock_kernel();
+ f = fget(fd);
+ if (!f)
+ goto out;
+ error = -ENOENT;
+ if (f->f_dentry)
+ error = getxattr(f->f_dentry, name, value, size);
+ fput(f);
+
+out:
+ unlock_kernel();
+ return error;
+}
+
+/*
+ * Extended attribute LIST operations
+ */
+static ssize_t
+listxattr(struct dentry *d, char *list, size_t size)
+{
+ ssize_t error;
+ char *klist;
+
+ klist = (char *)xattr_alloc(size, XATTR_LIST_MAX);
+ if (IS_ERR(klist))
+ return PTR_ERR(klist);
+
+ error = -EOPNOTSUPP;
+ if (d->d_inode->i_op && d->d_inode->i_op->listxattr) {
+ down(&d->d_inode->i_sem);
+ error = d->d_inode->i_op->listxattr(d, klist, size);
+ up(&d->d_inode->i_sem);
+ }
+
+ if (klist && error > 0)
+ if (copy_to_user(list, klist, error))
+ error = -EFAULT;
+ xattr_free(klist, size);
+ return error;
+}
+
+asmlinkage ssize_t
+sys_listxattr(char *path, char *list, size_t size)
+{
+ struct dentry *dentry;
+ int error;
+
+ lock_kernel();
+ dentry = namei(path);
+ error = PTR_ERR(dentry);
+ if (!IS_ERR(dentry)) {
+ error = listxattr(dentry, list, size);
+ dput(dentry);
+ }
+ unlock_kernel();
+ return error;
+}
+
+asmlinkage ssize_t
+sys_llistxattr(char *path, char *list, size_t size)
+{
+ struct dentry *dentry;
+ int error;
+
+ lock_kernel();
+ dentry = lnamei(path);
+ error = PTR_ERR(dentry);
+ if (!IS_ERR(dentry)) {
+ error = listxattr(dentry, list, size);
+ dput(dentry);
+ }
+ unlock_kernel();
+ return error;
+}
+
+asmlinkage ssize_t
+sys_flistxattr(int fd, char *list, size_t size)
+{
+ struct file *f;
+ int error = -EBADF;
+
+ lock_kernel();
+ f = fget(fd);
+ if (!f)
+ goto out;
+ error = -ENOENT;
+ if (f->f_dentry)
+ error = listxattr(f->f_dentry, list, size);
+ fput(f);
+
+out:
+ unlock_kernel();
+ return error;
+}
+
+/*
+ * Extended attribute REMOVE operations
+ */
+static long
+removexattr(struct dentry *d, char *name)
+{
+ int error;
+ char kname[XATTR_NAME_MAX + 1];
+
+ error = strncpy_from_user(kname, name, sizeof(kname));
+ if (error == 0 || error == sizeof(kname))
+ error = -ERANGE;
+ if (error < 0)
+ return error;
+
+ error = -EOPNOTSUPP;
+ if (d->d_inode->i_op && d->d_inode->i_op->removexattr) {
+ down(&d->d_inode->i_sem);
+ error = d->d_inode->i_op->removexattr(d, kname);
+ up(&d->d_inode->i_sem);
+ }
+ return error;
+}
+
+asmlinkage long
+sys_removexattr(char *path, char *name)
+{
+ struct dentry *dentry;
+ int error;
+
+ lock_kernel();
+ dentry = namei(path);
+ error = PTR_ERR(dentry);
+ if (!IS_ERR(dentry)) {
+ error = removexattr(dentry, name);
+ dput(dentry);
+ }
+ unlock_kernel();
+ return error;
+}
+
+asmlinkage long
+sys_lremovexattr(char *path, char *name)
+{
+ struct dentry *dentry;
+ int error;
+
+ lock_kernel();
+ dentry = lnamei(path);
+ error = PTR_ERR(dentry);
+ if (!IS_ERR(dentry)) {
+ error = removexattr(dentry, name);
+ dput(dentry);
+ }
+ unlock_kernel();
+ return error;
+}
+
+asmlinkage long
+sys_fremovexattr(int fd, char *name)
+{
+ struct file *f;
+ int error = -EBADF;
+
+ lock_kernel();
+ f = fget(fd);
+ if (!f)
+ goto out;
+ error = -ENOENT;
+ if (f->f_dentry)
+ error = removexattr(f->f_dentry, name);
+ fput(f);
+
+out:
+ unlock_kernel();
+ return error;
+}
diff -Nur linux-2.2.20/include/asm-i386/unistd.h linux-2.2.20ea/include/asm-i386/unistd.h
--- linux-2.2.20/include/asm-i386/unistd.h Sun Mar 25 18:31:05 2001
+++ linux-2.2.20ea/include/asm-i386/unistd.h Thu Feb 28 22:46:08 2002
@@ -195,6 +195,18 @@
#define __NR_getpmsg 188 /* some people actually want streams */
#define __NR_putpmsg 189 /* some people actually want streams */
#define __NR_vfork 190
+#define __NR_setxattr 226
+#define __NR_lsetxattr 227
+#define __NR_fsetxattr 228
+#define __NR_getxattr 229
+#define __NR_lgetxattr 230
+#define __NR_fgetxattr 231
+#define __NR_listxattr 232
+#define __NR_llistxattr 233
+#define __NR_flistxattr 234
+#define __NR_removexattr 235
+#define __NR_lremovexattr 236
+#define __NR_fremovexattr 237
/* user-visible error numbers are in the range -1 - -122: see */
diff -Nur linux-2.2.20/include/asm-sparc64/unistd.h linux-2.2.20ea/include/asm-sparc64/unistd.h
--- linux-2.2.20/include/asm-sparc64/unistd.h Sun Mar 25 18:37:40 2001
+++ linux-2.2.20ea/include/asm-sparc64/unistd.h Wed Apr 24 11:24:22 2002
@@ -184,24 +184,24 @@
/* #define __NR_exportfs 166 SunOS Specific */
#define __NR_mount 167 /* Common */
#define __NR_ustat 168 /* Common */
-/* #define __NR_semsys 169 SunOS Specific */
-/* #define __NR_msgsys 170 SunOS Specific */
-/* #define __NR_shmsys 171 SunOS Specific */
-/* #define __NR_auditsys 172 SunOS Specific */
-/* #define __NR_rfssys 173 SunOS Specific */
+ #define __NR_setxattr 169 /* SunOS Specific */
+#define __NR_lsetxattr 170 /* SunOS Specific */
+#define __NR_fsetxattr 171 /* SunOS Specific */
+#define __NR_getxattr 172 /* SunOS Specific */
+#define __NR_lgetxattr 173 /* SunOS Specific */
#define __NR_getdents 174 /* Common */
#define __NR_setsid 175 /* Common */
#define __NR_fchdir 176 /* Common */
-/* #define __NR_fchroot 177 SunOS Specific */
-/* #define __NR_vpixsys 178 SunOS Specific */
-/* #define __NR_aioread 179 SunOS Specific */
-/* #define __NR_aiowrite 180 SunOS Specific */
-/* #define __NR_aiowait 181 SunOS Specific */
-/* #define __NR_aiocancel 182 SunOS Specific */
+#define __NR_fgetxattr 177 /* SunOS Specific */
+#define __NR_listxattr 178 /* SunOS Specific */
+#define __NR_llistxattr 179 /* SunOS Specific */
+#define __NR_flistxattr 180 /* SunOS Specific */
+#define __NR_removexattr 181 /* SunOS Specific */
+#define __NR_lremovexattr 182 /* SunOS Specific */
#define __NR_sigpending 183 /* Common */
#define __NR_query_module 184 /* Linux Specific */
#define __NR_setpgid 185 /* Common */
-/* #define __NR_pathconf 186 SunOS Specific */
+#define __NR_fremovexattr 186 /* SunOS Specific */
/* #define __NR_fpathconf 187 SunOS Specific */
/* #define __NR_sysconf 188 SunOS Specific */
#define __NR_uname 189 /* Linux Specific */
diff -Nur linux-2.2.20/include/linux/cache_def.h linux-2.2.20ea/include/linux/cache_def.h
--- linux-2.2.20/include/linux/cache_def.h Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/include/linux/cache_def.h Tue Mar 19 15:28:50 2002
@@ -0,0 +1,19 @@
+/*
+ * linux/cache_def.h
+ * Handling of caches defined in drivers, filesystems, ...
+ *
+ * Copyright (C) 2001 by Andreas Gruenbacher,
+ */
+
+#ifndef LINUX_VERSION_CODE
+# include
+#endif
+
+struct cache_definition {
+ const char *name;
+ void (*shrink)(int, unsigned int);
+ struct list_head link;
+};
+
+extern void register_cache(struct cache_definition *);
+extern void unregister_cache(struct cache_definition *);
diff -Nur linux-2.2.20/include/linux/errno.h linux-2.2.20ea/include/linux/errno.h
--- linux-2.2.20/include/linux/errno.h Sun Mar 25 18:31:03 2001
+++ linux-2.2.20ea/include/linux/errno.h Sun Mar 24 23:39:21 2002
@@ -21,6 +21,10 @@
#define EBADTYPE 527 /* Type not supported by server */
#define EJUKEBOX 528 /* Request initiated, but will not complete before timeout */
+/* Defined for extended attributes */
+#define ENOATTR ENODATA /* No such attribute */
+#define ENOTSUP EOPNOTSUPP /* Operation not supported */
+
#endif
#endif
diff -Nur linux-2.2.20/include/linux/ext2_fs.h linux-2.2.20ea/include/linux/ext2_fs.h
--- linux-2.2.20/include/linux/ext2_fs.h Sun Mar 25 18:31:03 2001
+++ linux-2.2.20ea/include/linux/ext2_fs.h Wed Mar 20 02:09:09 2002
@@ -57,8 +57,6 @@
*/
#define EXT2_BAD_INO 1 /* Bad blocks inode */
#define EXT2_ROOT_INO 2 /* Root inode */
-#define EXT2_ACL_IDX_INO 3 /* ACL inode */
-#define EXT2_ACL_DATA_INO 4 /* ACL inode */
#define EXT2_BOOT_LOADER_INO 5 /* Boot loader inode */
#define EXT2_UNDEL_DIR_INO 6 /* Undelete directory inode */
@@ -86,7 +84,6 @@
#else
# define EXT2_BLOCK_SIZE(s) (EXT2_MIN_BLOCK_SIZE << (s)->s_log_block_size)
#endif
-#define EXT2_ACLE_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / sizeof (struct ext2_acl_entry))
#define EXT2_ADDR_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / sizeof (__u32))
#ifdef __KERNEL__
# define EXT2_BLOCK_SIZE_BITS(s) ((s)->s_blocksize_bits)
@@ -121,28 +118,6 @@
#endif
/*
- * ACL structures
- */
-struct ext2_acl_header /* Header of Access Control Lists */
-{
- __u32 aclh_size;
- __u32 aclh_file_count;
- __u32 aclh_acle_count;
- __u32 aclh_first_acle;
-};
-
-struct ext2_acl_entry /* Access Control List Entry */
-{
- __u32 acle_size;
- __u16 acle_perms; /* Access permissions */
- __u16 acle_type; /* Type of entry */
- __u16 acle_tag; /* User or group identity */
- __u16 acle_pad1;
- __u32 acle_next; /* Pointer on next entry for the */
- /* same inode or on next free entry */
-};
-
-/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
@@ -393,6 +368,7 @@
#ifdef __KERNEL__
#define EXT2_SB(sb) (&((sb)->u.ext2_sb))
+#define EXT2_I(inode) (&((inode)->u.ext2_i))
#else
/* Assume that user mode programs are passing in an ext2fs superblock, not
* a kernel struct super_block. This will allow us to call the feature-test
@@ -432,6 +408,7 @@
( EXT2_SB(sb)->s_feature_incompat & (mask) )
#define EXT2_FEATURE_COMPAT_DIR_PREALLOC 0x0001
+#define EXT2_FEATURE_COMPAT_EXT_ATTR 0x0008
#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001
#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002
@@ -440,7 +417,7 @@
#define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001
#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002
-#define EXT2_FEATURE_COMPAT_SUPP 0
+#define EXT2_FEATURE_COMPAT_SUPP EXT2_FEATURE_COMPAT_EXT_ATTR
#define EXT2_FEATURE_INCOMPAT_SUPP EXT2_FEATURE_INCOMPAT_FILETYPE
#define EXT2_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \
EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \
@@ -509,6 +486,18 @@
extern long long ext2_max_sizes[];
/*
+ * Test whether an inode is a fast symlink.
+ */
+static inline int ext2_inode_is_fast_symlink(struct inode *inode)
+{
+ int ea_blocks = inode->u.ext2_i.i_file_acl ?
+ (inode->i_sb->s_blocksize >> 9) : 0;
+
+ return (S_ISLNK(inode->i_mode) &&
+ inode->i_blocks - ea_blocks == 0);
+}
+
+/*
* Function prototypes
*/
@@ -556,6 +545,11 @@
extern void ext2_free_inode (struct inode *);
extern unsigned long ext2_count_free_inodes (struct super_block *);
extern void ext2_check_inodes_bitmap (struct super_block *);
+extern struct inode_operations ext2_chrdev_inode_operations;
+extern struct inode_operations ext2_blkdev_inode_operations;
+extern struct inode_operations ext2_fifo_inode_operations;
+extern struct inode_operations ext2_sock_inode_operations;
+
/* inode.c */
extern int ext2_bmap (struct inode *, int);
diff -Nur linux-2.2.20/include/linux/ext2_xattr.h linux-2.2.20ea/include/linux/ext2_xattr.h
--- linux-2.2.20/include/linux/ext2_xattr.h Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/include/linux/ext2_xattr.h Fri Apr 5 10:07:28 2002
@@ -0,0 +1,155 @@
+/*
+ File: linux/ext2_xattr.h
+
+ On-disk format of extended attributes for the ext2 filesystem.
+
+ (C) 2001 Andreas Gruenbacher,
+*/
+
+#include
+#include
+#include
+
+/* Magic value in attribute blocks */
+#define EXT2_XATTR_MAGIC 0xEA020000
+
+/* Maximum number of references to one attribute block */
+#define EXT2_XATTR_REFCOUNT_MAX 1024
+
+/* Name indexes */
+#define EXT2_XATTR_INDEX_MAX 10
+#define EXT2_XATTR_INDEX_USER 1
+
+struct ext2_xattr_header {
+ __u32 h_magic; /* magic number for identification */
+ __u32 h_refcount; /* reference count */
+ __u32 h_blocks; /* number of disk blocks used */
+ __u32 h_hash; /* hash value of all attributes */
+ __u32 h_reserved[4]; /* zero right now */
+};
+
+struct ext2_xattr_entry {
+ __u8 e_name_len; /* length of name */
+ __u8 e_name_index; /* attribute name index */
+ __u16 e_value_offs; /* offset in disk block of value */
+ __u32 e_value_block; /* disk block attribute is stored on (n/i) */
+ __u32 e_value_size; /* size of attribute value */
+ __u32 e_hash; /* hash value of name and value */
+ char e_name[0]; /* attribute name */
+};
+
+#define EXT2_XATTR_PAD_BITS 2
+#define EXT2_XATTR_PAD (1<e_name_len)) )
+#define EXT2_XATTR_SIZE(size) \
+ (((size) + EXT2_XATTR_ROUND) & ~EXT2_XATTR_ROUND)
+
+#ifdef __KERNEL__
+
+# ifdef CONFIG_EXT2_FS_XATTR
+
+struct ext2_xattr_handler {
+ char *prefix;
+ size_t (*list)(char *list, struct inode *inode, const char *name,
+ int name_len);
+ int (*get)(struct inode *inode, const char *name, void *buffer,
+ size_t size);
+ int (*set)(struct inode *inode, const char *name, void *buffer,
+ size_t size, int flags);
+};
+
+extern int ext2_xattr_register(int, struct ext2_xattr_handler *);
+extern void ext2_xattr_unregister(int, struct ext2_xattr_handler *);
+
+extern int ext2_setxattr(struct dentry *, const char *, void *, size_t, int);
+extern ssize_t ext2_getxattr(struct dentry *, const char *, void *, size_t);
+extern ssize_t ext2_listxattr(struct dentry *, char *, size_t);
+extern int ext2_removexattr(struct dentry *, const char *);
+
+extern int ext2_xattr_get(struct inode *, int, const char *, void *, size_t);
+extern int ext2_xattr_list(struct inode *, char *, size_t);
+extern int ext2_xattr_set(struct inode *, int, const char *, void *, size_t, int);
+
+extern void ext2_xattr_drop_inode(struct inode *);
+extern void ext2_xattr_put_super(struct super_block *);
+
+extern int init_ext2_xattr(void) __init;
+extern void exit_ext2_xattr(void);
+
+# else /* CONFIG_EXT2_FS_XATTR */
+# define ext2_setxattr NULL
+# define ext2_getxattr NULL
+# define ext2_listxattr NULL
+# define ext2_removexattr NULL
+
+static inline int
+ext2_xattr_get(struct inode *inode, int name_index,
+ const char *name, void *buffer, size_t size)
+{
+ return -ENOTSUP;
+}
+
+static inline int
+ext2_xattr_list(struct inode *inode, char *buffer, size_t size)
+{
+ return -ENOTSUP;
+}
+
+static inline int
+ext2_xattr_set(struct inode *inode, int name_index, const char *name,
+ void *value, size_t size, int flags)
+{
+ return -ENOTSUP;
+}
+
+static inline void
+ext2_xattr_drop_inode(struct inode *inode)
+{
+}
+
+static inline void
+ext2_xattr_put_super(struct super_block *sb)
+{
+}
+
+static inline int
+init_ext2_xattr(void)
+{
+ return 0;
+}
+
+static inline void
+exit_ext2_xattr(void)
+{
+}
+
+# endif /* CONFIG_EXT2_FS_XATTR */
+
+# ifdef CONFIG_EXT2_FS_XATTR_USER
+
+extern int init_ext2_xattr_user(void) __init;
+extern void exit_ext2_xattr_user(void);
+
+# else /* CONFIG_EXT2_FS_XATTR_USER */
+
+static inline int
+init_ext2_xattr_user(void)
+{
+ return 0;
+}
+
+static inline void
+exit_ext2_xattr_user(void)
+{
+}
+
+# endif /* CONFIG_EXT2_FS_XATTR_USER */
+
+#endif /* __KERNEL__ */
+
diff -Nur linux-2.2.20/include/linux/ext3_xattr.h linux-2.2.20ea/include/linux/ext3_xattr.h
--- linux-2.2.20/include/linux/ext3_xattr.h Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/include/linux/ext3_xattr.h Fri Apr 5 10:07:33 2002
@@ -0,0 +1,155 @@
+/*
+ File: linux/ext3_xattr.h
+
+ On-disk format of extended attributes for the ext3 filesystem.
+
+ (C) 2001 Andreas Gruenbacher,
+*/
+
+#include
+#include
+#include
+
+/* Magic value in attribute blocks */
+#define EXT3_XATTR_MAGIC 0xEA020000
+
+/* Maximum number of references to one attribute block */
+#define EXT3_XATTR_REFCOUNT_MAX 1024
+
+/* Name indexes */
+#define EXT3_XATTR_INDEX_MAX 10
+#define EXT3_XATTR_INDEX_USER 1
+
+struct ext3_xattr_header {
+ __u32 h_magic; /* magic number for identification */
+ __u32 h_refcount; /* reference count */
+ __u32 h_blocks; /* number of disk blocks used */
+ __u32 h_hash; /* hash value of all attributes */
+ __u32 h_reserved[4]; /* zero right now */
+};
+
+struct ext3_xattr_entry {
+ __u8 e_name_len; /* length of name */
+ __u8 e_name_index; /* attribute name index */
+ __u16 e_value_offs; /* offset in disk block of value */
+ __u32 e_value_block; /* disk block attribute is stored on (n/i) */
+ __u32 e_value_size; /* size of attribute value */
+ __u32 e_hash; /* hash value of name and value */
+ char e_name[0]; /* attribute name */
+};
+
+#define EXT3_XATTR_PAD_BITS 2
+#define EXT3_XATTR_PAD (1<e_name_len)) )
+#define EXT3_XATTR_SIZE(size) \
+ (((size) + EXT3_XATTR_ROUND) & ~EXT3_XATTR_ROUND)
+
+#ifdef __KERNEL__
+
+# ifdef CONFIG_EXT3_FS_XATTR
+
+struct ext3_xattr_handler {
+ char *prefix;
+ size_t (*list)(char *list, struct inode *inode, const char *name,
+ int name_len);
+ int (*get)(struct inode *inode, const char *name, void *buffer,
+ size_t size);
+ int (*set)(struct inode *inode, const char *name, void *buffer,
+ size_t size, int flags);
+};
+
+extern int ext3_xattr_register(int, struct ext3_xattr_handler *);
+extern void ext3_xattr_unregister(int, struct ext3_xattr_handler *);
+
+extern int ext3_setxattr(struct dentry *, const char *, void *, size_t, int);
+extern ssize_t ext3_getxattr(struct dentry *, const char *, void *, size_t);
+extern ssize_t ext3_listxattr(struct dentry *, char *, size_t);
+extern int ext3_removexattr(struct dentry *, const char *);
+
+extern int ext3_xattr_get(struct inode *, int, const char *, void *, size_t);
+extern int ext3_xattr_list(struct inode *, char *, size_t);
+extern int ext3_xattr_set(handle_t *handle, struct inode *, int, const char *, void *, size_t, int);
+
+extern void ext3_xattr_drop_inode(handle_t *, struct inode *);
+extern void ext3_xattr_put_super(struct super_block *);
+
+extern int init_ext3_xattr(void) __init;
+extern void exit_ext3_xattr(void);
+
+# else /* CONFIG_EXT3_FS_XATTR */
+# define ext3_setxattr NULL
+# define ext3_getxattr NULL
+# define ext3_listxattr NULL
+# define ext3_removexattr NULL
+
+static inline int
+ext3_xattr_get(struct inode *inode, int name_index, const char *name,
+ void *buffer, size_t size, int flags)
+{
+ return -ENOTSUP;
+}
+
+static inline int
+ext3_xattr_list(struct inode *inode, void *buffer, size_t size, int flags)
+{
+ return -ENOTSUP;
+}
+
+static inline int
+ext3_xattr_set(handle_t *handle, struct inode *inode, int name_index,
+ const char *name, void *value, size_t size, int flags)
+{
+ return -ENOTSUP;
+}
+
+static inline void
+ext3_xattr_drop_inode(handle_t *handle, struct inode *inode)
+{
+}
+
+static inline void
+ext3_xattr_put_super(struct super_block *sb)
+{
+}
+
+static inline int
+init_ext3_xattr(void)
+{
+ return 0;
+}
+
+static inline void
+exit_ext3_xattr(void)
+{
+}
+
+# endif /* CONFIG_EXT3_FS_XATTR */
+
+# ifdef CONFIG_EXT3_FS_XATTR_USER
+
+extern int init_ext3_xattr_user(void) __init;
+extern void exit_ext3_xattr_user(void);
+
+# else /* CONFIG_EXT3_FS_XATTR_USER */
+
+static inline int
+init_ext3_xattr_user(void)
+{
+ return 0;
+}
+
+static inline void
+exit_ext3_xattr_user(void)
+{
+}
+
+#endif /* CONFIG_EXT3_FS_XATTR_USER */
+
+#endif /* __KERNEL__ */
+
diff -Nur linux-2.2.20/include/linux/fs.h linux-2.2.20ea/include/linux/fs.h
--- linux-2.2.20/include/linux/fs.h Fri Nov 2 17:39:09 2001
+++ linux-2.2.20ea/include/linux/fs.h Sun Mar 24 23:49:51 2002
@@ -105,6 +105,16 @@
*/
/*
+ * These are the super block s_xattr_flags
+ */
+#define XATTR_MNT_FLAG_USER 1 /* Extended user attributes */
+
+#define __IS_XATTR_FLG(inode,flg) \
+ ((inode)->i_sb && \
+ (inode)->i_sb->s_xattr_flags & (XATTR_MNT_FLAG_ ## flg))
+
+#define IS_XATTR_USER(inode) __IS_XATTR_FLG(inode, USER)
+/*
* Flags that can be altered by MS_REMOUNT
*/
#define MS_RMT_MASK (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS|MS_MANDLOCK|MS_NOATIME|MS_NODIRATIME)
@@ -552,6 +562,7 @@
struct super_operations *s_op;
struct dquot_operations *dq_op;
unsigned long s_flags;
+ unsigned long s_xattr_flags;
unsigned long s_magic;
unsigned long s_time;
struct dentry *s_root;
@@ -659,6 +670,10 @@
int (*prepare_write) (struct file *, struct page *, unsigned, unsigned);
int (*sync_page) (struct page *);
void (*getlocality) (struct inode *, __u32 *) ;
+ int (*setxattr) (struct dentry *, const char *, void *, size_t, int);
+ ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
+ ssize_t (*listxattr) (struct dentry *, char *, size_t);
+ int (*removexattr) (struct dentry *, const char *);
};
struct super_operations {
@@ -774,6 +789,7 @@
extern void init_fifo(struct inode * inode);
+extern struct file_operations def_fifo_fops;
extern struct inode_operations fifo_inode_operations;
/* Invalid inode operations -- fs/bad_inode.c */
diff -Nur linux-2.2.20/include/linux/limits.h linux-2.2.20ea/include/linux/limits.h
--- linux-2.2.20/include/linux/limits.h Sun Mar 25 18:31:03 2001
+++ linux-2.2.20ea/include/linux/limits.h Thu Feb 28 22:58:14 2002
@@ -13,6 +13,9 @@
#define NAME_MAX 255 /* # chars in a file name */
#define PATH_MAX 4095 /* # chars in a path name */
#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */
+#define XATTR_NAME_MAX 255 /* # chars in an extended attribute name */
+#define XATTR_SIZE_MAX 65536 /* size of an extended attribute value (64k) */
+#define XATTR_LIST_MAX 65536 /* size of extended attribute namelist (64k) */
#define RTSIG_MAX 32
diff -Nur linux-2.2.20/include/linux/mbcache.h linux-2.2.20ea/include/linux/mbcache.h
--- linux-2.2.20/include/linux/mbcache.h Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/include/linux/mbcache.h Tue Mar 19 18:07:48 2002
@@ -0,0 +1,69 @@
+/*
+ File: linux/mbcache.h
+
+ (C) 2001 by Andreas Gruenbacher,
+*/
+
+/* Hardwire the number of additional indexes */
+#define MB_CACHE_INDEXES_COUNT 1
+
+struct mb_cache_entry;
+
+struct mb_cache_op {
+ void (*free)(struct mb_cache_entry *);
+};
+
+struct mb_cache {
+ struct list_head c_cache_list;
+ const char *c_name;
+ struct mb_cache_op c_op;
+ atomic_t c_entry_count;
+ int c_bucket_count;
+#ifndef MB_CACHE_INDEXES_COUNT
+ int c_indexes_count;
+#endif
+ kmem_cache_t *c_entry_cache;
+ struct list_head *c_block_hash;
+ struct list_head *c_indexes_hash[0];
+};
+
+struct mb_cache_entry_index {
+ struct list_head o_list;
+ unsigned int o_key;
+};
+
+struct mb_cache_entry {
+ struct list_head e_lru_list;
+ struct mb_cache *e_cache;
+ unsigned int e_used;
+ kdev_t e_dev;
+ unsigned long e_block;
+ struct list_head e_block_list;
+ struct mb_cache_entry_index e_indexes[0];
+};
+
+/* Functions on caches */
+
+struct mb_cache * mb_cache_create(const char *, struct mb_cache_op *, size_t,
+ int, int);
+void mb_cache_shrink(struct mb_cache *, kdev_t);
+void mb_cache_destroy(struct mb_cache *);
+
+/* Functions on cache entries */
+
+struct mb_cache_entry *mb_cache_entry_alloc(struct mb_cache *);
+int mb_cache_entry_insert(struct mb_cache_entry *, kdev_t, unsigned long,
+ unsigned int[]);
+void mb_cache_entry_rehash(struct mb_cache_entry *, unsigned int[]);
+void mb_cache_entry_release(struct mb_cache_entry *);
+void mb_cache_entry_takeout(struct mb_cache_entry *);
+void mb_cache_entry_free(struct mb_cache_entry *);
+struct mb_cache_entry *mb_cache_entry_dup(struct mb_cache_entry *);
+struct mb_cache_entry *mb_cache_entry_get(struct mb_cache *, kdev_t,
+ unsigned long);
+#if !defined(MB_CACHE_INDEXES_COUNT) || (MB_CACHE_INDEXES_COUNT > 0)
+struct mb_cache_entry *mb_cache_entry_find_first(struct mb_cache *cache, int,
+ kdev_t, unsigned int);
+struct mb_cache_entry *mb_cache_entry_find_next(struct mb_cache_entry *, int,
+ kdev_t, unsigned int);
+#endif
diff -Nur linux-2.2.20/include/linux/quotaops.h linux-2.2.20ea/include/linux/quotaops.h
--- linux-2.2.20/include/linux/quotaops.h Sun Mar 25 18:31:05 2001
+++ linux-2.2.20ea/include/linux/quotaops.h Fri Mar 1 00:30:36 2002
@@ -12,6 +12,8 @@
#include
+#define OLD_QUOTAS
+
#if defined(CONFIG_QUOTA)
/*
diff -Nur linux-2.2.20/include/linux/xattr.h linux-2.2.20ea/include/linux/xattr.h
--- linux-2.2.20/include/linux/xattr.h Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/include/linux/xattr.h Sun Mar 24 23:42:06 2002
@@ -0,0 +1,15 @@
+/*
+ File: linux/xattr.h
+
+ Extended attributes handling.
+
+ Copyright (C) 2001 by Andreas Gruenbacher
+ Copyright (C) 2001 SGI - Silicon Graphics, Inc
+*/
+#ifndef _LINUX_XATTR_H
+#define _LINUX_XATTR_H
+
+#define XATTR_CREATE 1 /* set value, fail if attr already exists */
+#define XATTR_REPLACE 2 /* set value, fail if attr does not exist */
+
+#endif /* _LINUX_XATTR_H */
diff -Nur linux-2.2.20/kernel/ksyms.c linux-2.2.20ea/kernel/ksyms.c
--- linux-2.2.20/kernel/ksyms.c Fri Nov 2 17:39:16 2001
+++ linux-2.2.20ea/kernel/ksyms.c Fri Mar 1 00:14:38 2002
@@ -31,6 +31,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -104,6 +105,8 @@
EXPORT_SYMBOL(kmem_cache_destroy);
EXPORT_SYMBOL(kmem_cache_alloc);
EXPORT_SYMBOL(kmem_cache_free);
+EXPORT_SYMBOL(register_cache);
+EXPORT_SYMBOL(unregister_cache);
EXPORT_SYMBOL(kmalloc);
EXPORT_SYMBOL(kfree);
EXPORT_SYMBOL(kfree_s);
@@ -404,6 +407,9 @@
EXPORT_SYMBOL(fifo_inode_operations);
EXPORT_SYMBOL(chrdev_inode_operations);
EXPORT_SYMBOL(blkdev_inode_operations);
+EXPORT_SYMBOL(def_fifo_fops);
+EXPORT_SYMBOL(def_chr_fops);
+EXPORT_SYMBOL(def_blk_fops);
EXPORT_SYMBOL(read_ahead);
EXPORT_SYMBOL(get_hash_table);
EXPORT_SYMBOL(get_empty_inode);
diff -Nur linux-2.2.20/mm/vmscan.c linux-2.2.20ea/mm/vmscan.c
--- linux-2.2.20/mm/vmscan.c Sun Mar 25 18:37:41 2001
+++ linux-2.2.20ea/mm/vmscan.c Fri Mar 1 00:13:16 2002
@@ -7,6 +7,7 @@
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
+ * Added [un]register_cache() 25.2.2000, Andreas Gruenbacher.
* Version: $Id: vmscan.c,v 1.5 1998/02/23 22:14:28 sct Exp $
*/
@@ -14,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -21,6 +23,41 @@
#include
/*
+ * Handling of caches defined in drivers, filesystems, ...
+ *
+ * The cache definition contains a callback for shrinking
+ * the cache.
+ *
+ * The [un]register_cache() functions may only be called when
+ * the kernel lock is held. The shrink() functions are also
+ * called with the kernel lock held.
+ */
+static LIST_HEAD(cache_definitions);
+
+void register_cache(struct cache_definition *cache)
+{
+ list_add(&cache->link, &cache_definitions);
+}
+
+void unregister_cache(struct cache_definition *cache)
+{
+ list_del(&cache->link);
+}
+
+static void shrink_other_caches(unsigned int priority, int gfp_mask)
+{
+ struct list_head *p = cache_definitions.prev;
+
+ while (p != &cache_definitions) {
+ struct cache_definition *cache =
+ list_entry(p, struct cache_definition, link);
+
+ cache->shrink(priority, gfp_mask);
+ p = p->prev;
+ }
+}
+
+/*
* The swap-out functions return 1 if they successfully
* threw something out, and we got a free page. It returns
* zero if it couldn't do anything, and any other value
@@ -411,6 +448,8 @@
goto done;
}
}
+
+ shrink_other_caches(priority, gfp_mask);
/* Then, try to page stuff out.. */
while (swap_out(priority, gfp_mask)) {
diff -Nur linux-2.2.20/xattr.c linux-2.2.20ea/xattr.c
--- linux-2.2.20/xattr.c Thu Jan 1 01:00:00 1970
+++ linux-2.2.20ea/xattr.c Wed Mar 20 01:56:10 2002
@@ -0,0 +1,1259 @@
+/*
+ * linux/fs/ext2/xattr.c
+ *
+ * Copyright (C) 2001 by Andreas Gruenbacher,
+ *
+ * Fix by Harrison Xing .
+ * Extended attributes for symlinks and special files added per
+ * suggestion of Luka Renko .
+ */
+
+/*
+ * Extended attributes are stored on disk blocks that allocated outside of
+ * any inode. The i_file_acl field is them made to point to this allocated
+ * block. If all extended attributes of an inode are identical, these
+ * inodes may share the same extended attribute block. Such situations
+ * are automatically detected by keeping a cache of recent attribute block
+ * numbers and hashes over the block's contents.
+ *
+ *
+ * Extended attribute block layout:
+ *
+ * +------------------+
+ * | header |
+ * ¦ entry 1 | |
+ * | entry 2 | | growing downwards
+ * | entry 3 | v
+ * | four null bytes |
+ * | . . . |
+ * | value 1 | ^
+ * | value 3 | | growing upwards
+ * | value 2 | |
+ * +------------------+
+ *
+ * The block header is followed by multiple entry descriptors. These entry
+ * descriptors are variable in size, and alligned to EXT2_XATTR_PAD
+ * byte boundaries. The entry descriptors are sorted by attribute name,
+ * so that two extended attribute blocks can be compared efficiently.
+ *
+ * Attribute values are aligned to the end of the block, stored in
+ * no specific order. They are also padded to EXT2_XATTR_PAD byte
+ * boundaries. No additional gaps are left between them.
+ *
+ * Locking strategy
+ * ----------------
+ * The VFS already holds the BKL and the inode->i_sem semaphore when any of
+ * the xattr inode operations is called, so we are guaranteed that only one
+ * processes accesses extended attributes of an inode at any time.
+ *
+ * For writing we also grab the ext2_xattr_sem semaphore. This ensures that
+ * only a single process is modifying an extended attribute block, even
+ * if the block is shared among inodes.
+ *
+ * Note for porting to 2.5
+ * -----------------------
+ * The BLK will no longer be held in the xattr inode operations.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#define EXT2_EA_USER "user."
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
+# define mark_buffer_dirty(bh) mark_buffer_dirty(bh, 1)
+#endif
+
+#define HDR(bh) ((struct ext2_xattr_header *)((bh)->b_data))
+#define ENTRY(ptr) ((struct ext2_xattr_entry *)(ptr))
+#define FIRST_ENTRY(bh) ENTRY(HDR(bh)+1)
+#define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0)
+
+#ifdef EXT2_XATTR_DEBUG
+# define ea_idebug(inode, f...) do { \
+ printk(KERN_DEBUG "inode %s:%ld: ", \
+ kdevname(inode->i_dev), inode->i_ino); \
+ printk(f); \
+ printk("\n"); \
+ } while (0)
+# define ea_bdebug(bh, f...) do { \
+ printk(KERN_DEBUG "block %s:%ld: ", \
+ kdevname(bh->b_dev), bh->b_blocknr); \
+ printk(f); \
+ printk("\n"); \
+ } while (0)
+#else
+# define ea_idebug(f...)
+# define ea_bdebug(f...)
+#endif
+
+static int ext2_xattr_set2(struct inode *, struct buffer_head *,
+ struct ext2_xattr_header *);
+
+#ifdef CONFIG_EXT2_FS_XATTR_SHARING
+
+static int ext2_xattr_cache_insert(struct buffer_head *);
+static struct buffer_head * ext2_xattr_cache_find(struct inode *,
+ struct ext2_xattr_header *);
+static void ext2_xattr_cache_remove(struct buffer_head *);
+static void ext2_xattr_rehash(struct ext2_xattr_header *,
+ struct ext2_xattr_entry *);
+
+static struct mb_cache *ext2_xattr_cache;
+
+#else
+# define ext2_xattr_cache_insert(bh) 0
+# define ext2_xattr_cache_find(inode, header) NULL
+# define ext2_xattr_cache_remove(bh) while(0) {}
+# define ext2_xattr_rehash(header, entry) while(0) {}
+#endif
+
+/*
+ * If a file system does not share extended attributes among inodes,
+ * we should not need the ext2_xattr_sem semaphore. However, the
+ * filesystem may still contain shared blocks, so we always take
+ * the lock.
+ */
+
+DECLARE_MUTEX(ext2_xattr_sem);
+
+static inline void
+ext2_xattr_lock(void)
+{
+ down(&ext2_xattr_sem);
+}
+
+static inline void
+ext2_xattr_unlock(void)
+{
+ up(&ext2_xattr_sem);
+}
+
+static inline int
+ext2_xattr_new_block(struct inode *inode, int * errp, int force)
+{
+ struct super_block *sb = inode->i_sb;
+ int goal = le32_to_cpu(EXT2_SB(sb)->s_es->s_first_data_block) +
+ EXT2_I(inode)->i_block_group * EXT2_BLOCKS_PER_GROUP(sb);
+
+ /* How can we enforce the allocation? */
+ int block = ext2_new_block(inode, goal, 0, 0, errp);
+#ifdef OLD_QUOTAS
+ if (!*errp)
+ inode->i_blocks += inode->i_sb->s_blocksize >> 9;
+#endif
+ return block;
+}
+
+/*
+ * Force the allocation of a block in quotas: We know that we will
+ * later free a block, so effectively the quotas won't change.
+ */
+static inline int
+ext2_xattr_quota_alloc(struct inode *inode, int force)
+{
+ /* How can we enforce the allocation? */
+#ifdef OLD_QUOTAS
+ int error = DQUOT_ALLOC_BLOCK(inode->i_sb, inode, 1);
+ if (!error)
+ inode->i_blocks += inode->i_sb->s_blocksize >> 9;
+#else
+ int error = DQUOT_ALLOC_BLOCK(inode, 1);
+#endif
+ return error;
+}
+
+#ifdef OLD_QUOTAS
+
+static inline void
+ext2_xattr_quota_free(struct inode *inode)
+{
+ DQUOT_FREE_BLOCK(inode->i_sb, inode, 1);
+ inode->i_blocks -= inode->i_sb->s_blocksize >> 9;
+}
+
+void
+ext2_xattr_free_block(struct inode * inode, unsigned long block)
+{
+ ext2_free_blocks(inode, block, 1);
+ inode->i_blocks -= inode->i_sb->s_blocksize >> 9;
+}
+
+#else
+# define ext2_xattr_quota_free(inode) \
+ DQUOT_FREE_BLOCK(inode, 1)
+# define ext2_xattr_free_block(inode, block) \
+ ext2_free_blocks(inode, block, 1)
+#endif
+
+
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+static int
+ext2_get_user_xattr(struct inode *inode, const char *name,
+ void *buffer, size_t size)
+{
+ int error;
+
+ if (!IS_XATTR_USER(inode))
+ return -ENOTSUP;
+ error = permission(inode, MAY_READ);
+ if (error)
+ return error;
+
+ return ext2_xattr_get(inode, EXT2_XATTR_INDEX_USER, name, buffer, size);
+}
+
+static int
+ext2_set_user_xattr(struct inode *inode, const char *name,
+ void *value, size_t size, int flags)
+{
+ int error;
+
+ if (!IS_XATTR_USER(inode))
+ return -ENOTSUP;
+ error = permission(inode, MAY_WRITE);
+ if (error)
+ return error;
+
+ return ext2_xattr_set(inode, EXT2_XATTR_INDEX_USER, name,
+ value, size, flags);
+}
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
+
+static inline struct buffer_head *
+sb_bread(struct super_block *sb, int block)
+{
+ return bread(sb->s_dev, block, sb->s_blocksize);
+}
+
+static inline struct buffer_head *
+sb_getblk(struct super_block *sb, int block)
+{
+ return getblk(sb->s_dev, block, sb->s_blocksize);
+}
+
+#endif
+
+/*
+ * Decode the extended attribute name, and translate it into
+ * the name index + name suffix.
+ */
+static int
+ext2_xattr_name(const char **name)
+{
+ if (*name) {
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+ if (!strncmp(*name, EXT2_EA_USER, sizeof(EXT2_EA_USER)-1)) {
+ if (!strcmp(*name+sizeof(EXT2_EA_USER)-1, ""))
+ return -EINVAL;
+ *name += 5;
+ return EXT2_XATTR_INDEX_USER;
+ } else
+#endif
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/*
+ * Inode operation getxattr()
+ *
+ * dentry->d_inode->i_sem down
+ * BKL held [before 2.5.x]
+ */
+ssize_t
+ext2_getxattr(struct dentry *dentry, const char *name,
+ void *buffer, size_t size)
+{
+ struct inode *inode = dentry->d_inode;
+ int name_index;
+
+ assert_down(&dentry->d_inode->i_sem);
+
+ name_index = ext2_xattr_name(&name);
+ switch(name_index) {
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+ case EXT2_XATTR_INDEX_USER:
+ return ext2_get_user_xattr(inode, name, buffer, size);
+#endif
+ default:
+ if (name_index >= 0)
+ name_index = -EINVAL;
+ return name_index;
+ }
+}
+
+/*
+ * Inode operation listxattr()
+ *
+ * dentry->d_inode->i_sem down
+ * BKL held [before 2.5.x]
+ */
+ssize_t
+ext2_listxattr(struct dentry *dentry, char *buffer, size_t size)
+{
+ assert_down(&dentry->d_inode->i_sem);
+
+ return ext2_xattr_list(dentry->d_inode, buffer, size);
+}
+
+/*
+ * Inode operation setxattr()
+ *
+ * dentry->d_inode->i_sem down
+ * BKL held [before 2.5.x]
+ */
+int
+ext2_setxattr(struct dentry *dentry, const char *name,
+ void *value, size_t size, int flags)
+{
+ struct inode *inode = dentry->d_inode;
+ int name_index;
+
+ assert_down(&dentry->d_inode->i_sem);
+
+ if (size == 0)
+ value = ""; /* empty EA, do not remove */
+
+ name_index = ext2_xattr_name(&name);
+ switch(name_index) {
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+ case EXT2_XATTR_INDEX_USER:
+ return ext2_set_user_xattr(inode, name,
+ value, size, flags);
+#endif
+ default:
+ if (name_index >= 0)
+ name_index = -EINVAL;
+ return name_index;
+ }
+}
+
+/*
+ * Inode operation removexattr()
+ *
+ * dentry->d_inode->i_sem down
+ * BKL held [before 2.5.x]
+ */
+int
+ext2_removexattr(struct dentry *dentry, const char *name)
+{
+ struct inode *inode = dentry->d_inode;
+ int name_index;
+
+ assert_down(&dentry->d_inode->i_sem);
+
+ name_index = ext2_xattr_name(&name);
+ switch(name_index) {
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+ case EXT2_XATTR_INDEX_USER:
+ return ext2_set_user_xattr(inode, name,
+ NULL, 0, XATTR_REPLACE);
+#endif
+ default:
+ if (name_index >= 0)
+ name_index = -EINVAL;
+ return name_index;
+ }
+}
+
+/*
+ * ext2_xattr_get()
+ *
+ * Copy an extended attribute into the buffer
+ * provided, or compute the buffer size required.
+ * Buffer is NULL to compute the size of the buffer required.
+ *
+ * Returns a negative error number on failure, or the number of bytes
+ * used / required on success.
+ */
+int
+ext2_xattr_get(struct inode *inode, int name_index, const char *name,
+ void *buffer, size_t buffer_size)
+{
+ struct buffer_head *bh = NULL;
+ struct ext2_xattr_entry *entry;
+ unsigned int block, size;
+ char *end;
+ int name_len, error;
+
+ ea_idebug(inode, "name=%d.%s, buffer=%p, buffer_size=%ld",
+ name_index, name, buffer, (long)buffer_size);
+
+ if (!IS_XATTR(inode))
+ return -ENOTSUP;
+ if (name == NULL)
+ return -EINVAL;
+ if (!EXT2_I(inode)->i_file_acl)
+ return -ENOATTR;
+ block = EXT2_I(inode)->i_file_acl;
+ ea_idebug(inode, "reading block %d", block);
+ bh = sb_bread(inode->i_sb, block);
+ if (!bh)
+ return -EIO;
+ ea_bdebug(bh, "b_count=%d, refcount=%d",
+ atomic_read(&(bh->b_count)), le32_to_cpu(HDR(bh)->h_refcount));
+ end = bh->b_data + bh->b_size;
+ if (HDR(bh)->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
+ HDR(bh)->h_blocks != cpu_to_le32(1)) {
+bad_block: ext2_error(inode->i_sb, "ext2_xattr_get",
+ "inode %ld: bad block %d", inode->i_ino, block);
+ error = -EIO;
+ goto cleanup;
+ }
+ /* find named attribute */
+ name_len = strlen(name);
+
+ error = -ERANGE;
+ if (name_len > 255)
+ goto cleanup;
+ entry = FIRST_ENTRY(bh);
+ while (!IS_LAST_ENTRY(entry)) {
+ struct ext2_xattr_entry *next =
+ EXT2_XATTR_NEXT(entry);
+ if ((char *)next >= end)
+ goto bad_block;
+ if (name_index == entry->e_name_index &&
+ name_len == entry->e_name_len &&
+ memcmp(name, entry->e_name, name_len) == 0)
+ goto found;
+ entry = next;
+ }
+ /* Check the remaining name entries */
+ while (!IS_LAST_ENTRY(entry)) {
+ struct ext2_xattr_entry *next =
+ EXT2_XATTR_NEXT(entry);
+ if ((char *)next >= end)
+ goto bad_block;
+ entry = next;
+ }
+ if (ext2_xattr_cache_insert(bh))
+ ea_idebug(inode, "cache insert failed");
+ error = -ENOATTR;
+ goto cleanup;
+found:
+ /* check the buffer size */
+ if (entry->e_value_block != 0)
+ goto bad_block;
+ size = le32_to_cpu(entry->e_value_size);
+ if (size > inode->i_sb->s_blocksize ||
+ le16_to_cpu(entry->e_value_offs) + size > inode->i_sb->s_blocksize)
+ goto bad_block;
+
+ if (ext2_xattr_cache_insert(bh))
+ ea_idebug(inode, "cache insert failed");
+ if (buffer) {
+ error = -ERANGE;
+ if (size > buffer_size)
+ goto cleanup;
+ /* return value of attribute */
+ memcpy(buffer, bh->b_data + le16_to_cpu(entry->e_value_offs),
+ size);
+ }
+ error = size;
+
+cleanup:
+ brelse(bh);
+
+ return error;
+}
+
+/*
+ * ext2_xattr_list()
+ *
+ * Copy a list of attribute names into the buffer
+ * provided, or compute the buffer size required.
+ * Buffer is NULL to compute the size of the buffer required.
+ *
+ * Returns a negative error number on failure, or the number of bytes
+ * used / required on success.
+ */
+int
+ext2_xattr_list(struct inode *inode, char *buffer, size_t buffer_size)
+{
+ struct buffer_head *bh = NULL;
+ struct ext2_xattr_entry *entry;
+ unsigned int block, size = 0;
+ char *buf, *end;
+ int error;
+
+ ea_idebug(inode, "buffer=%p, buffer_size=%ld",
+ buffer, (long)buffer_size);
+
+ if (!IS_XATTR(inode))
+ return -ENOTSUP;
+ if (!EXT2_I(inode)->i_file_acl)
+ return 0;
+ block = EXT2_I(inode)->i_file_acl;
+ ea_idebug(inode, "reading block %d", block);
+ bh = sb_bread(inode->i_sb, block);
+ if (!bh)
+ return -EIO;
+ ea_bdebug(bh, "b_count=%d, refcount=%d",
+ atomic_read(&(bh->b_count)), le32_to_cpu(HDR(bh)->h_refcount));
+ end = bh->b_data + bh->b_size;
+ if (HDR(bh)->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
+ HDR(bh)->h_blocks != cpu_to_le32(1)) {
+bad_block: ext2_error(inode->i_sb, "ext2_xattr_list",
+ "inode %ld: bad block %d", inode->i_ino, block);
+ error = -EIO;
+ goto cleanup;
+ }
+ /* compute the size required for the list of attribute names */
+ for (entry = FIRST_ENTRY(bh); !IS_LAST_ENTRY(entry);
+ entry = EXT2_XATTR_NEXT(entry)) {
+ struct ext2_xattr_entry *next =
+ EXT2_XATTR_NEXT(entry);
+ if ((char *)next >= end)
+ goto bad_block;
+ switch(entry->e_name_index) {
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+ case EXT2_XATTR_INDEX_USER:
+ if (!IS_XATTR_USER(inode))
+ continue;
+ size += 5 + entry->e_name_len;
+ break;
+#endif
+ default:
+ /* skip unrecognized name index */
+ continue;
+ }
+ size++;
+ }
+
+ if (ext2_xattr_cache_insert(bh))
+ ea_idebug(inode, "cache insert failed");
+ if (!buffer) {
+ error = size;
+ goto cleanup;
+ } else {
+ error = -ERANGE;
+ if (size > buffer_size)
+ goto cleanup;
+ }
+
+ /* list the attribute names */
+ buf = buffer;
+ for (entry = FIRST_ENTRY(bh); !IS_LAST_ENTRY(entry);
+ entry = EXT2_XATTR_NEXT(entry)) {
+ char *nsp = "", *name;
+ int nsp_len, name_len;
+
+ switch(entry->e_name_index) {
+#ifdef CONFIG_EXT2_FS_XATTR_USER
+ case EXT2_XATTR_INDEX_USER:
+ if (!IS_XATTR_USER(inode))
+ continue;
+ nsp = EXT2_EA_USER;
+ nsp_len = sizeof(EXT2_EA_USER)-1;
+ name = entry->e_name;
+ name_len = entry->e_name_len;
+ break;
+#endif
+ default:
+ /* skip unrecognized name index */
+ continue;
+ }
+
+ memcpy(buf, nsp, nsp_len);
+ buf += nsp_len;
+ memcpy(buf, name, name_len);
+ buf += name_len;
+ memset(buf++, '\0', 1);
+ }
+ error = size;
+
+cleanup:
+ brelse(bh);
+
+ return error;
+}
+
+/*
+ * If the EXT2_FEATURE_COMPAT_EXT_ATTR feature of this file system is
+ * not set, set it.
+ */
+static void ext2_xattr_update_super_block(struct super_block *sb)
+{
+ if (EXT2_HAS_COMPAT_FEATURE(sb, EXT2_FEATURE_COMPAT_EXT_ATTR))
+ return;
+
+ lock_super(sb);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
+ EXT2_SB(sb)->s_feature_compat |= EXT2_FEATURE_COMPAT_EXT_ATTR;
+#endif
+ EXT2_SB(sb)->s_es->s_feature_compat |=
+ cpu_to_le32(EXT2_FEATURE_COMPAT_EXT_ATTR);
+ sb->s_dirt = 1;
+ mark_buffer_dirty(EXT2_SB(sb)->s_sbh);
+ unlock_super(sb);
+}
+
+/*
+ * ext2_xattr_set()
+ *
+ * Create, replace or remove an extended attribute for this inode. Buffer
+ * is NULL to remove an existing extended attribute, and non-NULL to
+ * either replace an existing extended attribute, or create a new extended
+ * attribute. The flags XATTR_REPLACE and XATTR_CREATE
+ * specify that an extended attribute must exist and must not exist
+ * previous to the call, respectively.
+ *
+ * Returns 0, or a negative error number on failure.
+ */
+int
+ext2_xattr_set(struct inode *inode, int name_index, const char *name,
+ void *value, size_t value_len, int flags)
+{
+ struct super_block *sb = inode->i_sb;
+ struct buffer_head *bh = NULL;
+ struct ext2_xattr_header *header = NULL;
+ struct ext2_xattr_entry *here, *last;
+ unsigned int name_len;
+ int min_offs = sb->s_blocksize, not_found = 1, free, error;
+ char *end;
+
+ /*
+ * header -- Points either into bh, or to a temporarily
+ * allocated buffer.
+ * here -- The named entry found, or the place for inserting, within
+ * the block pointed to by header.
+ * last -- Points right after the last named entry within the block
+ * pointed to by header.
+ * min_offs -- The offset of the first value (values are aligned
+ * towards the end of the block).
+ * end -- Points right after the block pointed to by header.
+ */
+
+ ea_idebug(inode, "name=%d.%s, value=%p, value_len=%ld",
+ name_index, name, value, (long)value_len);
+
+ if (!IS_XATTR(inode))
+ return -ENOTSUP;
+ if (IS_RDONLY(inode))
+ return -EROFS;
+ if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
+ return -EPERM;
+ if (value == NULL)
+ value_len = 0;
+ if (name == NULL)
+ return -EINVAL;
+ name_len = strlen(name);
+ if (name_len > 255 || value_len > sb->s_blocksize)
+ return -ERANGE;
+ ext2_xattr_lock();
+
+ if (EXT2_I(inode)->i_file_acl) {
+ /* The inode already has an extended attribute block. */
+ int block = EXT2_I(inode)->i_file_acl;
+
+ bh = sb_bread(sb, block);
+ error = -EIO;
+ if (!bh)
+ goto cleanup;
+ ea_bdebug(bh, "b_count=%d, refcount=%d",
+ atomic_read(&(bh->b_count)),
+ le32_to_cpu(HDR(bh)->h_refcount));
+ header = HDR(bh);
+ end = bh->b_data + bh->b_size;
+ if (header->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
+ header->h_blocks != cpu_to_le32(1)) {
+bad_block: ext2_error(sb, "ext2_xattr_set",
+ "inode %ld: bad block %d", inode->i_ino, block);
+ error = -EIO;
+ goto cleanup;
+ }
+ /* Find the named attribute. */
+ here = FIRST_ENTRY(bh);
+ while (!IS_LAST_ENTRY(here)) {
+ struct ext2_xattr_entry *next = EXT2_XATTR_NEXT(here);
+ if ((char *)next >= end)
+ goto bad_block;
+ if (!here->e_value_block && here->e_value_size) {
+ int offs = le16_to_cpu(here->e_value_offs);
+ if (offs < min_offs)
+ min_offs = offs;
+ }
+ not_found = name_index - here->e_name_index;
+ if (!not_found)
+ not_found = name_len - here->e_name_len;
+ if (!not_found)
+ not_found = memcmp(name, here->e_name,name_len);
+ if (not_found <= 0)
+ break;
+ here = next;
+ }
+ last = here;
+ /* We still need to compute min_offs and last. */
+ while (!IS_LAST_ENTRY(last)) {
+ struct ext2_xattr_entry *next = EXT2_XATTR_NEXT(last);
+ if ((char *)next >= end)
+ goto bad_block;
+ if (!last->e_value_block && last->e_value_size) {
+ int offs = le16_to_cpu(last->e_value_offs);
+ if (offs < min_offs)
+ min_offs = offs;
+ }
+ last = next;
+ }
+
+ /* Check whether we have enough space left. */
+ free = min_offs - ((char*)last - (char*)header) - sizeof(__u32);
+ } else {
+ /* We will use a new extended attribute block. */
+ free = sb->s_blocksize -
+ sizeof(struct ext2_xattr_header) - sizeof(__u32);
+ here = last = NULL; /* avoid gcc uninitialized warning. */
+ }
+
+ if (not_found) {
+ /* Request to remove a nonexistent attribute? */
+ error = -ENOATTR;
+ if (flags & XATTR_REPLACE)
+ goto cleanup;
+ error = 0;
+ if (value == NULL)
+ goto cleanup;
+ else
+ free -= EXT2_XATTR_LEN(name_len);
+ } else {
+ /* Request to create an existing attribute? */
+ error = -EEXIST;
+ if (flags & XATTR_CREATE)
+ goto cleanup;
+ if (!here->e_value_block && here->e_value_size) {
+ unsigned int size = le32_to_cpu(here->e_value_size);
+
+ if (le16_to_cpu(here->e_value_offs) + size >
+ sb->s_blocksize || size > sb->s_blocksize)
+ goto bad_block;
+ free += EXT2_XATTR_SIZE(size);
+ }
+ }
+ free -= EXT2_XATTR_SIZE(value_len);
+ error = -ENOSPC;
+ if (free < 0)
+ goto cleanup;
+
+ /* Here we know that we can set the new attribute. */
+
+ if (header) {
+ if (header->h_refcount == cpu_to_le32(1)) {
+ ea_bdebug(bh, "modifying in-place");
+ ext2_xattr_cache_remove(bh);
+ } else {
+ int offset;
+
+ ea_bdebug(bh, "cloning");
+ header = kmalloc(bh->b_size, GFP_KERNEL);
+ error = -ENOMEM;
+ if (header == NULL)
+ goto cleanup;
+ memcpy(header, HDR(bh), bh->b_size);
+ header->h_refcount = cpu_to_le32(1);
+ offset = (char *)header - bh->b_data;
+ here = ENTRY((char *)here + offset);
+ last = ENTRY((char *)last + offset);
+ }
+ } else {
+ /* Allocate a buffer where we construct the new block. */
+ header = kmalloc(sb->s_blocksize, GFP_KERNEL);
+ error = -ENOMEM;
+ if (header == NULL)
+ goto cleanup;
+ memset(header, '\0', sb->s_blocksize);
+ end = (char *)header + sb->s_blocksize;
+ header->h_magic = cpu_to_le32(EXT2_XATTR_MAGIC);
+ header->h_blocks = header->h_refcount = cpu_to_le32(1);
+ last = here = ENTRY(header+1);
+ }
+
+ if (not_found) {
+ /* Insert the new name. */
+ int size = EXT2_XATTR_LEN(name_len);
+ int rest = (char *)last - (char *)here;
+ memmove((char *)here + size, here, rest);
+ memset(here, '\0', size);
+ here->e_name_index = name_index;
+ here->e_name_len = name_len;
+ memcpy(here->e_name, name, name_len);
+ } else {
+ /* Remove the old value. */
+ if (!here->e_value_block && here->e_value_size) {
+ char *first_val = (char *)header + min_offs;
+ int offs = le16_to_cpu(here->e_value_offs);
+ char *val = (char *)header + offs;
+ size_t size = EXT2_XATTR_SIZE(
+ le32_to_cpu(here->e_value_size));
+ memmove(first_val + size, first_val, val - first_val);
+ memset(first_val, '\0', size);
+ here->e_value_offs = 0;
+ min_offs += size;
+
+ /* Adjust all value offsets. */
+ last = ENTRY(header+1);
+ while (!IS_LAST_ENTRY(last)) {
+ int o = le16_to_cpu(last->e_value_offs);
+ if (!last->e_value_block && o < offs)
+ last->e_value_offs =
+ cpu_to_le16(o + size);
+ last = EXT2_XATTR_NEXT(last);
+ }
+ }
+ if (value == NULL) {
+ /* Remove this attribute. */
+ if (EXT2_XATTR_NEXT(ENTRY(header+1)) == last) {
+ /* This block is now empty. */
+ error = ext2_xattr_set2(inode, bh, NULL);
+ goto cleanup;
+ } else {
+ /* Remove the old name. */
+ int size = EXT2_XATTR_LEN(name_len);
+ last = ENTRY((char *)last - size);
+ memmove(here, (char*)here + size,
+ (char*)last - (char*)here);
+ memset(last, '\0', size);
+ }
+ }
+ }
+
+ if (value != NULL) {
+ /* Insert the new value. */
+ here->e_value_size = cpu_to_le32(value_len);
+ if (value_len) {
+ size_t size = EXT2_XATTR_SIZE(value_len);
+ char *val = (char *)header + min_offs - size;
+ here->e_value_offs =
+ cpu_to_le16((char *)val - (char *)header);
+ memset(val + size - EXT2_XATTR_PAD, '\0',
+ EXT2_XATTR_PAD); /* Clear the pad bytes. */
+ memcpy(val, value, value_len);
+ }
+ }
+ ext2_xattr_rehash(header, here);
+
+ error = ext2_xattr_set2(inode, bh, header);
+
+cleanup:
+ brelse(bh);
+ if (!(bh && header == HDR(bh)))
+ kfree(header);
+ ext2_xattr_unlock();
+
+ return error;
+}
+
+/*
+ * Second half of ext2_xattr_set(): Update the file system.
+ */
+static int
+ext2_xattr_set2(struct inode *inode, struct buffer_head *old_bh,
+ struct ext2_xattr_header *header)
+{
+ struct super_block *sb = inode->i_sb;
+ struct buffer_head *new_bh = NULL;
+ int error;
+
+ if (header) {
+ new_bh = ext2_xattr_cache_find(inode, header);
+ if (new_bh) {
+ /*
+ * We found an identical block in the cache.
+ * The old block will be released after updating
+ * the inode.
+ */
+ ea_bdebug(old_bh, "reusing block %ld",
+ new_bh->b_blocknr);
+
+ error = -EDQUOT;
+ if (ext2_xattr_quota_alloc(inode, 1))
+ goto cleanup;
+
+ HDR(new_bh)->h_refcount = cpu_to_le32(
+ le32_to_cpu(HDR(new_bh)->h_refcount) + 1);
+ ea_bdebug(new_bh, "refcount now=%d",
+ le32_to_cpu(HDR(new_bh)->h_refcount));
+ } else if (old_bh && header == HDR(old_bh)) {
+ /* Keep this block. */
+ new_bh = old_bh;
+ ext2_xattr_cache_insert(new_bh);
+ } else {
+ /* We need to allocate a new block */
+ int force = EXT2_I(inode)->i_file_acl != 0;
+ int block = ext2_xattr_new_block(inode, &error, force);
+ if (error)
+ goto cleanup;
+ ea_idebug(inode, "creating block %d", block);
+
+ new_bh = sb_getblk(sb, block);
+ if (!new_bh) {
+ ext2_xattr_free_block(inode, block);
+ error = -EIO;
+ goto cleanup;
+ }
+ lock_buffer(new_bh);
+ memcpy(new_bh->b_data, header, new_bh->b_size);
+ mark_buffer_uptodate(new_bh, 1);
+ unlock_buffer(new_bh);
+ ext2_xattr_cache_insert(new_bh);
+
+ ext2_xattr_update_super_block(sb);
+ }
+ mark_buffer_dirty(new_bh);
+ if (IS_SYNC(inode)) {
+ ll_rw_block(WRITE, 1, &new_bh);
+ wait_on_buffer(new_bh);
+ error = -EIO;
+ if (buffer_req(new_bh) && !buffer_uptodate(new_bh))
+ goto cleanup;
+ }
+ }
+
+ /* Update the inode. */
+ EXT2_I(inode)->i_file_acl = new_bh ? new_bh->b_blocknr : 0;
+ inode->i_ctime = CURRENT_TIME;
+ if (IS_SYNC(inode)) {
+ error = ext2_sync_inode (inode);
+ if (error)
+ goto cleanup;
+ } else
+ mark_inode_dirty(inode);
+
+ error = 0;
+ if (old_bh && old_bh != new_bh) {
+ /*
+ * If there was an old block, and we are not still using it,
+ * we now release the old block.
+ */
+ unsigned int refcount = le32_to_cpu(HDR(old_bh)->h_refcount);
+
+ if (refcount == 1) {
+ /* Free the old block. */
+ ea_bdebug(old_bh, "freeing");
+ ext2_xattr_free_block(inode, old_bh->b_blocknr);
+ mark_buffer_clean(old_bh);
+ } else {
+ /* Decrement the refcount only. */
+ refcount--;
+ HDR(old_bh)->h_refcount = cpu_to_le32(refcount);
+ ext2_xattr_quota_free(inode);
+ mark_buffer_dirty(old_bh);
+ ea_bdebug(old_bh, "refcount now=%d", refcount);
+ }
+ }
+
+cleanup:
+ if (old_bh != new_bh)
+ brelse(new_bh);
+
+ return error;
+}
+
+/*
+ * ext2_xattr_drop_inode()
+ *
+ * Free extended attribute resources associated with this inode. This
+ * is called immediately before an inode is freed.
+ */
+void
+ext2_xattr_drop_inode(struct inode *inode)
+{
+ struct buffer_head *bh;
+ unsigned int block = EXT2_I(inode)->i_file_acl;
+
+ if (!block)
+ return;
+ ext2_xattr_lock();
+
+ bh = sb_bread(inode->i_sb, block);
+ if (!bh) {
+ ext2_error(inode->i_sb, "ext2_xattr_drop_inode",
+ "inode %ld: block %d read error", inode->i_ino, block);
+ goto cleanup;
+ }
+ ea_bdebug(bh, "b_count=%d", atomic_read(&(bh->b_count)));
+ if (HDR(bh)->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
+ HDR(bh)->h_blocks != cpu_to_le32(1)) {
+ ext2_error(inode->i_sb, "ext2_xattr_drop_inode",
+ "inode %ld: bad block %d", inode->i_ino, block);
+ goto cleanup;
+ }
+ ea_bdebug(bh, "refcount now=%d", le32_to_cpu(HDR(bh)->h_refcount) - 1);
+ if (HDR(bh)->h_refcount == cpu_to_le32(1)) {
+ ext2_xattr_cache_remove(bh);
+ ext2_xattr_free_block(inode, block);
+ bforget(bh);
+ bh = NULL;
+ } else {
+ HDR(bh)->h_refcount = cpu_to_le32(
+ le32_to_cpu(HDR(bh)->h_refcount) - 1);
+ mark_buffer_dirty(bh);
+ if (IS_SYNC(inode)) {
+ ll_rw_block(WRITE, 1, &bh);
+ wait_on_buffer(bh);
+ }
+ ext2_xattr_quota_free(inode);
+ }
+ EXT2_I(inode)->i_file_acl = 0;
+
+cleanup:
+ brelse(bh);
+ ext2_xattr_unlock();
+}
+
+/*
+ * ext2_xattr_put_super()
+ *
+ * This is called when a file system is unmounted.
+ */
+void
+ext2_xattr_put_super(struct super_block *sb)
+{
+#ifdef CONFIG_EXT2_FS_XATTR_SHARING
+ mb_cache_shrink(ext2_xattr_cache, sb->s_dev);
+#endif
+}
+
+#ifdef CONFIG_EXT2_FS_XATTR_SHARING
+
+/*
+ * ext2_xattr_cache_insert()
+ *
+ * Create a new entry in the extended attribute cache, and insert
+ * it unless such an entry is already in the cache.
+ *
+ * Returns 0, or a negative error number on failure.
+ */
+static int
+ext2_xattr_cache_insert(struct buffer_head *bh)
+{
+ __u32 hash = le32_to_cpu(HDR(bh)->h_hash);
+ struct mb_cache_entry *ce;
+ int error;
+
+ ce = mb_cache_entry_alloc(ext2_xattr_cache);
+ if (!ce)
+ return -ENOMEM;
+ error = mb_cache_entry_insert(ce, bh->b_dev, bh->b_blocknr, &hash);
+ if (error) {
+ mb_cache_entry_free(ce);
+ if (error == -EBUSY) {
+ ea_bdebug(bh, "already in cache (%d cache entries)",
+ atomic_read(&ext2_xattr_cache->c_entry_count));
+ error = 0;
+ }
+ } else {
+ ea_bdebug(bh, "inserting [%x] (%d cache entries)", (int)hash,
+ atomic_read(&ext2_xattr_cache->c_entry_count));
+ mb_cache_entry_release(ce);
+ }
+ return error;
+}
+
+/*
+ * ext2_xattr_cmp()
+ *
+ * Compare two extended attribute blocks for equality.
+ *
+ * Returns 0 if the blocks are equal, 1 if they differ, and
+ * a negative error number on errors.
+ */
+static int
+ext2_xattr_cmp(struct ext2_xattr_header *header1,
+ struct ext2_xattr_header *header2)
+{
+ struct ext2_xattr_entry *entry1, *entry2;
+
+ entry1 = ENTRY(header1+1);
+ entry2 = ENTRY(header2+1);
+ while (!IS_LAST_ENTRY(entry1)) {
+ if (IS_LAST_ENTRY(entry2))
+ return 1;
+ if (entry1->e_hash != entry2->e_hash ||
+ entry1->e_name_len != entry2->e_name_len ||
+ entry1->e_value_size != entry2->e_value_size ||
+ memcmp(entry1->e_name, entry2->e_name, entry1->e_name_len))
+ return 1;
+ if (entry1->e_value_block != 0 || entry2->e_value_block != 0)
+ return -EIO;
+ if (memcmp((char *)header1 + le16_to_cpu(entry1->e_value_offs),
+ (char *)header2 + le16_to_cpu(entry2->e_value_offs),
+ le32_to_cpu(entry1->e_value_size)))
+ return 1;
+
+ entry1 = EXT2_XATTR_NEXT(entry1);
+ entry2 = EXT2_XATTR_NEXT(entry2);
+ }
+ if (!IS_LAST_ENTRY(entry2))
+ return 1;
+ return 0;
+}
+
+/*
+ * ext2_xattr_cache_find()
+ *
+ * Find an identical extended attribute block.
+ *
+ * Returns a pointer to the block found, or NULL if such a block was
+ * not found or an error occurred.
+ */
+static struct buffer_head *
+ext2_xattr_cache_find(struct inode *inode, struct ext2_xattr_header *header)
+{
+ __u32 hash = le32_to_cpu(header->h_hash);
+ struct mb_cache_entry *ce;
+
+ if (!header->h_hash)
+ return NULL; /* never share */
+ ea_idebug(inode, "looking for cached blocks [%x]", (int)hash);
+ ce = mb_cache_entry_find_first(ext2_xattr_cache, 0, inode->i_dev, hash);
+ while (ce) {
+ struct buffer_head *bh = sb_bread(inode->i_sb, ce->e_block);
+
+ if (!bh) {
+ ext2_error(inode->i_sb, "ext2_xattr_cache_find",
+ "inode %ld: block %ld read error",
+ inode->i_ino, ce->e_block);
+ } else if (le32_to_cpu(HDR(bh)->h_refcount) >
+ EXT2_XATTR_REFCOUNT_MAX) {
+ ea_idebug(inode, "block %ld refcount %d>%d",ce->e_block,
+ le32_to_cpu(HDR(bh)->h_refcount),
+ EXT2_XATTR_REFCOUNT_MAX);
+ } else if (!ext2_xattr_cmp(header, HDR(bh))) {
+ ea_bdebug(bh, "b_count=%d",atomic_read(&(bh->b_count)));
+ mb_cache_entry_release(ce);
+ return bh;
+ }
+ brelse(bh);
+ ce = mb_cache_entry_find_next(ce, 0, inode->i_dev, hash);
+ }
+ return NULL;
+}
+
+/*
+ * ext2_xattr_cache_remove()
+ *
+ * Remove the cache entry of a block from the cache. Called when a
+ * block becomes invalid.
+ */
+static void
+ext2_xattr_cache_remove(struct buffer_head *bh)
+{
+ struct mb_cache_entry *ce;
+
+ ce = mb_cache_entry_get(ext2_xattr_cache, bh->b_dev, bh->b_blocknr);
+ if (ce) {
+ ea_bdebug(bh, "removing (%d cache entries remaining)",
+ atomic_read(&ext2_xattr_cache->c_entry_count)-1);
+ mb_cache_entry_free(ce);
+ } else
+ ea_bdebug(bh, "no cache entry");
+}
+
+#define NAME_HASH_SHIFT 5
+#define VALUE_HASH_SHIFT 16
+
+/*
+ * ext2_xattr_hash_entry()
+ *
+ * Compute the hash of an extended attribute.
+ */
+static inline void ext2_xattr_hash_entry(struct ext2_xattr_header *header,
+ struct ext2_xattr_entry *entry)
+{
+ __u32 hash = 0;
+ char *name = entry->e_name;
+ int n;
+
+ for (n=0; n < entry->e_name_len; n++) {
+ hash = (hash << NAME_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - NAME_HASH_SHIFT)) ^
+ *name++;
+ }
+
+ if (entry->e_value_block == 0 && entry->e_value_size != 0) {
+ __u32 *value = (__u32 *)((char *)header +
+ le16_to_cpu(entry->e_value_offs));
+ for (n = (le32_to_cpu(entry->e_value_size) +
+ EXT2_XATTR_ROUND) >> EXT2_XATTR_PAD_BITS; n; n--) {
+ hash = (hash << VALUE_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - VALUE_HASH_SHIFT)) ^
+ le32_to_cpu(*value++);
+ }
+ }
+ entry->e_hash = cpu_to_le32(hash);
+}
+
+#undef NAME_HASH_SHIFT
+#undef VALUE_HASH_SHIFT
+
+#define BLOCK_HASH_SHIFT 16
+
+/*
+ * ext2_xattr_rehash()
+ *
+ * Re-compute the extended attribute hash value after an entry has changed.
+ */
+static void ext2_xattr_rehash(struct ext2_xattr_header *header,
+ struct ext2_xattr_entry *entry)
+{
+ struct ext2_xattr_entry *here;
+ __u32 hash = 0;
+
+ ext2_xattr_hash_entry(header, entry);
+ here = ENTRY(header+1);
+ while (!IS_LAST_ENTRY(here)) {
+ if (!here->e_hash) {
+ /* Block is not shared if an entry's hash value == 0 */
+ hash = 0;
+ break;
+ }
+ hash = (hash << BLOCK_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - BLOCK_HASH_SHIFT)) ^
+ le32_to_cpu(here->e_hash);
+ here = EXT2_XATTR_NEXT(here);
+ }
+ header->h_hash = cpu_to_le32(hash);
+}
+
+#undef BLOCK_HASH_SHIFT
+
+int ext2_xattr_init(void)
+{
+ ext2_xattr_cache = mb_cache_create("ext2_xattr", NULL,
+ sizeof(struct mb_cache_entry) +
+ sizeof(struct mb_cache_entry_index), 1, 61);
+ if (!ext2_xattr_cache)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void ext2_xattr_done(void)
+{
+ mb_cache_destroy(ext2_xattr_cache);
+}
+
+#else /* CONFIG_EXT2_FS_XATTR_SHARING */
+
+int ext2_xattr_init(void)
+{
+ return 0;
+}
+
+void ext2_xattr_done(void)
+{
+}
+
+#endif /* CONFIG_EXT2_FS_XATTR_SHARING */