AmendHub

Download

jcs

/

subtext

/

db.c

 

(View History)

jcs   *: Move views out of database to flat files Latest amendment: 566 on 2023-11-28

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 "logger.h"
27 #include "mail.h"
28 #include "main_menu.h"
29 #include "user.h"
30 #include "util.h"
31
32 struct struct_field config_fields[] = {
33 { "BBS Name", CONFIG_TYPE_STRING,
34 offsetof(struct config, name),
35 1, member_size(struct config, name) },
36 { "Phone Number", CONFIG_TYPE_STRING,
37 offsetof(struct config, phone_number),
38 1, member_size(struct config, phone_number) },
39 { "Location", CONFIG_TYPE_STRING,
40 offsetof(struct config, location),
41 1, member_size(struct config, location) },
42 { "Timezone (Abbrev)", CONFIG_TYPE_STRING,
43 offsetof(struct config, timezone),
44 1, member_size(struct config, timezone) },
45 { "Timezone (UTC Offset)", CONFIG_TYPE_SHORT,
46 offsetof(struct config, timezone_utcoff),
47 -1200, 1400 },
48 { "Allow Open Signup", CONFIG_TYPE_BOOLEAN,
49 offsetof(struct config, open_signup),
50 0, 0 },
51
52 { "Hostname", CONFIG_TYPE_STRING,
53 offsetof(struct config, hostname),
54 1, member_size(struct config, hostname) },
55 { "Telnet Port", CONFIG_TYPE_SHORT,
56 offsetof(struct config, telnet_port),
57 0, 65535,
58 CONFIG_REQUIRES_TELNET_REINIT },
59
60 { "Telnet Trusted Proxy IP", CONFIG_TYPE_IP,
61 offsetof(struct config, trusted_proxy_ip),
62 0, 1,
63 CONFIG_REQUIRES_TELNET_REINIT },
64 { "Telnet Trusted Proxy UDP Port", CONFIG_TYPE_SHORT,
65 offsetof(struct config, trusted_proxy_udp_port),
66 0, 65535,
67 CONFIG_REQUIRES_TELNET_REINIT },
68 { "IP Geolocation Database Path", CONFIG_TYPE_STRING,
69 offsetof(struct config, ipdb_path),
70 0, member_size(struct config, ipdb_path),
71 CONFIG_REQUIRES_IPDB_REINIT },
72
73 { "Modem Port", CONFIG_TYPE_SHORT,
74 offsetof(struct config, modem_port),
75 0, 2,
76 CONFIG_REQUIRES_SERIAL_REINIT },
77 { "Modem Port Speed", CONFIG_TYPE_LONG,
78 offsetof(struct config, modem_speed),
79 300, 115200,
80 CONFIG_REQUIRES_SERIAL_REINIT },
81 { "Modem Bits/Parity/Stop", CONFIG_TYPE_STRING,
82 offsetof(struct config, modem_parity),
83 1, member_size(struct config, modem_parity),
84 CONFIG_REQUIRES_SERIAL_REINIT },
85 { "Modem Init String", CONFIG_TYPE_STRING,
86 offsetof(struct config, modem_init),
87 1, member_size(struct config, modem_init),
88 CONFIG_REQUIRES_SERIAL_REINIT },
89 { "Modem Answer After Rings", CONFIG_TYPE_SHORT,
90 offsetof(struct config, modem_rings),
91 1, 100 },
92
93 { "Max Idle Minutes", CONFIG_TYPE_SHORT,
94 offsetof(struct config, max_idle_minutes),
95 0, USHRT_MAX },
96 { "Max Sysop Idle Minutes", CONFIG_TYPE_SHORT,
97 offsetof(struct config, max_sysop_idle_minutes),
98 0, USHRT_MAX },
99 { "Max Login Seconds", CONFIG_TYPE_SHORT,
100 offsetof(struct config, max_login_seconds),
101 1, USHRT_MAX },
102
103 { "Screen Blanker Idle Seconds", CONFIG_TYPE_SHORT,
104 offsetof(struct config, blanker_idle_seconds),
105 0, USHRT_MAX },
106 { "Screen Blanker Runtime Seconds", CONFIG_TYPE_SHORT,
107 offsetof(struct config, blanker_runtime_seconds),
108 1, USHRT_MAX },
109
110 { "Prune Session Logs After N Days", CONFIG_TYPE_SHORT,
111 offsetof(struct config, session_log_prune_days),
112 0, USHRT_MAX },
113 { "Prune Mail After N Days", CONFIG_TYPE_SHORT,
114 offsetof(struct config, mail_prune_days),
115 0, USHRT_MAX },
116
117 { "FTN Network Name", CONFIG_TYPE_STRING,
118 offsetof(struct config, ftn_network),
119 0, member_size(struct config, ftn_network),
120 CONFIG_REQUIRES_BINKP_REINIT },
121 { "FTN Local Node Address", CONFIG_TYPE_STRING,
122 offsetof(struct config, ftn_node_addr),
123 0, member_size(struct config, ftn_node_addr),
124 CONFIG_REQUIRES_BINKP_REINIT },
125 { "FTN Hub Node Address", CONFIG_TYPE_STRING,
126 offsetof(struct config, ftn_hub_addr),
127 0, member_size(struct config, ftn_hub_addr),
128 CONFIG_REQUIRES_BINKP_REINIT },
129 { "FTN Hub Packet Password", CONFIG_TYPE_PASSWORD,
130 offsetof(struct config, ftn_hub_pkt_password),
131 0, member_size(struct config, ftn_hub_pkt_password) },
132 { "FTN Hub Binkp Hostname", CONFIG_TYPE_STRING,
133 offsetof(struct config, binkp_hostname),
134 0, member_size(struct config, binkp_hostname),
135 CONFIG_REQUIRES_BINKP_REINIT },
136 { "FTN Hub Binkp Port", CONFIG_TYPE_SHORT,
137 offsetof(struct config, binkp_port),
138 0, 65535 },
139 { "FTN Hub Binkp Password", CONFIG_TYPE_PASSWORD,
140 offsetof(struct config, binkp_password),
141 0, member_size(struct config, binkp_password) },
142 { "FTN Hub Binkp Poll Seconds", CONFIG_TYPE_LONG,
143 offsetof(struct config, binkp_interval_seconds),
144 1, LONG_MAX },
145 { "FTN Delete Packets After Processing", CONFIG_TYPE_BOOLEAN,
146 offsetof(struct config, binkp_delete_done),
147 0, 0 },
148 { "FTN Max Tossed Message Size", CONFIG_TYPE_LONG,
149 offsetof(struct config, ftn_max_tossed_message_size),
150 0, LONG_MAX },
151 };
152 size_t nconfig_fields = nitems(config_fields);
153
154 struct db * db_init(Str255 file, short vrefnum, struct bile *bile);
155 short db_migrate(struct db *tdb, short is_new, Str255 basepath);
156 void db_config_load(struct db *tdb);
157 const char * db_view_filename(short id);
158
159 struct db *
160 db_open(Str255 file, short vrefnum, bool ignore_last)
161 {
162 Str255 filepath;
163 struct stat sb;
164 Point pt = { 75, 100 };
165 SFReply reply = { 0 };
166 SFTypeList types;
167 StringHandle lastfileh;
168 struct db *ret;
169
170 if (file[0]) {
171 getpath(vrefnum, file, filepath, true);
172 if (FStat(filepath, &sb) == 0)
173 return db_init(file, vrefnum, NULL);
174
175 warn("Failed to open %s", PtoCstr(file));
176 }
177
178 if (!ignore_last && (lastfileh = GetString(STR_LAST_DB))) {
179 HLock(lastfileh);
180 memcpy(filepath, *lastfileh, sizeof(filepath));
181 HUnlock(lastfileh);
182 ReleaseResource(lastfileh);
183 if (FStat(filepath, &sb) == 0) {
184 ret = db_init(filepath, 0, NULL);
185 return ret;
186 }
187 }
188
189 /* no file passed, no last file, prompt */
190 types[0] = DB_TYPE;
191 SFGetFile(pt, NULL, NULL, 1, &types, NULL, &reply);
192 if (reply.good) {
193 ret = db_init(reply.fName, reply.vRefNum, NULL);
194 return ret;
195 }
196
197 return db_create();
198 }
199
200 struct db *
201 db_create(void)
202 {
203 Point pt = { 75, 100 };
204 SFReply reply;
205 struct bile *bile;
206 short error = 0;
207
208 SFPutFile(pt, "\pCreate new Subtext DB:", "\p", NULL, &reply);
209 if (!reply.good)
210 return NULL;
211
212 bile = bile_create(reply.fName, reply.vRefNum, SUBTEXT_CREATOR,
213 DB_TYPE);
214 if (bile == NULL && bile_error(NULL) == dupFNErr) {
215 error = FSDelete(reply.fName, reply.vRefNum);
216 if (error)
217 panic("Failed to re-create file %s: %d", PtoCstr(reply.fName),
218 error);
219 bile = bile_create(reply.fName, reply.vRefNum, SUBTEXT_CREATOR,
220 DB_TYPE);
221 }
222
223 if (bile == NULL)
224 panic("Failed to create file %s: %d", PtoCstr(reply.fName), error);
225
226 return db_init(reply.fName, reply.vRefNum, bile);
227 }
228
229 struct db *
230 db_init(Str255 path, short vrefnum, struct bile *bile)
231 {
232 Str255 fullpath, newfullpath, viewsdir;
233 struct db *tdb;
234 Handle lastfileh;
235 short was_new, error;
236 long dirid;
237
238 was_new = (bile != NULL);
239
240 if (bile == NULL) {
241 bile = bile_open(path, vrefnum);
242 if (bile == NULL) {
243 if (ask("Attempt recovery with backup map?")) {
244 bile = bile_open_recover_map(path, vrefnum);
245 if (bile == NULL)
246 panic("Failed to recover DB file %s: %d",
247 PtoCstr(path), bile_error(NULL));
248 } else {
249 panic("Failed to open DB file %s: %d", PtoCstr(path),
250 bile_error(NULL));
251 }
252 }
253 }
254
255 /* we got this far, store it as the last-accessed file */
256 if (vrefnum == 0)
257 memcpy(&fullpath, path, sizeof(fullpath));
258 else
259 getpath(vrefnum, path, fullpath, true);
260 lastfileh = Get1Resource('STR ', STR_LAST_DB);
261 if (lastfileh)
262 xSetHandleSize(lastfileh, fullpath[0] + 1);
263 else
264 lastfileh = xNewHandle(fullpath[0] + 1);
265 HLock(lastfileh);
266 memcpy(*lastfileh, fullpath, fullpath[0] + 1);
267 HUnlock(lastfileh);
268 if (HomeResFile(lastfileh) == -1)
269 AddResource(lastfileh, 'STR ', STR_LAST_DB, "\pSTR_LAST_DB");
270 else
271 ChangedResource(lastfileh);
272 WriteResource(lastfileh);
273 DetachResource(lastfileh);
274
275 tdb = xmalloczero(sizeof(struct db));
276 if (tdb == NULL)
277 panic("Can't create db");
278 tdb->bile = bile;
279
280 /* create views directory if it doesn't exist */
281 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, viewsdir,
282 false) != 0)
283 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
284 PtoCstr(viewsdir);
285 strlcat((char *)viewsdir, ":views", sizeof(Str255));
286 CtoPstr(viewsdir);
287 if (!FIsDir(viewsdir)) {
288 error = DirCreate(tdb->bile->vrefnum, 0, viewsdir, &dirid);
289 if (error)
290 panic("Failed creating %s: %d", PtoCstr(viewsdir), error);
291 }
292
293 if (db_migrate(tdb, was_new, fullpath) != 0) {
294 bile_close(tdb->bile);
295 xfree(&tdb);
296 return NULL;
297 }
298
299 if (!was_new)
300 db_config_load(tdb);
301
302 memcpy(newfullpath, fullpath, sizeof(newfullpath));
303 PtoCstr(newfullpath);
304 strlcat((char *)&newfullpath, "-sessions", sizeof(newfullpath));
305 CtoPstr(newfullpath);
306
307 tdb->sessions_bile = bile_open(newfullpath, vrefnum);
308 if (tdb->sessions_bile == NULL) {
309 tdb->sessions_bile = bile_create(newfullpath, vrefnum,
310 SUBTEXT_CREATOR, SL_TYPE);
311 if (tdb->sessions_bile == NULL)
312 panic("Couldn't create %s: %d", PtoCstr(newfullpath),
313 bile_error(NULL));
314 }
315
316 memcpy(newfullpath, fullpath, sizeof(newfullpath));
317 PtoCstr(newfullpath);
318 strlcat((char *)&newfullpath, "-mail", sizeof(newfullpath));
319 CtoPstr(newfullpath);
320
321 tdb->mail_bile = bile_open(newfullpath, vrefnum);
322 if (tdb->mail_bile == NULL) {
323 tdb->mail_bile = bile_create(newfullpath, vrefnum,
324 SUBTEXT_CREATOR, MAIL_SPOOL_TYPE);
325 if (tdb->mail_bile == NULL)
326 panic("Couldn't create %s: %d", PtoCstr(newfullpath),
327 bile_error(NULL));
328 }
329
330 return tdb;
331 }
332
333 void
334 db_close(struct db *tdb)
335 {
336 short n;
337
338 bile_close(tdb->bile);
339 xfree(&tdb->bile);
340
341 if (tdb->sessions_bile != NULL) {
342 bile_close(tdb->sessions_bile);
343 xfree(&tdb->sessions_bile);
344 }
345
346 if (tdb->mail_bile != NULL) {
347 bile_close(tdb->mail_bile);
348 xfree(&tdb->mail_bile);
349 }
350
351 if (tdb->boards) {
352 for (n = 0; n < tdb->nboards; n++) {
353 if (tdb->boards[n].bile)
354 bile_close(tdb->boards[n].bile);
355 }
356 xfree(&tdb->boards);
357 }
358
359 if (tdb->folders) {
360 for (n = 0; n < tdb->nfolders; n++) {
361 if (tdb->folders[n].bile)
362 bile_close(tdb->folders[n].bile);
363 }
364 xfree(&tdb->folders);
365 }
366
367 xfree(&tdb);
368 }
369
370 short
371 db_migrate(struct db *tdb, short is_new, Str255 fullpath)
372 {
373 struct user *suser;
374 struct db *olddb;
375 char ver;
376
377 if (is_new) {
378 ver = DB_CUR_VERS;
379
380 /* setup some defaults */
381 sprintf(tdb->config.name, "Example Subtext BBS");
382 sprintf(tdb->config.phone_number, "(555) 867-5309");
383 sprintf(tdb->config.location, "Springfield");
384 sprintf(tdb->config.hostname, "bbs.example.com");
385 sprintf(tdb->config.timezone, "CT");
386 tdb->config.modem_speed = 9600;
387 sprintf(tdb->config.modem_init, "ATV1S0=2&D2");
388 sprintf(tdb->config.modem_parity, "8N1");
389 tdb->config.modem_rings = 1;
390 tdb->config.max_idle_minutes = 5;
391 tdb->config.max_sysop_idle_minutes = 60;
392 tdb->config.max_login_seconds = 90;
393 tdb->config.blanker_idle_seconds = (60 * 60);
394 tdb->config.blanker_runtime_seconds = 30;
395 tdb->config.session_log_prune_days = 21;
396 tdb->config.binkp_port = 24554;
397 tdb->config.binkp_interval_seconds = (60 * 60 * 3);
398 tdb->config.mail_prune_days = 180;
399 tdb->config.ftn_max_tossed_message_size = 16 * 1024;
400
401 db_config_save(tdb);
402
403 /* create a default sysop user */
404 suser = xmalloczero(sizeof(struct user));
405 if (suser == NULL)
406 panic("Can't allocate new user");
407 strncpy(suser->username, "sysop", sizeof(suser->username));
408 suser->created_at = Time;
409 suser->is_enabled = DB_TRUE;
410 user_set_password(suser, "p4ssw0rd");
411 suser->is_sysop = DB_TRUE;
412 /* user_save assumes db is already set */
413 olddb = db;
414 db = tdb;
415 user_save(suser);
416 xfree(&suser);
417 user_cache_usernames();
418 db = olddb;
419 } else {
420 if (bile_read(tdb->bile, DB_VERS_RTYPE, 1, &ver, 1) != 1)
421 ver = 1;
422
423 if (ver == DB_CUR_VERS)
424 return 0;
425
426 if (ask("Migrate this database from version %d to %d to open it?",
427 ver, DB_CUR_VERS) != ASK_YES)
428 return -1;
429 }
430
431 /* per-version migrations */
432 while (ver < DB_CUR_VERS) {
433 progress("Migrating from version %d to %d...", (short)ver,
434 (short)(ver + 1));
435
436 if (ver < 18)
437 panic("This Subtext database is too old to upgrade. Please "
438 "run an older Subtext release first to upgrade it.");
439
440 switch (ver) {
441 case 18: {
442 /* 18->19, ipdb_path, add session_log.location */
443 struct config new_config = { 0 };
444 Str255 newfullpath;
445 struct bile *sessions_bile;
446 size_t nids, n, size;
447 unsigned long *ids;
448 char *data;
449
450 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
451 sizeof(new_config));
452
453 new_config.ipdb_path[0] = '\0';
454
455 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
456 sizeof(new_config));
457
458 /* migrate session log entries */
459 memcpy(&newfullpath, fullpath, sizeof(newfullpath));
460 PtoCstr(newfullpath);
461 strlcat((char *)&newfullpath, "-sessions", sizeof(newfullpath));
462 CtoPstr(newfullpath);
463
464 sessions_bile = bile_open(newfullpath, tdb->bile->vrefnum);
465 if (sessions_bile == NULL)
466 /* nothing to migrate */
467 break;
468
469 nids = bile_ids_by_type(sessions_bile, SL_LOG_RTYPE, &ids);
470 for (n = 0; n < nids; n++) {
471 size = bile_read_alloc(sessions_bile, SL_LOG_RTYPE,
472 ids[n], &data);
473 size += member_size(struct session_log, location);
474 if (bile_resize(sessions_bile, SL_LOG_RTYPE,
475 ids[n], size) != size)
476 panic("failed resizing session log %ld", ids[n]);
477 }
478
479 bile_flush(sessions_bile, true);
480 xfree(&ids);
481 bile_close(sessions_bile);
482 break;
483 }
484 case 19: {
485 /* 19->20, modem rings */
486 struct config new_config = { 0 };
487
488 bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config,
489 sizeof(new_config));
490
491 new_config.modem_rings = 1;
492
493 bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config,
494 sizeof(new_config));
495 break;
496 }
497 case 20: {
498 /* 20->21, move views out of db */
499 size_t size;
500 char *data;
501
502 #define DB_TEXT_TYPE 'TEXT'
503 #define DB_TEXT_MENU_ID 1
504 #define DB_TEXT_SHORTMENU_ID 2
505 #define DB_TEXT_ISSUE_ID 3
506 #define DB_TEXT_SIGNUP_ID 4
507 #define DB_TEXT_PAGE_SYSOP_ID 5
508 #define DB_TEXT_NO_FREE_NODES_ID 6
509 #define DB_TEXT_SIGNOFF_ID 7
510 #define DB_TEXT_MENU_OPTIONS_ID 8
511
512 progress("Migrating views out of database...");
513
514 if ((size = bile_read_alloc(tdb->bile, DB_TEXT_TYPE,
515 DB_TEXT_MENU_ID, &data))) {
516 db_view_write(tdb, DB_VIEW_MENU, data, size);
517 xfree(&data);
518 bile_delete(tdb->bile, DB_TEXT_TYPE, DB_TEXT_MENU_ID, 0);
519 }
520 if ((size = bile_read_alloc(tdb->bile, DB_TEXT_TYPE,
521 DB_TEXT_SHORTMENU_ID, &data))) {
522 db_view_write(tdb, DB_VIEW_SHORTMENU, data, size);
523 xfree(&data);
524 bile_delete(tdb->bile, DB_TEXT_TYPE, DB_TEXT_SHORTMENU_ID,
525 0);
526 }
527 if ((size = bile_read_alloc(tdb->bile, DB_TEXT_TYPE,
528 DB_TEXT_ISSUE_ID, &data))) {
529 db_view_write(tdb, DB_VIEW_ISSUE, data, size);
530 xfree(&data);
531 bile_delete(tdb->bile, DB_TEXT_TYPE, DB_TEXT_ISSUE_ID, 0);
532 }
533 if ((size = bile_read_alloc(tdb->bile, DB_TEXT_TYPE,
534 DB_TEXT_SIGNUP_ID, &data))) {
535 db_view_write(tdb, DB_VIEW_SIGNUP, data, size);
536 xfree(&data);
537 bile_delete(tdb->bile, DB_TEXT_TYPE, DB_TEXT_SIGNUP_ID, 0);
538 }
539 if ((size = bile_read_alloc(tdb->bile, DB_TEXT_TYPE,
540 DB_TEXT_PAGE_SYSOP_ID, &data))) {
541 db_view_write(tdb, DB_VIEW_PAGE_SYSOP, data, size);
542 xfree(&data);
543 bile_delete(tdb->bile, DB_TEXT_TYPE, DB_TEXT_PAGE_SYSOP_ID,
544 0);
545 }
546 if ((size = bile_read_alloc(tdb->bile, DB_TEXT_TYPE,
547 DB_TEXT_NO_FREE_NODES_ID, &data))) {
548 db_view_write(tdb, DB_VIEW_NO_FREE_NODES, data, size);
549 xfree(&data);
550 bile_delete(tdb->bile, DB_TEXT_TYPE,
551 DB_TEXT_NO_FREE_NODES_ID, 0);
552 }
553 if ((size = bile_read_alloc(tdb->bile, DB_TEXT_TYPE,
554 DB_TEXT_SIGNOFF_ID, &data))) {
555 db_view_write(tdb, DB_VIEW_SIGNOFF, data, size);
556 xfree(&data);
557 bile_delete(tdb->bile, DB_TEXT_TYPE, DB_TEXT_SIGNOFF_ID, 0);
558 }
559 if ((size = bile_read_alloc(tdb->bile, DB_TEXT_TYPE,
560 DB_TEXT_MENU_OPTIONS_ID, &data))) {
561 db_view_write(tdb, DB_VIEW_MENU_OPTIONS, data, size);
562 xfree(&data);
563 bile_delete(tdb->bile, DB_TEXT_TYPE,
564 DB_TEXT_MENU_OPTIONS_ID, 0);
565 }
566
567 break;
568 }
569 }
570
571 ver++;
572 }
573
574 progress(NULL);
575
576 /* store new version */
577 ver = DB_CUR_VERS;
578 if (bile_write(tdb->bile, DB_VERS_RTYPE, 1, &ver, 1) != 1)
579 panic("Failed writing new version: %d", bile_error(tdb->bile));
580
581 return 0;
582 }
583
584 void
585 db_config_save(struct db *tdb)
586 {
587 if (bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &tdb->config,
588 sizeof(tdb->config)) != sizeof(tdb->config))
589 panic("db_config_save: failed to write: %d",
590 bile_error(tdb->bile));
591 }
592
593 void
594 db_config_load(struct db *tdb)
595 {
596 size_t rlen;
597 char *newconfig;
598
599 rlen = bile_read_alloc(tdb->bile, DB_CONFIG_RTYPE, 1, &newconfig);
600 if (rlen == 0 || bile_error(tdb->bile))
601 panic("db_config_load: error reading config: %d",
602 bile_error(tdb->bile));
603 if (rlen != sizeof(tdb->config))
604 warn("db_config_load: read config of size %lu, but expected %lu",
605 rlen, sizeof(tdb->config));
606
607 memcpy(&tdb->config, newconfig, sizeof(tdb->config));
608 xfree(&newconfig);
609 }
610
611 void
612 db_cache_boards(struct db *tdb)
613 {
614 Str255 db_filename, board_filename;
615 struct board_id_time_map first_map;
616 size_t n, size;
617 unsigned long *ids;
618 struct bile_object *obj;
619 char *data = NULL;
620
621 if (tdb->boards) {
622 for (n = 0; n < tdb->nboards; n++) {
623 if (tdb->boards[n].bile)
624 bile_close(tdb->boards[n].bile);
625 }
626 xfree(&tdb->boards);
627 }
628
629 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, db_filename,
630 false) != 0)
631 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
632 PtoCstr(db_filename);
633
634 tdb->nboards = bile_sorted_ids_by_type(tdb->bile, DB_BOARD_RTYPE,
635 &ids);
636 if (!tdb->nboards)
637 return;
638 tdb->boards = xcalloc(tdb->nboards, sizeof(struct board));
639 if (tdb->boards == NULL) {
640 tdb->nboards = 0;
641 return;
642 }
643
644 /*
645 * Read ids first so we have a consistent order, then try to open or
646 * fix/recreate each bile, which may change their order in the map
647 */
648 for (n = 0; n < tdb->nboards; n++) {
649 obj = bile_find(tdb->bile, DB_BOARD_RTYPE, ids[n]);
650 if (obj == NULL)
651 break;
652
653 size = bile_read_alloc(tdb->bile, DB_BOARD_RTYPE, obj->id,
654 &data);
655 bile_unmarshall_object(tdb->bile, board_object_fields,
656 nboard_object_fields, data, size, (char *)(&tdb->boards[n]),
657 sizeof(struct board), true);
658 xfree(&data);
659 }
660
661 xfree(&ids);
662
663 for (n = 0; n < tdb->nboards; n++) {
664 snprintf((char *)board_filename, sizeof(board_filename),
665 "%s:%lu.%s", db_filename, tdb->boards[n].id, BOARD_FILENAME_EXT);
666 CtoPstr(board_filename);
667 tdb->boards[n].bile = bile_open(board_filename,
668 tdb->bile->vrefnum);
669 if (tdb->boards[n].bile != NULL)
670 goto opened;
671
672 PtoCstr(board_filename);
673
674 if (bile_error(NULL) != fnfErr &&
675 ask("Attempt recovery of %s with backup map?", board_filename)) {
676 CtoPstr(board_filename);
677 tdb->boards[n].bile = bile_open_recover_map(board_filename,
678 tdb->bile->vrefnum);
679 if (tdb->boards[n].bile != NULL)
680 continue;
681 warn("Failed to recover board file %s: %d",
682 PtoCstr(board_filename), bile_error(NULL));
683 }
684
685 if (ask("Failed to open board bile %s (error %d), recreate it?",
686 board_filename, bile_error(NULL)) == false)
687 panic("Can't open board file, exiting");
688
689 tdb->boards[n].bile = db_board_create(tdb, &tdb->boards[n], true);
690 if (tdb->boards[n].bile == NULL)
691 panic("Failed to create board file %s: %d",
692 board_filename, bile_error(NULL));
693
694 opened:
695 size = bile_read(tdb->boards[n].bile,
696 BOARD_SORTED_ID_MAP_RTYPE, 1, &first_map, sizeof(first_map));
697 if (size != sizeof(first_map)) {
698 logger_printf("[db] Reindexing ids on board %s",
699 tdb->boards[n].name);
700 board_index_sorted_post_ids(&tdb->boards[n], NULL);
701 size = bile_read(tdb->boards[n].bile,
702 BOARD_SORTED_ID_MAP_RTYPE, 1, &first_map, sizeof(first_map));
703 if (size != sizeof(first_map)) {
704 tdb->boards[n].last_post_at = 0;
705 continue;
706 }
707 }
708 tdb->boards[n].last_post_at = first_map.time;
709 }
710 }
711
712 struct bile *
713 db_board_create(struct db *tdb, struct board *board, bool delete_first)
714 {
715 Str255 db_filename, board_filename;
716 struct bile *board_bile;
717 size_t size;
718 short ret;
719 char *data;
720
721 ret = bile_marshall_object(tdb->bile, board_object_fields,
722 nboard_object_fields, board, &data, &size);
723 if (ret != 0 || size == 0) {
724 warn("db_board_create: failed to marshall object");
725 return NULL;
726 }
727
728 if (bile_write(tdb->bile, DB_BOARD_RTYPE, board->id, data,
729 size) != size)
730 panic("save of new board failed: %d", bile_error(tdb->bile));
731 xfree(&data);
732
733 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, db_filename,
734 false) != 0)
735 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
736 PtoCstr(db_filename);
737
738 snprintf((char *)&board_filename, sizeof(board_filename), "%s:%lu.%s",
739 db_filename, board->id, BOARD_FILENAME_EXT);
740 CtoPstr(board_filename);
741
742 if (delete_first)
743 FSDelete(board_filename, tdb->bile->vrefnum);
744
745 board_bile = bile_create(board_filename, tdb->bile->vrefnum,
746 SUBTEXT_CREATOR, DB_BOARD_RTYPE);
747 if (board_bile == NULL)
748 panic("Failed creating new board bile at %s: %d",
749 PtoCstr(board_filename), bile_error(NULL));
750
751 return board_bile;
752 }
753
754 void
755 db_board_delete(struct db *tdb, struct board *board)
756 {
757 if (bile_delete(tdb->bile, DB_BOARD_RTYPE, board->id, 0) != 0) {
758 warn("deletion of board %ld failed: %d", board->id,
759 bile_error(tdb->bile));
760 return;
761 }
762 bile_close(board->bile);
763 }
764
765 void
766 db_cache_folders(struct db *tdb)
767 {
768 Str255 db_filename, folder_filename, folder_dir;
769 size_t n, size;
770 unsigned long *ids, id;
771 struct bile_object *obj;
772 char *data = NULL;
773 short error;
774
775 if (tdb->folders) {
776 for (n = 0; n < tdb->nfolders; n++) {
777 if (tdb->folders[n].bile)
778 bile_close(tdb->folders[n].bile);
779 }
780 xfree(&tdb->folders);
781 }
782
783 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, db_filename,
784 false) != 0)
785 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
786 PtoCstr(db_filename);
787
788 tdb->nfolders = bile_sorted_ids_by_type(tdb->bile, DB_FOLDER_RTYPE,
789 &ids);
790 if (!tdb->nfolders)
791 return;
792 tdb->folders = xcalloc(tdb->nfolders, sizeof(struct folder));
793 if (tdb->folders == NULL) {
794 tdb->nfolders = 0;
795 return;
796 }
797
798 /*
799 * Read ids first so we have a consistent order, then try to open or
800 * fix/recreate each bile, which may change their order in the map
801 */
802 for (n = 0; n < tdb->nfolders; n++) {
803 obj = bile_find(tdb->bile, DB_FOLDER_RTYPE, ids[n]);
804 if (obj == NULL)
805 break;
806
807 size = bile_read_alloc(tdb->bile, DB_FOLDER_RTYPE, obj->id, &data);
808 if (size == 0 || data == NULL)
809 break;
810 bile_unmarshall_object(tdb->bile, folder_object_fields,
811 nfolder_object_fields, data, size, (char *)(&tdb->folders[n]),
812 sizeof(struct folder), true);
813 xfree(&data);
814 }
815
816 xfree(&ids);
817
818 for (n = 0; n < tdb->nfolders; n++) {
819 snprintf((char *)folder_filename, sizeof(folder_filename),
820 "%s:%lu.%s", db_filename, tdb->folders[n].id,
821 FOLDER_FILENAME_EXT);
822 CtoPstr(folder_filename);
823 tdb->folders[n].bile = bile_open(folder_filename,
824 tdb->bile->vrefnum);
825 if (tdb->folders[n].bile != NULL)
826 continue;
827
828 PtoCstr(folder_filename);
829
830 if (bile_error(NULL) != fnfErr &&
831 ask("Attempt recovery of %s with backup map?", folder_filename)) {
832 CtoPstr(folder_filename);
833 tdb->folders[n].bile = bile_open_recover_map(folder_filename,
834 tdb->bile->vrefnum);
835 if (tdb->folders[n].bile != NULL)
836 continue;
837 warn("Failed to recover folder bile %s: %d",
838 PtoCstr(folder_filename), bile_error(NULL));
839 }
840
841 if (ask("Failed to open folder bile %s (error %d), recreate it?",
842 folder_filename, bile_error(NULL)) == false)
843 exit(0);
844
845 tdb->folders[n].bile = db_folder_create(tdb, &tdb->folders[n],
846 true);
847 if (tdb->folders[n].bile == NULL)
848 panic("Failed to create folder bile %s: %d",
849 folder_filename, bile_error(NULL));
850 }
851
852 for (n = 0; n < tdb->nfolders; n++) {
853 /* make sure directory exists */
854 memcpy(folder_dir, tdb->folders[n].path, sizeof(folder_dir));
855 CtoPstr(folder_dir);
856 if (!FIsDir(folder_dir) &&
857 ask("Folder %ld path \"%s\" does not exist, create it?",
858 tdb->folders[n].id, tdb->folders[n].path)) {
859 error = DirCreate(db->bile->vrefnum, 0, folder_dir, &id);
860 if (error)
861 warn("Failed creating %s: %d", tdb->folders[n].path,
862 error);
863 }
864 }
865 }
866
867 struct bile *
868 db_folder_create(struct db *tdb, struct folder *folder, bool delete_first)
869 {
870 Str255 db_filename, folder_filename, folder_dir;
871 struct bile *folder_bile;
872 size_t size;
873 short ret, newid, error;
874 char *data;
875
876 ret = bile_marshall_object(tdb->bile, folder_object_fields,
877 nfolder_object_fields, folder, &data, &size);
878 if (ret != 0 || size == 0) {
879 warn("db_folder_create: failed to marshall object");
880 return NULL;
881 }
882
883 if (bile_write(tdb->bile, DB_FOLDER_RTYPE, folder->id, data,
884 size) != size)
885 panic("save of new folder failed: %d", bile_error(tdb->bile));
886 xfree(&data);
887
888 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, db_filename,
889 false) != 0)
890 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
891 PtoCstr(db_filename);
892
893 snprintf((char *)&folder_filename, sizeof(folder_filename),
894 "%s:%lu.%s", db_filename, folder->id, FOLDER_FILENAME_EXT);
895 CtoPstr(folder_filename);
896
897 if (delete_first)
898 FSDelete(folder_filename, tdb->bile->vrefnum);
899
900 folder_bile = bile_create(folder_filename, tdb->bile->vrefnum,
901 SUBTEXT_CREATOR, DB_FOLDER_RTYPE);
902 if (folder_bile == NULL)
903 panic("Failed creating new folder bile at %s: %d",
904 PtoCstr(folder_filename), bile_error(NULL));
905
906 memcpy(&folder_dir, folder->path, sizeof(folder_dir));
907 CtoPstr(folder_dir);
908 if (!FIsDir(folder_dir)) {
909 error = DirCreate(tdb->bile->vrefnum, 0, folder_dir, &newid);
910 if (error)
911 warn("Failed creating %s: %d", folder->path, error);
912 }
913
914 return folder_bile;
915 }
916
917 void
918 db_folder_delete(struct db *tdb, struct folder *folder)
919 {
920 if (bile_delete(tdb->bile, DB_FOLDER_RTYPE, folder->id, 0) != 0) {
921 warn("deletion of folder %ld failed: %d", folder->id,
922 bile_error(tdb->bile));
923 return;
924 }
925 bile_close(folder->bile);
926 }
927
928 const char *
929 db_view_filename(short id)
930 {
931 switch (id) {
932 case DB_VIEW_MENU:
933 return "menu.txt";
934 case DB_VIEW_SHORTMENU:
935 return "short_menu.txt";
936 case DB_VIEW_ISSUE:
937 return "issue.txt";
938 case DB_VIEW_SIGNUP:
939 return "signup.txt";
940 case DB_VIEW_PAGE_SYSOP:
941 return "page_sysop.txt";
942 case DB_VIEW_NO_FREE_NODES:
943 return "no_free_nodes.txt";
944 case DB_VIEW_SIGNOFF:
945 return "signoff.txt";
946 case DB_VIEW_MENU_OPTIONS:
947 return "menu_options.txt";
948 default:
949 panic("db_view_filename: unknown id %d", id);
950 }
951 }
952
953 void
954 db_cache_views(struct db *tdb)
955 {
956 Str255 viewdir, viewpath;
957 struct stat sb;
958 struct main_menu_option *opts;
959 Handle default_menu;
960 short n;
961 size_t size;
962 short error, frefnum;
963
964 logger_printf("[db] Caching views");
965
966 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, viewdir,
967 false) != 0)
968 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
969 PtoCstr(viewdir);
970 strlcat((char *)viewdir, ":views:", sizeof(Str255));
971
972 for (n = 0; n < DB_VIEW_COUNT; n++) {
973 if (tdb->views[n])
974 xfree(&tdb->views[n]);
975 tdb->views[n] = NULL;
976
977 strlcpy((char *)viewpath, (char *)viewdir, sizeof(Str255));
978 strlcat((char *)viewpath, db_view_filename(n), sizeof(Str255));
979 CtoPstr(viewpath);
980
981 if (FStat(viewpath, &sb) != 0) {
982 if (n == DB_VIEW_MENU_OPTIONS) {
983 default_menu = GetResource('TEXT', MENU_DEFAULTS_ID);
984 if (default_menu == NULL)
985 panic("Failed to find TEXT resource %d for menu options",
986 MENU_DEFAULTS_ID);
987 size = GetHandleSize(default_menu);
988 HLock(default_menu);
989 db_view_write(tdb, n, *default_menu, size);
990 HUnlock(default_menu);
991 ReleaseResource(default_menu);
992 FStat(viewpath, &sb);
993 } else {
994 /* create a blank file so the sysop knows what to edit */
995 db_view_write(tdb, n, "", 0);
996
997 /* but leave the view NULL */
998 continue;
999 }
1000 }
1001
1002 if (sb.st_size == 0)
1003 continue;
1004
1005 tdb->views[n] = xmalloc(sb.st_size + 1);
1006 if (tdb->views[n] == NULL)
1007 panic("Failed allocating %ld for view %s",
1008 sb.st_size, PtoCstr(viewpath));
1009
1010 error = FSOpen(viewpath, 0, &frefnum);
1011 if (error)
1012 panic("Error opening view %s: %d", PtoCstr(viewpath),
1013 error);
1014
1015 size = sb.st_size;
1016 error = FSRead(frefnum, &size, tdb->views[n]);
1017 FSClose(frefnum);
1018
1019 tdb->views[n][sb.st_size] = '\0';
1020
1021 if (error && error != eofErr)
1022 panic("Error reading view %s: %d", PtoCstr(viewpath),
1023 error);
1024
1025 if (n == DB_VIEW_MENU_OPTIONS) {
1026 opts = main_menu_parse(tdb->views[n], sb.st_size);
1027 if (opts) {
1028 if (main_menu_options != NULL)
1029 xfree(&main_menu_options);
1030 main_menu_options = opts;
1031 }
1032 }
1033 }
1034 }
1035
1036 void
1037 db_view_write(struct db *tdb, short id, char *str, size_t size)
1038 {
1039 Str255 viewpath;
1040 size_t len;
1041 short error, frefnum;
1042
1043 if (getpath(tdb->bile->vrefnum, tdb->bile->filename, viewpath,
1044 false) != 0)
1045 panic("getpath failed on %s", PtoCstr(tdb->bile->filename));
1046 PtoCstr(viewpath);
1047 strlcat((char *)viewpath, ":views:", sizeof(Str255));
1048 strlcat((char *)viewpath, db_view_filename(id), sizeof(Str255));
1049 logger_printf("[db] Writing default view %s", viewpath);
1050 CtoPstr(viewpath);
1051
1052 error = Create(viewpath, 0, 'ttxt', 'TEXT');
1053 if (error == dupFNErr)
1054 panic("View file already exists: %s", PtoCstr(viewpath));
1055 if (error)
1056 panic("Failed creating %s: %d", PtoCstr(viewpath), error);
1057
1058 if ((error = FSOpen(viewpath, 0, &frefnum)))
1059 panic("Failed opening newly-created %s: %d", PtoCstr(viewpath),
1060 error);
1061
1062 len = size;
1063 if ((error = Allocate(frefnum, &len)))
1064 panic("Failed setting %s to size %ld: %d", PtoCstr(viewpath),
1065 size, error);
1066
1067 len = size;
1068 if ((error = FSWrite(frefnum, &len, str)))
1069 panic("Failed writing view to file %s: %d", PtoCstr(viewpath),
1070 error);
1071 if (len != size)
1072 panic("Short write of view to file %s: %ld != %ld",
1073 PtoCstr(viewpath), len, size);
1074
1075 FSClose(frefnum);
1076 }