1/*
2 * Create a squashfs filesystem.  This is a highly compressed read only
3 * filesystem.
4 *
5 * Copyright (c) 2008, 2009, 2010
6 * Phillip Lougher <phillip@lougher.demon.co.uk>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2,
11 * or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 *
22 * xattr.c
23 */
24
25#define TRUE 1
26#define FALSE 0
27
28#include <unistd.h>
29#include <stdio.h>
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <fcntl.h>
33#include <errno.h>
34#include <dirent.h>
35#include <string.h>
36#include <stdlib.h>
37#include <sys/xattr.h>
38
39#include "squashfs_fs.h"
40#include "squashfs_swap.h"
41#include "mksquashfs.h"
42#include "xattr.h"
43
44#ifdef SQUASHFS_TRACE
45#define TRACE(s, args...) \
46		do { \
47			printf("mksquashfs: "s, ## args); \
48		} while(0)
49#else
50#define TRACE(s, args...)
51#endif
52
53#define ERROR(s, args...) \
54		do { \
55			fprintf(stderr, s, ## args); \
56		} while(0)
57
58/* compressed xattr table */
59static char *xattr_table = NULL;
60static unsigned int xattr_size = 0;
61
62/* cached uncompressed xattr data */
63static char *data_cache = NULL;
64static int cache_bytes = 0, cache_size = 0;
65
66/* cached uncompressed xattr id table */
67static struct squashfs_xattr_id *xattr_id_table = NULL;
68static int xattr_ids = 0;
69
70/* saved compressed xattr table */
71unsigned int sxattr_bytes = 0, stotal_xattr_bytes = 0;
72
73/* saved cached uncompressed xattr data */
74static char *sdata_cache = NULL;
75static int scache_bytes = 0;
76
77/* saved cached uncompressed xattr id table */
78static int sxattr_ids = 0;
79
80/* xattr hash table for value duplicate detection */
81static struct xattr_list *dupl[65536];
82
83/* xattr hash table for id duplicate detection */
84static struct dupl_id *dupl_id[65536];
85
86/* file system globals from mksquashfs.c */
87extern int no_xattrs, noX;
88extern long long bytes;
89extern int fd;
90extern unsigned int xattr_bytes, total_xattr_bytes;
91
92/* helper functions from mksquashfs.c */
93extern unsigned short get_checksum(char *, int, unsigned short);
94extern void write_destination(int, long long, int, void *);
95extern long long generic_write_table(int, void *, int, void *, int);
96extern int mangle(char *, char *, int, int, int, int);
97
98/* helper functions and definitions from read_xattrs.c */
99extern int read_xattrs_from_disk(int, struct squashfs_super_block *);
100extern struct xattr_list *get_xattr(int, unsigned int *);
101extern struct prefix prefix_table[];
102
103
104static int get_prefix(struct xattr_list *xattr, char *name)
105{
106	int i;
107
108	xattr->full_name = strdup(name);
109
110	for(i = 0; prefix_table[i].type != -1; i++) {
111		struct prefix *p = &prefix_table[i];
112		if(strncmp(xattr->full_name, p->prefix, strlen(p->prefix)) == 0)
113			break;
114	}
115
116	if(prefix_table[i].type != -1) {
117		xattr->name = xattr->full_name + strlen(prefix_table[i].prefix);
118		xattr->size = strlen(xattr->name);
119	}
120
121	return prefix_table[i].type;
122}
123
124
125static int read_xattrs_from_system(char *filename, struct xattr_list **xattrs)
126{
127	ssize_t size, vsize;
128	char *xattr_names, *p;
129	int i;
130	struct xattr_list *xattr_list = NULL;
131
132	while(1) {
133		size = llistxattr(filename, NULL, 0);
134		if(size <= 0) {
135			if(size < 0 && errno != ENOTSUP)
136				ERROR("llistxattr for %s failed in read_attrs,"
137					" because %s\n", filename,
138					strerror(errno));
139			return 0;
140		}
141
142		xattr_names = malloc(size);
143		if(xattr_names == NULL) {
144			ERROR("Out of memory in read_attrs\n");
145			return 0;
146		}
147
148		size = llistxattr(filename, xattr_names, size);
149		if(size < 0) {
150			free(xattr_names);
151			if(errno == ERANGE)
152				/* xattr list grew?  Try again */
153				continue;
154			else {
155				ERROR("llistxattr for %s failed in read_attrs,"
156					" because %s\n", filename,
157					strerror(errno));
158				return 0;
159			}
160		}
161
162		break;
163	}
164
165	for(i = 0, p = xattr_names; p < xattr_names + size; i++) {
166		struct xattr_list *x = realloc(xattr_list, (i + 1) *
167						sizeof(struct xattr_list));
168		if(x == NULL) {
169			ERROR("Out of memory in read_attrs\n");
170			goto failed;
171		} else
172			xattr_list = x;
173
174		xattr_list[i].type = get_prefix(&xattr_list[i], p);
175		p += strlen(p) + 1;
176		if(xattr_list[i].type == -1) {
177			ERROR("Unrecognised xattr prefix %s\n",
178				xattr_list[i].full_name);
179			free(xattr_list[i].full_name);
180			i--;
181			continue;
182		}
183
184		while(1) {
185			vsize = lgetxattr(filename, xattr_list[i].full_name,
186								NULL, 0);
187			if(vsize < 0) {
188				ERROR("lgetxattr failed for %s in read_attrs,"
189					" because %s\n", filename,
190					strerror(errno));
191				free(xattr_list[i].full_name);
192				goto failed;
193			}
194
195			xattr_list[i].value = malloc(vsize);
196			if(xattr_list[i].value == NULL) {
197				ERROR("Out of memory in read_attrs\n");
198				free(xattr_list[i].full_name);
199				goto failed;
200			}
201
202			vsize = lgetxattr(filename, xattr_list[i].full_name,
203						xattr_list[i].value, vsize);
204			if(vsize < 0) {
205				free(xattr_list[i].value);
206				if(errno == ERANGE)
207					/* xattr grew?  Try again */
208					continue;
209				else {
210					ERROR("lgetxattr failed for %s in "
211						"read_attrs, because %s\n",
212						filename, strerror(errno));
213					free(xattr_list[i].full_name);
214					goto failed;
215				}
216			}
217
218			break;
219		}
220		xattr_list[i].vsize = vsize;
221
222		TRACE("read_xattrs_from_system: filename %s, xattr name %s,"
223			" vsize %d\n", filename, xattr_list[i].full_name,
224			xattr_list[i].vsize);
225	}
226	free(xattr_names);
227	*xattrs = xattr_list;
228	return i;
229
230failed:
231	while(--i >= 0) {
232		free(xattr_list[i].full_name);
233		free(xattr_list[i].value);
234	}
235	free(xattr_list);
236	free(xattr_names);
237	return 0;
238}
239
240
241static int get_xattr_size(struct xattr_list *xattr)
242{
243	int size = sizeof(struct squashfs_xattr_entry) +
244		sizeof(struct squashfs_xattr_val) + xattr->size;
245
246	if(xattr->type & XATTR_VALUE_OOL)
247		size += XATTR_VALUE_OOL_SIZE;
248	else
249		size += xattr->vsize;
250
251	return size;
252}
253
254
255static void *get_xattr_space(unsigned int req_size, long long *disk)
256{
257	int data_space;
258	unsigned short c_byte;
259
260	/*
261	 * Move and compress cached uncompressed data into xattr table.
262	 */
263	while(cache_bytes >= SQUASHFS_METADATA_SIZE) {
264		if((xattr_size - xattr_bytes) <
265				((SQUASHFS_METADATA_SIZE << 1)) + 2) {
266			xattr_table = realloc(xattr_table, xattr_size +
267				(SQUASHFS_METADATA_SIZE << 1) + 2);
268			if(xattr_table == NULL) {
269				goto failed;
270			}
271			xattr_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
272		}
273
274		c_byte = mangle(xattr_table + xattr_bytes + BLOCK_OFFSET,
275			data_cache, SQUASHFS_METADATA_SIZE,
276			SQUASHFS_METADATA_SIZE, noX, 0);
277		TRACE("Xattr block @ 0x%x, size %d\n", xattr_bytes, c_byte);
278		SQUASHFS_SWAP_SHORTS(&c_byte, xattr_table + xattr_bytes, 1);
279		xattr_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
280		memmove(data_cache, data_cache + SQUASHFS_METADATA_SIZE,
281			cache_bytes - SQUASHFS_METADATA_SIZE);
282		cache_bytes -= SQUASHFS_METADATA_SIZE;
283	}
284
285	/*
286	 * Ensure there's enough space in the uncompressed data cache
287	 */
288	data_space = cache_size - cache_bytes;
289	if(data_space < req_size) {
290			int realloc_size = req_size - data_space;
291			data_cache = realloc(data_cache, cache_size +
292				realloc_size);
293			if(data_cache == NULL) {
294				goto failed;
295			}
296			cache_size += realloc_size;
297	}
298
299	if(disk)
300		*disk = ((long long) xattr_bytes << 16) | cache_bytes;
301	cache_bytes += req_size;
302	return data_cache + cache_bytes - req_size;
303
304failed:
305	ERROR("Out of memory in inode table reallocation!\n");
306	return NULL;
307}
308
309
310static struct dupl_id *check_id_dupl(struct xattr_list *xattr_list, int xattrs)
311{
312	struct dupl_id *entry;
313	int i;
314	unsigned short checksum = 0;
315
316	/* compute checksum over all xattrs */
317	for(i = 0; i < xattrs; i++) {
318		struct xattr_list *xattr = &xattr_list[i];
319
320		checksum = get_checksum(xattr->full_name,
321					strlen(xattr->full_name), checksum);
322		checksum = get_checksum(xattr->value,
323					xattr->vsize, checksum);
324	}
325
326	for(entry = dupl_id[checksum]; entry; entry = entry->next) {
327		if (entry->xattrs != xattrs)
328			continue;
329
330		for(i = 0; i < xattrs; i++) {
331			struct xattr_list *xattr = &xattr_list[i];
332			struct xattr_list *dup_xattr = &entry->xattr_list[i];
333
334			if(strcmp(xattr->full_name, dup_xattr->full_name))
335				break;
336
337			if(memcmp(xattr->value, dup_xattr->value, xattr->vsize))
338				break;
339		}
340
341		if(i == xattrs)
342			break;
343	}
344
345	if(entry == NULL) {
346		/* no duplicate exists */
347		entry = malloc(sizeof(*entry));
348		if(entry == NULL) {
349			ERROR("malloc failed in check_ip_dupl\n");
350			return NULL;
351		}
352		entry->xattrs = xattrs;
353		entry->xattr_list = xattr_list;
354		entry->xattr_id = SQUASHFS_INVALID_XATTR;
355		entry->next = dupl_id[checksum];
356		dupl_id[checksum] = entry;
357	}
358
359	return entry;
360}
361
362
363static void check_value_dupl(struct xattr_list *xattr)
364{
365	struct xattr_list *entry;
366
367	if(xattr->vsize < XATTR_VALUE_OOL_SIZE)
368		return;
369
370	/* Check if this is a duplicate of an existing value */
371	xattr->vchecksum = get_checksum(xattr->value, xattr->vsize, 0);
372	for(entry = dupl[xattr->vchecksum]; entry; entry = entry->vnext) {
373		if(entry->vsize != xattr->vsize)
374			continue;
375
376		if(memcmp(entry->value, xattr->value, xattr->vsize) == 0)
377			break;
378	}
379
380	if(entry == NULL) {
381		/*
382		 * No duplicate exists, add to hash table, and mark as
383		 * requiring writing
384		 */
385		xattr->vnext = dupl[xattr->vchecksum];
386		dupl[xattr->vchecksum] = xattr;
387		xattr->ool_value = SQUASHFS_INVALID_BLK;
388	} else {
389		/*
390		 * Duplicate exists, make type XATTR_VALUE_OOL, and
391		 * remember where the duplicate is
392		 */
393		xattr->type |= XATTR_VALUE_OOL;
394		xattr->ool_value = entry->ool_value;
395		/* on appending don't free duplicate values because the
396		 * duplicate value already points to the non-duplicate value */
397		if(xattr->value != entry->value) {
398			free(xattr->value);
399			xattr->value = entry->value;
400		}
401	}
402}
403
404
405static int get_xattr_id(int xattrs, struct xattr_list *xattr_list,
406		long long xattr_disk, struct dupl_id *xattr_dupl)
407{
408	int i, size = 0;
409	struct squashfs_xattr_id *xattr_id;
410
411	xattr_id_table = realloc(xattr_id_table, (xattr_ids + 1) *
412		sizeof(struct squashfs_xattr_id));
413	if(xattr_id_table == NULL) {
414		ERROR("Out of memory in xattr_id_table reallocation!\n");
415		return -1;
416	}
417
418	/* get total uncompressed size of xattr data, needed for stat */
419	for(i = 0; i < xattrs; i++)
420		size += strlen(xattr_list[i].full_name) + 1 +
421			xattr_list[i].vsize;
422
423	xattr_id = &xattr_id_table[xattr_ids];
424	xattr_id->xattr = xattr_disk;
425	xattr_id->count = xattrs;
426	xattr_id->size = size;
427
428	/*
429	 * keep track of total uncompressed xattr data, needed for mksquashfs
430	 * file system summary
431	 */
432	total_xattr_bytes += size;
433
434	xattr_dupl->xattr_id = xattr_ids ++;
435	return xattr_dupl->xattr_id;
436}
437
438
439long long write_xattrs()
440{
441	unsigned short c_byte;
442	int i, avail_bytes;
443	char *datap = data_cache;
444	long long start_bytes = bytes;
445	struct squashfs_xattr_table header;
446
447	if(xattr_ids == 0)
448		return SQUASHFS_INVALID_BLK;
449
450	/*
451	 * Move and compress cached uncompressed data into xattr table.
452	 */
453	while(cache_bytes) {
454		if((xattr_size - xattr_bytes) <
455				((SQUASHFS_METADATA_SIZE << 1)) + 2) {
456			xattr_table = realloc(xattr_table, xattr_size +
457				(SQUASHFS_METADATA_SIZE << 1) + 2);
458			if(xattr_table == NULL) {
459				goto failed;
460			}
461			xattr_size += (SQUASHFS_METADATA_SIZE << 1) + 2;
462		}
463
464		avail_bytes = cache_bytes > SQUASHFS_METADATA_SIZE ?
465			SQUASHFS_METADATA_SIZE : cache_bytes;
466		c_byte = mangle(xattr_table + xattr_bytes + BLOCK_OFFSET, datap,
467			avail_bytes, SQUASHFS_METADATA_SIZE, noX, 0);
468		TRACE("Xattr block @ 0x%x, size %d\n", xattr_bytes, c_byte);
469		SQUASHFS_SWAP_SHORTS(&c_byte, xattr_table + xattr_bytes, 1);
470		xattr_bytes += SQUASHFS_COMPRESSED_SIZE(c_byte) + BLOCK_OFFSET;
471		datap += avail_bytes;
472		cache_bytes -= avail_bytes;
473	}
474
475	/*
476	 * Write compressed xattr table to file system
477	 */
478	write_destination(fd, bytes, xattr_bytes, xattr_table);
479        bytes += xattr_bytes;
480
481	/*
482	 * Swap if necessary the xattr id table
483	 */
484	for(i = 0; i < xattr_ids; i++)
485		SQUASHFS_INSWAP_XATTR_ID(&xattr_id_table[i]);
486
487	header.xattr_ids = xattr_ids;
488	header.xattr_table_start = start_bytes;
489	SQUASHFS_INSWAP_XATTR_TABLE(&header);
490
491	return generic_write_table(xattr_ids * sizeof(struct squashfs_xattr_id),
492		xattr_id_table, sizeof(header), &header, noX);
493
494failed:
495	ERROR("Out of memory in xattr_table reallocation!\n");
496	return -1;
497}
498
499
500int generate_xattrs(int xattrs, struct xattr_list *xattr_list)
501{
502	int total_size, i;
503	int xattr_value_max;
504	void *xp;
505	long long xattr_disk;
506	struct dupl_id *xattr_dupl;
507
508	/*
509	 * check if the file xattrs are a complete duplicate of a pre-existing
510	 * id
511	 */
512	xattr_dupl = check_id_dupl(xattr_list, xattrs);
513	if (xattr_dupl == NULL)
514		return SQUASHFS_INVALID_XATTR;
515	if(xattr_dupl->xattr_id != SQUASHFS_INVALID_XATTR)
516		return xattr_dupl->xattr_id;
517
518	/*
519	 * Scan the xattr_list deciding which type to assign to each
520	 * xattr.  The choice is fairly straightforward, and depends on the
521	 * size of each xattr name/value and the overall size of the
522	 * resultant xattr list stored in the xattr metadata table.
523	 *
524	 * Choices are whether to store data inline or out of line.
525	 *
526	 * The overall goal is to optimise xattr scanning and lookup, and
527	 * to enable the file system layout to scale from a couple of
528	 * small xattr name/values to a large number of large xattr
529	 * names/values without affecting performance.  While hopefully
530	 * enabling the common case of a couple of small xattr name/values
531	 * to be stored efficiently
532	 *
533	 * Code repeatedly scans, doing the following
534	 *		move xattr data out of line if it exceeds
535	 *		xattr_value_max.  Where xattr_value_max is
536	 *		initially XATTR_INLINE_MAX.  If the final uncompressed
537	 *		xattr list is larger than XATTR_TARGET_MAX then more
538	 *		aggressively move xattr data out of line by repeatedly
539	 *	 	setting inline threshold to 1/2, then 1/4, 1/8 of
540	 *		XATTR_INLINE_MAX until target achieved or there's
541	 *		nothing left to move out of line
542	 */
543	xattr_value_max = XATTR_INLINE_MAX;
544	while(1) {
545		for(total_size = 0, i = 0; i < xattrs; i++) {
546			struct xattr_list *xattr = &xattr_list[i];
547			xattr->type &= XATTR_PREFIX_MASK; /* all inline */
548			if (xattr->vsize > xattr_value_max)
549				xattr->type |= XATTR_VALUE_OOL;
550
551			total_size += get_xattr_size(xattr);
552		}
553
554		/*
555		 * If the total size of the uncompressed xattr list is <=
556		 * XATTR_TARGET_MAX we're done
557		 */
558		if(total_size <= XATTR_TARGET_MAX)
559			break;
560
561		if(xattr_value_max == XATTR_VALUE_OOL_SIZE)
562			break;
563
564		/*
565		 * Inline target not yet at minimum and so reduce it, and
566		 * try again
567		 */
568		xattr_value_max /= 2;
569		if(xattr_value_max < XATTR_VALUE_OOL_SIZE)
570			xattr_value_max = XATTR_VALUE_OOL_SIZE;
571	}
572
573	/*
574	 * Check xattr values for duplicates
575	 */
576	for(i = 0; i < xattrs; i++) {
577		check_value_dupl(&xattr_list[i]);
578	}
579
580	/*
581	 * Add each out of line value to the file system xattr table
582	 * if it doesn't already exist as a duplicate
583	 */
584	for(i = 0; i < xattrs; i++) {
585		struct xattr_list *xattr = &xattr_list[i];
586
587		if((xattr->type & XATTR_VALUE_OOL) &&
588				(xattr->ool_value == SQUASHFS_INVALID_BLK)) {
589			struct squashfs_xattr_val val;
590			int size = sizeof(val) + xattr->vsize;
591			xp = get_xattr_space(size, &xattr->ool_value);
592			val.vsize = xattr->vsize;
593			SQUASHFS_SWAP_XATTR_VAL(&val, xp);
594			memcpy(xp + sizeof(val), xattr->value, xattr->vsize);
595		}
596	}
597
598	/*
599	 * Create xattr list and add to file system xattr table
600	 */
601	get_xattr_space(0, &xattr_disk);
602	for(i = 0; i < xattrs; i++) {
603		struct xattr_list *xattr = &xattr_list[i];
604		struct squashfs_xattr_entry entry;
605		struct squashfs_xattr_val val;
606
607		xp = get_xattr_space(sizeof(entry) + xattr->size, NULL);
608		entry.type = xattr->type;
609		entry.size = xattr->size;
610		SQUASHFS_SWAP_XATTR_ENTRY(&entry, xp);
611		memcpy(xp + sizeof(entry), xattr->name, xattr->size);
612
613		if(xattr->type & XATTR_VALUE_OOL) {
614			int size = sizeof(val) + XATTR_VALUE_OOL_SIZE;
615			xp = get_xattr_space(size, NULL);
616			val.vsize = XATTR_VALUE_OOL_SIZE;
617			SQUASHFS_SWAP_XATTR_VAL(&val, xp);
618			SQUASHFS_SWAP_LONG_LONGS(&xattr->ool_value, xp +
619				sizeof(val), 1);
620		} else {
621			int size = sizeof(val) + xattr->vsize;
622			xp = get_xattr_space(size, &xattr->ool_value);
623			val.vsize = xattr->vsize;
624			SQUASHFS_SWAP_XATTR_VAL(&val, xp);
625			memcpy(xp + sizeof(val), xattr->value, xattr->vsize);
626		}
627	}
628
629	/*
630	 * Add to xattr id lookup table
631	 */
632	return get_xattr_id(xattrs, xattr_list, xattr_disk, xattr_dupl);
633}
634
635
636int read_xattrs(void *d)
637{
638	struct dir_ent *dir_ent = d;
639	struct inode_info *inode = dir_ent->inode;
640	char *filename = dir_ent->pathname;
641	struct xattr_list *xattr_list;
642	int xattrs;
643
644	if(no_xattrs || IS_PSEUDO(inode) || inode->root_entry)
645		return SQUASHFS_INVALID_XATTR;
646
647	xattrs = read_xattrs_from_system(filename, &xattr_list);
648	if(xattrs == 0)
649		return SQUASHFS_INVALID_XATTR;
650
651	return generate_xattrs(xattrs, xattr_list);
652}
653
654
655/*
656 * Add the existing xattr ids and xattr metadata in the file system being
657 * appended to, to the in-memory xattr cache.  This allows duplicate checking to
658 * take place against the xattrs already in the file system being appended to,
659 * and ensures the pre-existing xattrs are written out along with any new xattrs
660 */
661int get_xattrs(int fd, struct squashfs_super_block *sBlk)
662{
663	int ids, res, i, id;
664	unsigned int count;
665
666	TRACE("get_xattrs\n");
667
668	res = read_xattrs_from_disk(fd, sBlk);
669	if(res == SQUASHFS_INVALID_BLK || res == 0)
670		goto done;
671	ids = res;
672
673	/*
674	 * for each xattr id read and construct its list of xattr
675	 * name:value pairs, and add them to the in-memory xattr cache
676	 */
677	for(i = 0; i < ids; i++) {
678		struct xattr_list *xattr_list = get_xattr(i, &count);
679		if(xattr_list == NULL) {
680			res = 0;
681			goto done;
682		}
683		id = generate_xattrs(count, xattr_list);
684
685		/*
686		 * Sanity check, the new xattr id should be the same as the
687		 * xattr id in the original file system
688		 */
689		if(id != i) {
690			ERROR("BUG, different xattr_id in get_xattrs\n");
691			res = 0;
692			goto done;
693		}
694	}
695
696done:
697	return res;
698}
699
700
701/*
702 * Save current state of xattrs, needed for restoring state in the event of an
703 * abort in appending
704 */
705int save_xattrs()
706{
707	/* save the current state of the compressed xattr data */
708	sxattr_bytes = xattr_bytes;
709	stotal_xattr_bytes = total_xattr_bytes;
710
711	/*
712	 * save the current state of the cached uncompressed xattr data.
713	 * Note we have to save the contents of the data cache because future
714	 * operations will delete the current contents
715	 */
716	sdata_cache = malloc(cache_bytes);
717	if(sdata_cache == NULL)
718		goto failed;
719
720	memcpy(sdata_cache, data_cache, cache_bytes);
721	scache_bytes = cache_bytes;
722
723	/* save the current state of the xattr id table */
724	sxattr_ids = xattr_ids;
725
726	return TRUE;
727
728failed:
729	ERROR("Out of memory in save_xattrs\n");
730	return FALSE;
731}
732
733
734/*
735 * Restore xattrs in the event of an abort in appending
736 */
737void restore_xattrs()
738{
739	/* restore the state of the compressed xattr data */
740	xattr_bytes = sxattr_bytes;
741	total_xattr_bytes = stotal_xattr_bytes;
742
743	/* restore the state of the uncomoressed xattr data */
744	memcpy(data_cache, sdata_cache, scache_bytes);
745	cache_bytes = scache_bytes;
746
747	/* restore the state of the xattr id table */
748	xattr_ids = sxattr_ids;
749}
750