| /* |
| * Tux3 versioning filesystem in user space |
| * |
| * Original copyright (c) 2008 Daniel Phillips <phillips@phunq.net> |
| * Licensed under the GPL version 2 |
| * |
| * By contributing changes to this file you grant the original copyright holder |
| * the right to distribute those changes under any license. |
| */ |
| |
| #include "tux3.h" |
| #include "iattr.h" |
| |
| #ifndef trace |
| #define trace trace_on |
| #endif |
| |
| /* Xattr Atoms */ |
| |
| /* |
| * Atom count table: |
| * |
| * * Both tables are mapped into the atom table at a high logical offset. |
| * Allowing 32 bits worth of atom numbers, and with at lest 256 bytes |
| * per atom entry, we need about (1 << 32 + 8) = 1 TB dirent bytes |
| * for the atom dictionary, so the refcount tables start at block |
| * 2^40 >> 12 = 2^28. |
| * |
| * * The refcount table consists of pairs of blocks: even blocks with the low |
| * 16 bits of refcount and odd blocks with the high 16 bits. For 2^32 atoms |
| * that is 2^34 bytes at most, or 2^22 4K blocks. |
| * |
| * Atom reverse map: |
| * |
| * * When a new atom dirent is created we also set the reverse map for the |
| * dirent's atom number to the file offset at which the dirent was created. |
| * This will be 64 bits just to be lazy so that is 2^32 atoms * 8 bytes |
| * = 2^35 revmap bytes = 2^23 4K blocks. This starts just above the count |
| * table, which puts it at logical offset 2^28 + 2^23, leaving a gap after |
| * the count table in case we decide 32 bits of ref count is not enough. |
| */ |
| |
| typedef u32 atom_t; |
| |
| /* see dir.c */ |
| #define HEAD_ALIGN sizeof(inum_t) |
| #define HEAD_SIZE offsetof(tux_dirent, name) |
| /* FIXME: probably, we should limit maximum name length */ |
| #define MAX_ATOM_NAME_LEN (256 - HEAD_SIZE) |
| |
| #define MAX_ATABLE_SIZE_BITS 48 |
| #define ATOM_DICT_BITS 40 |
| #define ATOMREF_TABLE_BITS 34 |
| |
| /* |
| * FIXME: refcount bits is too small in theory. Because maximum |
| * refcount is maximum inodes. |
| */ |
| #define ATOMREF_SIZE 2 |
| #define ATOMREF_BLKBITS 1 |
| |
| #define UNATOM_SIZE 8 |
| #define UNATOM_BLKBITS 3 |
| /* Sign bit is used for error */ |
| #define UNATOM_FREE_MAGIC (0x6eadfceeULL << (sizeof(atom_t) * 8)) |
| #define UNATOM_FREE_MASK (0xffffffffULL << (sizeof(atom_t) * 8)) |
| |
| /* Initialize base address for dictionaries on atable */ |
| void atable_init_base(struct sb *sb) |
| { |
| sb->atomref_base = 1U << (ATOM_DICT_BITS - sb->blockbits); |
| sb->unatom_base = |
| sb->atomref_base + (1U << (ATOMREF_TABLE_BITS - sb->blockbits)); |
| } |
| |
| static inline atom_t entry_atom(tux_dirent *entry) |
| { |
| return be64_to_cpu(entry->inum); |
| } |
| |
| static struct buffer_head *blockread_unatom(struct inode *atable, atom_t atom, |
| unsigned *offset) |
| { |
| struct sb *sb = tux_sb(atable->i_sb); |
| unsigned shift = sb->blockbits - UNATOM_BLKBITS; |
| |
| *offset = atom & ~(-1 << shift); |
| return blockread(mapping(atable), sb->unatom_base + (atom >> shift)); |
| } |
| |
| static loff_t unatom_dict_read(struct inode *atable, atom_t atom) |
| { |
| struct buffer_head *buffer; |
| unsigned offset; |
| |
| buffer = blockread_unatom(atable, atom, &offset); |
| if (!buffer) |
| return -EIO; |
| |
| __be64 *unatom_dict = bufdata(buffer); |
| loff_t where = be64_to_cpu(unatom_dict[offset]); |
| blockput(buffer); |
| |
| return where; |
| } |
| |
| static loff_t unatom_dict_write(struct inode *atable, atom_t atom, loff_t where) |
| { |
| unsigned delta = tux3_get_current_delta(); |
| struct buffer_head *buffer, *clone; |
| loff_t old; |
| unsigned offset; |
| |
| buffer = blockread_unatom(atable, atom, &offset); |
| if (!buffer) |
| return -EIO; |
| |
| /* |
| * The atable is protected by i_mutex for now. |
| * blockdirty() should never return -EAGAIN. |
| * FIXME: need finer granularity locking |
| */ |
| clone = blockdirty(buffer, delta); |
| if (IS_ERR(clone)) { |
| assert(PTR_ERR(clone) != -EAGAIN); |
| blockput(buffer); |
| return PTR_ERR(clone); |
| } |
| |
| __be64 *unatom_dict = bufdata(clone); |
| old = be64_to_cpu(unatom_dict[offset]); |
| unatom_dict[offset] = cpu_to_be64(where); |
| mark_buffer_dirty_non(clone); |
| blockput(clone); |
| |
| return old; |
| } |
| |
| static int is_free_unatom(loff_t where) |
| { |
| return (where & UNATOM_FREE_MASK) == UNATOM_FREE_MAGIC; |
| } |
| |
| /* Convert atom to name */ |
| static int unatom(struct inode *atable, atom_t atom, char *name, unsigned size) |
| { |
| struct sb *sb = tux_sb(atable->i_sb); |
| struct buffer_head *buffer; |
| int err; |
| |
| loff_t where = unatom_dict_read(atable, atom); |
| if (where < 0) { |
| err = where; |
| goto error; |
| } |
| |
| buffer = blockread(mapping(atable), where >> sb->blockbits); |
| if (!buffer) { |
| err = -EIO; |
| goto error; |
| } |
| tux_dirent *entry = bufdata(buffer) + (where & sb->blockmask); |
| if (entry_atom(entry) != atom) { |
| tux3_fs_error(sb, "atom %x reverse entry broken", atom); |
| err = -EIO; |
| goto error_blockput; |
| } |
| unsigned len = entry->name_len; |
| if (size) { |
| if (len > size) { |
| err = -ERANGE; |
| goto error_blockput; |
| } |
| memcpy(name, entry->name, len); |
| } |
| blockput(buffer); |
| |
| return len; |
| |
| error_blockput: |
| blockput(buffer); |
| error: |
| return err; |
| } |
| |
| /* Find free atom */ |
| static int get_freeatom(struct inode *atable, atom_t *atom) |
| { |
| struct sb *sb = tux_sb(atable->i_sb); |
| atom_t freeatom = sb->freeatom; |
| |
| if (!freeatom) { |
| *atom = sb->atomgen++; |
| return 0; |
| } |
| |
| loff_t next = unatom_dict_read(atable, freeatom); |
| if (next < 0) |
| return next; |
| if (!is_free_unatom(next)) { |
| tux3_fs_error(sb, "something horrible happened"); |
| return -EIO; |
| } |
| |
| *atom = freeatom; |
| sb->freeatom = next & ~UNATOM_FREE_MASK; |
| |
| return 0; |
| } |
| |
| /* Find atom of name */ |
| static int find_atom(struct inode *atable, const char *name, unsigned len, |
| atom_t *atom) |
| { |
| struct sb *sb = tux_sb(atable->i_sb); |
| struct buffer_head *buffer; |
| tux_dirent *entry; |
| |
| entry = tux_find_entry(atable, name, len, &buffer, sb->atomdictsize); |
| if (IS_ERR(entry)) { |
| int err = PTR_ERR(entry); |
| if (err == -ENOENT) |
| return -ENODATA; |
| return err; |
| } |
| |
| *atom = entry_atom(entry); |
| blockput(buffer); |
| return 0; |
| } |
| |
| /* Make atom for name */ |
| static int make_atom(struct inode *atable, const char *name, unsigned len, |
| atom_t *atom) |
| { |
| struct sb *sb = tux_sb(atable->i_sb); |
| struct buffer_head *buffer; |
| loff_t where; |
| int err; |
| |
| err = find_atom(atable, name, len, atom); |
| if (!err) |
| return 0; |
| if (err != -ENODATA) |
| return err; |
| |
| err = get_freeatom(atable, atom); |
| if (err) |
| return err; |
| |
| where = tux_alloc_entry(atable, name, len, &sb->atomdictsize, &buffer); |
| if (where < 0) { |
| /* FIXME: better set a flag that unatom broke or something!!! */ |
| return where; |
| } |
| /* This releases buffer */ |
| tux_set_entry(buffer, bufdata(buffer) + (where & sb->blockmask), |
| *atom, 0); |
| |
| /* Enter into reverse map - maybe verify zero refs? */ |
| where = unatom_dict_write(atable, *atom, where); |
| if (where < 0) { |
| /* FIXME: better set a flag that unatom broke or something!!! */ |
| return where; |
| } |
| |
| return 0; |
| } |
| |
| /* Modify buffer of refcount, then release buffer */ |
| static int update_refcount(struct sb *sb, struct buffer_head *buffer, |
| unsigned offset, u16 val) |
| { |
| unsigned delta = tux3_get_current_delta(); |
| struct buffer_head *clone; |
| __be16 *refcount; |
| |
| /* |
| * The atable is protected by i_mutex for now. |
| * blockdirty() should never return -EAGAIN. |
| * FIXME: need finer granularity locking |
| */ |
| clone = blockdirty(buffer, delta); |
| if (IS_ERR(clone)) { |
| assert(PTR_ERR(clone) != -EAGAIN); |
| blockput(buffer); |
| return PTR_ERR(clone); |
| } |
| |
| refcount = bufdata(clone); |
| refcount[offset] = cpu_to_be16(val); |
| mark_buffer_dirty_non(clone); |
| blockput(clone); |
| |
| return 0; |
| } |
| |
| /* Modify atom refcount */ |
| static int atomref(struct inode *atable, atom_t atom, int use) |
| { |
| struct sb *sb = tux_sb(atable->i_sb); |
| unsigned shift = sb->blockbits - ATOMREF_BLKBITS; |
| unsigned block = sb->atomref_base + ATOMREF_SIZE * (atom >> shift); |
| unsigned offset = atom & ~(-1 << shift), kill = 0; |
| struct buffer_head *buffer; |
| __be16 *refcount; |
| int err; |
| |
| buffer = blockread(mapping(atable), block); |
| if (!buffer) |
| return -EIO; |
| |
| refcount = bufdata(buffer); |
| int low = be16_to_cpu(refcount[offset]) + use; |
| trace("inc atom %x by %d, offset %x[%x], low = %d", |
| atom, use, block, offset, low); |
| |
| /* This releases buffer */ |
| err = update_refcount(sb, buffer, offset, low); |
| if (err) |
| return err; |
| |
| if (!low || (low & (-1 << 16))) { |
| buffer = blockread(mapping(atable), block + 1); |
| if (!buffer) |
| return -EIO; |
| |
| refcount = bufdata(buffer); |
| int high = be16_to_cpu(refcount[offset]); |
| if (!low) |
| blockput(buffer); |
| else { |
| trace("carry %d, offset %x[%x], high = %d", |
| (low >> 16), block, offset, high); |
| high += (low >> 16); |
| assert(high >= 0); /* paranoia check */ |
| |
| /* This releases buffer */ |
| err = update_refcount(sb, buffer, offset, high); |
| if (err) { |
| /* FIXME: better set a flag that atomref broke |
| * or something! */ |
| return err; |
| } |
| } |
| |
| kill = !(low | high); |
| } |
| |
| if (kill) { |
| trace("delete atom %x", atom); |
| loff_t next = UNATOM_FREE_MAGIC | sb->freeatom; |
| loff_t where = unatom_dict_write(atable, atom, next); |
| if (where < 0) { |
| /* FIXME: better set a flag that unatom broke |
| * or something! */ |
| return -EIO; |
| } |
| sb->freeatom = atom; |
| |
| buffer = blockread(mapping(atable), where >> sb->blockbits); |
| if (!buffer) { |
| /* FIXME: better set a flag that unatom broke |
| * or something! */ |
| return -EIO; |
| } |
| |
| tux_dirent *entry = bufdata(buffer) + (where & sb->blockmask); |
| if (entry_atom(entry) == atom) { |
| /* FIXME: better set a flag that unatom broke |
| * or something! */ |
| err = tux_delete_entry(atable, buffer, entry); |
| if (err) |
| return err; |
| } else { |
| /* FIXME: better set a flag that unatom broke |
| * or something! */ |
| /* Corruption of refcount or something */ |
| tux3_fs_error(sb, "atom entry not found"); |
| blockput(buffer); |
| return -EIO; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* userland only */ |
| void dump_atoms(struct inode *atable) |
| { |
| struct sb *sb = tux_sb(atable->i_sb); |
| unsigned blocks = (sb->atomgen + (sb->blockmask >> ATOMREF_BLKBITS)) |
| >> (sb->blockbits - ATOMREF_BLKBITS); |
| |
| for (unsigned j = 0; j < blocks; j++) { |
| unsigned block = sb->atomref_base + ATOMREF_SIZE * j; |
| struct buffer_head *lobuf, *hibuf; |
| if (!(lobuf = blockread(mapping(atable), block))) |
| goto eek; |
| if (!(hibuf = blockread(mapping(atable), block + 1))) { |
| blockput(lobuf); |
| goto eek; |
| } |
| __be16 *lorefs = bufdata(lobuf), *hirefs = bufdata(hibuf); |
| for (unsigned i = 0; i < (sb->blocksize >> ATOMREF_BLKBITS); i++) { |
| unsigned refs = (be16_to_cpu(hirefs[i]) << 16) | be16_to_cpu(lorefs[i]); |
| if (!refs) |
| continue; |
| atom_t atom = i; |
| char name[100]; |
| int len = unatom(atable, atom, name, sizeof(name)); |
| if (len < 0) |
| goto eek; |
| __tux3_dbg("%.*s: atom 0x%08x, ref %u\n", |
| len, name, atom, refs); |
| } |
| blockput(lobuf); |
| blockput(hibuf); |
| } |
| return; |
| |
| eek: |
| tux3_err(sb, "atom name lookup failed"); |
| } |
| |
| /* userland only */ |
| void show_freeatoms(struct sb *sb) |
| { |
| struct inode *atable = sb->atable; |
| atom_t atom = sb->freeatom; |
| |
| while (atom) { |
| tux3_dbg("free atom: %x", atom); |
| loff_t next = unatom_dict_read(atable, atom); |
| if (next < 0) |
| goto eek; |
| if (!is_free_unatom(next)) |
| goto eek; |
| atom = next & ~UNATOM_FREE_MASK; |
| } |
| return; |
| |
| eek: |
| tux3_err(sb, "eek"); |
| } |
| |
| /* Xattr cache */ |
| |
| struct xcache_entry { |
| /* FIXME: 16bits? */ |
| u16 atom; /* atom of xattr data */ |
| u16 size; /* size of body[] */ |
| char body[]; |
| }; |
| |
| struct xcache { |
| u16 size; /* size of xattrs[] */ |
| u16 maxsize; /* allocated memory size */ |
| struct xcache_entry xattrs[]; |
| }; |
| |
| /* Free xcache memory */ |
| void free_xcache(struct inode *inode) |
| { |
| if (tux_inode(inode)->xcache) { |
| kfree(tux_inode(inode)->xcache); |
| tux_inode(inode)->xcache = NULL; |
| } |
| } |
| |
| /* Allocate new xcache memory */ |
| int new_xcache(struct inode *inode, unsigned size) |
| { |
| struct xcache *xcache; |
| |
| xcache = kmalloc(sizeof(*xcache) + size, GFP_NOFS); |
| if (!xcache) |
| return -ENOMEM; |
| |
| xcache->size = 0; |
| xcache->maxsize = size; |
| tux_inode(inode)->xcache = xcache; |
| |
| return 0; |
| } |
| |
| /* Expand xcache memory */ |
| static int expand_xcache(struct inode *inode, unsigned size) |
| { |
| #define MIN_ALLOC_SIZE (1 << 7) |
| struct xcache *xcache, *old = tux_inode(inode)->xcache; |
| |
| assert(!old || size > old->maxsize); |
| |
| /* FIXME: better allocation strategy? See xcache_update() comment */ |
| if (old) |
| size = max_t(unsigned, old->maxsize * 2, size); |
| else |
| size = ALIGN(size, MIN_ALLOC_SIZE); |
| trace("realloc xcache to %i", size); |
| |
| assert(size); |
| assert(size <= USHRT_MAX); |
| |
| xcache = kmalloc(sizeof(*xcache) + size, GFP_NOFS); |
| if (!xcache) |
| return -ENOMEM; |
| |
| if (!old) |
| xcache->size = 0; |
| else { |
| xcache->size = old->size; |
| memcpy(xcache->xattrs, old->xattrs, old->size); |
| kfree(old); |
| } |
| xcache->maxsize = size; |
| |
| tux_inode(inode)->xcache = xcache; |
| |
| return 0; |
| } |
| |
| static inline struct xcache_entry *xcache_next(struct xcache_entry *xattr) |
| { |
| return (void *)xattr->body + xattr->size; |
| } |
| |
| static inline struct xcache_entry *xcache_limit(struct xcache *xcache) |
| { |
| return (void *)xcache->xattrs + xcache->size; |
| } |
| |
| int xcache_dump(struct inode *inode) |
| { |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| |
| if (!xcache) |
| return 0; |
| |
| struct xcache_entry *xattr = xcache->xattrs; |
| struct xcache_entry *xlimit = xcache_limit(xcache); |
| |
| //__tux3_dbg("xattrs %p/%i", inode->xcache, inode->xcache->size); |
| while (xattr < xlimit) { |
| if (xattr->size > tux_sb(inode->i_sb)->blocksize) |
| goto bail; |
| __tux3_dbg("atom %.3x => ", xattr->atom); |
| if (xattr->size) |
| hexdump(xattr->body, xattr->size); |
| else |
| __tux3_dbg("<empty>\n"); |
| if ((xattr = xcache_next(xattr)) > xlimit) |
| goto fail; |
| } |
| assert(xattr == xlimit); |
| return 0; |
| |
| fail: |
| tux3_err(tux_sb(inode->i_sb), "corrupt xattrs"); |
| return -1; |
| bail: |
| tux3_err(tux_sb(inode->i_sb), "xattr too big"); |
| return -1; |
| } |
| |
| static struct xcache_entry *xcache_lookup(struct xcache *xcache, unsigned atom) |
| { |
| if (xcache) { |
| struct xcache_entry *xattr = xcache->xattrs; |
| struct xcache_entry *xlimit = xcache_limit(xcache); |
| while (xattr < xlimit) { |
| if (xattr->atom == atom) |
| return xattr; |
| if ((xattr = xcache_next(xattr)) > xlimit) |
| return ERR_PTR(-EINVAL); |
| } |
| assert(xattr == xlimit); |
| } |
| return ERR_PTR(-ENOATTR); |
| } |
| |
| static inline int remove_old(struct xcache *xcache, struct xcache_entry *xattr) |
| { |
| if (xattr) { |
| void *xlimit = xcache_limit(xcache); |
| void *next = xcache_next(xattr); |
| memmove(xattr, next, xlimit - next); |
| xcache->size -= next - (void *)xattr; |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* |
| * Things to improve about xcache_update: |
| * |
| * * It always allocates the new attribute at the end of the list because it |
| * is lazy and works by always deleting the attribute first then putting |
| * the new one at the end |
| * |
| * * If the size of the attribute did not change, does unecessary work |
| * |
| * * Should expand by binary factor |
| */ |
| static int xcache_update(struct inode *inode, unsigned atom, const void *data, |
| unsigned len, unsigned flags) |
| { |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| struct xcache_entry *xattr = xcache_lookup(xcache, atom); |
| int use = 0; |
| |
| if (IS_ERR(xattr)) { |
| if (PTR_ERR(xattr) != -ENOATTR || (flags & XATTR_REPLACE)) |
| return PTR_ERR(xattr); |
| |
| tux3_xattrdirty(inode); |
| } else { |
| if (flags & XATTR_CREATE) |
| return -EEXIST; |
| |
| tux3_xattrdirty(inode); |
| /* FIXME: if we can't insert new one, the xattr will lose */ |
| use -= remove_old(xcache, xattr); |
| } |
| |
| /* Insert new */ |
| unsigned more = sizeof(*xattr) + len; |
| if (!xcache || xcache->size + more > xcache->maxsize) { |
| unsigned oldsize = xcache ? xcache->size : 0; |
| int err = expand_xcache(inode, oldsize + more); |
| if (err) |
| return err; |
| } |
| xattr = xcache_limit(tux_inode(inode)->xcache); |
| //trace("expand by %i\n", more); |
| tux_inode(inode)->xcache->size += more; |
| memcpy(xattr->body, data, (xattr->size = len)); |
| xattr->atom = atom; |
| tux3_mark_inode_dirty(inode); |
| |
| use++; |
| if (use) { |
| /* FIXME: error check */ |
| atomref(tux_sb(inode->i_sb)->atable, atom, use); |
| } |
| |
| return 0; |
| } |
| |
| /* Inode is going to purge, remove xattrs */ |
| int xcache_remove_all(struct inode *inode) |
| { |
| struct sb *sb = tux_sb(inode->i_sb); |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| |
| if (xcache) { |
| struct xcache_entry *xattr = xcache->xattrs; |
| struct xcache_entry *xlimit = xcache_limit(xcache); |
| while (xattr < xlimit) { |
| /* |
| * FIXME: Inode is going to purse, what to do |
| * if error ? |
| */ |
| int err = atomref(sb->atable, xattr->atom, -1); |
| if (err) |
| return err; |
| |
| xattr = xcache_next(xattr); |
| } |
| assert(xattr == xlimit); |
| } |
| |
| free_xcache(inode); |
| |
| return 0; |
| } |
| |
| int get_xattr(struct inode *inode, const char *name, unsigned len, void *data, |
| unsigned size) |
| { |
| struct inode *atable = tux_sb(inode->i_sb)->atable; |
| atom_t atom; |
| int ret; |
| |
| mutex_lock(&atable->i_mutex); |
| ret = find_atom(atable, name, len, &atom); |
| if (ret) |
| goto out; |
| |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| struct xcache_entry *xattr = xcache_lookup(xcache, atom); |
| if (IS_ERR(xattr)) { |
| ret = PTR_ERR(xattr); |
| goto out; |
| } |
| ret = xattr->size; |
| if (ret <= size) |
| memcpy(data, xattr->body, ret); |
| else if (size) |
| ret = -ERANGE; |
| out: |
| mutex_unlock(&atable->i_mutex); |
| return ret; |
| } |
| |
| int set_xattr(struct inode *inode, const char *name, unsigned len, |
| const void *data, unsigned size, unsigned flags) |
| { |
| struct sb *sb = tux_sb(inode->i_sb); |
| struct inode *atable = sb->atable; |
| |
| mutex_lock(&atable->i_mutex); |
| change_begin(sb); |
| |
| atom_t atom; |
| int err = make_atom(atable, name, len, &atom); |
| if (!err) { |
| err = xcache_update(inode, atom, data, size, flags); |
| if (err) { |
| /* FIXME: maybe, recovery for make_atom */ |
| } |
| } |
| |
| change_end(sb); |
| mutex_unlock(&atable->i_mutex); |
| |
| return err; |
| } |
| |
| int del_xattr(struct inode *inode, const char *name, unsigned len) |
| { |
| struct sb *sb = tux_sb(inode->i_sb); |
| struct inode *atable = sb->atable; |
| int err; |
| |
| mutex_lock(&atable->i_mutex); |
| change_begin(sb); |
| |
| atom_t atom; |
| err = find_atom(atable, name, len, &atom); |
| if (!err) { |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| struct xcache_entry *xattr = xcache_lookup(xcache, atom); |
| if (IS_ERR(xattr)) { |
| err = PTR_ERR(xattr); |
| goto out; |
| } |
| |
| tux3_xattrdirty(inode); |
| int used = remove_old(xcache, xattr); |
| if (used) { |
| tux3_mark_inode_dirty(inode); |
| /* FIXME: error check */ |
| atomref(atable, atom, -used); |
| } |
| } |
| out: |
| change_end(sb); |
| mutex_unlock(&atable->i_mutex); |
| |
| return err; |
| } |
| |
| int list_xattr(struct inode *inode, char *text, size_t size) |
| { |
| struct sb *sb = tux_sb(inode->i_sb); |
| struct inode *atable = sb->atable; |
| |
| mutex_lock(&atable->i_mutex); |
| |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| if (!xcache) |
| return 0; |
| |
| struct xcache_entry *xattr = xcache->xattrs; |
| struct xcache_entry *xlimit = xcache_limit(xcache); |
| char *base = text, *top = text + size; |
| int err; |
| |
| while (xattr < xlimit) { |
| atom_t atom = xattr->atom; |
| if (size) { |
| /* FIXME: check error code for POSIX */ |
| int tail = top - text; |
| int len = unatom(atable, atom, text, tail); |
| if (len < 0) { |
| err = len; |
| goto error; |
| } |
| if (len == tail) { |
| err = -ERANGE; |
| goto error; |
| } |
| |
| *(text += len) = 0; |
| text++; |
| } else { |
| int len = unatom(atable, atom, NULL, 0); |
| if (len < 0) { |
| err = len; |
| goto error; |
| } |
| text += len + 1; |
| } |
| |
| if ((xattr = xcache_next(xattr)) > xlimit) { |
| tux3_fs_error(sb, "xcache bug"); |
| err = -EIO; |
| goto error; |
| } |
| } |
| assert(xattr == xlimit); |
| mutex_unlock(&atable->i_mutex); |
| |
| return text - base; |
| |
| error: |
| mutex_unlock(&atable->i_mutex); |
| return err; |
| } |
| |
| /* Xattr encode/decode */ |
| |
| unsigned encode_xsize(struct inode *inode) |
| { |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| |
| if (!xcache) |
| return 0; |
| |
| unsigned size = 0, xatsize = atsize[XATTR_ATTR]; |
| struct xcache_entry *xattr = xcache->xattrs; |
| struct xcache_entry *xlimit = xcache_limit(xcache); |
| |
| while (xattr < xlimit) { |
| size += 2 + xatsize + xattr->size; |
| xattr = xcache_next(xattr); |
| } |
| assert(xattr == xlimit); |
| return size; |
| } |
| |
| void *encode_xattrs(struct inode *inode, void *attrs, unsigned size) |
| { |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| |
| if (!xcache) |
| return attrs; |
| |
| struct xcache_entry *xattr = xcache->xattrs; |
| struct xcache_entry *xlimit = xcache_limit(xcache); |
| void *limit = attrs + size - 3; |
| |
| while (xattr < xlimit) { |
| if (attrs >= limit) |
| break; |
| //immediate xattr: kind+version:16, bytes:16, atom:16, data[bytes - 2] |
| //printf("xattr %x/%x ", xattr->atom, xattr->size); |
| attrs = encode_kind(attrs, XATTR_ATTR, tux_sb(inode->i_sb)->version); |
| attrs = encode16(attrs, xattr->size + 2); |
| attrs = encode16(attrs, xattr->atom); |
| memcpy(attrs, xattr->body, xattr->size); |
| attrs += xattr->size; |
| xattr = xcache_next(xattr); |
| } |
| return attrs; |
| } |
| |
| unsigned decode_xsize(struct inode *inode, void *attrs, unsigned size) |
| { |
| struct sb *sb = tux_sb(inode->i_sb); |
| unsigned total = 0, bytes; |
| void *limit = attrs + size; |
| |
| while (attrs < limit - 1) { |
| unsigned kind, version; |
| attrs = decode_kind(attrs, &kind, &version); |
| switch (kind) { |
| case XATTR_ATTR: |
| case IDATA_ATTR: |
| // immediate data: kind+version:16, bytes:16, data[bytes] |
| // immediate xattr: kind+version:16, bytes:16, atom:16, data[bytes - 2] |
| attrs = decode16(attrs, &bytes); |
| attrs += bytes; |
| if (version == sb->version) |
| total += sizeof(struct xcache_entry) + bytes - 2; |
| continue; |
| } |
| attrs += atsize[kind]; |
| } |
| return total; |
| } |
| |
| void *decode_xattr(struct inode *inode, void *attrs) |
| { |
| // immediate xattr: kind+version:16, bytes:16, atom:16, data[bytes - 2] |
| struct xcache *xcache = tux_inode(inode)->xcache; |
| struct xcache_entry *xattr = xcache_limit(xcache); |
| void *limit = xcache->xattrs + xcache->maxsize; |
| unsigned xsize, bytes, atom; |
| |
| attrs = decode16(attrs, &bytes); |
| attrs = decode16(attrs, &atom); |
| |
| /* FIXME: check limit!!! */ |
| assert((void *)xattr + sizeof(*xattr) <= limit); |
| *xattr = (struct xcache_entry){ |
| .atom = atom, |
| .size = bytes - 2, |
| }; |
| xsize = sizeof(*xattr) + xattr->size; |
| assert((void *)xattr + xsize <= limit); |
| |
| memcpy(xattr->body, attrs, xattr->size); |
| attrs += xattr->size; |
| xcache->size += xsize; |
| |
| return attrs; |
| } |