1: /*
2: * $Id: journal.c,v 1.7 2025/04/13 04:22:43 snw Exp $
3: * Implementation of FreeM journaling
4: *
5: *
6: * Author: Serena Willis <snw@coherent-logic.com>
7: * Copyright (C) 1998 MUG Deutschland
8: * Copyright (C) 2020, 2025 Coherent Logic Development LLC
9: *
10: *
11: * This file is part of FreeM.
12: *
13: * FreeM is free software: you can redistribute it and/or modify
14: * it under the terms of the GNU Affero Public License as published by
15: * the Free Software Foundation, either version 3 of the License, or
16: * (at your option) any later version.
17: *
18: * FreeM is distributed in the hope that it will be useful,
19: * but WITHOUT ANY WARRANTY; without even the implied warranty of
20: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21: * GNU Affero Public License for more details.
22: *
23: * You should have received a copy of the GNU Affero Public License
24: * along with FreeM. If not, see <https://www.gnu.org/licenses/>.
25: *
26: * $Log: journal.c,v $
27: * Revision 1.7 2025/04/13 04:22:43 snw
28: * Fix snprintf calls
29: *
30: * Revision 1.6 2025/04/10 01:24:38 snw
31: * Remove C++ style comments
32: *
33: * Revision 1.5 2025/04/03 20:48:14 snw
34: * Improve daemon error diagnostics and bump to 0.63.0-rc3
35: *
36: * Revision 1.4 2025/03/09 19:14:25 snw
37: * First phase of REUSE compliance and header reformat
38: *
39: *
40: * SPDX-FileCopyrightText: (C) 2025 Coherent Logic Development LLC
41: * SPDX-License-Identifier: AGPL-3.0-or-later
42: **/
43:
44: #include <stdio.h>
45: #include <string.h>
46: #include <stdlib.h>
47:
48: #include <sys/types.h>
49: #include <sys/stat.h>
50: #include <fcntl.h>
51: #include <time.h>
52:
53: #include <unistd.h>
54: #include <errno.h>
55:
56: #include "mpsdef.h"
57: #include "journal.h"
58: #include "transact.h"
59: #include "iniconf.h"
60: #include "shmmgr.h"
61:
62: unsigned long jnl_tran_id; /* transaction id for journaling */
63: unsigned long jnl_cut_threshold; /* byte limit of journal file before cutting new */
64: char jnl_file_path[PATH_MAX]; /* path to journal file */
65: char jnl_host_id[256]; /* host ID (configured at install time) */
66: short jnl_locked = FALSE; /* is the journal locked? */
67: int jnl_desc = 0; /* journal file descriptor */
68:
69: short jnl_enabled = FALSE;
70:
71: void jnl_cut(void);
72: void jnl_panic(char *msg);
73: void jnl_update_tid(void);
74: void jnl_lock(void);
75: void jnl_unlock(void);
76:
77: short jnl_init(char *jnlfile, char *hostid, unsigned long cut_threshold, unsigned long tran_id)
78: {
79:
80: jnl_hdr_t hdr;
81: char tmsg[256];
82:
83: char m[5] = "FRMJL";
84:
85: strncpy (jnl_host_id, hostid, 255);
86: jnl_cut_threshold = cut_threshold;
87:
88: /* cannot re-init in a running process */
89: if ((jnl_desc) && (tran_id == 0)) return FALSE;
90:
91: strncpy (jnl_file_path, jnlfile, PATH_MAX - 1);
92:
93: if (!file_exists (jnl_file_path)) {
94:
95: /* this is a new journal file */
96: jnl_tran_id = tran_id;
97:
98: jnl_desc = open (jnl_file_path, O_CREAT | O_APPEND | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
99:
100: snprintf (tmsg, sizeof (tmsg) - 1, "error creating new journal file '%s' [errno %d: '%s']", jnl_file_path, errno, strerror (errno));
101: if (jnl_desc == -1) jnl_panic (tmsg);
102:
103: jnl_lock ();
104:
105: memcpy (hdr.magic, m, 5);
106: hdr.fmt_version = FRM_JNL_VERSION;
107: snprintf (hdr.host_triplet, sizeof (hdr.host_triplet) - 1, "%s", HOST);
108:
109: if (write (jnl_desc, &hdr, sizeof (jnl_hdr_t)) == -1) {
110: snprintf (tmsg, sizeof (tmsg) - 1, "error %d writing to journal file", errno);
111: jnl_panic (tmsg);
112: }
113:
114: jnl_unlock ();
115:
116: close (jnl_desc);
117:
118: }
119: else {
120:
121: /* this journal file already exists */
122:
123: jnl_desc = open (jnl_file_path, O_APPEND | O_RDWR);
124:
125: lseek (jnl_desc, 0L, SEEK_SET);
126:
127: jnl_lock ();
128:
129: read (jnl_desc, &hdr, sizeof (jnl_hdr_t));
130:
131: if (strncmp (hdr.magic, m, 5) != 0) {
132:
133: set_io (UNIX);
134: fprintf (stderr, "%s is not a valid FreeM journal file.\n", jnl_file_path);
135: set_io (MUMPS);
136:
137: return FALSE;
138:
139: }
140:
141: if (hdr.fmt_version != FRM_JNL_VERSION) {
142:
143: set_io (UNIX);
144: fprintf (stderr, "Journal file version mismatch.\n");
145: set_io (MUMPS);
146:
147: return FALSE;
148:
149: }
150:
151: jnl_unlock ();
152:
153: close (jnl_desc);
154:
155: }
156:
157: jnl_desc = open (jnl_file_path, O_APPEND | O_RDWR);
158:
159: lseek (jnl_desc, 0L, SEEK_END);
160:
161: jnl_lock ();
162: jnl_update_tid ();
163: jnl_unlock ();
164:
165: jnl_enabled = TRUE;
166:
167: return TRUE;
168:
169: }
170:
171: void jnl_cleanup(void)
172: {
173:
174: if (jnl_desc) {
175: jnl_unlock ();
176: close (jnl_desc);
177: }
178:
179: return;
180:
181: }
182:
183: short jnl_ent_write(short action, char *key, char *data)
184: {
185:
186: jnl_ent_t ent;
187: size_t siz;
188: char msg[256];
189:
190: jnl_lock ();
191:
192: if ((tp_level == 0) && (action != JNLA_TSTART)) {
193: /* make sure we have the latest transaction ID */
194: jnl_update_tid ();
195: }
196:
197: siz = lseek (jnl_desc, 0L, SEEK_END);
198:
199: if ((siz + sizeof (jnl_ent_t)) >= jnl_cut_threshold) jnl_cut ();
200:
201: /* only increment the transaction ID if we're NOT in a transaction
202: or this action begins one. */
203: if ((tp_level == 0) || action == JNLA_TSTART) {
204: if (tp_get_sem () == FALSE) {
205: jnl_panic ("could not get transaction processing semaphore");
206: }
207: else {
208: jnl_tran_id++;
209: shm_config->hdr->tp_serial_number = jnl_tran_id;
210:
211: tp_release_sem ();
212: }
213: }
214:
215: ent.tran_id = jnl_tran_id;
216: ent.ts = time (NULL);
217: ent.pid = (pid_t) pid;
218: ent.action = action;
219:
220: strncpy (ent.host_id, jnl_host_id, 255);
221: strncpy (ent.key, key, 1023);
222: strncpy (ent.data, data, 1023);
223:
224: lseek (jnl_desc, 0L, SEEK_END);
225:
226: errno = 0;
227: if ((siz = write (jnl_desc, &ent, sizeof (jnl_ent_t))) < sizeof (jnl_ent_t)) {
228:
229: switch (errno) {
230:
231: case ENOSPC:
232: snprintf (msg, sizeof (msg) - 1, "ran out of disk space while attempting journal write");
233: break;
234:
235: default:
236: snprintf (msg, sizeof (msg) - 1, "%s", strerror (errno));
237: break;
238:
239: }
240:
241: jnl_panic (msg);
242:
243: }
244:
245: jnl_unlock ();
246:
247: return 1;
248:
249: }
250:
251: void jnl_update_tid(void)
252: {
253: jnl_ent_t ent;
254:
255: if (tp_get_sem () == TRUE) {
256:
257: if (first_process == TRUE) {
258:
259: if (!jnl_desc) return;
260:
261: lseek (jnl_desc, 0L, SEEK_END);
262: lseek (jnl_desc, -sizeof (jnl_ent_t), SEEK_CUR);
263:
264: read (jnl_desc, &ent, sizeof (jnl_ent_t));
265:
266: jnl_tran_id = ent.tran_id;
267:
268: shm_config->hdr->tp_serial_number = ent.tran_id;
269:
270: }
271: else {
272: jnl_tran_id = shm_config->hdr->tp_serial_number;
273: }
274:
275: tp_release_sem ();
276:
277: }
278: else {
279: jnl_panic ("jnl_update_tid: could not acquire transaction processing sempahore");
280: }
281:
282: }
283:
284: inline void jnl_lock(void)
285: {
286: struct flock lock;
287:
288: lock.l_type = F_WRLCK;
289: lock.l_whence = SEEK_SET;
290: lock.l_start = 0;
291: lock.l_len = 0;
292:
293: fcntl (jnl_desc, F_SETLK, &lock);
294:
295: jnl_locked = TRUE;
296:
297: return;
298: }
299:
300: inline void jnl_unlock(void)
301: {
302: struct flock lock;
303:
304: lock.l_type = F_UNLCK;
305: lock.l_whence = SEEK_SET;
306: lock.l_start = 0;
307: lock.l_len = 0;
308:
309: fcntl (jnl_desc, F_SETLK, &lock);
310:
311: jnl_locked = FALSE;
312:
313: return;
314: }
315:
316: void jnl_cut(void)
317: {
318: char cutname[PATH_MAX];
319:
320:
321: if (jnl_desc) {
322:
323: jnl_lock ();
324:
325: jnl_update_tid ();
326:
327: snprintf (cutname, PATH_MAX - 1, "%s.%ld", jnl_file_path, jnl_tran_id);
328: close (jnl_desc);
329:
330: rename (jnl_file_path, cutname);
331:
332: if(tp_level == 0) {
333: jnl_init (jnl_file_path, jnl_host_id, jnl_cut_threshold, ++jnl_tran_id);
334: }
335: else {
336: jnl_init (jnl_file_path, jnl_host_id, jnl_cut_threshold, jnl_tran_id);
337: }
338:
339: jnl_unlock();
340:
341: }
342:
343: return;
344:
345: }
346:
347: void jnl_panic(char *msg)
348: {
349: set_io (UNIX);
350:
351: if (tp_level > 0) {
352: fprintf (stderr, "journal error: [%s] (rolling back all transactions)\n", msg);
353: tp_trollback (tp_level);
354: }
355: else {
356: fprintf (stderr, "journal error: [%s]\n", msg);
357: }
358:
359: jnl_cleanup ();
360:
361: exit (1);
362: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>