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