AmendHub

Download

jcs

/

subtext

/

db.c

 

(View History)

jcs   db: Add ftn_max_tossed_message_size and max_sysop_idle_minutes Latest amendment: 469 on 2023-04-08

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