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