xref: /illumos-gate/usr/src/cmd/sendmail/db/log/log.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 1996, 1997, 1998
5  *	Sleepycat Software.  All rights reserved.
6  */
7 #include "config.h"
8 
9 #ifndef lint
10 static const char sccsid[] = "@(#)log.c	10.63 (Sleepycat) 10/10/98";
11 #endif /* not lint */
12 
13 #ifndef NO_SYSTEM_INCLUDES
14 #include <sys/types.h>
15 
16 #include <errno.h>
17 #include <shqueue.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 #endif
22 
23 #include "db_int.h"
24 #include "shqueue.h"
25 #include "log.h"
26 #include "db_dispatch.h"
27 #include "txn.h"
28 #include "txn_auto.h"
29 #include "common_ext.h"
30 
31 static int __log_recover __P((DB_LOG *));
32 
33 /*
34  * log_open --
35  *	Initialize and/or join a log.
36  */
37 int
38 log_open(path, flags, mode, dbenv, lpp)
39 	const char *path;
40 	u_int32_t flags;
41 	int mode;
42 	DB_ENV *dbenv;
43 	DB_LOG **lpp;
44 {
45 	DB_LOG *dblp;
46 	LOG *lp;
47 	int ret;
48 
49 	/* Validate arguments. */
50 #ifdef HAVE_SPINLOCKS
51 #define	OKFLAGS	(DB_CREATE | DB_THREAD)
52 #else
53 #define	OKFLAGS	(DB_CREATE)
54 #endif
55 	if ((ret = __db_fchk(dbenv, "log_open", flags, OKFLAGS)) != 0)
56 		return (ret);
57 
58 	/* Create and initialize the DB_LOG structure. */
59 	if ((ret = __os_calloc(1, sizeof(DB_LOG), &dblp)) != 0)
60 		return (ret);
61 
62 	if (path != NULL && (ret = __os_strdup(path, &dblp->dir)) != 0)
63 		goto err;
64 
65 	dblp->dbenv = dbenv;
66 	dblp->lfd = -1;
67 	ZERO_LSN(dblp->c_lsn);
68 	dblp->c_fd = -1;
69 
70 	/*
71 	 * The log region isn't fixed size because we store the registered
72 	 * file names there.  Make it fairly large so that we don't have to
73 	 * grow it.
74 	 */
75 #define	DEF_LOG_SIZE	(30 * 1024)
76 
77 	/* Map in the region. */
78 	dblp->reginfo.dbenv = dbenv;
79 	dblp->reginfo.appname = DB_APP_LOG;
80 	if (path == NULL)
81 		dblp->reginfo.path = NULL;
82 	else
83 		if ((ret = __os_strdup(path, &dblp->reginfo.path)) != 0)
84 			goto err;
85 	dblp->reginfo.file = DB_DEFAULT_LOG_FILE;
86 	dblp->reginfo.mode = mode;
87 	dblp->reginfo.size = DEF_LOG_SIZE;
88 	dblp->reginfo.dbflags = flags;
89 	dblp->reginfo.flags = REGION_SIZEDEF;
90 	if ((ret = __db_rattach(&dblp->reginfo)) != 0)
91 		goto err;
92 
93 	/*
94 	 * The LOG structure is first in the region, the rest of the region
95 	 * is free space.
96 	 */
97 	dblp->lp = dblp->reginfo.addr;
98 	dblp->addr = (u_int8_t *)dblp->lp + sizeof(LOG);
99 
100 	/* Initialize a created region. */
101 	if (F_ISSET(&dblp->reginfo, REGION_CREATED)) {
102 		__db_shalloc_init(dblp->addr, DEF_LOG_SIZE - sizeof(LOG));
103 
104 		/* Initialize the LOG structure. */
105 		lp = dblp->lp;
106 		lp->persist.lg_max = dbenv == NULL ? 0 : dbenv->lg_max;
107 		if (lp->persist.lg_max == 0)
108 			lp->persist.lg_max = DEFAULT_MAX;
109 		lp->persist.magic = DB_LOGMAGIC;
110 		lp->persist.version = DB_LOGVERSION;
111 		lp->persist.mode = mode;
112 		SH_TAILQ_INIT(&lp->fq);
113 
114 		/* Initialize LOG LSNs. */
115 		lp->lsn.file = 1;
116 		lp->lsn.offset = 0;
117 	}
118 
119 	/* Initialize thread information, mutex. */
120 	if (LF_ISSET(DB_THREAD)) {
121 		F_SET(dblp, DB_AM_THREAD);
122 		if ((ret = __db_shalloc(dblp->addr,
123 		    sizeof(db_mutex_t), MUTEX_ALIGNMENT, &dblp->mutexp)) != 0)
124 			goto err;
125 		(void)__db_mutex_init(dblp->mutexp, 0);
126 	}
127 
128 	/*
129 	 * If doing recovery, try and recover any previous log files before
130 	 * releasing the lock.
131 	 */
132 	if (F_ISSET(&dblp->reginfo, REGION_CREATED) &&
133 	    (ret = __log_recover(dblp)) != 0)
134 		goto err;
135 
136 	UNLOCK_LOGREGION(dblp);
137 	*lpp = dblp;
138 	return (0);
139 
140 err:	if (dblp->reginfo.addr != NULL) {
141 		if (dblp->mutexp != NULL)
142 			__db_shalloc_free(dblp->addr, dblp->mutexp);
143 
144 		UNLOCK_LOGREGION(dblp);
145 		(void)__db_rdetach(&dblp->reginfo);
146 		if (F_ISSET(&dblp->reginfo, REGION_CREATED))
147 			(void)log_unlink(path, 1, dbenv);
148 	}
149 
150 	if (dblp->reginfo.path != NULL)
151 		__os_freestr(dblp->reginfo.path);
152 	if (dblp->dir != NULL)
153 		__os_freestr(dblp->dir);
154 	__os_free(dblp, sizeof(*dblp));
155 	return (ret);
156 }
157 
158 /*
159  * __log_panic --
160  *	Panic a log.
161  *
162  * PUBLIC: void __log_panic __P((DB_ENV *));
163  */
164 void
165 __log_panic(dbenv)
166 	DB_ENV *dbenv;
167 {
168 	if (dbenv->lg_info != NULL)
169 		dbenv->lg_info->lp->rlayout.panic = 1;
170 }
171 
172 /*
173  * __log_recover --
174  *	Recover a log.
175  */
176 static int
177 __log_recover(dblp)
178 	DB_LOG *dblp;
179 {
180 	DBT dbt;
181 	DB_LSN lsn;
182 	LOG *lp;
183 	u_int32_t chk;
184 	int cnt, found_checkpoint, ret;
185 
186 	lp = dblp->lp;
187 
188 	/*
189 	 * Find a log file.  If none exist, we simply return, leaving
190 	 * everything initialized to a new log.
191 	 */
192 	if ((ret = __log_find(dblp, 0, &cnt)) != 0)
193 		return (ret);
194 	if (cnt == 0)
195 		return (0);
196 
197 	/*
198 	 * We have the last useful log file and we've loaded any persistent
199 	 * information.  Pretend that the log is larger than it can possibly
200 	 * be, and read the last file, looking for the last checkpoint and
201 	 * the log's end.
202 	 */
203 	lp->lsn.file = cnt + 1;
204 	lp->lsn.offset = 0;
205 	lsn.file = cnt;
206 	lsn.offset = 0;
207 
208 	/* Set the cursor.  Shouldn't fail, leave error messages on. */
209 	memset(&dbt, 0, sizeof(dbt));
210 	if ((ret = __log_get(dblp, &lsn, &dbt, DB_SET, 0)) != 0)
211 		return (ret);
212 
213 	/*
214 	 * Read to the end of the file, saving checkpoints.  This will fail
215 	 * at some point, so turn off error messages.
216 	 */
217 	found_checkpoint = 0;
218 	while (__log_get(dblp, &lsn, &dbt, DB_NEXT, 1) == 0) {
219 		if (dbt.size < sizeof(u_int32_t))
220 			continue;
221 		memcpy(&chk, dbt.data, sizeof(u_int32_t));
222 		if (chk == DB_txn_ckp) {
223 			lp->chkpt_lsn = lsn;
224 			found_checkpoint = 1;
225 		}
226 	}
227 
228 	/*
229 	 * We now know where the end of the log is.  Set the first LSN that
230 	 * we want to return to an application and the LSN of the last known
231 	 * record on disk.
232 	 */
233 	lp->lsn = lp->s_lsn = lsn;
234 	lp->lsn.offset += dblp->c_len;
235 
236 	/* Set up the current buffer information, too. */
237 	lp->len = dblp->c_len;
238 	lp->b_off = 0;
239 	lp->w_off = lp->lsn.offset;
240 
241 	/*
242 	 * It's possible that we didn't find a checkpoint because there wasn't
243 	 * one in the last log file.  Start searching.
244 	 */
245 	while (!found_checkpoint && cnt > 1) {
246 		lsn.file = --cnt;
247 		lsn.offset = 0;
248 
249 		/* Set the cursor.  Shouldn't fail, leave error messages on. */
250 		if ((ret = __log_get(dblp, &lsn, &dbt, DB_SET, 0)) != 0)
251 			return (ret);
252 
253 		/*
254 		 * Read to the end of the file, saving checkpoints.  Shouldn't
255 		 * fail, leave error messages on.
256 		 */
257 		while (__log_get(dblp, &lsn, &dbt, DB_NEXT, 0) == 0) {
258 			if (dbt.size < sizeof(u_int32_t))
259 				continue;
260 			memcpy(&chk, dbt.data, sizeof(u_int32_t));
261 			if (chk == DB_txn_ckp) {
262 				lp->chkpt_lsn = lsn;
263 				found_checkpoint = 1;
264 			}
265 		}
266 	}
267 	/*
268 	 * Reset the cursor lsn to the beginning of the log, so that an
269 	 * initial call to DB_NEXT does the right thing.
270 	 */
271 	ZERO_LSN(dblp->c_lsn);
272 
273 	/* If we never find a checkpoint, that's okay, just 0 it out. */
274 	if (!found_checkpoint)
275 		ZERO_LSN(lp->chkpt_lsn);
276 
277 	/*
278 	 * !!!
279 	 * The test suite explicitly looks for this string -- don't change
280 	 * it here unless you also change it there.
281 	 */
282 	__db_err(dblp->dbenv,
283 	    "Finding last valid log LSN: file: %lu offset %lu",
284 	    (u_long)lp->lsn.file, (u_long)lp->lsn.offset);
285 
286 	return (0);
287 }
288 
289 /*
290  * __log_find --
291  *	Try to find a log file.  If find_first is set, valp will contain
292  * the number of the first log file, else it will contain the number of
293  * the last log file.
294  *
295  * PUBLIC: int __log_find __P((DB_LOG *, int, int *));
296  */
297 int
298 __log_find(dblp, find_first, valp)
299 	DB_LOG *dblp;
300 	int find_first, *valp;
301 {
302 	u_int32_t clv, logval;
303 	int cnt, fcnt, ret;
304 	const char *dir;
305 	char **names, *p, *q;
306 
307 	*valp = 0;
308 
309 	/* Find the directory name. */
310 	if ((ret = __log_name(dblp, 1, &p, NULL, 0)) != 0)
311 		return (ret);
312 	if ((q = __db_rpath(p)) == NULL)
313 		dir = PATH_DOT;
314 	else {
315 		*q = '\0';
316 		dir = p;
317 	}
318 
319 	/* Get the list of file names. */
320 	ret = __os_dirlist(dir, &names, &fcnt);
321 	__os_freestr(p);
322 	if (ret != 0) {
323 		__db_err(dblp->dbenv, "%s: %s", dir, strerror(ret));
324 		return (ret);
325 	}
326 
327 	/*
328 	 * Search for a valid log file name, return a value of 0 on
329 	 * failure.
330 	 *
331 	 * XXX
332 	 * Assumes that atoi(3) returns a 32-bit number.
333 	 */
334 	for (cnt = fcnt, clv = logval = 0; --cnt >= 0;) {
335 		if (strncmp(names[cnt], LFPREFIX, sizeof(LFPREFIX) - 1) != 0)
336 			continue;
337 
338 		clv = atoi(names[cnt] + (sizeof(LFPREFIX) - 1));
339 		if (find_first) {
340 			if (logval != 0 && clv > logval)
341 				continue;
342 		} else
343 			if (logval != 0 && clv < logval)
344 				continue;
345 
346 		if (__log_valid(dblp, clv, 1) == 0)
347 			logval = clv;
348 	}
349 
350 	*valp = logval;
351 
352 	/* Discard the list. */
353 	__os_dirfree(names, fcnt);
354 
355 	return (0);
356 }
357 
358 /*
359  * log_valid --
360  *	Validate a log file.
361  *
362  * PUBLIC: int __log_valid __P((DB_LOG *, u_int32_t, int));
363  */
364 int
365 __log_valid(dblp, number, set_persist)
366 	DB_LOG *dblp;
367 	u_int32_t number;
368 	int set_persist;
369 {
370 	LOGP persist;
371 	ssize_t nw;
372 	char *fname;
373 	int fd, ret;
374 
375 	/* Try to open the log file. */
376 	if ((ret = __log_name(dblp,
377 	    number, &fname, &fd, DB_RDONLY | DB_SEQUENTIAL)) != 0) {
378 		__os_freestr(fname);
379 		return (ret);
380 	}
381 
382 	/* Try to read the header. */
383 	if ((ret = __os_seek(fd, 0, 0, sizeof(HDR), 0, SEEK_SET)) != 0 ||
384 	    (ret = __os_read(fd, &persist, sizeof(LOGP), &nw)) != 0 ||
385 	    nw != sizeof(LOGP)) {
386 		if (ret == 0)
387 			ret = EIO;
388 
389 		(void)__os_close(fd);
390 
391 		__db_err(dblp->dbenv,
392 		    "Ignoring log file: %s: %s", fname, strerror(ret));
393 		goto err;
394 	}
395 	(void)__os_close(fd);
396 
397 	/* Validate the header. */
398 	if (persist.magic != DB_LOGMAGIC) {
399 		__db_err(dblp->dbenv,
400 		    "Ignoring log file: %s: magic number %lx, not %lx",
401 		    fname, (u_long)persist.magic, (u_long)DB_LOGMAGIC);
402 		ret = EINVAL;
403 		goto err;
404 	}
405 	if (persist.version < DB_LOGOLDVER || persist.version > DB_LOGVERSION) {
406 		__db_err(dblp->dbenv,
407 		    "Ignoring log file: %s: unsupported log version %lu",
408 		    fname, (u_long)persist.version);
409 		ret = EINVAL;
410 		goto err;
411 	}
412 
413 	/*
414 	 * If we're going to use this log file, set the region's persistent
415 	 * information based on the headers.
416 	 */
417 	if (set_persist) {
418 		dblp->lp->persist.lg_max = persist.lg_max;
419 		dblp->lp->persist.mode = persist.mode;
420 	}
421 	ret = 0;
422 
423 err:	__os_freestr(fname);
424 	return (ret);
425 }
426 
427 /*
428  * log_close --
429  *	Close a log.
430  */
431 int
432 log_close(dblp)
433 	DB_LOG *dblp;
434 {
435 	u_int32_t i;
436 	int ret, t_ret;
437 
438 	LOG_PANIC_CHECK(dblp);
439 
440 	/* We may have opened files as part of XA; if so, close them. */
441 	__log_close_files(dblp);
442 
443 	/* Discard the per-thread pointer. */
444 	if (dblp->mutexp != NULL) {
445 		LOCK_LOGREGION(dblp);
446 		__db_shalloc_free(dblp->addr, dblp->mutexp);
447 		UNLOCK_LOGREGION(dblp);
448 	}
449 
450 	/* Close the region. */
451 	ret = __db_rdetach(&dblp->reginfo);
452 
453 	/* Close open files, release allocated memory. */
454 	if (dblp->lfd != -1 && (t_ret = __os_close(dblp->lfd)) != 0 && ret == 0)
455 		ret = t_ret;
456 	if (dblp->c_dbt.data != NULL)
457 		__os_free(dblp->c_dbt.data, dblp->c_dbt.ulen);
458 	if (dblp->c_fd != -1 &&
459 	    (t_ret = __os_close(dblp->c_fd)) != 0 && ret == 0)
460 		ret = t_ret;
461 	if (dblp->dbentry != NULL) {
462 		for (i = 0; i < dblp->dbentry_cnt; i++)
463 			if (dblp->dbentry[i].name != NULL)
464 				__os_freestr(dblp->dbentry[i].name);
465 		__os_free(dblp->dbentry,
466 		    (dblp->dbentry_cnt * sizeof(DB_ENTRY)));
467 	}
468 
469 	if (dblp->dir != NULL)
470 		__os_freestr(dblp->dir);
471 
472 	if (dblp->reginfo.path != NULL)
473 		__os_freestr(dblp->reginfo.path);
474 	__os_free(dblp, sizeof(*dblp));
475 
476 	return (ret);
477 }
478 
479 /*
480  * log_unlink --
481  *	Exit a log.
482  */
483 int
484 log_unlink(path, force, dbenv)
485 	const char *path;
486 	int force;
487 	DB_ENV *dbenv;
488 {
489 	REGINFO reginfo;
490 	int ret;
491 
492 	memset(&reginfo, 0, sizeof(reginfo));
493 	reginfo.dbenv = dbenv;
494 	reginfo.appname = DB_APP_LOG;
495 	if (path != NULL && (ret = __os_strdup(path, &reginfo.path)) != 0)
496 		return (ret);
497 	reginfo.file = DB_DEFAULT_LOG_FILE;
498 	ret = __db_runlink(&reginfo, force);
499 	if (reginfo.path != NULL)
500 		__os_freestr(reginfo.path);
501 	return (ret);
502 }
503 
504 /*
505  * log_stat --
506  *	Return LOG statistics.
507  */
508 int
509 log_stat(dblp, gspp, db_malloc)
510 	DB_LOG *dblp;
511 	DB_LOG_STAT **gspp;
512 	void *(*db_malloc) __P((size_t));
513 {
514 	LOG *lp;
515 	int ret;
516 
517 	*gspp = NULL;
518 	lp = dblp->lp;
519 
520 	LOG_PANIC_CHECK(dblp);
521 
522 	if ((ret = __os_malloc(sizeof(**gspp), db_malloc, gspp)) != 0)
523 		return (ret);
524 
525 	/* Copy out the global statistics. */
526 	LOCK_LOGREGION(dblp);
527 	**gspp = lp->stat;
528 
529 	(*gspp)->st_magic = lp->persist.magic;
530 	(*gspp)->st_version = lp->persist.version;
531 	(*gspp)->st_mode = lp->persist.mode;
532 	(*gspp)->st_lg_max = lp->persist.lg_max;
533 
534 	(*gspp)->st_region_nowait = lp->rlayout.lock.mutex_set_nowait;
535 	(*gspp)->st_region_wait = lp->rlayout.lock.mutex_set_wait;
536 
537 	(*gspp)->st_cur_file = lp->lsn.file;
538 	(*gspp)->st_cur_offset = lp->lsn.offset;
539 
540 	(*gspp)->st_refcnt = lp->rlayout.refcnt;
541 	(*gspp)->st_regsize = lp->rlayout.size;
542 
543 	UNLOCK_LOGREGION(dblp);
544 
545 	return (0);
546 }
547