summaryrefslogtreecommitdiffstats
path: root/squashfs-tools/xattr.c
diff options
context:
space:
mode:
Diffstat (limited to 'squashfs-tools/xattr.c')
-rw-r--r--squashfs-tools/xattr.c1322
1 files changed, 1322 insertions, 0 deletions
diff --git a/squashfs-tools/xattr.c b/squashfs-tools/xattr.c
new file mode 100644
index 0000000..f9f4cc3
--- /dev/null
+++ b/squashfs-tools/xattr.c
@@ -0,0 +1,1322 @@
+/*
+ * Create a squashfs filesystem. This is a highly compressed read only
+ * filesystem.
+ *
+ * Copyright (c) 2008, 2009, 2010, 2012, 2014, 2019, 2021, 2022
+ * Phillip Lougher <phillip@squashfs.org.uk>
+ *
+ * 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * xattr.c
+ */
+
+#include "endian_compat.h"
+
+#define TRUE 1
+#define FALSE 0
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/xattr.h>
+#include <regex.h>
+
+#include "squashfs_fs.h"
+#include "squashfs_swap.h"
+#include "mksquashfs.h"
+#include "xattr.h"
+#include "mksquashfs_error.h"
+#include "progressbar.h"
+#include "pseudo.h"
+#include "tar.h"
+#include "action.h"
+#include "merge_sort.h"
+
+#ifdef XATTR_NOFOLLOW /* Apple's xattrs */
+ #define llistxattr(path_, buf_, sz_) \
+ listxattr(path_, buf_, sz_, XATTR_NOFOLLOW)
+ #define lgetxattr(path_, name_, val_, sz_) \
+ getxattr(path_, name_, val_, sz_, 0, XATTR_NOFOLLOW)
+#endif
+
+/* compressed xattr table */
+static char *xattr_table = NULL;
+static unsigned int xattr_size = 0;
+
+/* cached uncompressed xattr data */
+static char *data_cache = NULL;
+static int cache_bytes = 0, cache_size = 0;
+
+/* cached uncompressed xattr id table */
+static struct squashfs_xattr_id *xattr_id_table = NULL;
+static int xattr_ids = 0;
+
+/* saved compressed xattr table */
+unsigned int sxattr_bytes = 0, stotal_xattr_bytes = 0;
+
+/* saved cached uncompressed xattr data */
+static char *sdata_cache = NULL;
+static int scache_bytes = 0;
+
+/* saved cached uncompressed xattr id table */
+static int sxattr_ids = 0;
+
+/* xattr hash table for value duplicate detection */
+static struct xattr_list *dupl_value[65536];
+
+/* xattr hash table for id duplicate detection */
+static struct dupl_id *dupl_id[65536];
+
+/* xattr-add option names and values */
+static struct xattr_add *xattr_add_list = NULL;
+static int xattr_add_count = 0;
+
+/* file system globals from mksquashfs.c */
+extern int no_xattrs, noX;
+extern long long bytes;
+extern int fd;
+extern unsigned int xattr_bytes, total_xattr_bytes;
+extern regex_t *xattr_exclude_preg;
+extern regex_t *xattr_include_preg;
+
+/* helper functions from mksquashfs.c */
+extern unsigned short get_checksum(char *, int, unsigned short);
+extern void write_destination(int, long long, long long, void *);
+extern long long generic_write_table(long long, void *, int, void *, int);
+extern int mangle(char *, char *, int, int, int, int);
+extern char *pathname(struct dir_ent *);
+
+/* helper functions and definitions from read_xattrs.c */
+extern unsigned int read_xattrs_from_disk(int, struct squashfs_super_block *, int, long long *);
+extern struct xattr_list *get_xattr(int, unsigned int *, int *);
+extern struct prefix prefix_table[];
+
+
+static int xattr_get_type(char *name)
+{
+ int i;
+
+ for(i = 0; prefix_table[i].type != -1; i++) {
+ struct prefix *p = &prefix_table[i];
+ if(strncmp(name, p->prefix, strlen(p->prefix)) == 0)
+ break;
+ }
+
+ return prefix_table[i].type;
+}
+
+
+static void xattr_copy_prefix(struct xattr_list *xattr, int t, char *name)
+{
+ xattr->full_name = strdup(name);
+ xattr->name = xattr->full_name + strlen(prefix_table[t].prefix);
+ xattr->size = strlen(xattr->name);
+}
+
+
+int xattr_get_prefix(struct xattr_list *xattr, char *name)
+{
+ int type = xattr_get_type(name);
+
+ if(type != -1)
+ xattr_copy_prefix(xattr, type, name);
+
+ return type;
+}
+
+
+static int read_xattrs_from_system(struct dir_ent *dir_ent, char *filename,
+ struct xattr_list **xattrs)
+{
+ ssize_t size, vsize;
+ char *xattr_names, *p;
+ int i = 0;
+ struct xattr_list *xattr_list = NULL;
+ struct xattr_data *xattr_exc_list;
+ struct xattr_data *xattr_inc_list;
+
+ while(1) {
+ size = llistxattr(filename, NULL, 0);
+ if(size <= 0) {
+ if(size < 0 && errno != ENOTSUP) {
+ ERROR_START("llistxattr for %s failed in "
+ "read_attrs, because %s", filename,
+ strerror(errno));
+ ERROR_EXIT(". Ignoring\n");
+ }
+ return 0;
+ }
+
+ xattr_names = malloc(size);
+ if(xattr_names == NULL)
+ MEM_ERROR();
+
+ size = llistxattr(filename, xattr_names, size);
+ if(size < 0) {
+ free(xattr_names);
+ if(errno == ERANGE)
+ /* xattr list grew? Try again */
+ continue;
+ else {
+ ERROR_START("llistxattr for %s failed in "
+ "read_attrs, because %s", filename,
+ strerror(errno));
+ ERROR_EXIT(". Ignoring\n");
+ return 0;
+ }
+ }
+
+ break;
+ }
+
+ xattr_exc_list = eval_xattr_exc_actions(root_dir, dir_ent);
+ xattr_inc_list = eval_xattr_inc_actions(root_dir, dir_ent);
+
+ for(p = xattr_names; p < xattr_names + size;) {
+ struct xattr_list *x;
+ int res;
+
+ res = match_xattr_exc_actions(xattr_exc_list, p);
+ if(res) {
+ p += strlen(p) + 1;
+ continue;
+ }
+
+ if(xattr_exclude_preg) {
+ res = regexec(xattr_exclude_preg, p, (size_t) 0, NULL, 0);
+ if(res == 0) {
+ p += strlen(p) + 1;
+ continue;
+ }
+ }
+
+ res = match_xattr_inc_actions(xattr_inc_list, p);
+ if(res) {
+ p += strlen(p) + 1;
+ continue;
+ }
+
+ if(xattr_include_preg) {
+ res = regexec(xattr_include_preg, p, (size_t) 0, NULL, 0);
+ if(res) {
+ p += strlen(p) + 1;
+ continue;
+ }
+ }
+
+ x = realloc(xattr_list, (i + 1) * sizeof(struct xattr_list));
+ if(x == NULL)
+ MEM_ERROR();
+ xattr_list = x;
+
+ xattr_list[i].type = xattr_get_prefix(&xattr_list[i], p);
+
+ if(xattr_list[i].type == -1) {
+ ERROR("Unrecognised xattr prefix %s\n", p);
+ p += strlen(p) + 1;
+ continue;
+ }
+
+ p += strlen(p) + 1;
+
+ while(1) {
+ vsize = lgetxattr(filename, xattr_list[i].full_name,
+ NULL, 0);
+ if(vsize < 0) {
+ ERROR_START("lgetxattr failed for %s in "
+ "read_attrs, because %s", filename,
+ strerror(errno));
+ ERROR_EXIT(". Ignoring\n");
+ free(xattr_list[i].full_name);
+ goto failed;
+ }
+
+ xattr_list[i].value = malloc(vsize);
+ if(xattr_list[i].value == NULL)
+ MEM_ERROR();
+
+ vsize = lgetxattr(filename, xattr_list[i].full_name,
+ xattr_list[i].value, vsize);
+ if(vsize < 0) {
+ free(xattr_list[i].value);
+ if(errno == ERANGE)
+ /* xattr grew? Try again */
+ continue;
+ else {
+ ERROR_START("lgetxattr failed for %s "
+ "in read_attrs, because %s",
+ filename, strerror(errno));
+ ERROR_EXIT(". Ignoring\n");
+ free(xattr_list[i].full_name);
+ goto failed;
+ }
+ }
+
+ break;
+ }
+
+ xattr_list[i].vsize = vsize;
+
+ TRACE("read_xattrs_from_system: filename %s, xattr name %s,"
+ " vsize %d\n", filename, xattr_list[i].full_name,
+ xattr_list[i].vsize);
+ i++;
+ }
+
+ free(xattr_names);
+
+ if(i > 0)
+ *xattrs = xattr_list;
+ else
+ free(xattr_list);
+ return i;
+
+failed:
+ while(--i >= 0) {
+ free(xattr_list[i].full_name);
+ free(xattr_list[i].value);
+ }
+ free(xattr_list);
+ free(xattr_names);
+ return 0;
+}
+
+
+static int get_xattr_size(struct xattr_list *xattr)
+{
+ int size = sizeof(struct squashfs_xattr_entry) +
+ sizeof(struct squashfs_xattr_val) + xattr->size;
+
+ if(xattr->type & XATTR_VALUE_OOL)
+ size += XATTR_VALUE_OOL_SIZE;
+ else
+ size += xattr->vsize;
+
+ return size;
+}
+
+
+static void *get_xattr_space(unsigned int req_size, long long *disk)
+{
+ int data_space;
+ unsigned short c_byte;
+
+ /*
+ * Move and compress cached uncompressed data into xattr table.
+ */
+ while(cache_bytes >= SQUASHFS_METADATA_SIZE) {
+ if((xattr_size - xattr_bytes) <
+ ((SQUASHFS_METADATA_SIZE << 1)) + 2) {
+ xattr_table = realloc(xattr_table, xattr_size +
+ (SQUASHFS_METADATA_SIZE << 1) + 2);
+ if(xattr_table == NULL)
+ MEM_ERROR();
+ xattr_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+ }
+
+ c_byte = mangle(xattr_table + xattr_bytes + BLOCK_OFFSET,
+ data_cache, SQUASHFS_METADATA_SIZE,
+ SQUASHFS_METADATA_SIZE, noX, 0);
+ TRACE("Xattr block @ 0x%x, size %d\n", xattr_bytes, c_byte);
+ SQUASHFS_SWAP_SHORTS(&c_byte, xattr_table + xattr_bytes, 1);
+ xattr_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+ memmove(data_cache, data_cache + SQUASHFS_METADATA_SIZE,
+ cache_bytes - SQUASHFS_METADATA_SIZE);
+ cache_bytes -= SQUASHFS_METADATA_SIZE;
+ }
+
+ /*
+ * Ensure there's enough space in the uncompressed data cache
+ */
+ data_space = cache_size - cache_bytes;
+ if(data_space < req_size) {
+ int realloc_size = req_size - data_space;
+ data_cache = realloc(data_cache, cache_size +
+ realloc_size);
+ if(data_cache == NULL)
+ MEM_ERROR();
+ cache_size += realloc_size;
+ }
+
+ if(disk)
+ *disk = ((long long) xattr_bytes << 16) | cache_bytes;
+ cache_bytes += req_size;
+ return data_cache + cache_bytes - req_size;
+}
+
+
+static struct dupl_id *check_id_dupl(struct xattr_list *xattr_list, int xattrs)
+{
+ struct dupl_id *entry;
+ int i;
+ unsigned short checksum = 0;
+
+ /* compute checksum over all xattrs */
+ for(i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+
+ checksum = get_checksum(xattr->full_name,
+ strlen(xattr->full_name), checksum);
+ checksum = get_checksum(xattr->value,
+ xattr->vsize, checksum);
+ }
+
+ for(entry = dupl_id[checksum]; entry; entry = entry->next) {
+ if (entry->xattrs != xattrs)
+ continue;
+
+ for(i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+ struct xattr_list *dup_xattr = &entry->xattr_list[i];
+
+ if(strcmp(xattr->full_name, dup_xattr->full_name))
+ break;
+
+ if(xattr->vsize != dup_xattr->vsize)
+ break;
+
+ if(memcmp(xattr->value, dup_xattr->value, xattr->vsize))
+ break;
+ }
+
+ if(i == xattrs)
+ break;
+ }
+
+ if(entry == NULL) {
+ /* no duplicate exists */
+ entry = malloc(sizeof(*entry));
+ if(entry == NULL)
+ MEM_ERROR();
+ entry->xattrs = xattrs;
+ entry->xattr_list = xattr_list;
+ entry->xattr_id = SQUASHFS_INVALID_XATTR;
+ entry->next = dupl_id[checksum];
+ dupl_id[checksum] = entry;
+ }
+
+ return entry;
+}
+
+
+static void check_value_dupl(struct xattr_list *xattr)
+{
+ struct xattr_list *entry;
+
+ if(xattr->vsize < XATTR_VALUE_OOL_SIZE)
+ return;
+
+ /* Check if this is a duplicate of an existing value */
+ xattr->vchecksum = get_checksum(xattr->value, xattr->vsize, 0);
+ for(entry = dupl_value[xattr->vchecksum]; entry; entry = entry->vnext) {
+ if(entry->vsize != xattr->vsize)
+ continue;
+
+ if(memcmp(entry->value, xattr->value, xattr->vsize) == 0)
+ break;
+ }
+
+ if(entry == NULL) {
+ /*
+ * No duplicate exists, add to hash table, and mark as
+ * requiring writing
+ */
+ xattr->vnext = dupl_value[xattr->vchecksum];
+ dupl_value[xattr->vchecksum] = xattr;
+ xattr->ool_value = SQUASHFS_INVALID_BLK;
+ } else {
+ /*
+ * Duplicate exists, make type XATTR_VALUE_OOL, and
+ * remember where the duplicate is
+ */
+ xattr->type |= XATTR_VALUE_OOL;
+ xattr->ool_value = entry->ool_value;
+ /* on appending don't free duplicate values because the
+ * duplicate value already points to the non-duplicate value */
+ if(xattr->value != entry->value) {
+ free(xattr->value);
+ xattr->value = entry->value;
+ }
+ }
+}
+
+
+static int get_xattr_id(int xattrs, struct xattr_list *xattr_list,
+ long long xattr_disk, struct dupl_id *xattr_dupl)
+{
+ int i, size = 0;
+ struct squashfs_xattr_id *xattr_id;
+
+ xattr_id_table = realloc(xattr_id_table, (xattr_ids + 1) *
+ sizeof(struct squashfs_xattr_id));
+ if(xattr_id_table == NULL)
+ MEM_ERROR();
+
+ /* get total uncompressed size of xattr data, needed for stat */
+ for(i = 0; i < xattrs; i++)
+ size += strlen(xattr_list[i].full_name) + 1 +
+ xattr_list[i].vsize;
+
+ xattr_id = &xattr_id_table[xattr_ids];
+ xattr_id->xattr = xattr_disk;
+ xattr_id->count = xattrs;
+ xattr_id->size = size;
+
+ /*
+ * keep track of total uncompressed xattr data, needed for mksquashfs
+ * file system summary
+ */
+ total_xattr_bytes += size;
+
+ xattr_dupl->xattr_id = xattr_ids ++;
+ return xattr_dupl->xattr_id;
+}
+
+
+long long write_xattrs()
+{
+ unsigned short c_byte;
+ int i, avail_bytes;
+ char *datap = data_cache;
+ long long start_bytes = bytes;
+ struct squashfs_xattr_table header = {};
+
+ if(xattr_ids == 0)
+ return SQUASHFS_INVALID_BLK;
+
+ /*
+ * Move and compress cached uncompressed data into xattr table.
+ */
+ while(cache_bytes) {
+ if((xattr_size - xattr_bytes) <
+ ((SQUASHFS_METADATA_SIZE << 1)) + 2) {
+ xattr_table = realloc(xattr_table, xattr_size +
+ (SQUASHFS_METADATA_SIZE << 1) + 2);
+ if(xattr_table == NULL)
+ MEM_ERROR();
+ xattr_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
+ }
+
+ avail_bytes = cache_bytes > SQUASHFS_METADATA_SIZE ?
+ SQUASHFS_METADATA_SIZE : cache_bytes;
+ c_byte = mangle(xattr_table + xattr_bytes + BLOCK_OFFSET, datap,
+ avail_bytes, SQUASHFS_METADATA_SIZE, noX, 0);
+ TRACE("Xattr block @ 0x%x, size %d\n", xattr_bytes, c_byte);
+ SQUASHFS_SWAP_SHORTS(&c_byte, xattr_table + xattr_bytes, 1);
+ xattr_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
+ datap += avail_bytes;
+ cache_bytes -= avail_bytes;
+ }
+
+ /*
+ * Write compressed xattr table to file system
+ */
+ write_destination(fd, bytes, xattr_bytes, xattr_table);
+ bytes += xattr_bytes;
+
+ /*
+ * Swap if necessary the xattr id table
+ */
+ for(i = 0; i < xattr_ids; i++)
+ SQUASHFS_INSWAP_XATTR_ID(&xattr_id_table[i]);
+
+ header.xattr_ids = xattr_ids;
+ header.xattr_table_start = start_bytes;
+ SQUASHFS_INSWAP_XATTR_TABLE(&header);
+
+ return generic_write_table(xattr_ids * sizeof(struct squashfs_xattr_id),
+ xattr_id_table, sizeof(header), &header, noX);
+}
+
+
+void free_xattr_list(int xattrs, struct xattr_list *xattr_list)
+{
+ int i;
+
+ for(i = 0; i < xattrs; i++) {
+ free(xattr_list[i].full_name);
+ free(xattr_list[i].value);
+ }
+
+ free(xattr_list);
+}
+
+
+int generate_xattrs(int xattrs, struct xattr_list *xattr_list)
+{
+ int total_size, i;
+ int xattr_value_max;
+ void *xp;
+ long long xattr_disk;
+ struct dupl_id *xattr_dupl;
+
+ /*
+ * check if the file xattrs are a complete duplicate of a pre-existing
+ * id
+ */
+ xattr_dupl = check_id_dupl(xattr_list, xattrs);
+ if(xattr_dupl->xattr_id != SQUASHFS_INVALID_XATTR) {
+ free_xattr_list(xattrs, xattr_list);
+ return xattr_dupl->xattr_id;
+ }
+
+ /*
+ * Scan the xattr_list deciding which type to assign to each
+ * xattr. The choice is fairly straightforward, and depends on the
+ * size of each xattr name/value and the overall size of the
+ * resultant xattr list stored in the xattr metadata table.
+ *
+ * Choices are whether to store data inline or out of line.
+ *
+ * The overall goal is to optimise xattr scanning and lookup, and
+ * to enable the file system layout to scale from a couple of
+ * small xattr name/values to a large number of large xattr
+ * names/values without affecting performance. While hopefully
+ * enabling the common case of a couple of small xattr name/values
+ * to be stored efficiently
+ *
+ * Code repeatedly scans, doing the following
+ * move xattr data out of line if it exceeds
+ * xattr_value_max. Where xattr_value_max is
+ * initially XATTR_INLINE_MAX. If the final uncompressed
+ * xattr list is larger than XATTR_TARGET_MAX then more
+ * aggressively move xattr data out of line by repeatedly
+ * setting inline threshold to 1/2, then 1/4, 1/8 of
+ * XATTR_INLINE_MAX until target achieved or there's
+ * nothing left to move out of line
+ */
+ xattr_value_max = XATTR_INLINE_MAX;
+ while(1) {
+ for(total_size = 0, i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+ xattr->type &= XATTR_PREFIX_MASK; /* all inline */
+ if (xattr->vsize > xattr_value_max)
+ xattr->type |= XATTR_VALUE_OOL;
+
+ total_size += get_xattr_size(xattr);
+ }
+
+ /*
+ * If the total size of the uncompressed xattr list is <=
+ * XATTR_TARGET_MAX we're done
+ */
+ if(total_size <= XATTR_TARGET_MAX)
+ break;
+
+ if(xattr_value_max == XATTR_VALUE_OOL_SIZE)
+ break;
+
+ /*
+ * Inline target not yet at minimum and so reduce it, and
+ * try again
+ */
+ xattr_value_max /= 2;
+ if(xattr_value_max < XATTR_VALUE_OOL_SIZE)
+ xattr_value_max = XATTR_VALUE_OOL_SIZE;
+ }
+
+ /*
+ * Check xattr values for duplicates
+ */
+ for(i = 0; i < xattrs; i++) {
+ check_value_dupl(&xattr_list[i]);
+ }
+
+ /*
+ * Add each out of line value to the file system xattr table
+ * if it doesn't already exist as a duplicate
+ */
+ for(i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+
+ if((xattr->type & XATTR_VALUE_OOL) &&
+ (xattr->ool_value == SQUASHFS_INVALID_BLK)) {
+ struct squashfs_xattr_val val;
+ int size = sizeof(val) + xattr->vsize;
+ xp = get_xattr_space(size, &xattr->ool_value);
+ val.vsize = xattr->vsize;
+ SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+ memcpy(xp + sizeof(val), xattr->value, xattr->vsize);
+ }
+ }
+
+ /*
+ * Create xattr list and add to file system xattr table
+ */
+ get_xattr_space(0, &xattr_disk);
+ for(i = 0; i < xattrs; i++) {
+ struct xattr_list *xattr = &xattr_list[i];
+ struct squashfs_xattr_entry entry;
+ struct squashfs_xattr_val val;
+
+ xp = get_xattr_space(sizeof(entry) + xattr->size, NULL);
+ entry.type = xattr->type;
+ entry.size = xattr->size;
+ SQUASHFS_SWAP_XATTR_ENTRY(&entry, xp);
+ memcpy(xp + sizeof(entry), xattr->name, xattr->size);
+
+ if(xattr->type & XATTR_VALUE_OOL) {
+ int size = sizeof(val) + XATTR_VALUE_OOL_SIZE;
+ xp = get_xattr_space(size, NULL);
+ val.vsize = XATTR_VALUE_OOL_SIZE;
+ SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+ SQUASHFS_SWAP_LONG_LONGS(&xattr->ool_value, xp +
+ sizeof(val), 1);
+ } else {
+ int size = sizeof(val) + xattr->vsize;
+ xp = get_xattr_space(size, &xattr->ool_value);
+ val.vsize = xattr->vsize;
+ SQUASHFS_SWAP_XATTR_VAL(&val, xp);
+ memcpy(xp + sizeof(val), xattr->value, xattr->vsize);
+ }
+ }
+
+ /*
+ * Add to xattr id lookup table
+ */
+ return get_xattr_id(xattrs, xattr_list, xattr_disk, xattr_dupl);
+}
+
+
+/*
+ * Instantiate two implementations of merge sort with different types and names
+ */
+SORT(sort_list, xattr_add, name, next);
+SORT(sort_xattr_list, xattr_list, full_name, vnext);
+
+
+int read_xattrs(void *d, int type)
+{
+ struct dir_ent *dir_ent = d;
+ struct inode_info *inode = dir_ent->inode;
+ char *filename = pathname(dir_ent);
+ struct xattr_list *xattr_list = NULL, *head;
+ int count, i = 0, j;
+ struct xattr_add *l1 = xattr_add_list, *l2 = NULL, *l3 = NULL;
+ struct xattr_add *action_add_list;
+
+ if(no_xattrs || inode->root_entry)
+ return SQUASHFS_INVALID_XATTR;
+
+ if(IS_TARFILE(inode))
+ i = read_xattrs_from_tarfile(inode, &xattr_list);
+ else if(!inode->dummy_root_dir && !IS_PSEUDO(inode))
+ i = read_xattrs_from_system(dir_ent, filename, &xattr_list);
+
+ action_add_list = eval_xattr_add_actions(root_dir, dir_ent, &count);
+
+ /*
+ * At this point we may have up to 3 lists of xattrs:
+ *
+ * 1. a list of xattrs created by the global xattrs-add command line
+ * 2. a list of xattrs created by one or more pseudo xattr definitions
+ * on this file.
+ * 3. a list of xattrs created by one or more xattr add actions on this
+ * file.
+ *
+ * The global xattrs are sorted, but, the pseudo xattr list and action
+ * xattr list are not.
+ *
+ * So sort the pseudo and action lists, and merge the three sorted lists
+ * together whilst adding them to the xattr_list
+ */
+
+ if(inode->xattr) {
+ sort_list(&(inode->xattr->xattr), inode->xattr->count);
+ l2 = inode->xattr->xattr;
+ }
+
+ if(action_add_list) {
+ sort_list(&action_add_list, count);
+ l3 = action_add_list;
+ }
+
+ while(l1 || l2 || l3) {
+ struct xattr_list *x;
+ struct xattr_add *entry;
+
+ if(l1 && l2 && l3) {
+ if(strcmp(l1->name, l2->name) <= 0) {
+ if(strcmp(l1->name, l3->name) <= 0) {
+ entry= l1;
+ l1 = l1->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+ } else {
+ if(strcmp(l2->name, l3->name) <= 0) {
+ entry = l2;
+ l2 = l2->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+ }
+ } else if(l1 && l2) {
+ if(strcmp(l1->name, l2->name) <= 0) {
+ entry = l1;
+ l1 = l1->next;
+ } else {
+ entry = l2;
+ l2 = l2->next;
+ }
+ } else if(l1 && l3) {
+ if(strcmp(l1->name, l3->name) <= 0) {
+ entry = l1;
+ l1 = l1->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+ } else if(l2 && l3) {
+ if(strcmp(l2->name, l3->name) <= 0) {
+ entry = l2;
+ l2 = l2->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+ } else if(l1) {
+ entry = l1;
+ l1 = l1->next;
+ } else if(l2) {
+ entry = l2;
+ l2 = l2->next;
+ } else {
+ entry = l3;
+ l3 = l3->next;
+ }
+
+ /*
+ * User extended attributes are only allowed for files and
+ * directories. See man 7 xattr for explanation.
+ */
+ if((entry->type == SQUASHFS_XATTR_USER) &&
+ (type != SQUASHFS_FILE_TYPE &&
+ type != SQUASHFS_DIR_TYPE))
+ continue;
+
+ x = realloc(xattr_list, (i + 1) * sizeof(struct xattr_list));
+ if(x == NULL)
+ MEM_ERROR();
+ xattr_list = x;
+
+ xattr_list[i].type = entry->type;
+ xattr_copy_prefix(&xattr_list[i], entry->type, entry->name);
+
+ xattr_list[i].value = malloc(entry->vsize);
+ if(xattr_list[i].value == NULL)
+ MEM_ERROR();
+
+ memcpy(xattr_list[i].value, entry->value, entry->vsize);
+ xattr_list[i].vsize = entry->vsize;
+
+ TRACE("read_xattrs: filename %s, xattr name %s,"
+ " vsize %d\n", filename, xattr_list[i].full_name,
+ xattr_list[i].vsize);
+ i++;
+ }
+
+ if(i == 0)
+ return SQUASHFS_INVALID_XATTR;
+ else if(i == 1)
+ goto skip_dup_check;
+
+ /*
+ * Sort and check xattr list for duplicates
+ */
+ for(j = 1; j < i; j++)
+ xattr_list[j - 1].vnext = &xattr_list[j];
+
+ xattr_list[i - 1].vnext = NULL;
+ head = xattr_list;
+
+ sort_xattr_list(&head, i);
+
+ for(j = 0; j < i - 1; head=head->vnext, j++)
+ if(strcmp(head->full_name, head->vnext->full_name) == 0)
+ BAD_ERROR("Duplicate xattr name %s in file %s\n",
+ head->full_name, filename);
+
+skip_dup_check:
+ return generate_xattrs(i, xattr_list);
+}
+
+
+/*
+ * Add the existing xattr ids and xattr metadata in the file system being
+ * appended to, to the in-memory xattr cache. This allows duplicate checking to
+ * take place against the xattrs already in the file system being appended to,
+ * and ensures the pre-existing xattrs are written out along with any new xattrs
+ */
+int get_xattrs(int fd, struct squashfs_super_block *sBlk)
+{
+ int res, i, id;
+ unsigned int count, ids;
+
+ TRACE("get_xattrs\n");
+
+ if(sBlk->xattr_id_table_start == SQUASHFS_INVALID_BLK)
+ return SQUASHFS_INVALID_BLK;
+
+ ids = read_xattrs_from_disk(fd, sBlk, FALSE, NULL);
+ if(ids == 0)
+ EXIT_MKSQUASHFS();
+
+ /*
+ * for each xattr id read and construct its list of xattr
+ * name:value pairs, and add them to the in-memory xattr cache
+ */
+ for(i = 0; i < ids; i++) {
+ struct xattr_list *xattr_list = get_xattr(i, &count, &res);
+ if(xattr_list == NULL && res == FALSE)
+ EXIT_MKSQUASHFS();
+
+ if(res) {
+ free_xattr(xattr_list, count);
+ return FALSE;
+ }
+ id = generate_xattrs(count, xattr_list);
+
+ /*
+ * Sanity check, the new xattr id should be the same as the
+ * xattr id in the original file system
+ */
+ if(id != i) {
+ ERROR("BUG, different xattr_id in get_xattrs\n");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * Save current state of xattrs, needed for restoring state in the event of an
+ * abort in appending
+ */
+void save_xattrs()
+{
+ /* save the current state of the compressed xattr data */
+ sxattr_bytes = xattr_bytes;
+ stotal_xattr_bytes = total_xattr_bytes;
+
+ /*
+ * save the current state of the cached uncompressed xattr data.
+ * Note we have to save the contents of the data cache because future
+ * operations will delete the current contents
+ */
+ sdata_cache = malloc(cache_bytes);
+ if(sdata_cache == NULL)
+ MEM_ERROR();
+
+ memcpy(sdata_cache, data_cache, cache_bytes);
+ scache_bytes = cache_bytes;
+
+ /* save the current state of the xattr id table */
+ sxattr_ids = xattr_ids;
+}
+
+
+/*
+ * Restore xattrs in the event of an abort in appending
+ */
+void restore_xattrs()
+{
+ /* restore the state of the compressed xattr data */
+ xattr_bytes = sxattr_bytes;
+ total_xattr_bytes = stotal_xattr_bytes;
+
+ /* restore the state of the uncomoressed xattr data */
+ memcpy(data_cache, sdata_cache, scache_bytes);
+ cache_bytes = scache_bytes;
+
+ /* restore the state of the xattr id table */
+ xattr_ids = sxattr_ids;
+}
+
+
+regex_t *xattr_regex(char *pattern, char *option)
+{
+ int error;
+ regex_t *preg = malloc(sizeof(regex_t));
+
+ if(preg == NULL)
+ MEM_ERROR();
+
+ error = regcomp(preg, pattern, REG_EXTENDED|REG_NOSUB);
+
+ if(error) {
+ char str[1024]; /* overflow safe */
+
+ regerror(error, preg, str, 1024);
+ BAD_ERROR("invalid regex %s in xattrs-%s option, because %s\n",
+ pattern, option, str);
+ }
+
+ return preg;
+}
+
+
+char *base64_decode(char *source, int size, int *bytes)
+{
+ char *dest;
+ unsigned char *dest_ptr, *source_ptr = (unsigned char *) source;
+ int bit_pos = 0;
+ int output = 0;
+ int count;
+
+ if(size % 4 == 0) {
+ /* Check for and ignore any end padding */
+ if(source_ptr[size - 2] == '=' && source_ptr[size - 1] == '=')
+ size -= 2;
+ else if(source_ptr[size - 1] == '=')
+ size --;
+ }
+
+ /* Calculate number of bytes the base64 encoding represents */
+ count = size * 3 / 4;
+
+ dest = malloc(count);
+
+ for(dest_ptr = (unsigned char *) dest; size; size --, source_ptr ++) {
+ int value = *source_ptr;
+
+ if(value >= 'A' && value <= 'Z')
+ value -= 'A';
+ else if(value >= 'a' && value <= 'z')
+ value -= 'a' - 26;
+ else if(value >= '0' && value <= '9')
+ value -= '0' - 52;
+ else if(value == '+')
+ value = 62;
+ else if(value == '/')
+ value = 63;
+ else
+ goto failed;
+
+ if(bit_pos == 24) {
+ dest_ptr[0] = output >> 16;
+ dest_ptr[1] = (output >> 8) & 0xff;
+ dest_ptr[2] = output & 0xff;
+ bit_pos = 0;
+ output = 0;
+ dest_ptr += 3;
+ }
+
+ output = (output << 6) | value;
+ bit_pos += 6;
+ }
+
+ output = output << (24 - bit_pos);
+
+ if(bit_pos == 6)
+ goto failed;
+
+ if(bit_pos >= 12)
+ dest_ptr[0] = output >> 16;
+
+ if(bit_pos >= 18)
+ dest_ptr[1] = (output >> 8) & 0xff;
+
+ if(bit_pos == 24)
+ dest_ptr[2] = output & 0xff;
+
+ *bytes = (dest_ptr - (unsigned char *) dest) + (bit_pos / 8);
+ return dest;
+
+failed:
+ free(dest);
+ return NULL;
+}
+
+
+char *hex_decode(char *source, int size, int *bytes)
+{
+ char *dest;
+ unsigned char *dest_ptr, *source_ptr = (unsigned char *) source;
+ int first = 0;
+
+ if(size % 2 != 0)
+ return NULL;
+
+ dest = malloc(size >> 2);
+ if(dest == NULL)
+ MEM_ERROR();
+
+ for(dest_ptr = (unsigned char *) dest ; size; size --) {
+ int digit = *source_ptr ++;
+
+ if(digit >= 'A' && digit <= 'F')
+ digit -= 'A' - 10;
+ else if(digit >= 'a' && digit <= 'f')
+ digit -= 'a' - 10;
+ else if(digit >= '0' && digit <= '9')
+ digit -= '0';
+ else
+ goto failed;
+
+ if(size % 2 == 0)
+ first = digit;
+ else
+ *dest_ptr ++ = (first << 4) | digit;
+ }
+
+ *bytes = dest_ptr - (unsigned char *) dest;
+
+ return dest;
+
+failed:
+ free(dest);
+ return NULL;
+}
+
+
+int decode_octal(unsigned char *ptr)
+{
+ int i, output = 0;
+
+ for(i = 0; i < 3; i++) {
+ int val = *ptr ++;
+
+ if(val < '0' || val > '7')
+ return -1;
+
+ output = (output << 3) | (val - '0');
+ }
+
+ return output < 256 ? output : -1;
+}
+
+
+char *text_decode(char *source, int *bytes)
+{
+ unsigned char *dest, *dest_ptr, *ptr = (unsigned char *) source;
+ int size = 0;
+
+ for(; *ptr; size ++, ptr ++) {
+ if(*ptr == '\\') {
+ if(ptr[1] != '\0' && ptr[2] != '\0' && ptr[3] != '\0')
+ ptr += 3;
+ else
+ return NULL;
+ }
+ }
+
+ dest = malloc(size);
+ if(dest == NULL)
+ MEM_ERROR();
+
+ *bytes = size;
+
+ for(ptr = (unsigned char *) source, dest_ptr = dest; size; size --) {
+ if(*ptr == '\\') {
+ int res = decode_octal(++ ptr);
+
+ if(res == -1)
+ goto failed;
+
+ *dest_ptr ++ = res;
+ ptr += 3;
+ } else
+ *dest_ptr ++ = *ptr ++;
+ }
+
+ return (char *) dest;
+
+failed:
+ free(dest);
+ return NULL;
+}
+
+
+struct xattr_add *xattr_parse(char *str, char *pre, char *option)
+{
+ struct xattr_add *entry;
+ char *value;
+ int prefix, size;
+
+ /*
+ * Look for the "=" separating the xattr name from the value
+ */
+ for(value = str; *value != '=' && *value != '\0'; value ++);
+ if(*value == '\0') {
+ ERROR("%sinvalid argument \"%s\" in %s option, because no "
+ "`=` found\n", pre, str, option);
+ goto failed;
+ }
+
+ if(value == str) {
+ ERROR("%sinvalid argument \"%s\" in %s option, because xattr "
+ "name is empty\n", pre, str, option);
+ goto failed;
+ }
+
+ if(*(value + 1) == '\0') {
+ ERROR("%sinvalid argument \"%s\" in %s option, because xattr "
+ "value is empty\n", pre, str, option);
+ goto failed;
+ }
+
+ entry = malloc(sizeof(struct xattr_add));
+ if(entry == NULL)
+ MEM_ERROR();
+
+ entry->name = strndup(str, value++ - str);
+ entry->type = xattr_get_type(entry->name);
+
+ if(entry->type == -1) {
+ ERROR("%s%s: unrecognised xattr prefix in %s\n", pre, option,
+ entry->name);
+ goto failed2;
+ }
+
+ /*
+ * Evaluate the format prefix (if any)
+ */
+ if(*(value + 1) == '\0')
+ /*
+ * By definition an xattr value of 1 byte hasn't a prefix,
+ * and should be treated as binary
+ */
+ prefix = 0;
+ else
+ prefix = (*value << 8) + *(value + 1);
+
+ switch(prefix) {
+ case PREFIX_BASE64_0S:
+ case PREFIX_BASE64_0s:
+ value += 2;
+ if(*value == 0) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "xattr value is empty after format prefix 0S "
+ "or 0s\n", pre, str, option);
+ goto failed2;
+ }
+
+ entry->value = base64_decode(value, strlen(value), &size);
+ entry->vsize = size;
+
+ if(entry->value == NULL) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "invalid base64 value\n", pre, str, option);
+ goto failed2;
+ }
+ break;
+
+ case PREFIX_HEX_0X:
+ case PREFIX_HEX_0x:
+ value += 2;
+ if(*value == 0) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "xattr value is empty after format prefix 0X "
+ "or 0x\n", pre, str, option);
+ goto failed2;
+ }
+
+ entry->value = hex_decode(value, strlen(value), &size);
+ entry->vsize = size;
+
+ if(entry->value == NULL) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "invalid hexidecimal value\n", pre, str, option);
+ goto failed2;
+ }
+ break;
+
+ case PREFIX_TEXT_0T:
+ case PREFIX_TEXT_0t:
+ value += 2;
+ if(*value == 0) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "xattr value is empty after format prefix 0T "
+ "or 0t\n", pre, str, option);
+ goto failed2;
+ }
+
+ entry->value = text_decode(value, &size);
+ entry->vsize = size;
+
+ if(entry->value == NULL) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "invalid text value\n", pre, str, option);
+ goto failed2;
+ }
+ break;
+
+ case PREFIX_BINARY_0B:
+ case PREFIX_BINARY_0b:
+ value += 2;
+ if(*value == 0) {
+ ERROR("%sinvalid argument %s in %s option, because "
+ "xattr value is empty after format prefix 0B "
+ "or 0b\n", pre, str, option);
+ goto failed2;
+ }
+
+ /* fall through */
+ default:
+ entry->vsize = strlen(value);
+ entry->value = malloc(entry->vsize);
+
+ if(entry->value == NULL)
+ MEM_ERROR();
+
+ memcpy(entry->value, value, entry->vsize);
+ }
+
+ return entry;
+
+failed2:
+ free(entry->name);
+ free(entry);
+failed:
+ return NULL;
+}
+
+
+void xattrs_add(char *str)
+{
+ struct xattr_add *entry;
+
+ entry = xattr_parse(str, "FATAL ERROR: ", "xattrs-add");
+
+ if(entry) {
+ entry->next = xattr_add_list;
+ xattr_add_list = entry;
+
+ xattr_add_count ++;
+ } else
+ exit(1);
+}
+
+
+int add_xattrs(void) {
+ return xattr_add_count;
+}
+
+
+void sort_xattr_add_list(void)
+{
+ sort_list(&xattr_add_list, xattr_add_count);
+}