2 * Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
19 * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
26 /* Each block in an archive is made up of one or more records, where each
27 * record is either a header record or a data record. The two are
28 * distinguished by the header magic string; the string 'AM' is
29 * explicitly excluded as an allowed filenum to prevent ambiguity. */
31 #define HEADER_MAGIC "AMANDA ARCHIVE FORMAT"
32 #define MAGIC_FILENUM 0x414d
33 #define HEADER_VERSION 1
34 #define EOA_BIT 0x80000000
36 typedef struct header_s {
37 /* magic is HEADER_MAGIC + ' ' + decimal version, NUL padded */
40 #define HEADER_SIZE (SIZEOF(header_t))
42 typedef struct record_s {
47 #define RECORD_SIZE (SIZEOF(record_t))
48 #define MAX_RECORD_DATA_SIZE (4*1024*1024)
50 #define MKRECORD(ptr, f, a, s, eoa) do { \
53 if (eoa) size |= EOA_BIT; \
54 r.filenum = htons(f); \
55 r.attrid = htons(a); \
56 r.size = htonl(size); \
57 memcpy(ptr, &r, sizeof(record_t)); \
60 /* N.B. - f, a, s, and eoa must be simple lvalues */
61 #define GETRECORD(ptr, f, a, s, eoa) do { \
63 memcpy(&r, ptr, sizeof(record_t)); \
71 f = ntohs(r.filenum); \
72 a = ntohs(r.attrid); \
75 /* performance knob: how much data will we buffer before just
76 * writing straight out of the user's buffers? */
77 #define WRITE_BUFFER_SIZE (512*1024)
80 int fd; /* file descriptor */
81 mode_t mode; /* mode O_RDONLY or O_WRONLY */
82 uint16_t maxfilenum; /* Next file number to allocate */
83 header_t hdr; /* pre-constructed header */
84 off_t position; /* current position in the archive */
85 GHashTable *files; /* List of all amar_file_t */
86 gboolean seekable; /* does lseek() work on this fd? */
88 /* internal buffer; on writing, this is WRITE_BUFFER_SIZE bytes, and
89 * always has at least RECORD_SIZE bytes free. */
96 amar_t *archive; /* archive for this file */
97 gint filenum; /* filenum of this file; gint is required by hash table */
98 GHashTable *attributes; /* all attributes for this file */
102 amar_file_t *file; /* file for this attribute */
103 gint attrid; /* id of this attribute */
104 gboolean wrote_eoa; /* If the attribute is finished */
112 amar_error_quark(void)
116 q = g_quark_from_static_string("amar_error");
125 if (archive->buf_len) {
126 if (full_write(archive->fd, archive->buf, archive->buf_len) != archive->buf_len) {
127 g_set_error(error, amar_error_quark(), errno,
128 "Error writing to amanda archive: %s", strerror(errno));
131 archive->buf_len = 0;
142 /* if it won't fit in the buffer, take the easy way out and flush it */
143 if (archive->buf_len + HEADER_SIZE >= WRITE_BUFFER_SIZE - RECORD_SIZE) {
144 if (!flush_buffer(archive, error))
148 memcpy(archive->buf + archive->buf_len, &archive->hdr, HEADER_SIZE);
149 archive->buf_len += HEADER_SIZE;
150 archive->position += HEADER_SIZE;
165 /* the buffer always has room for a new record header */
166 MKRECORD(archive->buf + archive->buf_len, filenum, attrid, data_size, eoa);
167 archive->buf_len += RECORD_SIZE;
169 /* is it worth copying this record into the buffer? */
170 if (archive->buf_len + RECORD_SIZE + data_size < WRITE_BUFFER_SIZE - RECORD_SIZE) {
173 memcpy(archive->buf + archive->buf_len, data, data_size);
174 archive->buf_len += data_size;
179 /* flush the buffer and write the new data, all in one syscall */
180 iov[0].iov_base = archive->buf;
181 iov[0].iov_len = archive->buf_len;
182 iov[1].iov_base = data;
183 iov[1].iov_len = data_size;
184 if (full_writev(archive->fd, iov, 2) < 0) {
185 g_set_error(error, amar_error_quark(), errno,
186 "Error writing to amanda archive: %s", strerror(errno));
189 archive->buf_len = 0;
192 archive->position += data_size + RECORD_SIZE;
206 amar_t *archive = malloc(SIZEOF(amar_t));
208 /* make some sanity checks first */
210 g_assert(mode == O_RDONLY || mode == O_WRONLY);
213 archive->mode = mode;
214 archive->maxfilenum = 0;
215 archive->position = 0;
216 archive->seekable = TRUE; /* assume seekable until lseek() fails */
217 archive->files = g_hash_table_new(g_int_hash, g_int_equal);
220 if (mode == O_WRONLY) {
221 archive->buf = g_malloc(WRITE_BUFFER_SIZE);
222 archive->buf_size = WRITE_BUFFER_SIZE;
224 archive->buf_len = 0;
226 if (mode == O_WRONLY) {
227 /* preformat a header with our version number */
228 bzero(archive->hdr.magic, HEADER_SIZE);
229 snprintf(archive->hdr.magic, HEADER_SIZE,
230 HEADER_MAGIC " %d", HEADER_VERSION);
232 /* and write it out to start the file */
233 if (!write_header(archive, error)) {
234 amar_close(archive, NULL); /* flushing buffer won't fail */
247 gboolean success = TRUE;
249 /* verify all files are done */
250 g_assert(g_hash_table_size(archive->files) == 0);
252 if (!flush_buffer(archive, error))
255 g_hash_table_destroy(archive->files);
256 if (archive->buf) g_free(archive->buf);
271 off_t *header_offset,
274 amar_file_t *file = NULL;
276 g_assert(archive->mode == O_WRONLY);
277 g_assert(filename_buf != NULL);
279 /* set filename_len if it wasn't specified */
281 filename_len = strlen(filename_buf);
282 g_assert(filename_len != 0);
284 if (filename_len > MAX_RECORD_DATA_SIZE) {
285 g_set_error(error, amar_error_quark(), ENOSPC,
286 "filename is too long for an amanda archive");
290 /* pick a new, unused filenum */
292 if (g_hash_table_size(archive->files) == 65535) {
293 g_set_error(error, amar_error_quark(), ENOSPC,
294 "No more file numbers available");
301 archive->maxfilenum++;
303 /* MAGIC_FILENUM can't be used because it matches the header record text */
304 if (archive->maxfilenum == MAGIC_FILENUM) {
308 /* see if this fileid is already in use */
309 filenum = archive->maxfilenum;
310 if (g_hash_table_lookup(archive->files, &filenum))
315 file = g_new0(amar_file_t, 1);
316 file->archive = archive;
317 file->filenum = archive->maxfilenum;
318 file->attributes = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, g_free);
319 g_hash_table_insert(archive->files, &file->filenum, file);
321 /* record the current position and write a header there, if desired */
323 *header_offset = archive->position;
324 if (!write_header(archive, error))
328 /* add a filename record */
329 if (!write_record(archive, file->filenum, AMAR_ATTR_FILENAME,
330 1, filename_buf, filename_len, error))
337 g_hash_table_remove(archive->files, &file->filenum);
338 g_hash_table_destroy(file->attributes);
346 gpointer key G_GNUC_UNUSED,
350 amar_attr_t *attr = value;
351 GError **error = user_data;
353 /* return immediately if we've already seen an error */
357 if (!attr->wrote_eoa) {
358 amar_attr_close(attr, error);
367 gboolean success = TRUE;
368 amar_t *archive = file->archive;
370 /* close all attributes that haven't already written EOA */
371 g_hash_table_foreach(file->attributes, foreach_attr_close, error);
375 /* write an EOF record */
377 if (!write_record(archive, file->filenum, AMAR_ATTR_EOF, 1,
382 /* remove from archive->file list */
383 g_hash_table_remove(archive->files, &file->filenum);
386 g_hash_table_destroy(file->attributes);
396 GError **error G_GNUC_UNUSED)
398 amar_attr_t *attribute;
399 gint attrid_gint = attrid;
401 /* make sure this attrid isn't already present */
402 g_assert(attrid >= AMAR_ATTR_APP_START);
403 g_assert(g_hash_table_lookup(file->attributes, &attrid_gint) == NULL);
405 attribute = malloc(SIZEOF(amar_attr_t));
406 attribute->file = file;
407 attribute->attrid = attrid;
408 attribute->wrote_eoa = FALSE;
409 g_hash_table_replace(file->attributes, &attribute->attrid, attribute);
411 /* (note this function cannot currently return an error) */
418 amar_attr_t *attribute,
421 amar_file_t *file = attribute->file;
422 amar_t *archive = file->archive;
425 /* write an empty record with EOA_BIT set if we haven't ended
426 * this attribute already */
427 if (!attribute->wrote_eoa) {
428 if (!write_record(archive, file->filenum, attribute->attrid,
431 attribute->wrote_eoa = TRUE;
438 amar_attr_add_data_buffer(
439 amar_attr_t *attribute,
440 gpointer data, gsize size,
444 amar_file_t *file = attribute->file;
445 amar_t *archive = file->archive;
447 g_assert(!attribute->wrote_eoa);
449 /* write records until we've consumed all of the buffer */
452 gboolean rec_eoa = FALSE;
454 if (size > MAX_RECORD_DATA_SIZE) {
455 rec_data_size = MAX_RECORD_DATA_SIZE;
457 rec_data_size = size;
462 if (!write_record(archive, file->filenum, attribute->attrid,
463 rec_eoa, data, rec_data_size, error))
466 data += rec_data_size;
467 size -= rec_data_size;
471 attribute->wrote_eoa = TRUE;
478 amar_attr_add_data_fd(
479 amar_attr_t *attribute,
484 amar_file_t *file = attribute->file;
485 amar_t *archive = file->archive;
488 gpointer buf = g_malloc(MAX_RECORD_DATA_SIZE);
490 g_assert(!attribute->wrote_eoa);
492 /* read and write until reaching EOF */
493 while ((size = full_read(fd, buf, MAX_RECORD_DATA_SIZE)) >= 0) {
494 if (!write_record(archive, file->filenum, attribute->attrid,
495 eoa && (size < MAX_RECORD_DATA_SIZE), buf, size, error))
500 if (size < MAX_RECORD_DATA_SIZE)
505 g_set_error(error, amar_error_quark(), errno,
506 "Error reading from fd %d: %s", fd, strerror(errno));
511 attribute->wrote_eoa = eoa;
524 /* Note that this implementation assumes that an archive will have a "small"
525 * number of open files at any time, and a limited number of attributes for
528 typedef struct attr_state_s {
530 amar_attr_handling_t *handling;
538 typedef struct file_state_s {
540 gpointer file_data; /* user's data */
546 typedef struct handling_params_s {
547 /* parameters from the user */
549 amar_attr_handling_t *handling_array;
550 amar_file_start_callback_t file_start_cb;
551 amar_file_finish_callback_t file_finish_cb;
553 /* tracking for open files and attributes */
558 gsize buf_size; /* allocated size */
559 gsize buf_len; /* number of active bytes .. */
560 gsize buf_offset; /* ..starting at buf + buf_offset */
562 gboolean just_lseeked; /* did we just call lseek? */
565 /* buffer-handling macros and functions */
567 /* Ensure that the archive buffer contains at least ATLEAST bytes. Returns
568 * FALSE if that many bytes are not available due to EOF or another error. */
572 handling_params_t *hp,
578 /* easy case of hp->buf_len >= atleast is taken care of by the macro, below */
583 /* If we just don't have space for this much data yet, then we'll have to reallocate
585 if (hp->buf_size < atleast) {
586 if (hp->buf_offset == 0) {
587 hp->buf = g_realloc(hp->buf, atleast);
589 gpointer newbuf = g_malloc(atleast);
591 memcpy(newbuf, hp->buf+hp->buf_offset, hp->buf_len);
597 hp->buf_size = atleast;
600 /* if we have space in this buffer to satisfy the request, but not without moving
601 * the existing data around, then move the data around */
602 else if (hp->buf_size - hp->buf_offset < atleast) {
603 memmove(hp->buf, hp->buf+hp->buf_offset, hp->buf_len);
607 /* as an optimization, if we just called lseek, then only read the requested
608 * bytes in case we're going to lseek again. */
609 if (hp->just_lseeked)
610 to_read = atleast - hp->buf_len;
612 to_read = hp->buf_size - hp->buf_offset - hp->buf_len;
614 bytes_read = full_read(archive->fd,
615 hp->buf+hp->buf_offset+hp->buf_len,
617 if (bytes_read < to_read)
619 hp->just_lseeked = FALSE;
621 hp->buf_len += bytes_read;
623 return hp->buf_len >= atleast;
626 #define buf_atleast(archive, hp, atleast) \
627 (((hp)->buf_len >= (atleast))? TRUE : buf_atleast_((archive), (hp), (atleast)))
629 /* Skip the buffer ahead by SKIPBYTES bytes. This will discard data from the
630 * buffer, and may call lseek() if some of the skipped bytes have not yet been
631 * read. Returns FALSE if the requisite bytes cannot be skipped due to EOF or
636 handling_params_t *hp,
639 /* easy case of buf_len > skipbytes is taken care of by the macro, below,
640 * so we know we're clearing out the entire buffer here */
642 skipbytes -= hp->buf_len;
648 if (archive->seekable) {
649 if (lseek(archive->fd, skipbytes, SEEK_CUR) < 0) {
650 /* did we fail because archive->fd is a pipe or something? */
651 if (errno == ESPIPE) {
652 archive->seekable = FALSE;
660 gsize toread = MIN(skipbytes, hp->buf_size);
661 gsize bytes_read = full_read(archive->fd, hp->buf, toread);
663 if (bytes_read < toread) {
668 skipbytes -= bytes_read;
675 #define buf_skip(archive, hp, skipbytes) \
676 (((skipbytes) <= (hp)->buf_len) ? \
677 ((hp)->buf_len -= (skipbytes), \
678 (hp)->buf_offset += (skipbytes), \
680 : buf_skip_((archive), (hp), (skipbytes)))
682 /* Get a pointer to the current position in the buffer */
683 #define buf_ptr(hp) ((hp)->buf + (hp)->buf_offset)
685 /* Get the amount of data currently available in the buffer */
686 #define buf_avail(hp) ((hp)->buf_len)
690 handling_params_t *hp,
695 gboolean success = TRUE;
696 if (!as->wrote_eoa && as->handling && as->handling->callback) {
697 success = as->handling->callback(hp->user_data, fs->filenum,
698 fs->file_data, as->attrid, as->handling->attrid_data,
699 &as->attr_data, as->buf, as->buf_len, TRUE, truncated);
709 handling_params_t *hp,
714 gboolean success = TRUE;
716 /* free up any attributes not yet ended */
717 for (iter = fs->attr_states; iter; iter = iter->next) {
718 attr_state_t *as = (attr_state_t *)iter->data;
719 success = success && finish_attr(hp, fs, as, TRUE);
721 g_slist_free(fs->attr_states);
722 fs->attr_states = NULL;
724 if (hp->file_finish_cb && !fs->ignore)
725 success = success && hp->file_finish_cb(hp->user_data, fs->filenum, &fs->file_data, truncated);
731 /* buffer the data and/or call the callback for this attribute */
734 handling_params_t *hp,
737 amar_attr_handling_t *hdl,
742 gboolean success = TRUE;
744 /* capture any conditions where we don't have to copy into the buffer */
745 if (hdl->min_size == 0 || (as->buf_len == 0 && len >= hdl->min_size)) {
746 success = success && hdl->callback(hp->user_data, fs->filenum,
747 fs->file_data, as->attrid, hdl->attrid_data, &as->attr_data,
748 buf, len, eoa, FALSE);
751 /* ok, copy into the buffer */
752 if (as->buf_len + len > as->buf_size) {
753 gpointer newbuf = g_malloc(as->buf_len + len);
755 memcpy(newbuf, as->buf, as->buf_len);
759 as->buf_size = as->buf_len + len;
761 memcpy(as->buf + as->buf_len, buf, len);
764 /* and call the callback if we have enough data or if this is the last attr */
765 if (as->buf_len >= hdl->min_size || eoa) {
766 success = success && hdl->callback(hp->user_data, fs->filenum,
767 fs->file_data, as->attrid, hdl->attrid_data, &as->attr_data,
768 as->buf, as->buf_len, eoa, FALSE);
781 amar_attr_handling_t *handling_array,
782 amar_file_start_callback_t file_start_cb,
783 amar_file_finish_callback_t file_finish_cb,
786 file_state_t *fs = NULL;
787 attr_state_t *as = NULL;
789 handling_params_t hp;
794 amar_attr_handling_t *hdl;
795 gboolean success = TRUE;
797 g_assert(archive->mode == O_RDONLY);
799 hp.user_data = user_data;
800 hp.handling_array = handling_array;
801 hp.file_start_cb = file_start_cb;
802 hp.file_finish_cb = file_finish_cb;
803 hp.file_states = NULL;
806 hp.buf_size = 1024; /* use a 1K buffer to start */
807 hp.buf = g_malloc(hp.buf_size);
809 hp.just_lseeked = FALSE;
811 /* check that we are starting at a header record, but don't advance
812 * the buffer past it */
813 if (buf_atleast(archive, &hp, RECORD_SIZE)) {
814 GETRECORD(buf_ptr(&hp), filenum, attrid, datasize, eoa);
815 if (filenum != MAGIC_FILENUM) {
816 g_set_error(error, amar_error_quark(), EINVAL,
817 "Archive read does not begin at a header record");
823 if (!buf_atleast(archive, &hp, RECORD_SIZE))
826 GETRECORD(buf_ptr(&hp), filenum, attrid, datasize, eoa);
828 /* handle headers specially */
829 if (G_UNLIKELY(filenum == MAGIC_FILENUM)) {
832 /* bail if an EOF occurred in the middle of the header */
833 if (!buf_atleast(archive, &hp, HEADER_SIZE))
836 if (sscanf(buf_ptr(&hp), HEADER_MAGIC " %d", &vers) != 1) {
837 g_set_error(error, amar_error_quark(), EINVAL,
838 "Invalid archive header");
842 if (vers > HEADER_VERSION) {
843 g_set_error(error, amar_error_quark(), EINVAL,
844 "Archive version %d is not supported", vers);
848 buf_skip(archive, &hp, HEADER_SIZE);
853 buf_skip(archive, &hp, RECORD_SIZE);
855 if (datasize > MAX_RECORD_DATA_SIZE) {
856 g_set_error(error, amar_error_quark(), EINVAL,
857 "Invalid record: data size must be less than %d",
858 MAX_RECORD_DATA_SIZE);
862 /* find the file_state_t, if it exists */
863 if (!fs || fs->filenum != filenum) {
865 for (iter = hp.file_states; iter; iter = iter->next) {
866 if (((file_state_t *)iter->data)->filenum == filenum) {
867 fs = (file_state_t *)iter->data;
873 /* get the "special" attributes out of the way */
874 if (G_UNLIKELY(attrid < AMAR_ATTR_APP_START)) {
875 if (attrid == AMAR_ATTR_EOF) {
877 g_set_error(error, amar_error_quark(), EINVAL,
878 "Archive contains an EOF record with nonzero size");
882 success = finish_file(&hp, fs, FALSE);
883 hp.file_states = g_slist_remove(hp.file_states, fs);
890 } else if (attrid == AMAR_ATTR_FILENAME) {
891 /* for filenames, we need the whole filename in the buffer */
892 if (!buf_atleast(archive, &hp, datasize))
896 /* TODO: warn - previous file did not end correctly */
897 success = finish_file(&hp, fs, TRUE);
898 hp.file_states = g_slist_remove(hp.file_states, fs);
906 unsigned int i, nul_padding = 1;
908 /* try to detect NULL padding bytes */
909 if (!buf_atleast(archive, &hp, 512 - RECORD_SIZE)) {
910 /* close to end of file */
914 /* check all byte == 0 */
915 for (i=0; i<512 - RECORD_SIZE; i++) {
922 g_set_error(error, amar_error_quark(), EINVAL,
923 "Archive file %d has an empty filename",
929 g_set_error(error, amar_error_quark(), EINVAL,
930 "Filename record for fileid %d does "
931 "not have its EOA bit set", (int)filenum);
935 fs = g_new0(file_state_t, 1);
936 fs->filenum = filenum;
937 hp.file_states = g_slist_prepend(hp.file_states, fs);
939 if (hp.file_start_cb) {
940 success = hp.file_start_cb(hp.user_data, filenum,
941 buf_ptr(&hp), datasize,
942 &fs->ignore, &fs->file_data);
947 buf_skip(archive, &hp, datasize);
951 g_set_error(error, amar_error_quark(), EINVAL,
952 "Unknown attribute id %d in archive file %d",
953 (int)attrid, (int)filenum);
958 /* if this is an unrecognized file or a known file that's being
959 * ignored, then skip it. */
960 if (!fs || fs->ignore) {
961 buf_skip(archive, &hp, datasize);
965 /* ok, this is an application attribute. Look up its as, if it exists. */
966 if (!as || as->attrid != attrid) {
968 for (iter = fs->attr_states; iter; iter = iter->next) {
969 if (((attr_state_t *)(iter->data))->attrid == attrid) {
970 as = (attr_state_t *)(iter->data);
976 /* and get the proper handling for that attribute */
980 hdl = hp.handling_array;
981 for (hdl = hp.handling_array; hdl->attrid != 0; hdl++) {
982 if (hdl->attrid == attrid)
987 /* As a shortcut, if this is a one-record attribute, handle it without
988 * creating a new attribute_state_t. */
992 /* a simple single-part callback */
993 if (buf_avail(&hp) >= datasize) {
994 success = hdl->callback(hp.user_data, filenum, fs->file_data, attrid,
995 hdl->attrid_data, &tmp, buf_ptr(&hp), datasize, eoa, FALSE);
998 buf_skip(archive, &hp, datasize);
1002 /* we only have part of the data, but if it's big enough to exceed
1003 * the attribute's min_size, then just call the callback for each
1004 * part of the data */
1005 else if (buf_avail(&hp) >= hdl->min_size) {
1006 gsize firstpart = buf_avail(&hp);
1007 gsize lastpart = datasize - firstpart;
1009 success = hdl->callback(hp.user_data, filenum, fs->file_data, attrid,
1010 hdl->attrid_data, &tmp, buf_ptr(&hp), firstpart, FALSE, FALSE);
1013 buf_skip(archive, &hp, firstpart);
1015 if (!buf_atleast(archive, &hp, lastpart))
1018 success = hdl->callback(hp.user_data, filenum, fs->file_data, attrid,
1019 hdl->attrid_data, &tmp, buf_ptr(&hp), lastpart, eoa, FALSE);
1022 buf_skip(archive, &hp, lastpart);
1026 /* no callback -> just skip it */
1027 buf_skip(archive, &hp, datasize);
1032 /* ok, set up a new attribute state */
1034 as = g_new0(attr_state_t, 1);
1035 as->attrid = attrid;
1037 fs->attr_states = g_slist_prepend(fs->attr_states, as);
1040 if (hdl->callback) {
1041 /* handle the data as one or two hunks, depending on whether it's
1042 * all in the buffer right now */
1043 if (buf_avail(&hp) >= datasize) {
1044 success = handle_hunk(&hp, fs, as, hdl, buf_ptr(&hp), datasize, eoa);
1047 buf_skip(archive, &hp, datasize);
1049 gsize hunksize = buf_avail(&hp);
1050 success = handle_hunk(&hp, fs, as, hdl, buf_ptr(&hp), hunksize, FALSE);
1053 buf_skip(archive, &hp, hunksize);
1055 hunksize = datasize - hunksize;
1056 if (!buf_atleast(archive, &hp, hunksize))
1059 handle_hunk(&hp, fs, as, hdl, buf_ptr(&hp), hunksize, eoa);
1060 buf_skip(archive, &hp, hunksize);
1063 buf_skip(archive, &hp, datasize);
1066 /* finish the attribute if this is its last record */
1068 success = finish_attr(&hp, fs, as, FALSE);
1069 fs->attr_states = g_slist_remove(fs->attr_states, as);
1076 /* close any open files, assuming that they have been truncated */
1078 for (iter = hp.file_states; iter; iter = iter->next) {
1079 file_state_t *fs = (file_state_t *)iter->data;
1080 finish_file(&hp, fs, TRUE);
1082 g_slist_free(hp.file_states);