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 | } |