AmendHub

Download

jcs

/

subtext

/

db.c

 

(View History)

jcs   db: Properly close board and folder biles at shutdown Latest amendment: 297 on 2022-11-30

1 /*
2 * Copyright (c) 2021-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 <stdio.h>
18 #include <string.h>
19 #include <time.h>
20
21 #include "subtext.h"
22 #include "board.h"
23 #include "bile.h"
24 #include "db.h"
25 #include "folder.h"
26 #include "user.h"
27 #include "util.h"
28
29 struct struct_field config_fields[] = {
30 { "BBS Name", CONFIG_TYPE_STRING,
31 offsetof(struct config, name),
32 1, member_size(struct config, name) },
33 { "Phone Number", CONFIG_TYPE_STRING,
34 offsetof(struct config, phone_number),
35 1, member_size(struct config, phone_number) },
36 { "Location", CONFIG_TYPE_STRING,
37 offsetof(struct config, location),
38 1, member_size(struct config, location) },
39 { "Timezone (Abbrev)", CONFIG_TYPE_STRING,
40 offsetof(struct config, timezone),
41 1, member_size(struct config, timezone) },
42 { "Allow Open Signup", CONFIG_TYPE_BOOLEAN,
43 offsetof(struct config, open_signup),
44 0, 0 },
45
46 { "Hostname", CONFIG_TYPE_STRING,
47 offsetof(struct config, hostname),
48 1, member_size(struct config, hostname) },
49 { "Telnet Port", CONFIG_TYPE_SHORT,
50 offsetof(struct config, telnet_port),
51 0, 65535 },
52
53 { "Telnet Trusted Proxy IP", CONFIG_TYPE_IP,
54 offsetof(struct config, trusted_proxy_ip),
55 0, 1 },
56 { "Telnet Trusted Proxy UDP Port", CONFIG_TYPE_SHORT,
57 offsetof(struct config, trusted_proxy_udp_port),
58 0, 65535 },
59
60 { "Modem Port", CONFIG_TYPE_SHORT,
61 offsetof(struct config, modem_port),
62 0, 2 },
63 { "Modem Port Speed", CONFIG_TYPE_LONG,
64 offsetof(struct config, modem_speed),
65 300, 115200 },
66 { "Modem Bits/Parity/Stop", CONFIG_TYPE_STRING,
67 offsetof(struct config, modem_parity),
68 member_size(struct config, modem_parity),
69 member_size(struct config, modem_parity) },
70 { "Modem Init String", CONFIG_TYPE_STRING,
71 offsetof(struct config, modem_init),
72 1, member_size(struct config, modem_init) },
73
74 { "Max Idle Minutes", CONFIG_TYPE_SHORT,
75 offsetof(struct config, max_idle_minutes),
76 1, USHRT_MAX },
77 { "Max Login Seconds", CONFIG_TYPE_SHORT,
78 offsetof(struct config, max_login_seconds),
79 1, USHRT_MAX },
80
81 { "Screen Blanker Idle Seconds", CONFIG_TYPE_SHORT,
82 offsetof(struct config, blanker_idle_seconds),
83 1, USHRT_MAX },
84 { "Screen Blanker Runtime Seconds", CONFIG_TYPE_SHORT,
85 offsetof(struct config, blanker_runtime_seconds),
86 1, USHRT_MAX },
87
88 { "Session Log History Days", CONFIG_TYPE_SHORT,
89 offsetof(struct config, session_log_days),
90 1, USHRT_MAX },
91 };
92 size_t nconfig_fields = nitems(config_fields);
93
94 struct db * db_init(Str255 file, short vrefnum, struct bile *bile);
95 short db_migrate(struct db *tdb, short is_new);
96 void db_config_load(struct db *tdb);
97
98 struct db *
99 db_open(Str255 file, short vrefnum)
100 {
101 Str255 filepath;
102 struct stat sb;
103 Point pt = { 75, 100 };
104 SFReply reply = { 0 };
105 SFTypeList types;
106 StringHandle lastfileh;
107 struct db *ret;
108
109 if (file[0]) {
110 getpath(vrefnum, file, &filepath, true);
111 if (FStat(filepath, &sb) == 0)
112 return db_init(file, vrefnum, NULL);
113
114 warn("Failed to open %s", PtoCstr(file));
115 }
116
117 if ((lastfileh = GetString(STR_LAST_DB))) {
118 HLock(lastfileh);
119 memcpy(filepath, *lastfileh, sizeof(filepath));
120 HUnlock(lastfileh);
121 ReleaseResource(lastfileh);
122 if (FStat(filepath, &sb) == 0) {
123 ret = db_init(filepath, 0, NULL);
124 return ret;
125 }
126 }
127
128 /* no file passed, no last file, prompt */
129 types[0] = DB_TYPE;
130 SFGetFile(pt, NULL, NULL, 1, &types, NULL, &reply);
131 if (reply.good) {
132 ret = db_init(reply.fName, reply.vRefNum, NULL);
133 return ret;
134 }
135
136 return db_create();
137 }
138
139 struct db *
140 db_create(void)
141 {
142 Point pt = { 75, 100 };
143 SFReply reply;
144 struct bile *bile;
145 short error;
146
147 SFPutFile(pt, "\pCreate new Subtext DB:", "\p", NULL, &reply);
148 if (!reply.good)
149 return NULL;
150
151 bile = bile_create(reply.fName, reply.vRefNum, SUBTEXT_CREATOR,
152 DB_TYPE);
153 if (bile == NULL && bile_error(NULL) == dupFNErr) {
154 error = FSDelete(reply.fName, reply.vRefNum);
155 if (error)
156 panic("Failed to re-create file %s: %d", PtoCstr(reply.fName),
157 error);
158 bile = bile_create(reply.fName, reply.vRefNum, SUBTEXT_CREATOR,
159 DB_TYPE);
160 }
161
162 if (bile == NULL)
163 panic("Failed to create file %s: %d", PtoCstr(reply.fName), error);
164
165 return db_init(reply.fName, reply.vRefNum, bile);
166 }
167
168 struct db *
169 db_init(Str255 path, short vrefnum, struct bile *bile)
170 {
171 Str255 fullpath;
172 struct db *tdb;
173 Handle resh, lastfileh;
174 short was_new, error, i;
175
176 was_new = (bile != NULL);
177
178 if (bile == NULL) {
179 bile = bile_open(path, vrefnum);
180 if (bile == NULL) {
181 if (ask("Attempt recovery with backup map?")) {
182 bile = bile_open_recover_map(path, vrefnum);
183 if (bile == NULL)
184 panic("Failed to recover DB file %s: %d",
185 PtoCstr(path), bile_error(NULL));
186 } else {
187 panic("Failed to open DB file %s: %d", PtoCstr(path),
188 bile_error(NULL));
189 }
190 }
191 }
192
193 /* we got this far, store it as the last-accessed file */
194 if (vrefnum == 0)
195 memcpy(fullpath, path, sizeof(fullpath));
196 else
197 getpath(vrefnum, path, &fullpath, true);
198 lastfileh = Get1Resource('STR ', STR_LAST_DB);
199 if (lastfileh)
200 xSetHandleSize(lastfileh, fullpath[0] + 1);
201 else
202 lastfileh = xNewHandle(fullpath[0] + 1);
203 HLock(lastfileh);
204 memcpy(*lastfileh, fullpath, fullpath[0] + 1);
205 HUnlock(lastfileh);
206 if (HomeResFile(lastfileh) == -1)
207 AddResource(lastfileh, 'STR ', STR_LAST_DB, "\pSTR_LAST_DB");
208 else
209 ChangedResource(lastfileh);
210 WriteResource(lastfileh);
211 DetachResource(lastfileh);
212
213 tdb = xmalloczero(sizeof(struct db), "db_init db");
214 tdb->bile = bile;
215
216 if (db_migrate(tdb, was_new) != 0) {
217 bile_close(tdb->bile);
218 xfree(&tdb);
219 return NULL;
220 }
221
222 if (!was_new) {
223 db_config_load(tdb);
224 db_cache_boards(tdb);
225 db_cache_folders(tdb);
226 }
227
228 PtoCstr(fullpath);
229 strlcat((char *)&fullpath, "-sessions", sizeof(fullpath));
230 CtoPstr(fullpath);
231
232 tdb->sessions_bile = bile_open(fullpath, vrefnum);
233 if (tdb->sessions_bile == NULL) {
234 tdb->sessions_bile = bile_create(fullpath, vrefnum,
235 SUBTEXT_CREATOR, SL_TYPE);
236 if (tdb->sessions_bile == NULL)
237 panic("Couldn't create %s: %d", PtoCstr(fullpath),
238 bile_error(NULL));
239 }
240
241 return tdb;
242 }
243
244 void
245 db_close(struct db *tdb)
246 {
247 short n;
248
249 bile_close(tdb->bile);
250 xfree(&tdb->bile);
251
252 if (tdb->sessions_bile != NULL) {
253 bile_close(tdb->sessions_bile);
254 xfree(&tdb->sessions_bile);
255 }
256
257 if (tdb->boards) {
258 for (n = 0; n < tdb->nboards; n++) {
259 if (tdb->boards[n].bile)
260 bile_close(tdb->boards[n].bile);
261 }
262 xfree(&tdb->boards);
263 }
264
265 if (tdb->folders) {
266 for (n = 0; n < tdb->nfolders; n++) {
267 if (tdb->folders[n].bile)
268 bile_close(tdb->folders[n].bile);
269 }
270 xfree(&tdb->folders);
271 }
272
273 xfree(&tdb);
274 }
275
276 short
277 db_migrate(struct db *tdb, short is_new)
278 {
279 struct user *user;
280 struct bile_object *bob;
281 struct db *olddb;
282 char *error;
283 char ver;
284
285 if (is_new) {
286 ver = DB_CUR_VERS;
287
288 /* setup some defaults */
289 sprintf(tdb->config.name, "Example Subtext BBS");
290 sprintf(tdb->config.phone_number, "(555) 867-5309");
291 sprintf(tdb->config.location, "Springfield");
292 sprintf(tdb->config.hostname, "bbs.example.com");
293 sprintf(tdb->config.timezone, "CT");
294 tdb->config.telnet_port = 0;
295 tdb->config.modem_port = 0;
296 tdb->config.modem_speed = 9600;
297 sprintf(tdb->config.modem_init, "ATQ0V1S0=2");
298 sprintf(tdb->config.modem_parity, "8N1");
299 tdb->config.max_idle_minutes = 5;
300 tdb->config.max_login_seconds = 90;
301 tdb->config.blanker_idle_seconds = (60 * 60);
302 tdb->config.blanker_runtime_seconds = 30;
303 tdb->config.session_log_days = 21;
304 db_config_save(tdb);
305
306 /* create a default sysop user */
307 user = xmalloczero(sizeof(struct user), "db_migrate user");
308 strncpy(user->username, "sysop", sizeof(user->username));
309 user->created_at = Time;
310 user->is_enabled = DB_TRUE;
311 user_set_password(user, "p4ssw0rd");
312 user->is_sysop = DB_TRUE;
313 /* user_save assumes db is already set */
314 olddb = db;
315 db = tdb;
316 user_save(user);
317 user_cache_usernames();
318 db = olddb;
319 } else {
320 if (bile_read(tdb->bile, DB_VERS_RTYPE, 1, &ver, 1) != 1)
321 ver = 1;
322
323 if (ver == DB_CUR_VERS)
324 return 0;
325
326 if (ask("Migrate this database from version %d to %d to open it?",
327 ver, DB_CUR_VERS) != ASK_YES)
328 return -1;
329 }
330
331 /* per-version migrations */
332 while (ver < DB_CUR_VERS) {
333 switch (ver) {
334 case 1: {
335 /* 1->2, added user is_enabled field */
336 struct user user;
337 size_t nusers, n;
338 unsigned long *ids = NULL;
339
340 nusers = bile_sorted_ids_by_type(tdb->bile, DB_USER_RTYPE,
341 &ids);
342 for (n = 0; n < nusers; n++) {
343 memset(&user, 0, sizeof(user));
344 bile_read(tdb->bile, DB_USER_RTYPE, ids[n], (char *)&user,
345 sizeof(user));
346 user.is_enabled = DB_TRUE;
347 bile_write(tdb->bile, DB_USER_RTYPE, ids[n], (char *)&user,
348 sizeof(user));
349 }
350 if (ids)
351 xfree(&ids);
352 break;
353 }
354 case 2: {
355 /* 2->3, added config open_signup field */
356 struct config new_config = { 0 };
357
358 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
359 sizeof(new_config));
360
361 new_config.open_signup = 0;
362
363 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
364 sizeof(new_config));
365 break;
366 }
367 case 3: {
368 /* 3->4, added max_login_seconds */
369 struct config new_config = { 0 };
370
371 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
372 sizeof(new_config));
373
374 new_config.max_login_seconds = 90;
375
376 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
377 sizeof(new_config));
378 break;
379 }
380 case 4: {
381 /* 4->5, added blanker fields */
382 struct config new_config = { 0 };
383
384 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
385 sizeof(new_config));
386
387 new_config.blanker_idle_seconds = (60 * 60);
388 new_config.blanker_runtime_seconds = 30;
389
390 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
391 sizeof(new_config));
392 break;
393 }
394 case 5: {
395 /* 5->6, added trusted_proxy_ip */
396 struct config new_config = { 0 };
397
398 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
399 sizeof(new_config));
400
401 new_config.trusted_proxy_ip = 0;
402
403 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
404 sizeof(new_config));
405 break;
406 }
407 case 6: {
408 /* 6->7, added modem_speed */
409 struct config new_config = { 0 };
410
411 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
412 sizeof(new_config));
413
414 new_config.modem_speed = 19200;
415
416 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
417 sizeof(new_config));
418 break;
419 }
420 case 7: {
421 /* 7->8, added trusted_proxy_udp_port */
422 struct config new_config = { 0 };
423
424 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
425 sizeof(new_config));
426
427 new_config.trusted_proxy_udp_port = 0;
428
429 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
430 sizeof(new_config));
431 break;
432 }
433 case 8: {
434 /* 8->9, added user.last_motd_seen */
435 struct user user;
436 size_t nusers, n;
437 unsigned long *ids = NULL;
438
439 nusers = bile_sorted_ids_by_type(tdb->bile, DB_USER_RTYPE,
440 &ids);
441 for (n = 0; n < nusers; n++) {
442 memset(&user, 0, sizeof(user));
443 bile_read(tdb->bile, DB_USER_RTYPE, ids[n], (char *)&user,
444 sizeof(user));
445 user.last_motd_seen = 0;
446 bile_write(tdb->bile, DB_USER_RTYPE, ids[n], (char *)&user,
447 sizeof(user));
448 }
449 if (ids)
450 xfree(&ids);
451 break;
452 }
453 case 9: {
454 /* 9->10, added board post via */
455 size_t nposts, n, j, size;
456 unsigned long *ids = NULL;
457 struct board *board;
458 char *data;
459 struct board_post post;
460 short ret;
461
462 db_cache_boards(tdb);
463
464 for (n = 0; n < tdb->nboards; n++) {
465 board = &tdb->boards[n];
466 nposts = bile_sorted_ids_by_type(board->bile,
467 BOARD_POST_RTYPE, &ids);
468 for (j = 0; j < nposts; j++) {
469 size = bile_read_alloc(board->bile, BOARD_POST_RTYPE,
470 ids[j], &data);
471 if (size == 0)
472 panic("failed fetching message %ld: %d", ids[j],
473 bile_error(board->bile));
474
475 /* pretend we read this much, just fill via with junk */
476 size += member_size(struct board_post, via);
477
478 bile_unmarshall_object(board->bile,
479 board_post_object_fields, nboard_post_object_fields,
480 data, size, &post, sizeof(post), true, "board_post");
481 xfree(&data);
482
483 /* zero out junk */
484 memset(&post.via, 0, sizeof(post.via));
485
486 ret = bile_marshall_object(board->bile,
487 board_post_object_fields, nboard_post_object_fields,
488 &post, &data, &size, "board_post post");
489 if (ret != 0 || size == 0)
490 panic("failed to marshall new post object");
491 if (bile_write(board->bile, BOARD_POST_RTYPE,
492 post.id, data, size) != size)
493 panic("bile_write of post failed! %d",
494 bile_error(board->bile));
495
496 xfree(&data);
497 }
498 if (ids != NULL)
499 xfree(&ids);
500 }
501 break;
502 }
503 case 10: {
504 /* 10->11, added session_log_days */
505 struct config new_config = { 0 };
506
507 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
508 sizeof(new_config));
509
510 new_config.session_log_days = 21;
511
512 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
513 sizeof(new_config));
514 break;
515 }
516 case 11: {
517 /* 11->12, added modem_parity */
518 struct config new_config = { 0 };
519
520 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
521 sizeof(new_config));
522
523 sprintf(new_config.modem_parity, "8N1");
524
525 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
526 sizeof(new_config));
527 break;
528 }
529 }
530
531 ver++;
532 }
533
534 /* store new version */
535 ver = DB_CUR_VERS;
536 if (bile_write(tdb->bile, DB_VERS_RTYPE, 1, &ver, 1) != 1)
537 panic("Failed writing new version: %d", bile_error(tdb->bile));
538
539 return 0;
540 }
541
542 void
543 db_config_save(struct db *tdb)
544 {
545 if (bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &tdb->config,
546 sizeof(tdb->config)) != sizeof(tdb->config))
547 panic("db_config_save: failed to write: %d",
548 bile_error(tdb->bile));
549 }
550
551 void
552 db_config_load(struct db *tdb)
553 {
554 size_t rlen;
555 char *newconfig;
556
557 rlen = bile_read_alloc(tdb->bile, DB_CONFIG_RTYPE, 1, &newconfig);
558 if (rlen == 0 || bile_error(tdb->bile))
559 panic("db_config_load: error reading config: %d",
560 bile_error(tdb->bile));
561 if (rlen != sizeof(tdb->config))
562 warn("db_config_load: read config of size %lu, but expected %lu",
563 rlen, sizeof(tdb->config));
564
565 memcpy(&tdb->config, newconfig, sizeof(tdb->config));
566 xfree(&newconfig);
567 }
568
569 void
570 db_cache_boards(struct db *tdb)
571 {
572 Str255 db_filename, board_filename;
573 size_t n, size;
574 unsigned long *ids;
575 short error;
576 struct bile_object *obj;
577 char *data = NULL;
578 char note[MALLOC_NOTE_SIZE];
579
580 if (tdb->boards) {
581 for (n = 0; n < tdb->nboards; n++) {
582 if (tdb->boards[n].bile)
583 bile_close(tdb->boards[n].bile);
584 }
585 xfree(&tdb->boards);
586 }
587
588 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, &db_filename,
589 false) != 0)
590 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
591 PtoCstr(db_filename);
592
593 tdb->nboards = bile_sorted_ids_by_type(tdb->bile, DB_BOARD_RTYPE,
594 &ids);
595 if (!tdb->nboards)
596 return;
597 tdb->boards = xcalloc(tdb->nboards, sizeof(struct board),
598 "db_cache_boards");
599
600 /*
601 * Read ids first so we have a consistent order, then try to open or
602 * fix/recreate each bile, which may change their order in the map
603 */
604 for (n = 0; n < tdb->nboards; n++) {
605 obj = bile_find(tdb->bile, DB_BOARD_RTYPE, ids[n]);
606 if (obj == NULL)
607 break;
608
609 size = bile_read_alloc(tdb->bile, DB_BOARD_RTYPE, obj->id,
610 &data);
611 snprintf(note, sizeof(note), "db_cache_boards %ld", obj->id);
612 bile_unmarshall_object(tdb->bile, board_object_fields,
613 nboard_object_fields, data, size, (char *)(&tdb->boards[n]),
614 sizeof(struct board), true, note);
615 xfree(&data);
616 }
617
618 xfree(&ids);
619
620 for (n = 0; n < tdb->nboards; n++) {
621 snprintf((char *)board_filename, sizeof(board_filename),
622 "%s:%ld.%s", db_filename, tdb->boards[n].id, BOARD_FILENAME_EXT);
623 CtoPstr(board_filename);
624 tdb->boards[n].bile = bile_open(board_filename,
625 tdb->bile->vrefnum);
626 if (tdb->boards[n].bile != NULL)
627 continue;
628
629 PtoCstr(board_filename);
630
631 if (bile_error(NULL) != fnfErr &&
632 ask("Attempt recovery of %s with backup map?", board_filename)) {
633 CtoPstr(board_filename);
634 tdb->boards[n].bile = bile_open_recover_map(board_filename,
635 tdb->bile->vrefnum);
636 if (tdb->boards[n].bile != NULL)
637 continue;
638 warn("Failed to recover board file %s: %d",
639 PtoCstr(board_filename), bile_error(NULL));
640 }
641
642 if (ask("Failed to open board bile %s (error %d), recreate it?",
643 board_filename, bile_error(NULL)) == false)
644 exit(0);
645
646 tdb->boards[n].bile = db_board_create(tdb, &tdb->boards[n], true);
647 if (tdb->boards[n].bile == NULL)
648 panic("Failed to create board file %s: %d",
649 board_filename, bile_error(NULL));
650 }
651 }
652
653 struct bile *
654 db_board_create(struct db *tdb, struct board *board, bool delete_first)
655 {
656 Str255 db_filename, board_filename;
657 struct bile *board_bile;
658 size_t size;
659 short ret;
660 char *data;
661
662 ret = bile_marshall_object(tdb->bile, board_object_fields,
663 nboard_object_fields, board, &data, &size, "db_board_create");
664 if (ret != 0 || size == 0) {
665 warn("db_board_create: failed to marshall object");
666 return;
667 }
668
669 if (bile_write(tdb->bile, DB_BOARD_RTYPE, board->id, data,
670 size) != size)
671 panic("save of new board failed: %d", bile_error(tdb->bile));
672 xfree(&data);
673
674 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, &db_filename,
675 false) != 0)
676 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
677 PtoCstr(db_filename);
678
679 snprintf((char *)board_filename, sizeof(board_filename), "%s:%ld.%s",
680 db_filename, board->id, BOARD_FILENAME_EXT);
681 CtoPstr(board_filename);
682
683 if (delete_first)
684 FSDelete(board_filename, tdb->bile->vrefnum);
685
686 board_bile = bile_create(board_filename, tdb->bile->vrefnum,
687 SUBTEXT_CREATOR, DB_BOARD_RTYPE);
688 if (board_bile == NULL)
689 panic("Failed creating new board bile at %s: %d",
690 PtoCstr(board_filename), bile_error(NULL));
691
692 return board_bile;
693 }
694
695 void
696 db_cache_folders(struct db *tdb)
697 {
698 Str255 db_filename, folder_filename, folder_dir;
699 size_t n, size;
700 unsigned long *ids;
701 struct bile_object *obj;
702 char *data = NULL;
703 char note[MALLOC_NOTE_SIZE];
704
705 if (tdb->folders) {
706 for (n = 0; n < tdb->nfolders; n++) {
707 if (tdb->folders[n].bile)
708 bile_close(tdb->folders[n].bile);
709 }
710 xfree(&tdb->folders);
711 }
712
713 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, &db_filename,
714 false) != 0)
715 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
716 PtoCstr(db_filename);
717
718 tdb->nfolders = bile_sorted_ids_by_type(tdb->bile, DB_FOLDER_RTYPE,
719 &ids);
720 if (!tdb->nfolders)
721 return;
722 tdb->folders = xcalloc(tdb->nfolders, sizeof(struct folder),
723 "db_cache_folders");
724
725 /*
726 * Read ids first so we have a consistent order, then try to open or
727 * fix/recreate each bile, which may change their order in the map
728 */
729 for (n = 0; n < tdb->nfolders; n++) {
730 obj = bile_find(tdb->bile, DB_FOLDER_RTYPE, ids[n]);
731 if (obj == NULL)
732 break;
733
734 size = bile_read_alloc(tdb->bile, DB_FOLDER_RTYPE, obj->id, &data);
735 snprintf(note, sizeof(note), "db_cache_folders %ld", obj->id);
736 bile_unmarshall_object(tdb->bile, folder_object_fields,
737 nfolder_object_fields, data, size, (char *)(&tdb->folders[n]),
738 sizeof(struct folder), true, note);
739 xfree(&data);
740 }
741
742 xfree(&ids);
743
744 for (n = 0; n < tdb->nfolders; n++) {
745 snprintf((char *)folder_filename, sizeof(folder_filename),
746 "%s:%ld.%s", db_filename, tdb->folders[n].id,
747 FOLDER_FILENAME_EXT);
748 CtoPstr(folder_filename);
749 tdb->folders[n].bile = bile_open(folder_filename,
750 tdb->bile->vrefnum);
751 if (tdb->folders[n].bile != NULL)
752 continue;
753
754 PtoCstr(folder_filename);
755
756 if (bile_error(NULL) != fnfErr &&
757 ask("Attempt recovery of %s with backup map?",
758 folder_filename)) {
759 CtoPstr(folder_filename);
760 tdb->folders[n].bile = bile_open_recover_map(folder_filename,
761 tdb->bile->vrefnum);
762 if (tdb->folders[n].bile != NULL)
763 continue;
764 warn("Failed to recover folder bile %s: %d",
765 PtoCstr(folder_filename), bile_error(NULL));
766 }
767
768 if (ask("Failed to open folder bile %s (error %d), recreate it?",
769 folder_filename, bile_error(NULL)) == false)
770 exit(0);
771
772 tdb->folders[n].bile = db_folder_create(tdb, &tdb->folders[n],
773 true);
774 if (tdb->folders[n].bile == NULL)
775 panic("Failed to create folder bile %s: %d",
776 folder_filename, bile_error(NULL));
777 }
778
779 for (n = 0; n < tdb->nfolders; n++) {
780 /* make sure directory exists */
781 memcpy(folder_dir, tdb->folders[n].path, sizeof(folder_dir));
782 CtoPstr(folder_dir);
783 if (!FIsDir(folder_dir))
784 warn("Folder %ld path \"%s\" does not exist",
785 tdb->folders[n].id, tdb->folders[n].path);
786 }
787 }
788
789 struct bile *
790 db_folder_create(struct db *tdb, struct folder *folder, bool delete_first)
791 {
792 Str255 db_filename, folder_filename, folder_dir;
793 struct bile *folder_bile;
794 size_t size;
795 short ret, newid, error;
796 char *data;
797
798 ret = bile_marshall_object(tdb->bile, folder_object_fields,
799 nfolder_object_fields, folder, &data, &size, "db_folder_create");
800 if (ret != 0 || size == 0) {
801 warn("db_folder_create: failed to marshall object");
802 return;
803 }
804
805 if (bile_write(tdb->bile, DB_FOLDER_RTYPE, folder->id, data,
806 size) != size)
807 panic("save of new folder failed: %d", bile_error(tdb->bile));
808 xfree(&data);
809
810 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, &db_filename,
811 false) != 0)
812 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
813 PtoCstr(db_filename);
814
815 snprintf((char *)folder_filename, sizeof(folder_filename),
816 "%s:%ld.%s", db_filename, folder->id, FOLDER_FILENAME_EXT);
817 CtoPstr(folder_filename);
818
819 if (delete_first)
820 FSDelete(folder_filename, tdb->bile->vrefnum);
821
822 folder_bile = bile_create(folder_filename, tdb->bile->vrefnum,
823 SUBTEXT_CREATOR, DB_FOLDER_RTYPE);
824 if (folder_bile == NULL)
825 panic("Failed creating new folder bile at %s: %d",
826 PtoCstr(folder_filename), bile_error(NULL));
827
828 memcpy(folder_dir, folder->path, sizeof(folder_dir));
829 CtoPstr(folder_dir);
830 if (!FIsDir(folder_dir)) {
831 error = DirCreate(tdb->bile->vrefnum, 0, folder_dir, &newid);
832 if (error)
833 warn("Failed creating %s: %d", folder->path, error);
834 }
835
836 return folder_bile;
837 }