aboutsummaryrefslogtreecommitdiff
path: root/lib/sqfshelper/write_xattr.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqfshelper/write_xattr.c')
-rw-r--r--lib/sqfshelper/write_xattr.c279
1 files changed, 279 insertions, 0 deletions
diff --git a/lib/sqfshelper/write_xattr.c b/lib/sqfshelper/write_xattr.c
new file mode 100644
index 0000000..2263fbe
--- /dev/null
+++ b/lib/sqfshelper/write_xattr.c
@@ -0,0 +1,279 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * write_xattr.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "meta_writer.h"
+#include "highlevel.h"
+#include "util.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+static int write_key(meta_writer_t *mw, const char *key, tree_xattr_t *xattr,
+ bool value_is_ool)
+{
+ sqfs_xattr_entry_t kent;
+ int type;
+
+ type = sqfs_get_xattr_prefix_id(key);
+ if (type < 0) {
+ fprintf(stderr, "unsupported xattr key '%s'\n", key);
+ return -1;
+ }
+
+ key = strchr(key, '.');
+ assert(key != NULL);
+ ++key;
+
+ if (value_is_ool)
+ type |= SQUASHFS_XATTR_FLAG_OOL;
+
+ kent.type = htole16(type);
+ kent.size = htole16(strlen(key));
+
+ if (meta_writer_append(mw, &kent, sizeof(kent)))
+ return -1;
+ if (meta_writer_append(mw, key, strlen(key)))
+ return -1;
+
+ xattr->size += sizeof(sqfs_xattr_entry_t) + strlen(key);
+ return 0;
+}
+
+static int write_value(meta_writer_t *mw, const char *value,
+ tree_xattr_t *xattr, uint64_t *value_ref_out)
+{
+ sqfs_xattr_value_t vent;
+ uint32_t offset;
+ uint64_t block;
+
+ meta_writer_get_position(mw, &block, &offset);
+ *value_ref_out = (block << 16) | (offset & 0xFFFF);
+
+ vent.size = htole32(strlen(value));
+
+ if (meta_writer_append(mw, &vent, sizeof(vent)))
+ return -1;
+
+ if (meta_writer_append(mw, value, strlen(value)))
+ return -1;
+
+ xattr->size += sizeof(vent) + strlen(value);
+ return 0;
+}
+
+static int write_value_ool(meta_writer_t *mw, uint64_t location,
+ tree_xattr_t *xattr)
+{
+ sqfs_xattr_value_t vent;
+ uint64_t ref;
+
+ vent.size = htole32(sizeof(location));
+ if (meta_writer_append(mw, &vent, sizeof(vent)))
+ return -1;
+
+ ref = htole64(location);
+ if (meta_writer_append(mw, &ref, sizeof(ref)))
+ return -1;
+
+ xattr->size += sizeof(vent) + sizeof(ref);
+ return 0;
+}
+
+static bool should_store_ool(fstree_t *fs, const char *value, size_t index)
+{
+ size_t refcount;
+
+ refcount = str_table_get_ref_count(&fs->xattr_values, index);
+ if (refcount < 2)
+ return false;
+
+ /*
+ Storing in line needs this many bytes: refcount * len
+
+ Storing out-of-line needs this many: len + (refcount - 1) * 8
+
+ Out-of-line prefereable if:
+ refcount * len > len + (refcount - 1) * 8
+ => refcount * len - len > (refcount - 1) * 8
+ => (refcount - 1) * len > (refcount - 1) * 8
+ => len > 8
+
+ Note that this only holds iff refcount - 1 != 0, i.e. refcount > 1,
+ otherwise we would be dividing by 0 in the 3rd step.
+ */
+ return strlen(value) > sizeof(uint64_t);
+}
+
+static int write_kv_pairs(fstree_t *fs, meta_writer_t *mw, tree_xattr_t *xattr,
+ uint64_t *ool_locations)
+{
+ uint32_t key_idx, val_idx;
+ const char *key, *value;
+ uint64_t ref;
+ size_t i;
+
+ for (i = 0; i < xattr->num_attr; ++i) {
+ key_idx = xattr->attr[i].key_index;
+ val_idx = xattr->attr[i].value_index;
+
+ key = str_table_get_string(&fs->xattr_keys, key_idx);
+ value = str_table_get_string(&fs->xattr_values, val_idx);
+
+ if (ool_locations[val_idx] == 0xFFFFFFFFFFFFFFFF) {
+ if (write_key(mw, key, xattr, false))
+ return -1;
+
+ if (write_value(mw, value, xattr, &ref))
+ return -1;
+
+ if (should_store_ool(fs, value, val_idx))
+ ool_locations[val_idx] = ref;
+ } else {
+ if (write_key(mw, key, xattr, true))
+ return -1;
+
+ if (write_value_ool(mw, ool_locations[val_idx], xattr))
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static uint64_t *create_ool_locations_table(fstree_t *fs)
+{
+ uint64_t *table;
+ size_t i;
+
+ table = alloc_array(sizeof(uint64_t), fs->xattr_values.num_strings);
+
+ if (table == NULL) {
+ perror("allocating Xattr OOL locations table");
+ return NULL;
+ }
+
+ for (i = 0; i < fs->xattr_values.num_strings; ++i) {
+ table[i] = 0xFFFFFFFFFFFFFFFFUL;
+ }
+
+ return table;
+}
+
+int write_xattr(int outfd, fstree_t *fs, sqfs_super_t *super,
+ compressor_t *cmp)
+{
+ uint64_t kv_start, id_start, block, *tbl, *ool_locations;
+ size_t i = 0, count = 0, blocks;
+ sqfs_xattr_id_table_t idtbl;
+ sqfs_xattr_id_t id_ent;
+ meta_writer_t *mw;
+ tree_xattr_t *it;
+ uint32_t offset;
+
+ if (fs->xattr == NULL)
+ return 0;
+
+ ool_locations = create_ool_locations_table(fs);
+ if (ool_locations == NULL)
+ return -1;
+
+ mw = meta_writer_create(outfd, cmp, false);
+ if (mw == NULL)
+ goto fail_ool;
+
+ /* write xattr key-value pairs */
+ kv_start = super->bytes_used;
+
+ for (it = fs->xattr; it != NULL; it = it->next) {
+ meta_writer_get_position(mw, &it->block, &it->offset);
+ it->size = 0;
+
+ if (write_kv_pairs(fs, mw, it, ool_locations))
+ goto fail_mw;
+
+ ++count;
+ }
+
+ if (meta_writer_flush(mw))
+ goto fail_mw;
+
+ meta_writer_get_position(mw, &block, &offset);
+ meta_writer_reset(mw);
+
+ super->bytes_used += block;
+
+ /* allocate location table */
+ blocks = (count * sizeof(id_ent)) / SQFS_META_BLOCK_SIZE;
+
+ if ((count * sizeof(id_ent)) % SQFS_META_BLOCK_SIZE)
+ ++blocks;
+
+ tbl = alloc_array(sizeof(uint64_t), blocks);
+
+ if (tbl == NULL) {
+ perror("generating xattr ID table");
+ goto fail_mw;
+ }
+
+ /* write ID table referring to key value pairs, record offsets */
+ id_start = 0;
+ tbl[i++] = htole64(super->bytes_used);
+
+ for (it = fs->xattr; it != NULL; it = it->next) {
+ id_ent.xattr = htole64((it->block << 16) | it->offset);
+ id_ent.count = htole32(it->num_attr);
+ id_ent.size = htole32(it->size);
+
+ if (meta_writer_append(mw, &id_ent, sizeof(id_ent)))
+ goto fail_tbl;
+
+ meta_writer_get_position(mw, &block, &offset);
+
+ if (block != id_start) {
+ id_start = block;
+ tbl[i++] = htole64(super->bytes_used + id_start);
+ }
+ }
+
+ if (meta_writer_flush(mw))
+ goto fail_tbl;
+
+ meta_writer_get_position(mw, &block, &offset);
+ super->bytes_used += block;
+
+ /* write offset table */
+ idtbl.xattr_table_start = htole64(kv_start);
+ idtbl.xattr_ids = htole32(count);
+ idtbl.unused = 0;
+
+ if (write_data("writing xattr ID table", outfd, &idtbl, sizeof(idtbl)))
+ goto fail_tbl;
+
+ if (write_data("writing xattr ID table",
+ outfd, tbl, sizeof(tbl[0]) * blocks)) {
+ goto fail_tbl;
+ }
+
+ super->xattr_id_table_start = super->bytes_used;
+ super->bytes_used += sizeof(idtbl) + sizeof(tbl[0]) * blocks;
+ super->flags &= ~SQFS_FLAG_NO_XATTRS;
+
+ free(tbl);
+ meta_writer_destroy(mw);
+ free(ool_locations);
+ return 0;
+fail_tbl:
+ free(tbl);
+fail_mw:
+ meta_writer_destroy(mw);
+fail_ool:
+ free(ool_locations);
+ return -1;
+}