AmendHub

Download

jcs

/

subtext

/

db.c

 

(View History)

jcs   db: Minor bug fixes, memory leaks Latest amendment: 642 on 2026-09-22

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