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