Imported Upstream version 3.3.2
[debian/amanda] / amar-src / amar.c
1 /*
2  * Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
3  *
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.
7  *
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
11  * for more details.
12  *
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
16  *
17  * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18  * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
19  */
20
21 #include "amanda.h"
22 #include "util.h"
23 #include "amar.h"
24
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. */
29
30 #define HEADER_MAGIC "AMANDA ARCHIVE FORMAT"
31 #define MAGIC_FILENUM 0x414d
32 #define HEADER_VERSION 1
33 #define EOA_BIT 0x80000000
34
35 typedef struct header_s {
36     /* magic is HEADER_MAGIC + ' ' + decimal version, NUL padded */
37     char     magic[28];
38 } header_t;
39 #define HEADER_SIZE (SIZEOF(header_t))
40
41 typedef struct record_s {
42     uint16_t filenum;
43     uint16_t attrid;
44     uint32_t size;
45 } record_t;
46 #define RECORD_SIZE (SIZEOF(record_t))
47 #define MAX_RECORD_DATA_SIZE (4*1024*1024)
48
49 #define MKRECORD(ptr, f, a, s, eoa) do { \
50     record_t r; \
51     uint32_t size = s; \
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)); \
57 } while(0)
58
59 /* N.B. - f, a, s, and eoa must be simple lvalues */
60 #define GETRECORD(ptr, f, a, s, eoa) do { \
61     record_t r; \
62     memcpy(&r, ptr, sizeof(record_t)); \
63     s = ntohl(r.size); \
64     if (s & EOA_BIT) { \
65         eoa = TRUE; \
66         s &= ~EOA_BIT; \
67     } else { \
68         eoa = FALSE; \
69     } \
70     f = ntohs(r.filenum); \
71     a = ntohs(r.attrid); \
72 } while(0)
73
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)
77
78 struct amar_s {
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? */
86
87     /* internal buffer; on writing, this is WRITE_BUFFER_SIZE bytes, and
88      * always has at least RECORD_SIZE bytes free. */
89     gpointer buf;
90     size_t buf_len;
91     size_t buf_size;
92 };
93
94 struct amar_file_s {
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 */
98 };
99
100 struct amar_attr_s {
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 */
104 };
105
106 /*
107  * Internal functions
108  */
109
110 GQuark
111 amar_error_quark(void)
112 {
113     static GQuark q;
114     if (!q)
115         q = g_quark_from_static_string("amar_error");
116     return q;
117 }
118
119 static gboolean
120 flush_buffer(
121         amar_t *archive,
122         GError **error)
123 {
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));
128             return FALSE;
129         }
130         archive->buf_len = 0;
131     }
132
133     return TRUE;
134 }
135
136 static gboolean
137 write_header(
138         amar_t *archive,
139         GError **error)
140 {
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))
144             return FALSE;
145     }
146
147     memcpy(archive->buf + archive->buf_len, &archive->hdr, HEADER_SIZE);
148     archive->buf_len += HEADER_SIZE;
149     archive->position += HEADER_SIZE;
150
151     return TRUE;
152 }
153
154 static gboolean
155 write_record(
156         amar_t *archive,
157         uint16_t filenum,
158         uint16_t attrid,
159         gboolean eoa,
160         gpointer data,
161         gsize data_size,
162         GError **error)
163 {
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;
167
168     /* is it worth copying this record into the buffer? */
169     if (archive->buf_len + RECORD_SIZE + data_size < WRITE_BUFFER_SIZE - RECORD_SIZE) {
170         /* yes, it is */
171         if (data_size)
172             memcpy(archive->buf + archive->buf_len, data, data_size);
173         archive->buf_len += data_size;
174     } else {
175         /* no, it's not */
176         struct iovec iov[2];
177
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));
186             return FALSE;
187         }
188         archive->buf_len = 0;
189     }
190
191     archive->position += data_size + RECORD_SIZE;
192     return TRUE;
193 }
194
195 /*
196  * Public functions
197  */
198
199 amar_t *
200 amar_new(
201     int       fd,
202     mode_t mode,
203     GError **error)
204 {
205     amar_t *archive = malloc(SIZEOF(amar_t));
206
207     /* make some sanity checks first */
208     g_assert(fd >= 0);
209     g_assert(mode == O_RDONLY || mode == O_WRONLY);
210
211     archive->fd = fd;
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);
217     archive->buf = NULL;
218
219     if (mode == O_WRONLY) {
220         archive->buf = g_malloc(WRITE_BUFFER_SIZE);
221         archive->buf_size = WRITE_BUFFER_SIZE;
222     }
223     archive->buf_len = 0;
224
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);
230
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 */
234             return NULL;
235         }
236     }
237
238     return archive;
239 }
240
241 gboolean
242 amar_close(
243     amar_t *archive,
244     GError **error)
245 {
246     gboolean success = TRUE;
247
248     /* verify all files are done */
249     g_assert(g_hash_table_size(archive->files) == 0);
250
251     if (!flush_buffer(archive, error))
252         success = FALSE;
253
254     g_hash_table_destroy(archive->files);
255     if (archive->buf) g_free(archive->buf);
256     amfree(archive);
257
258     return success;
259 }
260
261 /*
262  * Writing
263  */
264
265 amar_file_t *
266 amar_new_file(
267     amar_t *archive,
268     char *filename_buf,
269     gsize filename_len,
270     off_t *header_offset,
271     GError **error)
272 {
273     amar_file_t *file = NULL;
274
275     g_assert(archive->mode == O_WRONLY);
276     g_assert(filename_buf != NULL);
277
278     /* set filename_len if it wasn't specified */
279     if (!filename_len)
280         filename_len = strlen(filename_buf);
281     g_assert(filename_len != 0);
282
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");
286         return NULL;
287     }
288
289     /* pick a new, unused filenum */
290
291     if (g_hash_table_size(archive->files) == 65535) {
292         g_set_error(error, amar_error_quark(), ENOSPC,
293                     "No more file numbers available");
294         return NULL;
295     }
296
297     do {
298         gint filenum;
299
300         archive->maxfilenum++;
301
302         /* MAGIC_FILENUM can't be used because it matches the header record text */
303         if (archive->maxfilenum == MAGIC_FILENUM) {
304             continue;
305         }
306
307         /* see if this fileid is already in use */
308         filenum = archive->maxfilenum;
309         if (g_hash_table_lookup(archive->files, &filenum))
310             continue;
311
312     } while (0);
313
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);
319
320     /* record the current position and write a header there, if desired */
321     if (header_offset) {
322         *header_offset = archive->position;
323         if (!write_header(archive, error))
324             goto error_exit;
325     }
326
327     /* add a filename record */
328     if (!write_record(archive, file->filenum, AMAR_ATTR_FILENAME,
329                       1, filename_buf, filename_len, error))
330         goto error_exit;
331
332     return file;
333
334 error_exit:
335     if (file) {
336         g_hash_table_remove(archive->files, &file->filenum);
337         g_hash_table_destroy(file->attributes);
338         g_free(file);
339     }
340     return NULL;
341 }
342
343 static void
344 foreach_attr_close(
345         gpointer key G_GNUC_UNUSED,
346         gpointer value,
347         gpointer user_data)
348 {
349     amar_attr_t *attr = value;
350     GError **error = user_data;
351
352     /* return immediately if we've already seen an error */
353     if (*error)
354         return;
355
356     if (!attr->wrote_eoa) {
357         amar_attr_close(attr, error);
358     }
359 }
360
361 gboolean
362 amar_file_close(
363     amar_file_t *file,
364     GError **error)
365 {
366     gboolean success = TRUE;
367     amar_t *archive = file->archive;
368
369     /* close all attributes that haven't already written EOA */
370     g_hash_table_foreach(file->attributes, foreach_attr_close, error);
371     if (*error)
372         success = FALSE;
373
374     /* write an EOF record */
375     if (success) {
376         if (!write_record(archive, file->filenum, AMAR_ATTR_EOF, 1,
377                           NULL, 0, error))
378             success = FALSE;
379     }
380
381     /* remove from archive->file list */
382     g_hash_table_remove(archive->files, &file->filenum);
383
384     /* clean up */
385     g_hash_table_destroy(file->attributes);
386     amfree(file);
387
388     return success;
389 }
390
391 amar_attr_t *
392 amar_new_attr(
393     amar_file_t *file,
394     uint16_t attrid,
395     GError **error G_GNUC_UNUSED)
396 {
397     amar_attr_t *attribute;
398     gint attrid_gint = attrid;
399
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);
403
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);
409
410     /* (note this function cannot currently return an error) */
411
412     return attribute;
413 }
414
415 gboolean
416 amar_attr_close(
417     amar_attr_t *attribute,
418     GError **error)
419 {
420     amar_file_t   *file    = attribute->file;
421     amar_t        *archive = file->archive;
422     gboolean rv = TRUE;
423
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,
428                           1, NULL, 0, error))
429             rv = FALSE;
430         attribute->wrote_eoa = TRUE;
431     }
432
433     return rv;
434 }
435
436 gboolean
437 amar_attr_add_data_buffer(
438     amar_attr_t *attribute,
439     gpointer data, gsize size,
440     gboolean eoa,
441     GError **error)
442 {
443     amar_file_t *file = attribute->file;
444     amar_t *archive = file->archive;
445
446     g_assert(!attribute->wrote_eoa);
447
448     /* write records until we've consumed all of the buffer */
449     while (size) {
450         gsize rec_data_size;
451         gboolean rec_eoa = FALSE;
452
453         if (size > MAX_RECORD_DATA_SIZE) {
454             rec_data_size = MAX_RECORD_DATA_SIZE;
455         } else {
456             rec_data_size = size;
457             if (eoa)
458                 rec_eoa = TRUE;
459         }
460
461         if (!write_record(archive, file->filenum, attribute->attrid,
462                           rec_eoa, data, rec_data_size, error))
463             return FALSE;
464
465         data += rec_data_size;
466         size -= rec_data_size;
467     }
468
469     if (eoa) {
470         attribute->wrote_eoa = TRUE;
471     }
472
473     return TRUE;
474 }
475
476 off_t
477 amar_attr_add_data_fd(
478     amar_attr_t *attribute,
479     int fd,
480     gboolean eoa,
481     GError **error)
482 {
483     amar_file_t   *file    = attribute->file;
484     amar_t        *archive = file->archive;
485     gssize size;
486     off_t filesize = 0;
487     gpointer buf = g_malloc(MAX_RECORD_DATA_SIZE);
488
489     g_assert(!attribute->wrote_eoa);
490
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))
495             goto error_exit;
496
497         filesize += size;
498
499         if (size < MAX_RECORD_DATA_SIZE)
500             break;
501     }
502
503     if (size < 0) {
504         g_set_error(error, amar_error_quark(), errno,
505                     "Error reading from fd %d: %s", fd, strerror(errno));
506         goto error_exit;
507     }
508     g_free(buf);
509
510     attribute->wrote_eoa = eoa;
511
512     return filesize;
513
514 error_exit:
515     g_free(buf);
516     return -1;
517 }
518
519 /*
520  * Reading
521  */
522
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
525  * each file. */
526
527 typedef struct attr_state_s {
528     uint16_t attrid;
529     amar_attr_handling_t *handling;
530     gpointer buf;
531     gsize buf_len;
532     gsize buf_size;
533     gpointer attr_data;
534     gboolean wrote_eoa;
535 } attr_state_t;
536
537 typedef struct file_state_s {
538     uint16_t filenum;
539     gpointer file_data; /* user's data */
540     gboolean ignore;
541
542     GSList *attr_states;
543 } file_state_t;
544
545 typedef struct handling_params_s {
546     /* parameters from the user */
547     gpointer user_data;
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;
551
552     /* tracking for open files and attributes */
553     GSList *file_states;
554
555     /* read buffer */
556     gpointer buf;
557     gsize buf_size; /* allocated size */
558     gsize buf_len; /* number of active bytes .. */
559     gsize buf_offset; /* ..starting at buf + buf_offset */
560     gboolean got_eof;
561     gboolean just_lseeked; /* did we just call lseek? */
562 } handling_params_t;
563
564 /* buffer-handling macros and functions */
565
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. */
568 static gboolean
569 buf_atleast_(
570     amar_t *archive,
571     handling_params_t *hp,
572     gsize atleast)
573 {
574     gsize to_read;
575     gsize bytes_read;
576
577     /* easy case of hp->buf_len >= atleast is taken care of by the macro, below */
578
579     if (hp->got_eof)
580         return FALSE;
581
582     /* If we just don't have space for this much data yet, then we'll have to reallocate
583      * the buffer */
584     if (hp->buf_size < atleast) {
585         if (hp->buf_offset == 0) {
586             hp->buf = g_realloc(hp->buf, atleast);
587         } else {
588             gpointer newbuf = g_malloc(atleast);
589             if (hp->buf) {
590                 memcpy(newbuf, hp->buf+hp->buf_offset, hp->buf_len);
591                 g_free(hp->buf);
592             }
593             hp->buf = newbuf;
594             hp->buf_offset = 0;
595         }
596         hp->buf_size = atleast;
597     }
598
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);
603         hp->buf_offset = 0;
604     }
605
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;
610     else
611         to_read = hp->buf_size - hp->buf_offset - hp->buf_len;
612
613     bytes_read = full_read(archive->fd,
614                            hp->buf+hp->buf_offset+hp->buf_len,
615                            to_read);
616     if (bytes_read < to_read)
617         hp->got_eof = TRUE;
618     hp->just_lseeked = FALSE;
619
620     hp->buf_len += bytes_read;
621
622     return hp->buf_len >= atleast;
623 }
624
625 #define buf_atleast(archive, hp, atleast) \
626     (((hp)->buf_len >= (atleast))? TRUE : buf_atleast_((archive), (hp), (atleast)))
627
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
631  * another error. */
632 static gboolean
633 buf_skip_(
634     amar_t *archive,
635     handling_params_t *hp,
636     gsize skipbytes)
637 {
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 */
640
641     skipbytes -= hp->buf_len;
642     hp->buf_len = 0;
643
644     hp->buf_offset = 0;
645
646 retry:
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;
652                 goto retry;
653             }
654             hp->got_eof = TRUE;
655             return FALSE;
656         }
657     } else {
658         while (skipbytes) {
659             gsize toread = MIN(skipbytes, hp->buf_size);
660             gsize bytes_read = full_read(archive->fd, hp->buf, toread);
661
662             if (bytes_read < toread) {
663                 hp->got_eof = TRUE;
664                 return FALSE;
665             }
666
667             skipbytes -= bytes_read;
668         }
669     }
670
671     return TRUE;
672 }
673
674 #define buf_skip(archive, hp, skipbytes) \
675     (((skipbytes) <= (hp)->buf_len) ? \
676         ((hp)->buf_len -= (skipbytes), \
677          (hp)->buf_offset += (skipbytes), \
678          TRUE) \
679       : buf_skip_((archive), (hp), (skipbytes)))
680
681 /* Get a pointer to the current position in the buffer */
682 #define buf_ptr(hp) ((hp)->buf + (hp)->buf_offset)
683
684 /* Get the amount of data currently available in the buffer */
685 #define buf_avail(hp) ((hp)->buf_len)
686
687 static gboolean
688 finish_attr(
689     handling_params_t *hp,
690     file_state_t *fs,
691     attr_state_t *as,
692     gboolean truncated)
693 {
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);
699     }
700     amfree(as->buf);
701     amfree(as);
702
703     return success;
704 }
705
706 static gboolean
707 finish_file(
708     handling_params_t *hp,
709     file_state_t *fs,
710     gboolean truncated)
711 {
712     GSList *iter;
713     gboolean success = TRUE;
714
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);
719     }
720     g_slist_free(fs->attr_states);
721     fs->attr_states = NULL;
722
723     if (hp->file_finish_cb && !fs->ignore)
724         success = success && hp->file_finish_cb(hp->user_data, fs->filenum, &fs->file_data, truncated);
725
726     amfree(fs);
727     return success;
728 }
729
730 /* buffer the data and/or call the callback for this attribute */
731 static gboolean
732 handle_hunk(
733     handling_params_t *hp,
734     file_state_t *fs,
735     attr_state_t *as,
736     amar_attr_handling_t *hdl,
737     gpointer buf,
738     gsize len,
739     gboolean eoa)
740 {
741     gboolean success = TRUE;
742
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);
748         as->wrote_eoa = eoa;
749     } else {
750         /* ok, copy into the buffer */
751         if (as->buf_len + len > as->buf_size) {
752             gpointer newbuf = g_malloc(as->buf_len + len);
753             if (as->buf) {
754                 memcpy(newbuf, as->buf, as->buf_len);
755                 g_free(as->buf);
756             }
757             as->buf = newbuf;
758             as->buf_size = as->buf_len + len;
759         }
760         memcpy(as->buf + as->buf_len, buf, len);
761         as->buf_len += len;
762
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);
768             as->buf_len = 0;
769             as->wrote_eoa = eoa;
770         }
771     }
772
773     return success;
774 }
775
776 gboolean
777 amar_read(
778         amar_t *archive,
779         gpointer user_data,
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,
783         GError **error)
784 {
785     file_state_t *fs = NULL;
786     attr_state_t *as = NULL;
787     GSList *iter;
788     handling_params_t hp;
789     uint16_t filenum;
790     uint16_t attrid;
791     uint32_t datasize;
792     gboolean eoa;
793     amar_attr_handling_t *hdl;
794     gboolean success = TRUE;
795
796     g_assert(archive->mode == O_RDONLY);
797
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;
803     hp.buf_len = 0;
804     hp.buf_offset = 0;
805     hp.buf_size = 1024; /* use a 1K buffer to start */
806     hp.buf = g_malloc(hp.buf_size);
807     hp.got_eof = FALSE;
808     hp.just_lseeked = FALSE;
809
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");
817             return FALSE;
818         }
819     }
820
821     while (1) {
822         if (!buf_atleast(archive, &hp, RECORD_SIZE))
823             break;
824
825         GETRECORD(buf_ptr(&hp), filenum, attrid, datasize, eoa);
826
827         /* handle headers specially */
828         if (G_UNLIKELY(filenum == MAGIC_FILENUM)) {
829             int vers;
830
831             /* bail if an EOF occurred in the middle of the header */
832             if (!buf_atleast(archive, &hp, HEADER_SIZE))
833                 break;
834
835             if (sscanf(buf_ptr(&hp), HEADER_MAGIC " %d", &vers) != 1) {
836                 g_set_error(error, amar_error_quark(), EINVAL,
837                             "Invalid archive header");
838                 return FALSE;
839             }
840
841             if (vers > HEADER_VERSION) {
842                 g_set_error(error, amar_error_quark(), EINVAL,
843                             "Archive version %d is not supported", vers);
844                 return FALSE;
845             }
846
847             buf_skip(archive, &hp, HEADER_SIZE);
848
849             continue;
850         }
851
852         buf_skip(archive, &hp, RECORD_SIZE);
853
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);
858             return FALSE;
859         }
860
861         /* find the file_state_t, if it exists */
862         if (!fs || fs->filenum != filenum) {
863             fs = NULL;
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;
867                     break;
868                 }
869             }
870         }
871
872         /* get the "special" attributes out of the way */
873         if (G_UNLIKELY(attrid < AMAR_ATTR_APP_START)) {
874             if (attrid == AMAR_ATTR_EOF) {
875                 if (datasize != 0) {
876                     g_set_error(error, amar_error_quark(), EINVAL,
877                                 "Archive contains an EOF record with nonzero size");
878                     return FALSE;
879                 }
880                 if (fs) {
881                     success = finish_file(&hp, fs, FALSE);
882                     hp.file_states = g_slist_remove(hp.file_states, fs);
883                     as = NULL;
884                     fs = NULL;
885                     if (!success)
886                         break;
887                 }
888                 continue;
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))
892                     break;
893
894                 if (fs) {
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);
898                     as = NULL;
899                     fs = NULL;
900                     if (!success)
901                         break;
902                 }
903
904                 if (!datasize) {
905                     unsigned int i, nul_padding = 1;
906                     char *bb;
907                     /* try to detect NULL padding bytes */
908                     if (!buf_atleast(archive, &hp, 512 - RECORD_SIZE)) {
909                         /* close to end of file */
910                         break;
911                     }
912                     bb = buf_ptr(&hp);
913                     /* check all byte == 0 */
914                     for (i=0; i<512 - RECORD_SIZE; i++) {
915                         if (*bb++ != 0)
916                             nul_padding = 0;
917                     }
918                     if (nul_padding) {
919                         break;
920                     }
921                     g_set_error(error, amar_error_quark(), EINVAL,
922                                 "Archive file %d has an empty filename",
923                                 (int)filenum);
924                     return FALSE;
925                 }
926
927                 if (!eoa) {
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);
931                     return FALSE;
932                 }
933
934                 fs = g_new0(file_state_t, 1);
935                 fs->filenum = filenum;
936                 hp.file_states = g_slist_prepend(hp.file_states, fs);
937
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);
942                     if (!success)
943                         break;
944                 }
945
946                 buf_skip(archive, &hp, datasize);
947
948                 continue;
949             } else {
950                 g_set_error(error, amar_error_quark(), EINVAL,
951                             "Unknown attribute id %d in archive file %d",
952                             (int)attrid, (int)filenum);
953                 return FALSE;
954             }
955         }
956
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);
961             continue;
962         }
963
964         /* ok, this is an application attribute.  Look up its as, if it exists. */
965         if (!as || as->attrid != attrid) {
966             as = NULL;
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);
970                     break;
971                 }
972             }
973         }
974
975         /* and get the proper handling for that attribute */
976         if (as) {
977             hdl = as->handling;
978         } else {
979             hdl = hp.handling_array;
980             for (hdl = hp.handling_array; hdl->attrid != 0; hdl++) {
981                 if (hdl->attrid == attrid)
982                     break;
983             }
984         }
985
986         /* As a shortcut, if this is a one-record attribute, handle it without
987          * creating a new attribute_state_t. */
988         if (eoa && !as) {
989             gpointer tmp = NULL;
990             if (hdl->callback) {
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);
995                     if (!success)
996                         break;
997                     buf_skip(archive, &hp, datasize);
998                     continue;
999                 }
1000
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;
1007
1008                     success = hdl->callback(hp.user_data, filenum, fs->file_data, attrid,
1009                             hdl->attrid_data, &tmp, buf_ptr(&hp), firstpart, FALSE, FALSE);
1010                     if (!success)
1011                         break;
1012                     buf_skip(archive, &hp, firstpart);
1013
1014                     if (!buf_atleast(archive, &hp, lastpart))
1015                         break;
1016
1017                     success = hdl->callback(hp.user_data, filenum, fs->file_data, attrid,
1018                             hdl->attrid_data, &tmp, buf_ptr(&hp), lastpart, eoa, FALSE);
1019                     if (!success)
1020                         break;
1021                     buf_skip(archive, &hp, lastpart);
1022                     continue;
1023                 }
1024             } else {
1025                 /* no callback -> just skip it */
1026                 buf_skip(archive, &hp, datasize);
1027                 continue;
1028             }
1029         }
1030
1031         /* ok, set up a new attribute state */
1032         if (!as) {
1033             as = g_new0(attr_state_t, 1);
1034             as->attrid = attrid;
1035             as->handling = hdl;
1036             fs->attr_states = g_slist_prepend(fs->attr_states, as);
1037         }
1038
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);
1044                 if (!success)
1045                     break;
1046                 buf_skip(archive, &hp, datasize);
1047             } else {
1048                 gsize hunksize = buf_avail(&hp);
1049                 success = handle_hunk(&hp, fs, as, hdl, buf_ptr(&hp), hunksize, FALSE);
1050                 if (!success)
1051                     break;
1052                 buf_skip(archive, &hp, hunksize);
1053
1054                 hunksize = datasize - hunksize;
1055                 if (!buf_atleast(archive, &hp, hunksize))
1056                     break;
1057
1058                 handle_hunk(&hp, fs, as, hdl, buf_ptr(&hp), hunksize, eoa);
1059                 buf_skip(archive, &hp, hunksize);
1060             }
1061         } else {
1062             buf_skip(archive, &hp, datasize);
1063         }
1064
1065         /* finish the attribute if this is its last record */
1066         if (eoa) {
1067             success = finish_attr(&hp, fs, as, FALSE);
1068             fs->attr_states = g_slist_remove(fs->attr_states, as);
1069             if (!success)
1070                 break;
1071             as = NULL;
1072         }
1073     }
1074
1075     /* close any open files, assuming that they have been truncated */
1076
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);
1080     }
1081     g_slist_free(hp.file_states);
1082     g_free(hp.buf);
1083
1084     return success;
1085 }