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