/* * Create a squashfs filesystem. This is a highly compressed read only * filesystem. * * Copyright (c) 2008, 2009, 2010, 2012, 2014 * Phillip Lougher * * 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 */ #define TRUE 1 #define FALSE 0 #include #include #include #include #include #include #include #include #include #include #include "squashfs_fs.h" #include "squashfs_swap.h" #include "mksquashfs.h" #include "xattr.h" #include "error.h" #include "progressbar.h" /* 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]; /* 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; /* helper functions from mksquashfs.c */ extern unsigned short get_checksum(char *, int, unsigned short); extern void write_destination(int, long long, int, void *); extern long long generic_write_table(int, 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 int read_xattrs_from_disk(int, struct squashfs_super_block *); extern struct xattr_list *get_xattr(int, unsigned int *, int); extern struct prefix prefix_table[]; static int get_prefix(struct xattr_list *xattr, char *name) { int i; xattr->full_name = strdup(name); for(i = 0; prefix_table[i].type != -1; i++) { struct prefix *p = &prefix_table[i]; if(strncmp(xattr->full_name, p->prefix, strlen(p->prefix)) == 0) break; } if(prefix_table[i].type != -1) { xattr->name = xattr->full_name + strlen(prefix_table[i].prefix); xattr->size = strlen(xattr->name); } return prefix_table[i].type; } static int read_xattrs_from_system(char *filename, struct xattr_list **xattrs) { ssize_t size, vsize; char *xattr_names, *p; int i; struct xattr_list *xattr_list = NULL; 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"); } 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"); return 0; } } break; } for(i = 0, p = xattr_names; p < xattr_names + size; i++) { struct xattr_list *x = realloc(xattr_list, (i + 1) * sizeof(struct xattr_list)); if(x == NULL) MEM_ERROR(); xattr_list = x; xattr_list[i].type = get_prefix(&xattr_list[i], p); p += strlen(p) + 1; if(xattr_list[i].type == -1) { ERROR("Unrecognised xattr prefix %s\n", xattr_list[i].full_name); free(xattr_list[i].full_name); i--; continue; } 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"); 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"); 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); } free(xattr_names); *xattrs = 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(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); } 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) 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); } int read_xattrs(void *d) { struct dir_ent *dir_ent = d; struct inode_info *inode = dir_ent->inode; char *filename = pathname(dir_ent); struct xattr_list *xattr_list; int xattrs; if(no_xattrs || IS_PSEUDO(inode) || inode->root_entry) return SQUASHFS_INVALID_XATTR; xattrs = read_xattrs_from_system(filename, &xattr_list); if(xattrs == 0) return SQUASHFS_INVALID_XATTR; return generate_xattrs(xattrs, 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 ids, res, i, id; unsigned int count; TRACE("get_xattrs\n"); res = read_xattrs_from_disk(fd, sBlk); if(res == SQUASHFS_INVALID_BLK || res == 0) goto done; ids = res; /* * 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, 0); if(xattr_list == NULL) { res = 0; goto done; } 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"); res = 0; goto done; } } done: return res; } /* * 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; }