aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext2fs/Makefile3
-rw-r--r--ext2fs/ext2_fs.h3
-rw-r--r--ext2fs/ext2fs.c14
-rw-r--r--ext2fs/ext2fs.h23
-rw-r--r--ext2fs/ialloc.c2
-rw-r--r--ext2fs/inode.c222
-rw-r--r--ext2fs/xattr.c876
-rw-r--r--ext2fs/xattr.h85
8 files changed, 1163 insertions, 65 deletions
diff --git a/ext2fs/Makefile b/ext2fs/Makefile
index 88f8f465..0c2f4a24 100644
--- a/ext2fs/Makefile
+++ b/ext2fs/Makefile
@@ -21,7 +21,8 @@ makemode := server
target = ext2fs
SRCS = balloc.c dir.c ext2fs.c getblk.c hyper.c ialloc.c \
- inode.c pager.c pokel.c truncate.c storeinfo.c msg.c xinl.c
+ inode.c pager.c pokel.c truncate.c storeinfo.c msg.c xinl.c \
+ xattr.c
OBJS = $(SRCS:.c=.o)
HURDLIBS = diskfs pager iohelp fshelp store ports ihash shouldbeinlibc
LDLIBS = -lpthread $(and $(HAVE_LIBBZ2),-lbz2) $(and $(HAVE_LIBZ),-lz)
diff --git a/ext2fs/ext2_fs.h b/ext2fs/ext2_fs.h
index b1caeefa..019ba154 100644
--- a/ext2fs/ext2_fs.h
+++ b/ext2fs/ext2_fs.h
@@ -462,6 +462,7 @@ struct ext2_super_block {
( 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
@@ -470,7 +471,7 @@ struct ext2_super_block {
#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| \
diff --git a/ext2fs/ext2fs.c b/ext2fs/ext2fs.c
index 4f38c927..b4c865c8 100644
--- a/ext2fs/ext2fs.c
+++ b/ext2fs/ext2fs.c
@@ -63,6 +63,10 @@ pthread_spinlock_t modified_global_blocks_lock = PTHREAD_SPINLOCK_INITIALIZER;
int ext2_debug_flag;
#endif
+/* Use extended attribute-based translator records. */
+int use_xattr_translator_records;
+#define X_XATTR_TRANSLATOR_RECORDS -1
+
/* Ext2fs-specific options. */
static const struct argp_option
options[] =
@@ -72,6 +76,8 @@ options[] =
" (not compiled in)"
#endif
},
+ {"x-xattr-translator-records", X_XATTR_TRANSLATOR_RECORDS, 0, 0,
+ "Store translator records in extended attributes (experimental)"},
#ifdef ALTERNATE_SBLOCK
/* XXX This is not implemented. */
{"sblock", 'S', "BLOCKNO", 0,
@@ -89,6 +95,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
struct
{
int debug_flag;
+ int use_xattr_translator_records;
#ifdef ALTERNATE_SBLOCK
unsigned int sb_block;
#endif
@@ -99,6 +106,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
case 'D':
values->debug_flag = 1;
break;
+ case X_XATTR_TRANSLATOR_RECORDS:
+ values->use_xattr_translator_records = 1;
+ break;
#ifdef ALTERNATE_SBLOCK
case 'S':
values->sb_block = strtoul (arg, &arg, 0);
@@ -134,6 +144,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
#endif
}
+ use_xattr_translator_records = values->use_xattr_translator_records;
break;
default:
@@ -151,6 +162,9 @@ diskfs_append_args (char **argz, size_t *argz_len)
/* Get the standard things. */
err = diskfs_append_std_options (argz, argz_len);
+ if (!err && use_xattr_translator_records)
+ err = argz_add (argz, argz_len, "--x-xattr-translator-records");
+
#ifdef EXT2FS_DEBUG
if (!err && ext2_debug_flag)
err = argz_add (argz, argz_len, "--debug");
diff --git a/ext2fs/ext2fs.h b/ext2fs/ext2fs.h
index a3d22b28..2104dba3 100644
--- a/ext2fs/ext2fs.h
+++ b/ext2fs/ext2fs.h
@@ -17,6 +17,9 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+#ifndef _EXT2FS_H
+#define _EXT2FS_H
+
#include <mach.h>
#include <hurd.h>
#include <hurd/ports.h>
@@ -575,3 +578,23 @@ extern void _ext2_panic (const char *, const char *, ...)
extern void ext2_warning (const char *, ...)
__attribute__ ((format (printf, 1, 2)));
+
+/* ---------------------------------------------------------------- */
+/* xattr.c */
+
+error_t ext2_list_xattr (struct node *np, char *buffer, size_t *len);
+error_t ext2_get_xattr (struct node *np, const char *name, char *value, size_t *len);
+error_t ext2_set_xattr (struct node *np, const char *name, const char *value, size_t len, int flags);
+error_t ext2_free_xattr_block (struct node *np);
+
+/* Use extended attribute-based translator records.
+ *
+ * This flag allows users to opt-in to the use of extended attributes
+ * for storing translator records. We will make this the default once
+ * we feel confident that the implementation is fine.
+ *
+ * XXX: Remove this in Hurd 1.0 (or 0.10, or whatever follows 0.9).
+ */
+int use_xattr_translator_records;
+
+#endif
diff --git a/ext2fs/ialloc.c b/ext2fs/ialloc.c
index 2809371a..71bfb8cb 100644
--- a/ext2fs/ialloc.c
+++ b/ext2fs/ialloc.c
@@ -62,6 +62,8 @@ diskfs_free_node (struct node *np, mode_t old_mode)
ext2_debug ("freeing inode %u", inum);
+ ext2_free_xattr_block (np);
+
pthread_spin_lock (&global_lock);
if (inum < EXT2_FIRST_INO (sblock) || inum > sblock->s_inodes_count)
diff --git a/ext2fs/inode.c b/ext2fs/inode.c
index ccc8d699..17dded6a 100644
--- a/ext2fs/inode.c
+++ b/ext2fs/inode.c
@@ -26,6 +26,7 @@
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/statvfs.h>
+#include <sys/xattr.h>
/* these flags aren't actually defined by a header file yet, so temporarily
disable them if necessary. */
@@ -166,8 +167,16 @@ diskfs_user_read_node (struct node *np, struct lookup_context *ctx)
{
st->st_mode = di->i_mode | (di->i_mode_high << 16);
st->st_mode &= ~S_ITRANS;
+
if (di->i_translator)
st->st_mode |= S_IPTRANS;
+ else
+ {
+ size_t datalen = 0;
+ err = ext2_get_xattr (np, "gnu.translator", NULL, &datalen);
+ if (! err && datalen > 0)
+ st->st_mode |= S_IPTRANS;
+ }
st->st_uid = di->i_uid | (di->i_uid_high << 16);
st->st_gid = di->i_gid | (di->i_gid_high << 16);
@@ -540,81 +549,144 @@ error_t
diskfs_set_translator (struct node *np, const char *name, unsigned namelen,
struct protid *cred)
{
- daddr_t blkno;
error_t err;
- char buf[block_size];
- struct ext2_inode *di;
assert (!diskfs_readonly);
if (sblock->s_creator_os != EXT2_OS_HURD)
return EOPNOTSUPP;
- if (namelen + 2 > block_size)
- return ENAMETOOLONG;
-
err = diskfs_catch_exception ();
if (err)
return err;
- di = dino_ref (np->cache_id);
- blkno = di->i_translator;
-
- if (namelen && !blkno)
+ /* If xattr is supported for this filesystem, use xattr to store translator
+ record, otherwise, use legacy translator record */
+ if (EXT2_HAS_COMPAT_FEATURE (sblock, EXT2_FEATURE_COMPAT_EXT_ATTR)
+ && use_xattr_translator_records)
{
- /* Allocate block for translator */
- blkno =
- ext2_new_block ((diskfs_node_disknode (np)->info.i_block_group
- * EXT2_BLOCKS_PER_GROUP (sblock))
- + sblock->s_first_data_block,
- 0, 0, 0);
- if (blkno == 0)
+ daddr_t blkno;
+ struct ext2_inode *di;
+
+ di = dino_ref (np->cache_id);
+ blkno = di->i_translator;
+
+ /* If a legacy translator record found, clear it */
+ if (blkno)
+ {
+ ext2_debug ("Old translator record found, clear it");
+
+ /* Clear block for translator going away. */
+ di->i_translator = 0;
+ diskfs_node_disknode (np)->info_i_translator = 0;
+ record_global_poke (di);
+ ext2_free_blocks (blkno, 1);
+
+ np->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block;
+ np->dn_stat.st_mode &= ~S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
+ else
+ dino_deref (di);
+
+ /* Use xattr to store translator record, with key "gnu.translator" */
+ if (namelen)
{
- dino_deref (di);
- diskfs_end_catch_exception ();
- return ENOSPC;
+ err = ext2_set_xattr (np, "gnu.translator", name, namelen, 0);
+
+ if (!err)
+ {
+ np->dn_stat.st_mode |= S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
}
+ else
+ {
+ /* Removing the translator. */
+ err = ext2_set_xattr (np, "gnu.translator", NULL, 0, 0);
- di->i_translator = blkno;
- diskfs_node_disknode (np)->info_i_translator = blkno;
- record_global_poke (di);
+ if (err == ENODATA)
+ /* Happens if the key did not exist in the first place. */
+ err = 0;
- np->dn_stat.st_blocks += 1 << log2_stat_blocks_per_fs_block;
- np->dn_set_ctime = 1;
- }
- else if (!namelen && blkno)
- {
- /* Clear block for translator going away. */
- di->i_translator = 0;
- diskfs_node_disknode (np)->info_i_translator = 0;
- record_global_poke (di);
- ext2_free_blocks (blkno, 1);
-
- np->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block;
- np->dn_stat.st_mode &= ~S_IPTRANS;
- np->dn_set_ctime = 1;
+ if (!err)
+ {
+ /* Do not use hurd extensions on non-hurd created filesystem */
+ np->dn_stat.st_mode &= ~S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
+ }
}
else
- dino_deref (di);
-
- if (namelen)
{
- void *blkptr;
+ /* Use legacy translator record when xattr is not supported */
+ daddr_t blkno;
+ struct ext2_inode *di;
+ char buf[block_size];
- buf[0] = namelen & 0xFF;
- buf[1] = (namelen >> 8) & 0xFF;
- memcpy (buf + 2, name, namelen);
+ if (namelen + 2 > block_size)
+ return ENAMETOOLONG;
- blkptr = disk_cache_block_ref (blkno);
- memcpy (blkptr, buf, block_size);
- record_global_poke (blkptr);
+ di = dino_ref (np->cache_id);
+ blkno = di->i_translator;
- np->dn_stat.st_mode |= S_IPTRANS;
- np->dn_set_ctime = 1;
+ if (namelen && !blkno)
+ {
+ /* Allocate block for translator */
+ blkno =
+ ext2_new_block ((diskfs_node_disknode (np)->info.i_block_group
+ * EXT2_BLOCKS_PER_GROUP (sblock))
+ + sblock->s_first_data_block,
+ 0, 0, 0);
+ if (blkno == 0)
+ {
+ dino_deref (di);
+ diskfs_end_catch_exception ();
+ return ENOSPC;
+ }
+
+ di->i_translator = blkno;
+ diskfs_node_disknode (np)->info_i_translator = blkno;
+ record_global_poke (di);
+
+ np->dn_stat.st_blocks += 1 << log2_stat_blocks_per_fs_block;
+ np->dn_set_ctime = 1;
+ }
+ else if (!namelen && blkno)
+ {
+ /* Clear block for translator going away. */
+ di->i_translator = 0;
+ diskfs_node_disknode (np)->info_i_translator = 0;
+ record_global_poke (di);
+ ext2_free_blocks (blkno, 1);
+
+ np->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block;
+ np->dn_stat.st_mode &= ~S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
+ else
+ dino_deref (di);
+
+ if (namelen)
+ {
+ void *blkptr;
+
+ buf[0] = namelen & 0xFF;
+ buf[1] = (namelen >> 8) & 0xFF;
+ memcpy (buf + 2, name, namelen);
+
+ blkptr = disk_cache_block_ref (blkno);
+ memcpy (blkptr, buf, block_size);
+ record_global_poke (blkptr);
+
+ np->dn_stat.st_mode |= S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
}
diskfs_end_catch_exception ();
return err;
+
}
/* Implement the diskfs_get_translator callback from the diskfs library.
@@ -624,11 +696,12 @@ diskfs_get_translator (struct node *np, char **namep, unsigned *namelen)
{
error_t err = 0;
daddr_t blkno;
- unsigned datalen;
+ size_t datalen;
void *transloc;
struct ext2_inode *di;
- assert (sblock->s_creator_os == EXT2_OS_HURD);
+ if (sblock->s_creator_os != EXT2_OS_HURD)
+ return EOPNOTSUPP;
err = diskfs_catch_exception ();
if (err)
@@ -637,23 +710,46 @@ diskfs_get_translator (struct node *np, char **namep, unsigned *namelen)
di = dino_ref (np->cache_id);
blkno = di->i_translator;
dino_deref (di);
- assert (blkno);
- transloc = disk_cache_block_ref (blkno);
- datalen =
- ((unsigned char *)transloc)[0] + (((unsigned char *)transloc)[1] << 8);
- if (datalen > block_size - 2)
- err = EFTYPE; /* ? */
- else
+ /* If an old translator record found, read it firstly */
+ if (blkno)
{
- *namep = malloc (datalen);
- if (!*namep)
- err = ENOMEM;
+ /* If xattr is no supported by this filesystem, don't report a warning */
+ if (EXT2_HAS_COMPAT_FEATURE (sblock, EXT2_FEATURE_COMPAT_EXT_ATTR)
+ && use_xattr_translator_records)
+ ext2_debug ("This is an old translator record, please update it");
+
+ transloc = disk_cache_block_ref (blkno);
+ datalen =
+ ((unsigned char *)transloc)[0] + (((unsigned char *)transloc)[1] << 8);
+ if (datalen > block_size - 2)
+ err = EFTYPE; /* ? */
else
- memcpy (*namep, transloc + 2, datalen);
+ {
+ *namep = malloc (datalen);
+ if (!*namep)
+ err = ENOMEM;
+ else
+ memcpy (*namep, transloc + 2, datalen);
+ }
+
+ disk_cache_block_deref (transloc);
+ diskfs_end_catch_exception ();
+
+ *namelen = datalen;
+ return err;
}
- disk_cache_block_deref (transloc);
+ err = ext2_get_xattr (np, "gnu.translator", NULL, &datalen);
+ if (err)
+ return err;
+
+ *namep = malloc (datalen);
+ if (!*namep)
+ err = ENOMEM;
+ else
+ err = ext2_get_xattr (np, "gnu.translator", *namep, &datalen);
+
diskfs_end_catch_exception ();
*namelen = datalen;
diff --git a/ext2fs/xattr.c b/ext2fs/xattr.c
new file mode 100644
index 00000000..5ce84ce5
--- /dev/null
+++ b/ext2fs/xattr.c
@@ -0,0 +1,876 @@
+ /* Ext2 support for extended attributes
+
+ Copyright (C) 2006, 2016 Free Software Foundation, Inc.
+
+ Written by Thadeu Lima de Souza Cascardo <cascardo@dcc.ufmg.br>
+ and Shengyu Zhang <lastavengers@outlook.com>
+
+ This program 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, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "ext2fs.h"
+#include "xattr.h"
+#include <stdlib.h>
+#include <string.h>
+#include <sys/xattr.h>
+
+struct _xattr_prefix
+{
+ int index;
+ char *prefix;
+ ssize_t size;
+};
+
+/* Prefixes are represented as numbers when stored in ext2 filesystems. */
+struct _xattr_prefix
+xattr_prefixes[] =
+{
+ {
+ 1, "user.", sizeof "user." - 1},
+ {
+ 7, "gnu.", sizeof "gnu." - 1},
+ {
+ 0, NULL, 0}
+};
+
+/*
+ * Given an attribute name in full_name, the ext2 number (index) and
+ * suffix name (name) are given. Returns the index in the array
+ * indicating whether a corresponding prefix was found or not.
+ */
+static int
+xattr_name_prefix (const char *full_name, int *index, const char **name)
+{
+ int i;
+
+ for (i = 0; xattr_prefixes[i].prefix != NULL; i++)
+ {
+ if (!strncmp (xattr_prefixes[i].prefix, full_name,
+ xattr_prefixes[i].size))
+ {
+ *name = full_name + xattr_prefixes[i].size;
+ *index = xattr_prefixes[i].index;
+ break;
+ }
+ }
+ return i;
+}
+
+#define NAME_HASH_SHIFT 5
+#define VALUE_HASH_SHIFT 16
+
+/* Given a xattr block header and a entry, compute the hash of this
+ * entry.
+ */
+static void
+xattr_entry_hash (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 + entry->e_value_offs);
+ for (n = (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))
+ ^ *value++;
+ }
+ }
+
+ entry->e_hash = hash;
+
+}
+
+#undef NAME_HASH_SHIFT
+#undef VALUE_HASH_SHIFT
+
+#define BLOCK_HASH_SHIFT 16
+
+/* Given a xattr block header and a entry, re-compute the
+ * hash of the entry after it has changed, and computer the hash
+ * of the header.
+ */
+static void
+xattr_entry_rehash (struct ext2_xattr_header *header,
+ struct ext2_xattr_entry *entry)
+{
+
+ __u32 hash = 0;
+ struct ext2_xattr_entry *position;
+
+ xattr_entry_hash (header, entry);
+
+ position = EXT2_XATTR_ENTRY_FIRST (header);
+ while (!EXT2_XATTR_ENTRY_LAST (position))
+ {
+ if (position->e_hash == 0)
+ {
+ /* 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))
+ ^ position->e_hash;
+
+ position = EXT2_XATTR_ENTRY_NEXT (position);
+ }
+
+ header->h_hash = hash;
+
+}
+
+#undef BLOCK_HASH_SHIFT
+
+/*
+ * Given an entry, appends its name to a buffer. The provided buffer
+ * length is reduced by the name size, even if the buffer is NULL (for
+ * computing the list size). Returns EOPNOTSUPP (operation not
+ * supported) if the entry index cannot be found on the array of
+ * supported prefixes. If a buffer is provided (not NULL) and its
+ * length is not enough for name, ERANGE is returned.
+ */
+static error_t
+xattr_entry_list (struct ext2_xattr_entry *entry, char *buffer, size_t *len)
+{
+
+ int i;
+ size_t size;
+
+ for (i = 0; xattr_prefixes[i].prefix != NULL; i++)
+ {
+ if (entry->e_name_index == xattr_prefixes[i].index)
+ break;
+ }
+
+ if (xattr_prefixes[i].prefix == NULL)
+ return EOPNOTSUPP;
+
+ size = xattr_prefixes[i].size + entry->e_name_len + 1;
+
+ if (buffer)
+ {
+ if (size <= *len)
+ {
+ memcpy (buffer, xattr_prefixes[i].prefix, xattr_prefixes[i].size);
+ buffer += xattr_prefixes[i].size;
+ memcpy (buffer, entry->e_name, entry->e_name_len);
+ buffer += entry->e_name_len;
+ *buffer++ = 0;
+ }
+ else
+ {
+ return ERANGE;
+ }
+ }
+
+ *len -= size;
+ return 0;
+
+}
+
+/*
+ * Given the xattr block, an entry and a attribute name, retrieves its
+ * value. The value length is also returned through parameter len. In
+ * case the name prefix cannot be found in the prefix array,
+ * EOPNOTSUPP is returned, indicating the prefix is not supported. In
+ * case there is not enough space in the buffer provided, ERANGE is
+ * returned. If the value buffer was NULL, the length is returned
+ * through len parameter and the function is successfull (returns 0).
+ * If the entry does not match the name, ENODATA is returned, and
+ * parameter cmp is set to the comparison value (less than 0 if a
+ * entry with name full_name should be before the current entry,
+ * more than 0 otherwise.
+ */
+static error_t
+xattr_entry_get (void *block, struct ext2_xattr_entry *entry,
+ const char *full_name, char *value, size_t *len, int *cmp)
+{
+
+ int i;
+ int index;
+ int tmp_cmp;
+ const char *name;
+
+ i = xattr_name_prefix (full_name, &index, &name);
+
+ if (xattr_prefixes[i].prefix == NULL)
+ return EOPNOTSUPP;
+
+ tmp_cmp = index - entry->e_name_index;
+ if (!tmp_cmp)
+ tmp_cmp = strlen (name) - entry->e_name_len;
+ if (!tmp_cmp)
+ tmp_cmp = strncmp (name, entry->e_name, entry->e_name_len);
+
+ if (tmp_cmp)
+ {
+ if (cmp)
+ *cmp = tmp_cmp;
+ return ENODATA;
+ }
+
+ if (value)
+ {
+ if (*len < entry->e_value_size)
+ {
+ return ERANGE;
+ }
+ memcpy (value, block + entry->e_value_offs, entry->e_value_size);
+ }
+
+ *len = entry->e_value_size;
+ return 0;
+
+}
+
+/*
+ * Creates an entry in the xattr block, giving its header, the last
+ * entry, the position where this new one should be inserted, the name
+ * of the attribute, its value and the value length, and, the
+ * remaining space in the block (parameter rest). If no space is
+ * available for the required size of the entry, ERANGE is returned.
+ */
+static error_t
+xattr_entry_create (struct ext2_xattr_header *header,
+ struct ext2_xattr_entry *last,
+ struct ext2_xattr_entry *position,
+ const char *full_name, const char *value,
+ size_t len, size_t rest)
+{
+
+ int i;
+ size_t name_len;
+ off_t start;
+ off_t end;
+ size_t entry_size;
+ size_t value_size;
+ int index;
+ const char *name;
+
+ i = xattr_name_prefix (full_name, &index, &name);
+
+ if (xattr_prefixes[i].prefix == NULL)
+ return EOPNOTSUPP;
+
+ name_len = strlen (name);
+ entry_size = EXT2_XATTR_ENTRY_SIZE (name_len);
+ value_size = EXT2_XATTR_ALIGN (len);
+
+ if (rest < 4 || entry_size + value_size > rest - 4)
+ {
+ return ERANGE;
+ }
+
+ start = EXT2_XATTR_ENTRY_OFFSET (header, position);
+ end = EXT2_XATTR_ENTRY_OFFSET (header, last);
+
+ /* Leave room for new entry */
+ memmove ((char *) position + entry_size, position, end - start);
+
+ position->e_name_len = name_len;
+ position->e_name_index = index;
+ position->e_value_offs = end + rest - value_size;
+ position->e_value_block = 0;
+ position->e_value_size = len;
+ strncpy (position->e_name, name, name_len);
+
+ memcpy ((char *) header + position->e_value_offs, value, len);
+ memset ((char *) header + position->e_value_offs + len, 0,
+ value_size - len);
+
+ return 0;
+
+}
+
+/*
+ * Removes an entry from the xattr block, giving a pointer to the
+ * block header, the last attribute entry, the position of the entry
+ * to be removed and the remaining space in the block.
+ */
+static error_t
+xattr_entry_remove (struct ext2_xattr_header *header,
+ struct ext2_xattr_entry *last,
+ struct ext2_xattr_entry *position, size_t rest)
+{
+
+ size_t size;
+ off_t start;
+ off_t end;
+ struct ext2_xattr_entry *entry;
+
+ /* Remove the value */
+ size = EXT2_XATTR_ALIGN (position->e_value_size);
+ start = EXT2_XATTR_ENTRY_OFFSET (header, last) + rest;
+ end = position->e_value_offs;
+
+ memmove ((char *) header + start + size, (char *) header + start,
+ end - start);
+ memset ((char *) header + start, 0, size);
+
+ /* Adjust all value offsets */
+ entry = EXT2_XATTR_ENTRY_FIRST (header);
+ while (!EXT2_XATTR_ENTRY_LAST (entry))
+ {
+ if (entry->e_value_offs < end)
+ entry->e_value_offs += size;
+ entry = EXT2_XATTR_ENTRY_NEXT (entry);
+ }
+
+ /* Remove the name */
+ size = EXT2_XATTR_ENTRY_SIZE (position->e_name_len);
+ start = EXT2_XATTR_ENTRY_OFFSET (header, position);
+ end = EXT2_XATTR_ENTRY_OFFSET (header, last);
+
+ memmove ((char *) header + start , (char *) header + start + size,
+ end - (start + size));
+ memset ((char *) header + end - size, 0, size);
+
+ return 0;
+
+}
+
+/*
+ * Replaces the value of an existing attribute entry, given the block
+ * header, the last entry, the entry whose value should be replaced,
+ * the new value, its length, and the remaining space in the block.
+ * Returns ERANGE if there is not enough space (when the new value is
+ * bigger than the old one).
+ */
+static error_t
+xattr_entry_replace (struct ext2_xattr_header *header,
+ struct ext2_xattr_entry *last,
+ struct ext2_xattr_entry *position,
+ const char *value, size_t len, size_t rest)
+{
+
+ size_t old_size;
+ size_t new_size;
+
+ old_size = EXT2_XATTR_ALIGN (position->e_value_size);
+ new_size = EXT2_XATTR_ALIGN (len);
+
+ if (rest < 4 || new_size - old_size > rest - 4)
+ return ERANGE;
+
+ if (new_size != old_size)
+ {
+ off_t start;
+ off_t end;
+ struct ext2_xattr_entry *entry;
+
+ start = EXT2_XATTR_ENTRY_OFFSET (header, last) + rest;
+ end = position->e_value_offs;
+
+ /* Remove the old value */
+ memmove ((char *) header + start + old_size, (char *) header + start,
+ end - start);
+
+ /* Adjust all value offsets */
+ entry = EXT2_XATTR_ENTRY_FIRST (header);
+ while (!EXT2_XATTR_ENTRY_LAST (entry))
+ {
+ if (entry->e_value_offs < end)
+ entry->e_value_offs += old_size;
+ entry = EXT2_XATTR_ENTRY_NEXT (entry);
+ }
+
+ position->e_value_offs = start - (new_size - old_size);
+ }
+
+ position->e_value_size = len;
+
+ /* Write the new value */
+ memcpy ((char *) header + position->e_value_offs, value, len);
+ memset ((char *) header + position->e_value_offs + len, 0, new_size - len);
+
+ return 0;
+
+}
+
+
+/*
+ * Given a node, free extended attributes block associated with
+ * this node.
+ */
+error_t
+ext2_free_xattr_block (struct node *np)
+{
+ error_t err;
+ block_t blkno;
+ void *block;
+ struct ext2_inode *ei;
+ struct ext2_xattr_header *header;
+
+ if (!EXT2_HAS_COMPAT_FEATURE (sblock, EXT2_FEATURE_COMPAT_EXT_ATTR))
+ {
+ ext2_warning ("Filesystem has no support for extended attributes.");
+ return EOPNOTSUPP;
+ }
+
+ err = 0;
+ block = NULL;
+
+ ei = dino_ref (np->cache_id);
+ blkno = ei->i_file_acl;
+
+ if (blkno == 0)
+ {
+ err = 0;
+ goto cleanup;
+ }
+
+ assert (!diskfs_readonly);
+
+ block = disk_cache_block_ref (blkno);
+ header = EXT2_XATTR_HEADER (block);
+
+ if (header->h_magic != EXT2_XATTR_BLOCK_MAGIC || header->h_blocks != 1)
+ {
+ ext2_warning ("Invalid extended attribute block.");
+ err = EIO;
+ goto cleanup;
+ }
+
+ if (header->h_refcount == 1)
+ {
+ ext2_debug("free block %d", blkno);
+
+ disk_cache_block_deref (block);
+ ext2_free_blocks(blkno, 1);
+
+ np->dn_stat.st_blocks -= 1 << log2_stat_blocks_per_fs_block;
+ np->dn_stat.st_mode &= ~S_IPTRANS;
+ np->dn_set_ctime = 1;
+ }
+ else
+ {
+ ext2_debug("h_refcount: %d", header->h_refcount);
+
+ header->h_refcount--;
+ record_global_poke (block);
+ }
+
+
+ ei->i_file_acl = 0;
+ record_global_poke (ei);
+
+ return err;
+
+cleanup:
+ if (block)
+ disk_cache_block_deref (block);
+
+ dino_deref (ei);
+
+ return err;
+
+}
+
+/*
+ * Given a node, return its list of attribute names in a buffer.
+ * The size of used/required buffer will returned through parameter
+ * len, even if the buffer is NULL. Returns EOPNOTSUPP if underlying
+ * filesystem has no extended attributes support. Returns EIO if
+ * xattr block is invalid (has no valid h_magic number).
+ */
+error_t
+ext2_list_xattr (struct node *np, char *buffer, size_t *len)
+{
+
+ error_t err;
+ block_t blkno;
+ void *block;
+ struct ext2_inode *ei;
+ struct ext2_xattr_header *header;
+ struct ext2_xattr_entry *entry;
+
+ if (!EXT2_HAS_COMPAT_FEATURE (sblock, EXT2_FEATURE_COMPAT_EXT_ATTR))
+ {
+ ext2_warning ("Filesystem has no support for extended attributes.");
+ return EOPNOTSUPP;
+ }
+
+ if (!len)
+ return EINVAL;
+
+ size_t size = *len;
+
+ ei = dino_ref (np->cache_id);
+ blkno = ei->i_file_acl;
+ dino_deref (ei);
+
+ if (blkno == 0)
+ {
+ *len = 0;
+ return 0;
+ }
+
+ err = EIO;
+ block = disk_cache_block_ref (blkno);
+
+ header = EXT2_XATTR_HEADER (block);
+ if (header->h_magic != EXT2_XATTR_BLOCK_MAGIC || header->h_blocks != 1)
+ {
+ ext2_warning ("Invalid extended attribute block.");
+ err = EIO;
+ goto cleanup;
+ }
+
+ entry = EXT2_XATTR_ENTRY_FIRST (header);
+
+ while (!EXT2_XATTR_ENTRY_LAST (entry))
+ {
+ err = xattr_entry_list (entry, buffer, &size);
+ if (err)
+ goto cleanup;
+ if (buffer)
+ buffer += strlen (buffer) + 1;
+ entry = EXT2_XATTR_ENTRY_NEXT (entry);
+ }
+
+ *len = *len - size;
+
+cleanup:
+ disk_cache_block_deref (block);
+
+ return err;
+
+}
+
+
+/*
+ * Given a node and an attribute name, returns the value and its
+ * length in a buffer. The length is returned through parameter len
+ * even if the value is NULL. May return EOPNOTSUPP if underlying
+ * filesystem does not support extended attributes or the given name
+ * prefix. If there is no sufficient space in value buffer or
+ * attribute name is too long, returns ERANGE. Returns EIO if xattr
+ * block is invalid and ENODATA if there is no such block or no entry
+ * in the block matching the name.
+ */
+error_t
+ext2_get_xattr (struct node *np, const char *name, char *value, size_t *len)
+{
+
+ size_t size;
+ int err;
+ void *block;
+ struct ext2_inode *ei;
+ struct ext2_xattr_header *header;
+ struct ext2_xattr_entry *entry;
+
+ if (!EXT2_HAS_COMPAT_FEATURE (sblock, EXT2_FEATURE_COMPAT_EXT_ATTR))
+ {
+ ext2_warning ("Filesystem has no support for extended attributes.");
+ return EOPNOTSUPP;
+ }
+
+ if (!name || !len)
+ return EINVAL;
+
+ if (strlen(name) > 255)
+ return ERANGE;
+
+ ei = dino_ref (np->cache_id);
+
+ if (ei->i_file_acl == 0)
+ {
+ dino_deref (ei);
+ return ENODATA;
+ }
+
+ block = disk_cache_block_ref (ei->i_file_acl);
+ dino_deref (ei);
+
+ header = EXT2_XATTR_HEADER (block);
+ if (header->h_magic != EXT2_XATTR_BLOCK_MAGIC || header->h_blocks != 1)
+ {
+ ext2_warning ("Invalid extended attribute block.");
+ err = EIO;
+ goto cleanup;
+ }
+
+ err = ENODATA;
+ entry = EXT2_XATTR_ENTRY_FIRST (header);
+
+ while (!EXT2_XATTR_ENTRY_LAST (entry))
+ {
+ size = *len;
+ err = xattr_entry_get (block, entry, name, value, &size, NULL);
+ if (err!= ENODATA)
+ break;
+ entry = EXT2_XATTR_ENTRY_NEXT (entry);
+ }
+
+ if (!err)
+ *len = size;
+
+cleanup:
+ disk_cache_block_deref (block);
+
+ return err;
+
+}
+
+/*
+ * Set the value of an attribute giving the node, the attribute name,
+ * value, the value length and flags. If name or value is too long,
+ * ERANGE is returned. If flags is XATTR_CREATE, the
+ * attribute is created if no existing matching entry is found.
+ * Otherwise, EEXIST is returned. If flags is XATTR_REPLACE, the
+ * attribute value is replaced if an entry is found and ENODATA is
+ * returned otherwise. If no flags are used, the entry is properly
+ * created or replaced. The entry is removed if value is NULL and no
+ * flags are used. In this case, if any flags are used, EINVAL is
+ * returned. If no matching entry is found, ENODATA is returned.
+ * EOPNOTSUPP is returned in case extended attributes or the name
+ * prefix are not supported. If there is no space available in the
+ * block, ERANGE is returned. If there is no any entry after removing
+ * the specified entry, free the xattr block.
+ */
+error_t
+ext2_set_xattr (struct node *np, const char *name, const char *value,
+ size_t len, int flags)
+{
+
+ int found;
+ size_t rest;
+ error_t err;
+ block_t blkno;
+ void *block;
+ struct ext2_inode *ei;
+ struct ext2_xattr_header *header;
+ struct ext2_xattr_entry *entry;
+ struct ext2_xattr_entry *location;
+
+ if (!EXT2_HAS_COMPAT_FEATURE (sblock, EXT2_FEATURE_COMPAT_EXT_ATTR))
+ {
+ ext2_warning ("Filesystem has no support for extended attributes.");
+ return EOPNOTSUPP;
+ }
+
+ if (!name)
+ return EINVAL;
+
+ if (strlen(name) > 255 || len > block_size)
+ return ERANGE;
+
+ ei = dino_ref (np->cache_id);
+ blkno = ei->i_file_acl;
+
+ /* Avoid allocating a block if this is a request to delete data. */
+ if (blkno == 0 && value == NULL)
+ {
+ block = NULL;
+ err = ENODATA;
+ goto cleanup;
+ }
+
+ if (blkno == 0)
+ {
+ /* Allocate and initialize new block */
+ block_t goal;
+
+ assert (!diskfs_readonly);
+
+ goal = sblock->s_first_data_block + np->dn->info.i_block_group *
+ EXT2_BLOCKS_PER_GROUP (sblock);
+ blkno = ext2_new_block (goal, 0, 0, 0);
+
+ if (blkno == 0)
+ {
+ err = ENOSPC;
+ goto cleanup;
+ }
+
+ block = disk_cache_block_ref (blkno);
+ memset (block, 0, block_size);
+
+ header = EXT2_XATTR_HEADER (block);
+ header->h_magic = EXT2_XATTR_BLOCK_MAGIC;
+ header->h_blocks = 1;
+ header->h_refcount = 1;
+ }
+ else
+ {
+ block = disk_cache_block_ref (blkno);
+ header = EXT2_XATTR_HEADER (block);
+ if (header->h_magic != EXT2_XATTR_BLOCK_MAGIC || header->h_blocks != 1)
+ {
+ ext2_warning ("Invalid extended attribute block.");
+ err = EIO;
+ goto cleanup;
+ }
+ }
+
+ entry = EXT2_XATTR_ENTRY_FIRST (header);
+ location = NULL;
+
+ rest = block_size;
+ err = ENODATA;
+ found = FALSE;
+
+ while (!EXT2_XATTR_ENTRY_LAST (entry))
+ {
+ size_t size;
+ int cmp;
+
+ err = xattr_entry_get (NULL, entry, name, NULL, &size, &cmp);
+ if (err == 0)
+ {
+ location = entry;
+ found = TRUE;
+ }
+ else if (err == ENODATA)
+ {
+ /* The xattr entries are sorted by attribute name */
+ if (cmp < 0 && !found)
+ {
+ location = entry;
+ found = FALSE;
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ rest -= EXT2_XATTR_ALIGN (entry->e_value_size);
+ entry = EXT2_XATTR_ENTRY_NEXT (entry);
+ }
+
+ if (err != 0 && err != ENODATA)
+ {
+ goto cleanup;
+ }
+
+ if (location == NULL)
+ location = entry;
+
+ rest = rest - EXT2_XATTR_ENTRY_OFFSET (header, entry);
+ ext2_debug("space rest: %d", rest);
+
+ /* 4 null bytes after xattr entry */
+ if (rest < 4)
+ {
+ err = EIO;
+ goto cleanup;
+ }
+
+ if (value && flags & XATTR_CREATE)
+ {
+ if (found)
+ {
+ err = EEXIST;
+ goto cleanup;
+ }
+ else
+ err = xattr_entry_create (header, entry, location, name, value, len,
+ rest);
+ }
+ else if (value && flags & XATTR_REPLACE)
+ {
+ if (!found)
+ {
+ err = ENODATA;
+ goto cleanup;
+ }
+ else
+ err = xattr_entry_replace (header, entry, location, value, len, rest);
+ }
+ else if (value)
+ {
+ if (found)
+ err = xattr_entry_replace (header, entry, location, value, len, rest);
+ else
+ err = xattr_entry_create (header, entry, location, name, value, len,
+ rest);
+ }
+ else
+ {
+ if (flags & XATTR_REPLACE || flags & XATTR_CREATE)
+ {
+ err = EINVAL;
+ goto cleanup;
+ }
+ else if (!found)
+ {
+ err = ENODATA;
+ goto cleanup;
+ }
+ else
+ err = xattr_entry_remove (header, entry, location, rest);
+ }
+
+ /* Check if the xattr block is empty */
+ entry = EXT2_XATTR_ENTRY_FIRST (header);
+ int empty = EXT2_XATTR_ENTRY_LAST (entry);
+
+ if (err == 0)
+ {
+ if (empty)
+ {
+ disk_cache_block_deref (block);
+ dino_deref (ei);
+
+ return ext2_free_xattr_block (np);
+ }
+ else
+ {
+ xattr_entry_rehash (header, location);
+
+ record_global_poke (block);
+
+ if (ei->i_file_acl == 0)
+ {
+ np->dn_stat.st_blocks += 1 << log2_stat_blocks_per_fs_block;
+ np->dn_set_ctime = 1;
+
+ ei->i_file_acl = blkno;
+ record_global_poke (ei);
+ }
+ else
+ dino_deref (ei);
+
+ return 0;
+ }
+ }
+
+cleanup:
+ if (block)
+ disk_cache_block_deref (block);
+ if (ei->i_file_acl == 0 && blkno != 0)
+ /* We allocated a block, but for some reason we did not register
+ it. */
+ ext2_free_blocks (blkno, 1);
+ dino_deref (ei);
+
+ return err;
+
+}
diff --git a/ext2fs/xattr.h b/ext2fs/xattr.h
new file mode 100644
index 00000000..245f896e
--- /dev/null
+++ b/ext2fs/xattr.h
@@ -0,0 +1,85 @@
+ /* Ext2 support for extended attributes
+
+ Copyright (C) 2006, 2016 Free Software Foundation, Inc.
+
+ Written by Thadeu Lima de Souza Cascardo <cascardo@dcc.ufmg.br>
+ and Shengyu Zhang <lastavengers@outlook.com>
+
+ This program 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, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef EXT2_XATTR_H
+#define EXT2_XATTR_H
+
+#include "ext2fs.h"
+
+/* Identifies whether a block is a proper xattr block. */
+#define EXT2_XATTR_BLOCK_MAGIC 0xEA020000
+
+/* xattr block header. */
+struct ext2_xattr_header
+{
+ __u32 h_magic; /* h_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 */
+};
+
+/* xattr entry in xattr block. */
+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 << EXT2_XATTR_PAD_BITS)
+#define EXT2_XATTR_ROUND (EXT2_XATTR_PAD - 1)
+
+/* Entry alignment in xattr block. */
+#define EXT2_XATTR_ALIGN(x) (((unsigned long) (x) + \
+ EXT2_XATTR_ROUND) & \
+ (~EXT2_XATTR_ROUND))
+
+/* Given a fs block, return the xattr header. */
+#define EXT2_XATTR_HEADER(block) ((struct ext2_xattr_header *) block)
+
+/* Aligned size of entry, including the name length. */
+#define EXT2_XATTR_ENTRY_SIZE(len) EXT2_XATTR_ALIGN ((sizeof \
+ (struct ext2_xattr_entry) + \
+ len))
+
+/* Offset of entry, given the block header. */
+#define EXT2_XATTR_ENTRY_OFFSET(header, entry) ((off_t) ((char *) entry - \
+ (char *) header))
+
+/* First entry of xattr block, given its header. */
+#define EXT2_XATTR_ENTRY_FIRST(header) ((struct ext2_xattr_entry *) (header + 1))
+
+/* Next entry, giving an entry. */
+#define EXT2_XATTR_ENTRY_NEXT(entry) ((struct ext2_xattr_entry *) \
+ ((char *) entry + \
+ EXT2_XATTR_ENTRY_SIZE \
+ (entry->e_name_len)))
+
+/* Checks if this entry is the last (not valid) one. */
+#define EXT2_XATTR_ENTRY_LAST(entry) (*(unsigned long *) entry == 0UL)
+
+#endif