diff options
-rw-r--r-- | lib/sqfs/xattr_writer.c | 306 |
1 files changed, 301 insertions, 5 deletions
diff --git a/lib/sqfs/xattr_writer.c b/lib/sqfs/xattr_writer.c index 7643d49..318c613 100644 --- a/lib/sqfs/xattr_writer.c +++ b/lib/sqfs/xattr_writer.c @@ -20,6 +20,7 @@ #include <stdlib.h> #include <string.h> +#include <assert.h> #define XATTR_KEY_BUCKETS 31 @@ -92,6 +93,9 @@ typedef struct kv_block_desc_t { struct kv_block_desc_t *next; size_t start; size_t count; + + uint64_t start_ref; + size_t size_bytes; } kv_block_desc_t; struct sqfs_xattr_writer_t { @@ -290,12 +294,304 @@ int sqfs_xattr_writer_end(sqfs_xattr_writer_t *xwr, sqfs_u32 *out) /*****************************************************************************/ +static sqfs_s32 write_key(sqfs_meta_writer_t *mw, const char *key, + bool value_is_ool) +{ + sqfs_xattr_entry_t kent; + int type, err; + size_t len; + + type = sqfs_get_xattr_prefix_id(key); + assert(type >= 0); + + key = strchr(key, '.'); + assert(key != NULL); + ++key; + len = strlen(key); + + if (value_is_ool) + type |= SQFS_XATTR_FLAG_OOL; + + memset(&kent, 0, sizeof(kent)); + kent.type = htole16(type); + kent.size = htole16(len); + + err = sqfs_meta_writer_append(mw, &kent, sizeof(kent)); + if (err) + return err; + + err = sqfs_meta_writer_append(mw, key, len); + if (err) + return err; + + return sizeof(kent) + len; +} + +static sqfs_s32 write_value(sqfs_meta_writer_t *mw, const char *value_str, + sqfs_u64 *value_ref_out) +{ + sqfs_xattr_value_t vent; + sqfs_u32 offset; + sqfs_u64 block; + size_t size; + void *value; + int err; + + value = from_base32(value_str, &size); + if (value == NULL) + return SQFS_ERROR_ALLOC; + + memset(&vent, 0, sizeof(vent)); + vent.size = htole32(size); + + sqfs_meta_writer_get_position(mw, &block, &offset); + *value_ref_out = (block << 16) | (offset & 0xFFFF); + + err = sqfs_meta_writer_append(mw, &vent, sizeof(vent)); + if (err) + goto fail; + + err = sqfs_meta_writer_append(mw, value, size); + if (err) + goto fail; + + return sizeof(vent) + size; +fail: + free(value); + return err; +} + +static sqfs_s32 write_value_ool(sqfs_meta_writer_t *mw, sqfs_u64 location) +{ + sqfs_xattr_value_t vent; + sqfs_u64 ref; + int err; + + memset(&vent, 0, sizeof(vent)); + vent.size = htole32(sizeof(location)); + ref = htole64(location); + + err = sqfs_meta_writer_append(mw, &vent, sizeof(vent)); + if (err) + return err; + + err = sqfs_meta_writer_append(mw, &ref, sizeof(ref)); + if (err) + return err; + + return sizeof(vent) + sizeof(ref); +} + +static bool should_store_ool(const char *val_str, size_t refcount) +{ + 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 iff refcount > 1 and: + refcount * len > len + (refcount - 1) * 8 + => refcount * len - len > (refcount - 1) * 8 + => (refcount - 1) * len > (refcount - 1) * 8 + => len > 8 + */ + return (strlen(val_str) / 2) > sizeof(sqfs_u64); +} + +static int write_block_pairs(sqfs_xattr_writer_t *xwr, sqfs_meta_writer_t *mw, + kv_block_desc_t *blk, sqfs_u64 *ool_locations) +{ + sqfs_u32 key_idx, val_idx; + const char *key_str, *value_str; + sqfs_s32 diff, total = 0; + size_t i, refcount; + sqfs_u64 ref; + + for (i = 0; i < blk->count; ++i) { + key_idx = GET_KEY(xwr->kv_pairs[blk->start + i]); + val_idx = GET_VALUE(xwr->kv_pairs[blk->start + i]); + + key_str = str_table_get_string(&xwr->keys, key_idx); + value_str = str_table_get_string(&xwr->values, val_idx); + + if (ool_locations[val_idx] == 0xFFFFFFFFFFFFFFFFUL) { + diff = write_key(mw, key_str, false); + if (diff < 0) + return diff; + total += diff; + + diff = write_value(mw, value_str, &ref); + if (diff < 0) + return diff; + total += diff; + + refcount = str_table_get_ref_count(&xwr->values, + val_idx); + + if (should_store_ool(value_str, refcount)) + ool_locations[val_idx] = ref; + } else { + diff = write_key(mw, key_str, true); + if (diff < 0) + return diff; + total += diff; + + diff = write_value_ool(mw, ool_locations[val_idx]); + if (diff < 0) + return diff; + total += diff; + } + } + + return total; +} + +static int write_kv_pairs(sqfs_xattr_writer_t *xwr, sqfs_meta_writer_t *mw) +{ + sqfs_u64 block, *ool_locations; + kv_block_desc_t *blk; + sqfs_u32 offset; + sqfs_s32 size; + size_t i; + + ool_locations = alloc_array(sizeof(ool_locations[0]), + xwr->values.num_strings); + if (ool_locations == NULL) + return SQFS_ERROR_ALLOC; + + for (i = 0; i < xwr->values.num_strings; ++i) + ool_locations[i] = 0xFFFFFFFFFFFFFFFFUL; + + for (blk = xwr->kv_blocks; blk != NULL; blk = blk->next) { + sqfs_meta_writer_get_position(mw, &block, &offset); + blk->start_ref = (block << 16) | (offset & 0xFFFF); + + size = write_block_pairs(xwr, mw, blk, ool_locations); + if (size < 0) { + free(ool_locations); + return size; + } + + blk->size_bytes = size; + } + + free(ool_locations); + return sqfs_meta_writer_flush(mw); +} + +static int write_id_table(sqfs_xattr_writer_t *xwr, sqfs_meta_writer_t *mw, + sqfs_u64 *locations) +{ + sqfs_xattr_id_t id_ent; + kv_block_desc_t *blk; + sqfs_u32 offset; + sqfs_u64 block; + size_t i = 0; + int err; + + locations[i++] = 0; + + for (blk = xwr->kv_blocks; blk != NULL; blk = blk->next) { + memset(&id_ent, 0, sizeof(id_ent)); + id_ent.xattr = htole64(blk->start_ref); + id_ent.count = htole32(blk->count); + id_ent.size = htole32(blk->size_bytes); + + err = sqfs_meta_writer_append(mw, &id_ent, sizeof(id_ent)); + if (err) + return err; + + sqfs_meta_writer_get_position(mw, &block, &offset); + if (block != locations[i - 1]) + locations[i++] = block; + } + + return sqfs_meta_writer_flush(mw); +} + +static int write_location_table(sqfs_xattr_writer_t *xwr, uint64_t kv_start, + sqfs_file_t *file, const sqfs_super_t *super, + sqfs_u64 *locations, size_t loc_count) +{ + sqfs_xattr_id_table_t idtbl; + int err; + + memset(&idtbl, 0, sizeof(idtbl)); + idtbl.xattr_table_start = htole64(kv_start); + idtbl.xattr_ids = htole32(xwr->num_blocks); + + err = file->write_at(file, super->xattr_id_table_start, + &idtbl, sizeof(idtbl)); + if (err) + return err; + + return file->write_at(file, super->xattr_id_table_start + sizeof(idtbl), + locations, sizeof(locations[0]) * loc_count); +} + +static int alloc_location_table(sqfs_xattr_writer_t *xwr, sqfs_u64 **tbl_out, + size_t *szout) +{ + sqfs_u64 *locations; + size_t size, count; + + if (SZ_MUL_OV(xwr->num_blocks, sizeof(sqfs_xattr_id_t), &size)) + return SQFS_ERROR_OVERFLOW; + + count = size / SQFS_META_BLOCK_SIZE; + if (size % SQFS_META_BLOCK_SIZE) + ++count; + + locations = alloc_array(sizeof(sqfs_u64), count); + if (locations == NULL) + return SQFS_ERROR_ALLOC; + + *tbl_out = locations; + *szout = count; + return 0; +} + int sqfs_xattr_writer_flush(sqfs_xattr_writer_t *xwr, sqfs_file_t *file, sqfs_super_t *super, sqfs_compressor_t *cmp) { - (void)xwr; - (void)file; - (void)super; - (void)cmp; - return 0; + sqfs_u64 *locations = NULL, kv_start, id_start; + sqfs_meta_writer_t *mw; + size_t i, count; + int err; + + mw = sqfs_meta_writer_create(file, cmp, 0); + if (mw == NULL) + return SQFS_ERROR_ALLOC; + + kv_start = file->get_size(file); + err = write_kv_pairs(xwr, mw); + if (err) + goto out; + + sqfs_meta_writer_reset(mw); + + id_start = file->get_size(file); + err = alloc_location_table(xwr, &locations, &count); + if (err) + goto out; + + err = write_id_table(xwr, mw, locations); + if (err) + goto out; + + super->xattr_id_table_start = file->get_size(file); + super->flags &= ~SQFS_FLAG_NO_XATTRS; + + for (i = 0; i < count; ++i) + locations[i] = htole64(locations[i] + id_start); + + err = write_location_table(xwr, kv_start, file, super, + locations, count); +out: + free(locations); + sqfs_meta_writer_destroy(mw); + return err; } |