AmendHub

Download

jcs

/

subtext

/

bile.c

 

(View History)

jcs   bile: Minor bug fixes Latest amendment: 641 on 2026-09-22

1 /*
2 * Copyright (c) 2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <string.h>
18 #include <stdio.h>
19 #include "bile.h"
20 #include "util.h"
21
22 /* for errors not specific to a bile */
23 static short _bile_error = 0;
24
25 static short _bile_open_ignore_primary_map = 0;
26
27 struct bile_object * bile_alloc(struct bile *bile, const OSType type,
28 const unsigned long id, const size_t size);
29 struct bile_object * bile_object_in_map(struct bile *bile,
30 const OSType type, const unsigned long id);
31 short bile_read_map(struct bile *bile,
32 struct bile_object *map_ptr);
33 size_t bile_xwriteat(struct bile *bile, const size_t pos,
34 const void *data, const size_t len);
35 void bile_check_sanity(struct bile *bile);
36
37 /* Public API */
38
39 short
40 bile_error(struct bile *bile)
41 {
42 if (bile == NULL)
43 return _bile_error;
44
45 return bile->last_error;
46 }
47
48 struct bile *
49 bile_create(const Str255 filename, short vrefnum, const OSType creator,
50 const OSType type)
51 {
52 struct bile *bile = NULL;
53 size_t len;
54 short fh = -1;
55 char *tmp;
56
57 _bile_error = 0;
58
59 bile = xmalloczero(sizeof(struct bile));
60 if (bile == NULL)
61 return NULL;
62
63 /* create file */
64 _bile_error = Create(filename, vrefnum, creator, type);
65 if (_bile_error)
66 goto create_bail;
67
68 _bile_error = FSOpen(filename, vrefnum, &fh);
69 if (_bile_error)
70 goto create_bail;
71
72 _bile_error = SetEOF(fh, BILE_ALLOCATE_SIZE);
73 if (_bile_error)
74 goto create_bail;
75
76 SetFPos(fh, fsFromStart, 0);
77
78 memcpy(bile->magic, BILE_MAGIC, sizeof(bile->magic));
79 bile->vrefnum = vrefnum;
80 bile->map_ptr.type = BILE_TYPE_MAPPTR;
81 memcpy(bile->filename, filename, sizeof(bile->filename));
82
83 /* write magic */
84 len = BILE_MAGIC_LEN;
85 tmp = xstrdup(BILE_MAGIC);
86 if (tmp == NULL)
87 goto create_bail;
88 _bile_error = FSWrite(fh, &len, tmp);
89 xfree(&tmp);
90 if (_bile_error)
91 goto create_bail;
92
93 /* write header pointing to blank map */
94 len = sizeof(bile->map_ptr);
95 _bile_error = FSWrite(fh, &len, &bile->map_ptr);
96 if (_bile_error)
97 goto create_bail;
98
99 len = sizeof(bile->old_map_ptr);
100 _bile_error = FSWrite(fh, &len, &bile->old_map_ptr);
101 if (_bile_error)
102 goto create_bail;
103
104 /* padding */
105 len = BILE_HEADER_LEN - BILE_MAGIC_LEN - BILE_OBJECT_SIZE -
106 BILE_OBJECT_SIZE;
107 tmp = xmalloczero(len);
108 if (tmp == NULL)
109 goto create_bail;
110 _bile_error = FSWrite(fh, &len, tmp);
111 xfree(&tmp);
112 if (_bile_error)
113 goto create_bail;
114
115 GetFPos(fh, &bile->file_size);
116
117 if (bile->file_size != BILE_HEADER_LEN)
118 panic("bile_create: incorrect length after writing: %ld (!= %ld)",
119 bile->file_size, (long)BILE_HEADER_LEN);
120
121 bile->frefnum = fh;
122 return bile;
123
124 create_bail:
125 if (fh != -1)
126 FSClose(fh);
127 xfree(&bile);
128 return NULL;
129 }
130
131 struct bile *
132 bile_open(const Str255 filename, short vrefnum)
133 {
134 struct bile *bile = NULL;
135 char magic[BILE_MAGIC_LEN + 1];
136 size_t file_size, size;
137 short fh = -1;
138
139 _bile_error = 0;
140
141 bile = xmalloczero(sizeof(struct bile));
142 if (bile == NULL)
143 return NULL;
144
145 /* open file */
146 _bile_error = FSOpen(filename, vrefnum, &fh);
147 if (_bile_error)
148 goto open_bail;
149
150 _bile_error = SetFPos(fh, fsFromLEOF, 0);
151 if (_bile_error)
152 goto open_bail;
153 GetFPos(fh, &file_size);
154 SetFPos(fh, fsFromStart, 0);
155
156 memcpy(bile->magic, BILE_MAGIC, sizeof(bile->magic));
157 bile->vrefnum = vrefnum;
158 memcpy(bile->filename, filename, sizeof(bile->filename));
159 bile->file_size = file_size;
160
161 /* verify magic */
162 size = BILE_MAGIC_LEN;
163 _bile_error = FSRead(fh, &size, &magic);
164 if (_bile_error)
165 goto open_bail;
166
167 if (strncmp(magic, BILE_MAGIC, BILE_MAGIC_LEN) != 0) {
168 if (strncmp(magic, BILE1_MAGIC, BILE1_MAGIC_LEN) == 0)
169 _bile_error = BILE_ERR_NEED_UPGRADE_1;
170 else
171 _bile_error = -1;
172 goto open_bail;
173 }
174
175 /* load map pointer */
176 size = sizeof(bile->map_ptr);
177 _bile_error = FSRead(fh, &size, &bile->map_ptr);
178 if (_bile_error)
179 goto open_bail;
180
181 /* old map pointer */
182 size = sizeof(bile->old_map_ptr);
183 _bile_error = FSRead(fh, &size, &bile->old_map_ptr);
184 if (_bile_error)
185 goto open_bail;
186
187 if (_bile_open_ignore_primary_map) {
188 if (!bile->old_map_ptr.size)
189 goto open_bail;
190 bile->map_ptr = bile->old_map_ptr;
191 }
192
193 bile->frefnum = fh;
194
195 if (bile->map_ptr.size) {
196 if (bile_read_map(bile, &bile->map_ptr) != 0) {
197 warn("bile_open: Failed reading map");
198 goto open_bail;
199 }
200 }
201
202 return bile;
203
204 open_bail:
205 if (fh != -1)
206 FSClose(fh);
207 xfree(&bile);
208 return NULL;
209 }
210
211 void
212 bile_check_sanity(struct bile *bile)
213 {
214 if (bile == NULL)
215 panic("bile_check_sanity: NULL bile");
216
217 /*
218 * We check this at file open time, so if we got here and the magic
219 * is bad, we're pointing at a bogus address
220 */
221 if (memcmp(bile->magic, BILE_MAGIC, sizeof(bile->magic)) != 0)
222 panic("bile_check_sanity: bogus magic");
223 }
224
225 struct bile *
226 bile_open_recover_map(const Str255 filename, short vrefnum)
227 {
228 struct bile *bile;
229
230 _bile_open_ignore_primary_map = 1;
231 bile = bile_open(filename, vrefnum);
232 _bile_open_ignore_primary_map = 0;
233
234 if (bile) {
235 bile->map_ptr = bile->old_map_ptr;
236 bile_write_map(bile);
237 bile_flush(bile, true);
238 }
239
240 return bile;
241 }
242
243 short
244 bile_flush(struct bile *bile, bool and_vol)
245 {
246 IOParam pb;
247 short ret;
248
249 bile_check_sanity(bile);
250
251 memset(&pb, 0, sizeof(pb));
252 pb.ioRefNum = bile->frefnum;
253 ret = PBFlushFile(&pb, false);
254 if (ret != noErr)
255 return ret;
256
257 if (and_vol) {
258 memset(&pb, 0, sizeof(pb));
259 /* XXX: this flushes default volume, maybe flush specific volume */
260 ret = PBFlushVol(&pb, false);
261 if (ret != noErr)
262 return ret;
263 }
264
265 return 0;
266 }
267
268 void
269 bile_close(struct bile *bile)
270 {
271 bile_check_sanity(bile);
272
273 _bile_error = 0;
274
275 FSClose(bile->frefnum);
276 bile->frefnum = -1;
277 if (bile->map != NULL)
278 xfree(&bile->map);
279 }
280
281 struct bile_object *
282 bile_find(struct bile *bile, const OSType type, const unsigned long id)
283 {
284 struct bile_object *o, *ocopy;
285
286 bile_check_sanity(bile);
287
288 o = bile_object_in_map(bile, type, id);
289 if (o == NULL)
290 return NULL;
291
292 ocopy = xmalloc(BILE_OBJECT_SIZE);
293 if (ocopy == NULL) {
294 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
295 return NULL;
296 }
297
298 memcpy(ocopy, o, BILE_OBJECT_SIZE);
299
300 return ocopy;
301 }
302
303 size_t
304 bile_count_by_type(struct bile *bile, const OSType type)
305 {
306 struct bile_object *o;
307 size_t n, count = 0;
308
309 bile_check_sanity(bile);
310
311 _bile_error = bile->last_error = 0;
312
313 for (n = 0; n < bile->nobjects; n++) {
314 o = &bile->map[n];
315 if (o->type == type)
316 count++;
317 }
318
319 return count;
320 }
321
322 size_t
323 bile_ids_by_type(struct bile *bile, const OSType type, unsigned long **ret)
324 {
325 struct bile_object *o;
326 size_t count, size = 0, n;
327 unsigned long *ids = NULL;
328
329 count = 0;
330 *ret = NULL;
331
332 bile_check_sanity(bile);
333
334 for (n = 0; n < bile->nobjects; n++) {
335 o = &bile->map[n];
336 if (o->type != type)
337 continue;
338
339 if (!grow_to_fit(&ids, &size, count * sizeof(size_t), sizeof(size_t),
340 10 * sizeof(size_t))) {
341 if (ids != NULL)
342 xfree(&ids);
343 *ret = NULL;
344 return 0;
345 }
346 ids[count++] = o->id;
347 }
348
349 *ret = ids;
350 return count;
351 }
352
353 size_t
354 bile_sorted_ids_by_type(struct bile *bile, const OSType type,
355 unsigned long **ret)
356 {
357 size_t count, n, j, t;
358 unsigned long *ids = NULL;
359
360 count = bile_ids_by_type(bile, type, &ids);
361 if (count == 0) {
362 *ret = NULL;
363 return 0;
364 }
365
366 for (n = 1; n < count; n++) {
367 for (j = n; j > 0; j--) {
368 t = ids[j];
369 if (t > ids[j - 1])
370 break;
371 ids[j] = ids[j - 1];
372 ids[j - 1] = t;
373 }
374 }
375
376 *ret = ids;
377 return count;
378 }
379
380 struct bile_object *
381 bile_get_nth_of_type(struct bile *bile, const unsigned long index,
382 const OSType type)
383 {
384 struct bile_object *o, *ocopy;
385 size_t n, count = 0;
386
387 bile_check_sanity(bile);
388
389 _bile_error = bile->last_error = 0;
390
391 for (n = 0; n < bile->nobjects; n++) {
392 o = &bile->map[n];
393 if (o->type != type)
394 continue;
395
396 if (count == index) {
397 ocopy = xmalloc(BILE_OBJECT_SIZE);
398 if (ocopy == NULL) {
399 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
400 return NULL;
401 }
402 memcpy(ocopy, o, BILE_OBJECT_SIZE);
403 return ocopy;
404 }
405 count++;
406 }
407
408 return NULL;
409 }
410
411 unsigned long
412 bile_next_id(struct bile *bile, const OSType type)
413 {
414 struct bile_object *o;
415 size_t n;
416 unsigned long id = 1;
417 unsigned long highest;
418
419 bile_check_sanity(bile);
420
421 _bile_error = bile->last_error = 0;
422
423 for (n = 0; n < bile->nobjects; n++) {
424 o = &bile->map[n];
425 if (o->type == type && o->id >= id)
426 id = o->id + 1;
427 }
428
429 o = bile_find(bile, BILE_TYPE_HIGHESTID, type);
430 if (o) {
431 if (bile_read_object(bile, o, &highest,
432 sizeof(unsigned long)) == sizeof(unsigned long)) {
433 if (highest > id)
434 id = highest + 1;
435 }
436 xfree(&o);
437 if (_bile_error)
438 return 0;
439 }
440
441 return id;
442 }
443
444 short
445 bile_delete(struct bile *bile, const OSType type, const unsigned long id,
446 const unsigned short flags)
447 {
448 static char zero[128] = { 0 };
449 struct bile_object *o;
450 size_t pos, size, wsize, n;
451 unsigned long highest;
452
453 bile_check_sanity(bile);
454
455 _bile_error = bile->last_error = 0;
456
457 o = bile_object_in_map(bile, type, id);
458 if (o == NULL) {
459 if (_bile_error)
460 return _bile_error;
461 return -1;
462 }
463
464 o->type = BILE_TYPE_PURGE;
465 pos = o->pos;
466 size = o->size + BILE_OBJECT_SIZE;
467
468 if (flags & BILE_DELETE_FLAG_ZERO) {
469 _bile_error = bile->last_error = SetFPos(bile->frefnum, fsFromStart,
470 pos);
471 if (_bile_error)
472 return -1;
473
474 while (size > 0) {
475 wsize = MIN(128, size);
476 size -= wsize;
477
478 _bile_error = bile->last_error = FSWrite(bile->frefnum, &wsize,
479 zero);
480 if (_bile_error)
481 return -1;
482 }
483 }
484
485 /*
486 * If this is the highest id of this type, store it so it won't get
487 * handed out again from bile_next_id
488 */
489 highest = id;
490 for (n = 0; n < bile->nobjects; n++) {
491 o = &bile->map[n];
492 if (o->type == type && o->id > highest) {
493 highest = o->id;
494 break;
495 }
496 }
497
498 if (highest == id) {
499 /* store the type as the id, and the highest id as the data */
500 bile_write(bile, BILE_TYPE_HIGHESTID, type, &highest,
501 sizeof(unsigned long));
502 if (_bile_error)
503 return -1;
504
505 /* bile_write wrote a new map for us */
506 } else if (flags & BILE_DELETE_FLAG_PURGE) {
507 bile_write_map(bile);
508 if (_bile_error)
509 return -1;
510 }
511
512 return 0;
513 }
514
515 size_t
516 bile_read_object(struct bile *bile, const struct bile_object *o,
517 void *data, const size_t len)
518 {
519 struct bile_object verify;
520 size_t rsize, wantlen;
521
522 bile_check_sanity(bile);
523
524 if (o == NULL)
525 panic("bile_read_object: NULL object passed");
526 if (data == NULL)
527 panic("bile_read_object: NULL data pointer passed");
528 if (len == 0)
529 panic("bile_read_object: zero len");
530
531 _bile_error = bile->last_error = 0;
532
533 if (o->pos + BILE_OBJECT_SIZE + o->size > bile->file_size) {
534 warn("bile_read_object: object %s:%ld pos %ld size %ld > "
535 "file size %ld", OSTypeToString(o->type), o->id, o->pos,
536 o->size, bile->file_size);
537 _bile_error = bile->last_error = BILE_ERR_BOGUS_OBJECT;
538 return 0;
539 }
540
541 _bile_error = bile->last_error = SetFPos(bile->frefnum, fsFromStart,
542 o->pos);
543 if (_bile_error) {
544 warn("bile_read_object: object %s:%lu points to bogus position "
545 "%lu", OSTypeToString(o->type), o->id, o->pos);
546 _bile_error = bile->last_error = BILE_ERR_BOGUS_OBJECT;
547 return 0;
548 }
549
550 rsize = BILE_OBJECT_SIZE;
551 _bile_error = bile->last_error = FSRead(bile->frefnum, &rsize,
552 &verify);
553 if (_bile_error)
554 return 0;
555
556 if (verify.id != o->id) {
557 warn("bile_read_object: object %s:%ld pos %ld wrong id %ld, "
558 "expected %ld", OSTypeToString(o->type), o->id, o->pos,
559 verify.id, o->id);
560 _bile_error = bile->last_error = BILE_ERR_BOGUS_OBJECT;
561 return 0;
562 }
563 if (verify.type != o->type) {
564 warn("bile_read_object: object %s:%ld pos %ld wrong type %ld, "
565 "expected %ld", OSTypeToString(o->type), o->id, o->pos,
566 verify.type, o->type);
567 _bile_error = bile->last_error = BILE_ERR_BOGUS_OBJECT;
568 return 0;
569 }
570 if (verify.size != o->size) {
571 warn("bile_read_object: object %s:%ld pos %ld wrong size %ld, "
572 "expected %ld", OSTypeToString(o->type), o->id, o->pos,
573 verify.size, o->size);
574 _bile_error = bile->last_error = BILE_ERR_BOGUS_OBJECT;
575 return 0;
576 }
577
578 wantlen = len;
579 if (wantlen > o->size)
580 wantlen = o->size;
581
582 rsize = wantlen;
583 _bile_error = bile->last_error = FSRead(bile->frefnum, &rsize, data);
584 if (_bile_error)
585 return 0;
586
587 if (rsize != wantlen) {
588 warn("bile_read_object: %s:%lu: needed to read %ld, read %ld",
589 OSTypeToString(o->type), o->id, wantlen, rsize);
590 _bile_error = bile->last_error = BILE_ERR_BOGUS_OBJECT;
591 return 0;
592 }
593
594 return rsize;
595 }
596
597 size_t
598 bile_read(struct bile *bile, const OSType type, const unsigned long id,
599 void *data, const size_t len)
600 {
601 struct bile_object *o;
602
603 bile_check_sanity(bile);
604
605 if (data == NULL)
606 panic("bile_read: NULL data pointer passed");
607
608 _bile_error = bile->last_error = 0;
609
610 o = bile_object_in_map(bile, type, id);
611 if (o == NULL) {
612 _bile_error = bile->last_error = -1;
613 return 0;
614 }
615
616 return bile_read_object(bile, o, data, len);
617 }
618
619 size_t
620 bile_read_alloc(struct bile *bile, const OSType type,
621 const unsigned long id, void *data_ptr)
622 {
623 struct bile_object *o;
624 char **data;
625
626 bile_check_sanity(bile);
627
628 if (data_ptr == NULL)
629 panic("bile_read: NULL data pointer passed");
630
631 data = (char **)data_ptr;
632 _bile_error = bile->last_error = 0;
633
634 o = bile_object_in_map(bile, type, id);
635 if (o == NULL) {
636 _bile_error = bile->last_error = -1;
637 *data = NULL;
638 return 0;
639 }
640
641 *data = xmalloc(o->size);
642 if (*data == NULL) {
643 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
644 return 0;
645 }
646
647 return bile_read_object(bile, o, *data, o->size);
648 }
649
650 size_t
651 bile_resize(struct bile *bile, const OSType type,
652 const unsigned long id, size_t new_size)
653 {
654 struct bile_object *o, ocopy;
655 size_t ret;
656 char *data;
657
658 bile_check_sanity(bile);
659
660 o = bile_object_in_map(bile, type, id);
661 if (o == NULL) {
662 _bile_error = bile->last_error = -1;
663 return 0;
664 }
665
666 /* if we're growing, fill new space with zero */
667 data = xmalloczero(MAX(o->size, new_size));
668 if (data == NULL) {
669 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
670 return 0;
671 }
672
673 ret = bile_read_object(bile, o, data, o->size);
674 if (ret != o->size) {
675 _bile_error = bile->last_error = -1;
676 xfree(&data);
677 return 0;
678 }
679
680 memcpy(&ocopy, o, sizeof(ocopy));
681 ret = bile_write(bile, ocopy.type, ocopy.id, data, new_size);
682 xfree(&data);
683
684 return ret;
685 }
686
687 size_t
688 bile_write(struct bile *bile, const OSType type, const unsigned long id,
689 const void *data, const size_t len)
690 {
691 struct bile_object *old, *new_obj;
692 size_t wrote;
693
694 bile_check_sanity(bile);
695
696 if (len == 0)
697 panic("bile_write: zero len passed");
698 if (data == NULL)
699 panic("bile_write: NULL data pointer passed");
700 if (id == 0) {
701 warn("bile_write: id cannot be 0");
702 _bile_error = bile->last_error = BILE_ERR_BOGUS_OBJECT;
703 return 0;
704 }
705
706 _bile_error = bile->last_error = 0;
707
708 if ((old = bile_object_in_map(bile, type, id)) != NULL)
709 old->type = BILE_TYPE_PURGE;
710
711 new_obj = bile_alloc(bile, type, id, len);
712 if (new_obj == NULL) {
713 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
714 return 0;
715 }
716
717 wrote = bile_xwriteat(bile, new_obj->pos, new_obj, BILE_OBJECT_SIZE);
718 if (wrote != BILE_OBJECT_SIZE || bile->last_error)
719 return 0;
720 wrote = bile_xwriteat(bile, new_obj->pos + BILE_OBJECT_SIZE, data, len);
721 if (wrote != len || bile->last_error)
722 return 0;
723
724 SetFPos(bile->frefnum, fsFromLEOF, 0);
725 GetFPos(bile->frefnum, &bile->file_size);
726
727 bile_write_map(bile);
728 if (bile->last_error)
729 return 0;
730
731 return wrote;
732 }
733
734 short
735 bile_marshall_object(struct bile *bile,
736 const struct bile_object_field *fields, const size_t nfields,
737 void *object, void *ret_ptr, size_t *ret_size)
738 {
739 char **ret;
740 char *data, *ptr;
741 size_t size = 0, fsize = 0, n;
742 bool write = false;
743
744 bile_check_sanity(bile);
745
746 if (ret_ptr == NULL)
747 panic("bile_pack_object invalid ret");
748
749 ret = (char **)ret_ptr;
750 *ret = NULL;
751 *ret_size = 0;
752
753 iterate_fields:
754 for (n = 0; n < nfields; n++) {
755 if (fields[n].size < 0) {
756 /*
757 * Dynamically-sized field, get length from its length field
758 * and multiply by negative size, so -1 is the actual length but
759 * -4 could be used if length field is number of longs
760 */
761 ptr = (char *)object + fields[n].object_len_off;
762 fsize = *(size_t *)ptr * -(fields[n].size);
763 } else
764 fsize = fields[n].size;
765
766 if (write) {
767 if (fields[n].size < 0) {
768 /* field is dynamically allocated, first write size */
769 memcpy(data + size, &fsize, sizeof(fsize));
770 size += sizeof(fsize);
771 if (!fsize)
772 continue;
773 }
774
775 ptr = (char *)object + fields[n].struct_off;
776 if (fields[n].size < 0) {
777 ptr = (char *)*(unsigned long *)ptr;
778 if (ptr == 0)
779 panic("bile_pack_object field[%lu] points to NULL", n);
780 }
781
782 memcpy(data + size, ptr, fsize);
783 } else if (fields[n].size < 0) {
784 /* account for dynamic field length */
785 size += sizeof(fsize);
786 }
787
788 size += fsize;
789 }
790
791 if (!write) {
792 data = xmalloc(size);
793 if (data == NULL) {
794 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
795 return BILE_ERR_NO_MEMORY;
796 }
797 write = true;
798 size = 0;
799 goto iterate_fields;
800 }
801
802 *ret = data;
803 *ret_size = size;
804
805 return 0;
806 }
807
808 short
809 bile_unmarshall_object(struct bile *bile,
810 const struct bile_object_field *fields, const size_t nfields,
811 const void *data, const size_t data_size, void *object,
812 const size_t object_size, bool deep)
813 {
814 size_t off, fsize = 0, n;
815 char *ptr, *dptr;
816
817 bile_check_sanity(bile);
818
819 for (off = 0, n = 0; n < nfields; n++) {
820 if (fields[n].size < 0) {
821 /* dynamically-sized field, read length */
822 if (off + sizeof(fsize) > data_size)
823 panic("bile_unmarshall_object: truncated length at field "
824 "%lu of %lu", n + 1, nfields);
825 memcpy(&fsize, (char *)data + off, sizeof(fsize));
826 off += sizeof(fsize);
827 } else
828 fsize = fields[n].size;
829
830 if (fsize > data_size - off)
831 panic("bile_unmarshall_object: overflow at field %lu of %lu",
832 n + 1, nfields);
833
834 ptr = (char *)object + fields[n].struct_off;
835
836 if (fields[n].size < 0 && deep) {
837 if (fsize == 0) {
838 memset(ptr, 0, sizeof(dptr));
839 continue;
840 }
841 dptr = xmalloc(fsize);
842 if (dptr == NULL) {
843 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
844 return BILE_ERR_NO_MEMORY;
845 }
846 memcpy(ptr, &dptr, sizeof(dptr));
847 ptr = dptr;
848 }
849
850 if (fields[n].size < 0 && !deep)
851 memset(ptr, 0, sizeof(dptr));
852 else {
853 if (fields[n].size > 0 &&
854 fields[n].struct_off + fsize > object_size)
855 panic("bile_unmarshall_object: overflow writing to object "
856 "at field %lu! (%lu > %lu)", n + 1,
857 fields[n].struct_off + fsize, object_size);
858 memcpy(ptr, (char *)data + off, fsize);
859 }
860
861 off += fsize;
862 }
863
864 return 0;
865 }
866
867 short
868 bile_verify(struct bile *bile)
869 {
870 size_t n, size, pos;
871 char data;
872
873 bile_check_sanity(bile);
874
875 _bile_error = bile->last_error = 0;
876
877 for (n = 0, pos = 0; n < bile->nobjects; n++) {
878 size = bile_read_object(bile, &bile->map[n], &data, 1);
879 if (bile_error(bile))
880 return bile_error(bile);
881 else if (size == 0)
882 return -1;
883
884 if (bile->map[n].pos <= pos)
885 return -1;
886 pos = bile->map[n].pos + bile->map[n].size;
887 }
888
889 return 0;
890 }
891
892 /* Private API */
893
894 struct bile_object *
895 bile_object_in_map(struct bile *bile, const OSType type,
896 const unsigned long id)
897 {
898 struct bile_object *o;
899 size_t n;
900
901 bile_check_sanity(bile);
902
903 _bile_error = bile->last_error = 0;
904
905 /* look backwards, optimizing for newer data */
906 for (n = bile->nobjects; n > 0; n--) {
907 o = &bile->map[n - 1];
908 if (o->type == type && o->id == id)
909 return o;
910 }
911
912 return NULL;
913 }
914
915 struct bile_object *
916 bile_alloc(struct bile *bile, const OSType type, const unsigned long id,
917 const size_t size)
918 {
919 size_t last_pos = BILE_HEADER_LEN;
920 size_t n, map_pos;
921 struct bile_object *new_map;
922
923 bile_check_sanity(bile);
924
925 _bile_error = bile->last_error = 0;
926
927 /* find a gap big enough to hold our object + its size */
928 for (n = 0; n < bile->nobjects; n++) {
929 if (bile->map[n].pos - last_pos >= (size + BILE_OBJECT_SIZE))
930 break;
931 last_pos = bile->map[n].pos + BILE_OBJECT_SIZE + bile->map[n].size;
932 }
933
934 /*
935 * The map is always sorted, so walk the map to find out where to
936 * wedge a copy of this new object, then return a pointer to it in
937 * the map.
938 */
939
940 map_pos = bile->nobjects;
941 for (n = 0; n + 1 < bile->nobjects; n++) {
942 if (n == 0 && last_pos < bile->map[n].pos) {
943 map_pos = 0;
944 break;
945 }
946 if (last_pos > bile->map[n].pos &&
947 last_pos < bile->map[n + 1].pos) {
948 map_pos = n + 1;
949 break;
950 }
951 }
952
953 new_map = xreallocarray(bile->map, bile->nobjects + 1,
954 BILE_OBJECT_SIZE);
955 if (new_map == NULL) {
956 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
957 return NULL;
958 }
959 bile->map = new_map;
960 bile->nobjects++;
961 if (map_pos + 1 < bile->nobjects) {
962 /* shift remaining objects up */
963 memmove(&bile->map[map_pos + 1], &bile->map[map_pos],
964 sizeof(struct bile_object) * (bile->nobjects - map_pos - 1));
965 }
966
967 bile->map[map_pos].type = type;
968 bile->map[map_pos].id = id;
969 bile->map[map_pos].size = size;
970 bile->map[map_pos].pos = last_pos;
971
972 return &bile->map[map_pos];
973 }
974
975 short
976 bile_read_map(struct bile *bile, struct bile_object *map_ptr)
977 {
978 size_t size;
979 struct bile_object map_obj, *map;
980
981 bile_check_sanity(bile);
982
983 if (map_ptr->pos + map_ptr->size > bile->file_size) {
984 warn("bile_read_map: map points to %lu + %lu, but file is only %lu",
985 map_ptr->pos, map_ptr->size, bile->file_size);
986 return -1;
987 }
988
989 if (map_ptr->size % BILE_OBJECT_SIZE != 0) {
990 warn("bile_read_map: map pointer size is not a multiple of object "
991 "size (%lu): %lu", BILE_OBJECT_SIZE, map_ptr->size);
992 return -1;
993 }
994
995 /* read and verify map object header map_ptr points to */
996 _bile_error = SetFPos(bile->frefnum, fsFromStart, map_ptr->pos);
997 if (_bile_error)
998 return -1;
999
1000 size = sizeof(struct bile_object);
1001 _bile_error = FSRead(bile->frefnum, &size, &map_obj);
1002 if (_bile_error)
1003 return -1;
1004
1005 if (map_obj.pos != map_ptr->pos) {
1006 warn("bile_read_map: map pointer points to %lu but object "
1007 "there has position %lu", map_ptr->pos, map_obj.pos);
1008 return -1;
1009 }
1010
1011 if (map_obj.size != map_ptr->size) {
1012 warn("bile_read_map: map is supposed to have size %lu but "
1013 "object pointed to has size %lu", map_ptr->size, map_obj.size);
1014 return -1;
1015 }
1016
1017 /* read entire map */
1018 size = map_obj.size;
1019 map = xmalloczero(size);
1020 if (map == NULL) {
1021 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
1022 return -1;
1023 }
1024 _bile_error = FSRead(bile->frefnum, &size, map);
1025 if (_bile_error) {
1026 xfree(&map);
1027 return -1;
1028 }
1029
1030 bile->map = map;
1031 bile->nobjects = map_obj.size / BILE_OBJECT_SIZE;
1032
1033 return 0;
1034 }
1035
1036 short
1037 bile_write_map(struct bile *bile)
1038 {
1039 struct bile_object saved_map_obj, *obj, *new_map_obj, *new_map,
1040 *new_map_obj_in_new_map = NULL;
1041 size_t new_map_size, new_nobjects, new_map_id;
1042 size_t n;
1043 short ret;
1044
1045 bile_check_sanity(bile);
1046
1047 _bile_error = bile->last_error = 0;
1048
1049 /* allocate a new map slightly larger than we need */
1050 new_nobjects = bile->nobjects;
1051 if (bile->map_ptr.pos)
1052 new_map_id = bile->map_ptr.id + 1;
1053 else {
1054 /* new file, map never written, allocate another object for map */
1055 new_nobjects++;
1056 new_map_id = 1;
1057 }
1058 new_map_size = BILE_OBJECT_SIZE * new_nobjects;
1059 new_map_obj = bile_alloc(bile, BILE_TYPE_MAP, new_map_id,
1060 new_map_size);
1061 if (new_map_obj == NULL) {
1062 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
1063 return BILE_ERR_NO_MEMORY;
1064 }
1065 new_map = xcalloc(BILE_OBJECT_SIZE, bile->nobjects);
1066 if (new_map == NULL) {
1067 _bile_error = bile->last_error = BILE_ERR_NO_MEMORY;
1068 return BILE_ERR_NO_MEMORY;
1069 }
1070
1071 for (n = 0, new_nobjects = 0; n < bile->nobjects; n++) {
1072 obj = &bile->map[n];
1073
1074 if (obj->type == BILE_TYPE_MAP && obj->pos == bile->map_ptr.pos)
1075 /* don't include old map in new one */
1076 continue;
1077 if (obj->type == BILE_TYPE_PURGE)
1078 continue;
1079
1080 if (obj->type == BILE_TYPE_MAP && obj->pos == new_map_obj->pos)
1081 new_map_obj_in_new_map = &new_map[new_nobjects];
1082
1083 new_map[new_nobjects++] = *obj;
1084 }
1085
1086 if (new_map_obj_in_new_map == NULL)
1087 panic("bile_write_map: no new map object in new map");
1088
1089 /* shrink to new object count */
1090 new_map_size = BILE_OBJECT_SIZE * new_nobjects;
1091 new_map_obj->size = new_map_size;
1092 new_map_obj_in_new_map->size = new_map_size;
1093
1094 /* write object header */
1095 bile_xwriteat(bile, new_map_obj->pos, new_map_obj, BILE_OBJECT_SIZE);
1096 if (bile->last_error)
1097 return -1;
1098
1099 /* and then the map contents */
1100 bile_xwriteat(bile, new_map_obj->pos + BILE_OBJECT_SIZE, new_map,
1101 new_map_size);
1102 if (bile->last_error)
1103 return -1;
1104
1105 SetFPos(bile->frefnum, fsFromLEOF, 0);
1106 GetFPos(bile->frefnum, &bile->file_size);
1107
1108 if ((ret = bile_flush(bile, false)) != noErr) {
1109 warn("bile_write_map: flush failed: %d", ret);
1110 return -1;
1111 }
1112
1113 /* successfully wrote new map, switch over */
1114 saved_map_obj = *new_map_obj;
1115 xfree(&bile->map);
1116 bile->nobjects = new_nobjects;
1117 bile->map = new_map;
1118 bile->old_map_ptr.pos = bile->map_ptr.pos;
1119 bile->old_map_ptr.size = bile->map_ptr.size;
1120 bile->old_map_ptr.id = bile->map_ptr.id;
1121 bile->map_ptr.pos = saved_map_obj.pos;
1122 bile->map_ptr.size = saved_map_obj.size;
1123 bile->map_ptr.id = saved_map_obj.id;
1124
1125 /* write new pointer to point at new map object */
1126 bile_xwriteat(bile, BILE_MAGIC_LEN, &bile->map_ptr,
1127 sizeof(bile->map_ptr));
1128 if (bile->last_error)
1129 return -1;
1130 bile_xwriteat(bile, BILE_MAGIC_LEN + sizeof(bile->map_ptr),
1131 &bile->old_map_ptr, sizeof(bile->old_map_ptr));
1132 if (bile->last_error)
1133 return -1;
1134
1135 if ((ret = bile_flush(bile, false)) != noErr) {
1136 warn("bile_write_map: final flush failed: %d", ret);
1137 return -1;
1138 }
1139
1140 return 0;
1141 }
1142
1143 size_t
1144 bile_xwriteat(struct bile *bile, const size_t pos, const void *data,
1145 const size_t len)
1146 {
1147 size_t wsize, tsize;
1148
1149 bile_check_sanity(bile);
1150
1151 _bile_error = bile->last_error = 0;
1152
1153 if (pos + len > bile->file_size) {
1154 /* add new space aligning to BILE_ALLOCATE_SIZE */
1155 tsize = pos + len;
1156 tsize += BILE_ALLOCATE_SIZE - (tsize % BILE_ALLOCATE_SIZE);
1157 _bile_error = SetEOF(bile->frefnum, tsize);
1158 if (_bile_error)
1159 return 0;
1160 _bile_error = bile->last_error = bile_flush(bile, true);
1161 if (_bile_error)
1162 return 0;
1163 }
1164
1165 _bile_error = bile->last_error = SetFPos(bile->frefnum, fsFromStart,
1166 pos);
1167 if (_bile_error)
1168 return 0;
1169
1170 wsize = len;
1171 _bile_error = bile->last_error = FSWrite(bile->frefnum, &wsize, data);
1172 if (_bile_error)
1173 return 0;
1174 if (wsize != len)
1175 panic("bile_xwriteat: short write of %lu at %lu to %s: %lu",
1176 len, pos, PtoCstr(bile->filename), wsize);
1177
1178 SetFPos(bile->frefnum, fsFromLEOF, 0);
1179 GetFPos(bile->frefnum, &bile->file_size);
1180
1181 return wsize;
1182 }