2 * Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
25 /* Each block in an archive is made up of one or more records, where each
26 * record is either a header record or a data record. The two are
27 * distinguished by the header magic string; the string 'AM' is
28 * explicitly excluded as an allowed filenum to prevent ambiguity. */
30 #define HEADER_MAGIC "AMANDA ARCHIVE FORMAT"
31 #define MAGIC_FILENUM 0x414d
32 #define HEADER_VERSION 1
33 #define EOA_BIT 0x80000000
35 typedef struct header_s {
36 /* magic is HEADER_MAGIC + ' ' + decimal version, NUL padded */
39 #define HEADER_SIZE (SIZEOF(header_t))
41 typedef struct record_s {
46 #define RECORD_SIZE (SIZEOF(record_t))
47 #define MAX_RECORD_DATA_SIZE (4*1024*1024)
49 #define MKRECORD(ptr, f, a, s, eoa) do { \
52 if (eoa) size |= EOA_BIT; \
53 r.filenum = htons(f); \
54 r.attrid = htons(a); \
55 r.size = htonl(size); \
56 memcpy(ptr, &r, sizeof(record_t)); \
59 /* N.B. - f, a, s, and eoa must be simple lvalues */
60 #define GETRECORD(ptr, f, a, s, eoa) do { \
62 memcpy(&r, ptr, sizeof(record_t)); \
70 f = ntohs(r.filenum); \
71 a = ntohs(r.attrid); \
74 /* performance knob: how much data will we buffer before just
75 * writing straight out of the user's buffers? */
76 #define WRITE_BUFFER_SIZE (512*1024)
79 int fd; /* file descriptor */
80 mode_t mode; /* mode O_RDONLY or O_WRONLY */
81 uint16_t maxfilenum; /* Next file number to allocate */
82 header_t hdr; /* pre-constructed header */
83 off_t position; /* current position in the archive */
84 GHashTable *files; /* List of all amar_file_t */
85 gboolean seekable; /* does lseek() work on this fd? */
87 /* internal buffer; on writing, this is WRITE_BUFFER_SIZE bytes, and
88 * always has at least RECORD_SIZE bytes free. */
95 amar_t *archive; /* archive for this file */
96 gint filenum; /* filenum of this file; gint is required by hash table */
97 GHashTable *attributes; /* all attributes for this file */
101 amar_file_t *file; /* file for this attribute */
102 gint attrid; /* id of this attribute */
103 gboolean wrote_eoa; /* If the attribute is finished */
111 amar_error_quark(void)
115 q = g_quark_from_static_string("amar_error");
124 if (archive->buf_len) {
125 if (full_write(archive->fd, archive->buf, archive->buf_len) != archive->buf_len) {
126 g_set_error(error, amar_error_quark(), errno,
127 "Error writing to amanda archive: %s", strerror(errno));
130 archive->buf_len = 0;
141 /* if it won't fit in the buffer, take the easy way out and flush it */
142 if (archive->buf_len + HEADER_SIZE >= WRITE_BUFFER_SIZE - RECORD_SIZE) {
143 if (!flush_buffer(archive, error))
147 memcpy(archive->buf + archive->buf_len, &archive->hdr, HEADER_SIZE);
148 archive->buf_len += HEADER_SIZE;
149 archive->position += HEADER_SIZE;
164 /* the buffer always has room for a new record header */
165 MKRECORD(archive->buf + archive->buf_len, filenum, attrid, data_size, eoa);
166 archive->buf_len += RECORD_SIZE;
168 /* is it worth copying this record into the buffer? */
169 if (archive->buf_len + RECORD_SIZE + data_size < WRITE_BUFFER_SIZE - RECORD_SIZE) {
172 memcpy(archive->buf + archive->buf_len, data, data_size);
173 archive->buf_len += data_size;
178 /* flush the buffer and write the new data, all in one syscall */
179 iov[0].iov_base = archive->buf;
180 iov[0].iov_len = archive->buf_len;
181 iov[1].iov_base = data;
182 iov[1].iov_len = data_size;
183 if (full_writev(archive->fd, iov, 2) < 0) {
184 g_set_error(error, amar_error_quark(), errno,
185 "Error writing to amanda archive: %s", strerror(errno));
188 archive->buf_len = 0;
191 archive->position += data_size + RECORD_SIZE;
205 amar_t *archive = malloc(SIZEOF(amar_t));
207 /* make some sanity checks first */
209 g_assert(mode == O_RDONLY || mode == O_WRONLY);
212 archive->mode = mode;
213 archive->maxfilenum = 0;
214 archive->position = 0;
215 archive->seekable = TRUE; /* assume seekable until lseek() fails */
216 archive->files = g_hash_table_new(g_int_hash, g_int_equal);
219 if (mode == O_WRONLY) {
220 archive->buf = g_malloc(WRITE_BUFFER_SIZE);
221 archive->buf_size = WRITE_BUFFER_SIZE;
223 archive->buf_len = 0;
225 if (mode == O_WRONLY) {
226 /* preformat a header with our version number */
227 bzero(archive->hdr.magic, HEADER_SIZE);
228 snprintf(archive->hdr.magic, HEADER_SIZE,
229 HEADER_MAGIC " %d", HEADER_VERSION);
231 /* and write it out to start the file */
232 if (!write_header(archive, error)) {
233 amar_close(archive, NULL); /* flushing buffer won't fail */
246 gboolean success = TRUE;
248 /* verify all files are done */
249 g_assert(g_hash_table_size(archive->files) == 0);
251 if (!flush_buffer(archive, error))
254 g_hash_table_destroy(archive->files);
255 if (archive->buf) g_free(archive->buf);
270 off_t *header_offset,
273 amar_file_t *file = NULL;
275 g_assert(archive->mode == O_WRONLY);
276 g_assert(filename_buf != NULL);
278 /* set filename_len if it wasn't specified */
280 filename_len = strlen(filename_buf);
281 g_assert(filename_len != 0);
283 if (filename_len > MAX_RECORD_DATA_SIZE) {
284 g_set_error(error, amar_error_quark(), ENOSPC,
285 "filename is too long for an amanda archive");
289 /* pick a new, unused filenum */
291 if (g_hash_table_size(archive->files) == 65535) {
292 g_set_error(error, amar_error_quark(), ENOSPC,
293 "No more file numbers available");
300 archive->maxfilenum++;
302 /* MAGIC_FILENUM can't be used because it matches the header record text */
303 if (archive->maxfilenum == MAGIC_FILENUM) {
307 /* see if this fileid is already in use */
308 filenum = archive->maxfilenum;
309 if (g_hash_table_lookup(archive->files, &filenum))
314 file = g_new0(amar_file_t, 1);
315 file->archive = archive;
316 file->filenum = archive->maxfilenum;
317 file->attributes = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, g_free);
318 g_hash_table_insert(archive->files, &file->filenum, file);
320 /* record the current position and write a header there, if desired */
322 *header_offset = archive->position;
323 if (!write_header(archive, error))
327 /* add a filename record */
328 if (!write_record(archive, file->filenum, AMAR_ATTR_FILENAME,
329 1, filename_buf, filename_len, error))
336 g_hash_table_remove(archive->files, &file->filenum);
337 g_hash_table_destroy(file->attributes);
345 gpointer key G_GNUC_UNUSED,
349 amar_attr_t *attr = value;
350 GError **error = user_data;
352 /* return immediately if we've already seen an error */
356 if (!attr->wrote_eoa) {
357 amar_attr_close(attr, error);
366 gboolean success = TRUE;
367 amar_t *archive = file->archive;
369 /* close all attributes that haven't already written EOA */
370 g_hash_table_foreach(file->attributes, foreach_attr_close, error);
374 /* write an EOF record */
376 if (!write_record(archive, file->filenum, AMAR_ATTR_EOF, 1,
381 /* remove from archive->file list */
382 g_hash_table_remove(archive->files, &file->filenum);
385 g_hash_table_destroy(file->attributes);
395 GError **error G_GNUC_UNUSED)
397 amar_attr_t *attribute;
398 gint attrid_gint = attrid;
400 /* make sure this attrid isn't already present */
401 g_assert(attrid >= AMAR_ATTR_APP_START);
402 g_assert(g_hash_table_lookup(file->attributes, &attrid_gint) == NULL);
404 attribute = malloc(SIZEOF(amar_attr_t));
405 attribute->file = file;
406 attribute->attrid = attrid;
407 attribute->wrote_eoa = FALSE;
408 g_hash_table_replace(file->attributes, &attribute->attrid, attribute);
410 /* (note this function cannot currently return an error) */
417 amar_attr_t *attribute,
420 amar_file_t *file = attribute->file;
421 amar_t *archive = file->archive;
424 /* write an empty record with EOA_BIT set if we haven't ended
425 * this attribute already */
426 if (!attribute->wrote_eoa) {
427 if (!write_record(archive, file->filenum, attribute->attrid,
430 attribute->wrote_eoa = TRUE;
437 amar_attr_add_data_buffer(
438 amar_attr_t *attribute,
439 gpointer data, gsize size,
443 amar_file_t *file = attribute->file;
444 amar_t *archive = file->archive;
446 g_assert(!attribute->wrote_eoa);
448 /* write records until we've consumed all of the buffer */
451 gboolean rec_eoa = FALSE;
453 if (size > MAX_RECORD_DATA_SIZE) {
454 rec_data_size = MAX_RECORD_DATA_SIZE;
456 rec_data_size = size;
461 if (!write_record(archive, file->filenum, attribute->attrid,
462 rec_eoa, data, rec_data_size, error))
465 data += rec_data_size;
466 size -= rec_data_size;
470 attribute->wrote_eoa = TRUE;
477 amar_attr_add_data_fd(
478 amar_attr_t *attribute,
483 amar_file_t *file = attribute->file;
484 amar_t *archive = file->archive;
487 gpointer buf = g_malloc(MAX_RECORD_DATA_SIZE);
489 g_assert(!attribute->wrote_eoa);
491 /* read and write until reaching EOF */
492 while ((size = full_read(fd, buf, MAX_RECORD_DATA_SIZE)) >= 0) {
493 if (!write_record(archive, file->filenum, attribute->attrid,
494 eoa && (size < MAX_RECORD_DATA_SIZE), buf, size, error))
499 if (size < MAX_RECORD_DATA_SIZE)
504 g_set_error(error, amar_error_quark(), errno,
505 "Error reading from fd %d: %s", fd, strerror(errno));
510 attribute->wrote_eoa = eoa;
523 /* Note that this implementation assumes that an archive will have a "small"
524 * number of open files at any time, and a limited number of attributes for
527 typedef struct attr_state_s {
529 amar_attr_handling_t *handling;
537 typedef struct file_state_s {
539 gpointer file_data; /* user's data */
545 typedef struct handling_params_s {
546 /* parameters from the user */
548 amar_attr_handling_t *handling_array;
549 amar_file_start_callback_t file_start_cb;
550 amar_file_finish_callback_t file_finish_cb;
552 /* tracking for open files and attributes */
557 gsize buf_size; /* allocated size */
558 gsize buf_len; /* number of active bytes .. */
559 gsize buf_offset; /* ..starting at buf + buf_offset */
561 gboolean just_lseeked; /* did we just call lseek? */
564 /* buffer-handling macros and functions */
566 /* Ensure that the archive buffer contains at least ATLEAST bytes. Returns
567 * FALSE if that many bytes are not available due to EOF or another error. */
571 handling_params_t *hp,
577 /* easy case of hp->buf_len >= atleast is taken care of by the macro, below */
582 /* If we just don't have space for this much data yet, then we'll have to reallocate
584 if (hp->buf_size < atleast) {
585 if (hp->buf_offset == 0) {
586 hp->buf = g_realloc(hp->buf, atleast);
588 gpointer newbuf = g_malloc(atleast);
590 memcpy(newbuf, hp->buf+hp->buf_offset, hp->buf_len);
596 hp->buf_size = atleast;
599 /* if we have space in this buffer to satisfy the request, but not without moving
600 * the existing data around, then move the data around */
601 else if (hp->buf_size - hp->buf_offset < atleast) {
602 memmove(hp->buf, hp->buf+hp->buf_offset, hp->buf_len);
606 /* as an optimization, if we just called lseek, then only read the requested
607 * bytes in case we're going to lseek again. */
608 if (hp->just_lseeked)
609 to_read = atleast - hp->buf_len;
611 to_read = hp->buf_size - hp->buf_offset - hp->buf_len;
613 bytes_read = full_read(archive->fd,
614 hp->buf+hp->buf_offset+hp->buf_len,
616 if (bytes_read < to_read)
618 hp->just_lseeked = FALSE;
620 hp->buf_len += bytes_read;
622 return hp->buf_len >= atleast;
625 #define buf_atleast(archive, hp, atleast) \
626 (((hp)->buf_len >= (atleast))? TRUE : buf_atleast_((archive), (hp), (atleast)))
628 /* Skip the buffer ahead by SKIPBYTES bytes. This will discard data from the
629 * buffer, and may call lseek() if some of the skipped bytes have not yet been
630 * read. Returns FALSE if the requisite bytes cannot be skipped due to EOF or
635 handling_params_t *hp,
638 /* easy case of buf_len > skipbytes is taken care of by the macro, below,
639 * so we know we're clearing out the entire buffer here */
641 skipbytes -= hp->buf_len;
647 if (archive->seekable) {
648 if (lseek(archive->fd, skipbytes, SEEK_CUR) < 0) {
649 /* did we fail because archive->fd is a pipe or something? */
650 if (errno == ESPIPE) {
651 archive->seekable = FALSE;
659 gsize toread = MIN(skipbytes, hp->buf_size);
660 gsize bytes_read = full_read(archive->fd, hp->buf, toread);
662 if (bytes_read < toread) {
667 skipbytes -= bytes_read;
674 #define buf_skip(archive, hp, skipbytes) \
675 (((skipbytes) <= (hp)->buf_len) ? \
676 ((hp)->buf_len -= (skipbytes), \
677 (hp)->buf_offset += (skipbytes), \
679 : buf_skip_((archive), (hp), (skipbytes)))
681 /* Get a pointer to the current position in the buffer */
682 #define buf_ptr(hp) ((hp)->buf + (hp)->buf_offset)
684 /* Get the amount of data currently available in the buffer */
685 #define buf_avail(hp) ((hp)->buf_len)
689 handling_params_t *hp,
694 gboolean success = TRUE;
695 if (!as->wrote_eoa && as->handling && as->handling->callback) {
696 success = as->handling->callback(hp->user_data, fs->filenum,
697 fs->file_data, as->attrid, as->handling->attrid_data,
698 &as->attr_data, as->buf, as->buf_len, TRUE, truncated);
708 handling_params_t *hp,
713 gboolean success = TRUE;
715 /* free up any attributes not yet ended */
716 for (iter = fs->attr_states; iter; iter = iter->next) {
717 attr_state_t *as = (attr_state_t *)iter->data;
718 success = success && finish_attr(hp, fs, as, TRUE);
720 g_slist_free(fs->attr_states);
721 fs->attr_states = NULL;
723 if (hp->file_finish_cb && !fs->ignore)
724 success = success && hp->file_finish_cb(hp->user_data, fs->filenum, &fs->file_data, truncated);
730 /* buffer the data and/or call the callback for this attribute */
733 handling_params_t *hp,
736 amar_attr_handling_t *hdl,
741 gboolean success = TRUE;
743 /* capture any conditions where we don't have to copy into the buffer */
744 if (hdl->min_size == 0 || (as->buf_len == 0 && len >= hdl->min_size)) {
745 success = success && hdl->callback(hp->user_data, fs->filenum,
746 fs->file_data, as->attrid, hdl->attrid_data, &as->attr_data,
747 buf, len, eoa, FALSE);
750 /* ok, copy into the buffer */
751 if (as->buf_len + len > as->buf_size) {
752 gpointer newbuf = g_malloc(as->buf_len + len);
754 memcpy(newbuf, as->buf, as->buf_len);
758 as->buf_size = as->buf_len + len;
760 memcpy(as->buf + as->buf_len, buf, len);
763 /* and call the callback if we have enough data or if this is the last attr */
764 if (as->buf_len >= hdl->min_size || eoa) {
765 success = success && hdl->callback(hp->user_data, fs->filenum,
766 fs->file_data, as->attrid, hdl->attrid_data, &as->attr_data,
767 as->buf, as->buf_len, eoa, FALSE);
780 amar_attr_handling_t *handling_array,
781 amar_file_start_callback_t file_start_cb,
782 amar_file_finish_callback_t file_finish_cb,
785 file_state_t *fs = NULL;
786 attr_state_t *as = NULL;
788 handling_params_t hp;
793 amar_attr_handling_t *hdl;
794 gboolean success = TRUE;
796 g_assert(archive->mode == O_RDONLY);
798 hp.user_data = user_data;
799 hp.handling_array = handling_array;
800 hp.file_start_cb = file_start_cb;
801 hp.file_finish_cb = file_finish_cb;
802 hp.file_states = NULL;
805 hp.buf_size = 1024; /* use a 1K buffer to start */
806 hp.buf = g_malloc(hp.buf_size);
808 hp.just_lseeked = FALSE;
810 /* check that we are starting at a header record, but don't advance
811 * the buffer past it */
812 if (buf_atleast(archive, &hp, RECORD_SIZE)) {
813 GETRECORD(buf_ptr(&hp), filenum, attrid, datasize, eoa);
814 if (filenum != MAGIC_FILENUM) {
815 g_set_error(error, amar_error_quark(), EINVAL,
816 "Archive read does not begin at a header record");
822 if (!buf_atleast(archive, &hp, RECORD_SIZE))
825 GETRECORD(buf_ptr(&hp), filenum, attrid, datasize, eoa);
827 /* handle headers specially */
828 if (G_UNLIKELY(filenum == MAGIC_FILENUM)) {
831 /* bail if an EOF occurred in the middle of the header */
832 if (!buf_atleast(archive, &hp, HEADER_SIZE))
835 if (sscanf(buf_ptr(&hp), HEADER_MAGIC " %d", &vers) != 1) {
836 g_set_error(error, amar_error_quark(), EINVAL,
837 "Invalid archive header");
841 if (vers > HEADER_VERSION) {
842 g_set_error(error, amar_error_quark(), EINVAL,
843 "Archive version %d is not supported", vers);
847 buf_skip(archive, &hp, HEADER_SIZE);
852 buf_skip(archive, &hp, RECORD_SIZE);
854 if (datasize > MAX_RECORD_DATA_SIZE) {
855 g_set_error(error, amar_error_quark(), EINVAL,
856 "Invalid record: data size must be less than %d",
857 MAX_RECORD_DATA_SIZE);
861 /* find the file_state_t, if it exists */
862 if (!fs || fs->filenum != filenum) {
864 for (iter = hp.file_states; iter; iter = iter->next) {
865 if (((file_state_t *)iter->data)->filenum == filenum) {
866 fs = (file_state_t *)iter->data;
872 /* get the "special" attributes out of the way */
873 if (G_UNLIKELY(attrid < AMAR_ATTR_APP_START)) {
874 if (attrid == AMAR_ATTR_EOF) {
876 g_set_error(error, amar_error_quark(), EINVAL,
877 "Archive contains an EOF record with nonzero size");
881 success = finish_file(&hp, fs, FALSE);
882 hp.file_states = g_slist_remove(hp.file_states, fs);
889 } else if (attrid == AMAR_ATTR_FILENAME) {
890 /* for filenames, we need the whole filename in the buffer */
891 if (!buf_atleast(archive, &hp, datasize))
895 /* TODO: warn - previous file did not end correctly */
896 success = finish_file(&hp, fs, TRUE);
897 hp.file_states = g_slist_remove(hp.file_states, fs);
905 unsigned int i, nul_padding = 1;
907 /* try to detect NULL padding bytes */
908 if (!buf_atleast(archive, &hp, 512 - RECORD_SIZE)) {
909 /* close to end of file */
913 /* check all byte == 0 */
914 for (i=0; i<512 - RECORD_SIZE; i++) {
921 g_set_error(error, amar_error_quark(), EINVAL,
922 "Archive file %d has an empty filename",
928 g_set_error(error, amar_error_quark(), EINVAL,
929 "Filename record for fileid %d does "
930 "not have its EOA bit set", (int)filenum);
934 fs = g_new0(file_state_t, 1);
935 fs->filenum = filenum;
936 hp.file_states = g_slist_prepend(hp.file_states, fs);
938 if (hp.file_start_cb) {
939 success = hp.file_start_cb(hp.user_data, filenum,
940 buf_ptr(&hp), datasize,
941 &fs->ignore, &fs->file_data);
946 buf_skip(archive, &hp, datasize);
950 g_set_error(error, amar_error_quark(), EINVAL,
951 "Unknown attribute id %d in archive file %d",
952 (int)attrid, (int)filenum);
957 /* if this is an unrecognized file or a known file that's being
958 * ignored, then skip it. */
959 if (!fs || fs->ignore) {
960 buf_skip(archive, &hp, datasize);
964 /* ok, this is an application attribute. Look up its as, if it exists. */
965 if (!as || as->attrid != attrid) {
967 for (iter = fs->attr_states; iter; iter = iter->next) {
968 if (((attr_state_t *)(iter->data))->attrid == attrid) {
969 as = (attr_state_t *)(iter->data);
975 /* and get the proper handling for that attribute */
979 hdl = hp.handling_array;
980 for (hdl = hp.handling_array; hdl->attrid != 0; hdl++) {
981 if (hdl->attrid == attrid)
986 /* As a shortcut, if this is a one-record attribute, handle it without
987 * creating a new attribute_state_t. */
991 /* a simple single-part callback */
992 if (buf_avail(&hp) >= datasize) {
993 success = hdl->callback(hp.user_data, filenum, fs->file_data, attrid,
994 hdl->attrid_data, &tmp, buf_ptr(&hp), datasize, eoa, FALSE);
997 buf_skip(archive, &hp, datasize);
1001 /* we only have part of the data, but if it's big enough to exceed
1002 * the attribute's min_size, then just call the callback for each
1003 * part of the data */
1004 else if (buf_avail(&hp) >= hdl->min_size) {
1005 gsize firstpart = buf_avail(&hp);
1006 gsize lastpart = datasize - firstpart;
1008 success = hdl->callback(hp.user_data, filenum, fs->file_data, attrid,
1009 hdl->attrid_data, &tmp, buf_ptr(&hp), firstpart, FALSE, FALSE);
1012 buf_skip(archive, &hp, firstpart);
1014 if (!buf_atleast(archive, &hp, lastpart))
1017 success = hdl->callback(hp.user_data, filenum, fs->file_data, attrid,
1018 hdl->attrid_data, &tmp, buf_ptr(&hp), lastpart, eoa, FALSE);
1021 buf_skip(archive, &hp, lastpart);
1025 /* no callback -> just skip it */
1026 buf_skip(archive, &hp, datasize);
1031 /* ok, set up a new attribute state */
1033 as = g_new0(attr_state_t, 1);
1034 as->attrid = attrid;
1036 fs->attr_states = g_slist_prepend(fs->attr_states, as);
1039 if (hdl->callback) {
1040 /* handle the data as one or two hunks, depending on whether it's
1041 * all in the buffer right now */
1042 if (buf_avail(&hp) >= datasize) {
1043 success = handle_hunk(&hp, fs, as, hdl, buf_ptr(&hp), datasize, eoa);
1046 buf_skip(archive, &hp, datasize);
1048 gsize hunksize = buf_avail(&hp);
1049 success = handle_hunk(&hp, fs, as, hdl, buf_ptr(&hp), hunksize, FALSE);
1052 buf_skip(archive, &hp, hunksize);
1054 hunksize = datasize - hunksize;
1055 if (!buf_atleast(archive, &hp, hunksize))
1058 handle_hunk(&hp, fs, as, hdl, buf_ptr(&hp), hunksize, eoa);
1059 buf_skip(archive, &hp, hunksize);
1062 buf_skip(archive, &hp, datasize);
1065 /* finish the attribute if this is its last record */
1067 success = finish_attr(&hp, fs, as, FALSE);
1068 fs->attr_states = g_slist_remove(fs->attr_states, as);
1075 /* close any open files, assuming that they have been truncated */
1077 for (iter = hp.file_states; iter; iter = iter->next) {
1078 file_state_t *fs = (file_state_t *)iter->data;
1079 finish_file(&hp, fs, TRUE);
1081 g_slist_free(hp.file_states);