AmendHub

Download

jcs

/

subtext

/

db.c

 

(View History)

jcs   db: Free bile object in db_cache_boards Latest amendment: 591 on 2024-02-16

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