1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 1996, 1997, 1998
5  *	Sleepycat Software.  All rights reserved.
6  */
7 
8 #include "config.h"
9 
10 #ifndef lint
11 static const char sccsid[] = "@(#)db_appinit.c	10.66 (Sleepycat) 12/7/98";
12 #endif /* not lint */
13 
14 #ifndef NO_SYSTEM_INCLUDES
15 #include <sys/types.h>
16 
17 #include <ctype.h>
18 #include <errno.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #endif
23 
24 #include "db_int.h"
25 #include "shqueue.h"
26 #include "db_page.h"
27 #include "btree.h"
28 #include "hash.h"
29 #include "log.h"
30 #include "txn.h"
31 #include "clib_ext.h"
32 #include "common_ext.h"
33 
34 static int __db_home __P((DB_ENV *, const char *, u_int32_t));
35 static int __db_parse __P((DB_ENV *, char *));
36 static int __db_tmp_open __P((DB_ENV *, u_int32_t, char *, int *));
37 
38 /*
39  * This conflict array is used for concurrent db access (cdb).  It
40  * uses the same locks as the db_rw_conflict array, but adds an IW
41  * mode to be used for write cursors.
42  */
43 static u_int8_t const db_cdb_conflicts[] = {
44 	/*		N   R   W  IW */
45 	/*    N */	0,  0,  0,  0,
46 	/*    R */	0,  0,  1,  0,
47 	/*    W */	0,  1,  1,  1,
48 	/*   IW */	0,  0,  1,  1
49 };
50 
51 /*
52  * db_version --
53  *	Return version information.
54  */
55 char *
db_version(majverp,minverp,patchp)56 db_version(majverp, minverp, patchp)
57 	int *majverp, *minverp, *patchp;
58 {
59 	if (majverp != NULL)
60 		*majverp = DB_VERSION_MAJOR;
61 	if (minverp != NULL)
62 		*minverp = DB_VERSION_MINOR;
63 	if (patchp != NULL)
64 		*patchp = DB_VERSION_PATCH;
65 	return ((char *)DB_VERSION_STRING);
66 }
67 
68 /*
69  * db_appinit --
70  *	Initialize the application environment.
71  */
72 int
db_appinit(db_home,db_config,dbenv,flags)73 db_appinit(db_home, db_config, dbenv, flags)
74 	const char *db_home;
75 	char * const *db_config;
76 	DB_ENV *dbenv;
77 	u_int32_t flags;
78 {
79 	FILE *fp;
80 	int mode, ret;
81 	char * const *p;
82 	char *lp, buf[MAXPATHLEN * 2];
83 
84 	fp = NULL;
85 
86 	/* Validate arguments. */
87 	if (dbenv == NULL)
88 		return (EINVAL);
89 
90 #ifdef HAVE_SPINLOCKS
91 #define	OKFLAGS								\
92     (DB_CREATE | DB_INIT_CDB | DB_INIT_LOCK | DB_INIT_LOG |		\
93     DB_INIT_MPOOL | DB_INIT_TXN | DB_MPOOL_PRIVATE | DB_NOMMAP |	\
94     DB_RECOVER | DB_RECOVER_FATAL | DB_THREAD | DB_TXN_NOSYNC |		\
95     DB_USE_ENVIRON | DB_USE_ENVIRON_ROOT)
96 #else
97 #define	OKFLAGS								\
98     (DB_CREATE | DB_INIT_CDB | DB_INIT_LOCK | DB_INIT_LOG |		\
99     DB_INIT_MPOOL | DB_INIT_TXN | DB_MPOOL_PRIVATE | DB_NOMMAP |	\
100     DB_RECOVER | DB_RECOVER_FATAL | DB_TXN_NOSYNC |			\
101     DB_USE_ENVIRON | DB_USE_ENVIRON_ROOT)
102 #endif
103 	if ((ret = __db_fchk(dbenv, "db_appinit", flags, OKFLAGS)) != 0)
104 		return (ret);
105 
106 	/* Transactions imply logging. */
107 	if (LF_ISSET(DB_INIT_TXN))
108 		LF_SET(DB_INIT_LOG);
109 
110 	/* Convert the db_appinit(3) flags. */
111 	if (LF_ISSET(DB_THREAD))
112 		F_SET(dbenv, DB_ENV_THREAD);
113 
114 	/* Set the database home. */
115 	if ((ret = __db_home(dbenv, db_home, flags)) != 0)
116 		goto err;
117 
118 	/* Parse the config array. */
119 	for (p = db_config; p != NULL && *p != NULL; ++p)
120 		if ((ret = __db_parse(dbenv, *p)) != 0)
121 			goto err;
122 
123 	/*
124 	 * Parse the config file.
125 	 *
126 	 * XXX
127 	 * Don't use sprintf(3)/snprintf(3) -- the former is dangerous, and
128 	 * the latter isn't standard, and we're manipulating strings handed
129 	 * us by the application.
130 	 */
131 	if (dbenv->db_home != NULL) {
132 #define	CONFIG_NAME	"/DB_CONFIG"
133 		if (strlen(dbenv->db_home) +
134 		    strlen(CONFIG_NAME) + 1 > sizeof(buf)) {
135 			ret = ENAMETOOLONG;
136 			goto err;
137 		}
138 		(void)strcpy(buf, dbenv->db_home);
139 		(void)strcat(buf, CONFIG_NAME);
140 		if ((fp = fopen(buf, "r")) != NULL) {
141 			while (fgets(buf, sizeof(buf), fp) != NULL) {
142 				if ((lp = strchr(buf, '\n')) == NULL) {
143 					__db_err(dbenv,
144 					    "%s: line too long", CONFIG_NAME);
145 					ret = EINVAL;
146 					goto err;
147 				}
148 				*lp = '\0';
149 				if (buf[0] == '\0' ||
150 				    buf[0] == '#' || isspace(buf[0]))
151 					continue;
152 
153 				if ((ret = __db_parse(dbenv, buf)) != 0)
154 					goto err;
155 			}
156 			(void)fclose(fp);
157 			fp = NULL;
158 		}
159 	}
160 
161 	/* Set up the tmp directory path. */
162 	if (dbenv->db_tmp_dir == NULL && (ret = __os_tmpdir(dbenv, flags)) != 0)
163 		goto err;
164 
165 	/*
166 	 * Flag that the structure has been initialized by the application.
167 	 * Note, this must be set before calling into the subsystems as it
168 	 * is used when we're doing file naming.
169 	 */
170 	F_SET(dbenv, DB_ENV_APPINIT);
171 
172 	/*
173 	 * If we are doing recovery, remove all the old shared memory
174 	 * regions.
175 	 */
176 	if (LF_ISSET(DB_RECOVER | DB_RECOVER_FATAL)) {
177 		if ((ret = log_unlink(NULL, 1, dbenv)) != 0)
178 			goto err;
179 		if ((ret = memp_unlink(NULL, 1, dbenv)) != 0)
180 			goto err;
181 		if ((ret = lock_unlink(NULL, 1, dbenv)) != 0)
182 			goto err;
183 		if ((ret = txn_unlink(NULL, 1, dbenv)) != 0)
184 			goto err;
185 	}
186 
187 	/*
188 	 * Create the new shared regions.
189 	 *
190 	 * Default permissions are read-write for both owner and group.
191 	 */
192 	mode = __db_omode("rwrw--");
193 	if (LF_ISSET(DB_INIT_CDB)) {
194 		if (LF_ISSET(DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN)) {
195 			ret = EINVAL;
196 			goto err;
197 		}
198 		F_SET(dbenv, DB_ENV_CDB);
199 		dbenv->lk_conflicts = db_cdb_conflicts;
200 		dbenv->lk_modes = DB_LOCK_RW_N + 1;
201 		if ((ret = lock_open(NULL, LF_ISSET(DB_CREATE | DB_THREAD),
202 		    mode, dbenv, &dbenv->lk_info)) != 0)
203 			goto err;
204 	}
205 	if (LF_ISSET(DB_INIT_LOCK) && (ret = lock_open(NULL,
206 	    LF_ISSET(DB_CREATE | DB_THREAD),
207 	    mode, dbenv, &dbenv->lk_info)) != 0)
208 		goto err;
209 	if (LF_ISSET(DB_INIT_LOG) && (ret = log_open(NULL,
210 	    LF_ISSET(DB_CREATE | DB_THREAD),
211 	    mode, dbenv, &dbenv->lg_info)) != 0)
212 		goto err;
213 	if (LF_ISSET(DB_INIT_MPOOL) && (ret = memp_open(NULL,
214 	    LF_ISSET(DB_CREATE | DB_MPOOL_PRIVATE | DB_NOMMAP | DB_THREAD),
215 	    mode, dbenv, &dbenv->mp_info)) != 0)
216 		goto err;
217 	if (LF_ISSET(DB_INIT_TXN) && (ret = txn_open(NULL,
218 	    LF_ISSET(DB_CREATE | DB_THREAD | DB_TXN_NOSYNC),
219 	    mode, dbenv, &dbenv->tx_info)) != 0)
220 		goto err;
221 
222 	/*
223 	 * If the application is running with transactions, initialize the
224 	 * function tables.  Once that's done, do recovery for any previous
225 	 * run.
226 	 */
227 	if (LF_ISSET(DB_INIT_TXN)) {
228 		if ((ret = __bam_init_recover(dbenv)) != 0)
229 			goto err;
230 		if ((ret = __db_init_recover(dbenv)) != 0)
231 			goto err;
232 		if ((ret = __ham_init_recover(dbenv)) != 0)
233 			goto err;
234 		if ((ret = __log_init_recover(dbenv)) != 0)
235 			goto err;
236 		if ((ret = __txn_init_recover(dbenv)) != 0)
237 			goto err;
238 
239 		if (LF_ISSET(DB_RECOVER | DB_RECOVER_FATAL) &&
240 		    (ret = __db_apprec(dbenv,
241 		    LF_ISSET(DB_RECOVER | DB_RECOVER_FATAL))) != 0)
242 			goto err;
243 	}
244 
245 	return (ret);
246 
247 err:	if (fp != NULL)
248 		(void)fclose(fp);
249 
250 	(void)db_appexit(dbenv);
251 	return (ret);
252 }
253 
254 /*
255  * db_appexit --
256  *	Close down the default application environment.
257  */
258 int
db_appexit(dbenv)259 db_appexit(dbenv)
260 	DB_ENV *dbenv;
261 {
262 	int ret, t_ret;
263 	char **p;
264 
265 	ret = 0;
266 
267 	/* Close subsystems. */
268 	if (dbenv->tx_info && (t_ret = txn_close(dbenv->tx_info)) != 0)
269 		if (ret == 0)
270 			ret = t_ret;
271 	if (dbenv->lg_info && (t_ret = log_close(dbenv->lg_info)) != 0)
272 		if (ret == 0)
273 			ret = t_ret;
274 	if (dbenv->mp_info && (t_ret = memp_close(dbenv->mp_info)) != 0)
275 		if (ret == 0)
276 			ret = t_ret;
277 	if (dbenv->lk_info && (t_ret = lock_close(dbenv->lk_info)) != 0)
278 		if (ret == 0)
279 			ret = t_ret;
280 
281 	/* Clear initialized flag (after subsystems, it affects naming). */
282 	F_CLR(dbenv, DB_ENV_APPINIT);
283 
284 	/* Free allocated memory. */
285 	if (dbenv->db_home != NULL)
286 		__os_freestr(dbenv->db_home);
287 	if ((p = dbenv->db_data_dir) != NULL) {
288 		for (; *p != NULL; ++p)
289 			__os_freestr(*p);
290 		__os_free(dbenv->db_data_dir,
291 		    dbenv->data_cnt * sizeof(char **));
292 	}
293 	if (dbenv->db_log_dir != NULL)
294 		__os_freestr(dbenv->db_log_dir);
295 	if (dbenv->db_tmp_dir != NULL)
296 		__os_freestr(dbenv->db_tmp_dir);
297 
298 	return (ret);
299 }
300 
301 #define	DB_ADDSTR(str) {						\
302 	if ((str) != NULL) {						\
303 		/* If leading slash, start over. */			\
304 		if (__os_abspath(str)) {				\
305 			p = start;					\
306 			slash = 0;					\
307 		}							\
308 		/* Append to the current string. */			\
309 		len = strlen(str);					\
310 		if (slash)						\
311 			*p++ = PATH_SEPARATOR[0];			\
312 		memcpy(p, str, len);					\
313 		p += len;						\
314 		slash = strchr(PATH_SEPARATOR, p[-1]) == NULL;		\
315 	}								\
316 }
317 
318 /*
319  * __db_appname --
320  *	Given an optional DB environment, directory and file name and type
321  *	of call, build a path based on the db_appinit(3) rules, and return
322  *	it in allocated space.
323  *
324  * PUBLIC: int __db_appname __P((DB_ENV *,
325  * PUBLIC:    APPNAME, const char *, const char *, u_int32_t, int *, char **));
326  */
327 int
__db_appname(dbenv,appname,dir,file,tmp_oflags,fdp,namep)328 __db_appname(dbenv, appname, dir, file, tmp_oflags, fdp, namep)
329 	DB_ENV *dbenv;
330 	APPNAME appname;
331 	const char *dir, *file;
332 	u_int32_t tmp_oflags;
333 	int *fdp;
334 	char **namep;
335 {
336 	DB_ENV etmp;
337 	size_t len;
338 	int data_entry, ret, slash, tmp_create, tmp_free;
339 	const char *a, *b, *c;
340 	char *p, *start;
341 
342 	a = b = c = NULL;
343 	data_entry = -1;
344 	tmp_create = tmp_free = 0;
345 
346 	/*
347 	 * We don't return a name when creating temporary files, just an fd.
348 	 * Default to error now.
349 	 */
350 	if (fdp != NULL)
351 		*fdp = -1;
352 	if (namep != NULL)
353 		*namep = NULL;
354 
355 	/*
356 	 * Absolute path names are never modified.  If the file is an absolute
357 	 * path, we're done.  If the directory is, simply append the file and
358 	 * return.
359 	 */
360 	if (file != NULL && __os_abspath(file))
361 		return (__os_strdup(file, namep));
362 	if (dir != NULL && __os_abspath(dir)) {
363 		a = dir;
364 		goto done;
365 	}
366 
367 	/*
368 	 * DB_ENV  DIR	   APPNAME	   RESULT
369 	 * -------------------------------------------
370 	 * null	   null	   none		   <tmp>/file
371 	 * null	   set	   none		   DIR/file
372 	 * set	   null	   none		   DB_HOME/file
373 	 * set	   set	   none		   DB_HOME/DIR/file
374 	 *
375 	 * DB_ENV  FILE	   APPNAME	   RESULT
376 	 * -------------------------------------------
377 	 * null	   null	   DB_APP_DATA	   <tmp>/<create>
378 	 * null	   set	   DB_APP_DATA	   ./file
379 	 * set	   null	   DB_APP_DATA	   <tmp>/<create>
380 	 * set	   set	   DB_APP_DATA	   DB_HOME/DB_DATA_DIR/file
381 	 *
382 	 * DB_ENV  DIR	   APPNAME	   RESULT
383 	 * -------------------------------------------
384 	 * null	   null	   DB_APP_LOG	   <tmp>/file
385 	 * null	   set	   DB_APP_LOG	   DIR/file
386 	 * set	   null	   DB_APP_LOG	   DB_HOME/DB_LOG_DIR/file
387 	 * set	   set	   DB_APP_LOG	   DB_HOME/DB_LOG_DIR/DIR/file
388 	 *
389 	 * DB_ENV	   APPNAME	   RESULT
390 	 * -------------------------------------------
391 	 * null		   DB_APP_TMP*	   <tmp>/<create>
392 	 * set		   DB_APP_TMP*	   DB_HOME/DB_TMP_DIR/<create>
393 	 */
394 retry:	switch (appname) {
395 	case DB_APP_NONE:
396 		if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT)) {
397 			if (dir == NULL)
398 				goto tmp;
399 			a = dir;
400 		} else {
401 			a = dbenv->db_home;
402 			b = dir;
403 		}
404 		break;
405 	case DB_APP_DATA:
406 		if (dir != NULL) {
407 			__db_err(dbenv,
408 			    "DB_APP_DATA: illegal directory specification");
409 			return (EINVAL);
410 		}
411 
412 		if (file == NULL) {
413 			tmp_create = 1;
414 			goto tmp;
415 		}
416 		if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT))
417 			a = PATH_DOT;
418 		else {
419 			a = dbenv->db_home;
420 			if (dbenv->db_data_dir != NULL &&
421 			    (b = dbenv->db_data_dir[++data_entry]) == NULL) {
422 				data_entry = -1;
423 				b = dbenv->db_data_dir[0];
424 			}
425 		}
426 		break;
427 	case DB_APP_LOG:
428 		if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT)) {
429 			if (dir == NULL)
430 				goto tmp;
431 			a = dir;
432 		} else {
433 			a = dbenv->db_home;
434 			b = dbenv->db_log_dir;
435 			c = dir;
436 		}
437 		break;
438 	case DB_APP_TMP:
439 		if (dir != NULL || file != NULL) {
440 			__db_err(dbenv,
441 		    "DB_APP_TMP: illegal directory or file specification");
442 			return (EINVAL);
443 		}
444 
445 		tmp_create = 1;
446 		if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT))
447 			goto tmp;
448 		else {
449 			a = dbenv->db_home;
450 			b = dbenv->db_tmp_dir;
451 		}
452 		break;
453 	}
454 
455 	/* Reference a file from the appropriate temporary directory. */
456 	if (0) {
457 tmp:		if (dbenv == NULL || !F_ISSET(dbenv, DB_ENV_APPINIT)) {
458 			memset(&etmp, 0, sizeof(etmp));
459 			if ((ret = __os_tmpdir(&etmp, DB_USE_ENVIRON)) != 0)
460 				return (ret);
461 			tmp_free = 1;
462 			a = etmp.db_tmp_dir;
463 		} else
464 			a = dbenv->db_tmp_dir;
465 	}
466 
467 done:	len =
468 	    (a == NULL ? 0 : strlen(a) + 1) +
469 	    (b == NULL ? 0 : strlen(b) + 1) +
470 	    (c == NULL ? 0 : strlen(c) + 1) +
471 	    (file == NULL ? 0 : strlen(file) + 1);
472 
473 	/*
474 	 * Allocate space to hold the current path information, as well as any
475 	 * temporary space that we're going to need to create a temporary file
476 	 * name.
477 	 */
478 #define	DB_TRAIL	"XXXXXX"
479 	if ((ret =
480 	    __os_malloc(len + sizeof(DB_TRAIL) + 10, NULL, &start)) != 0) {
481 		if (tmp_free)
482 			__os_freestr(etmp.db_tmp_dir);
483 		return (ret);
484 	}
485 
486 	slash = 0;
487 	p = start;
488 	DB_ADDSTR(a);
489 	DB_ADDSTR(b);
490 	DB_ADDSTR(file);
491 	*p = '\0';
492 
493 	/* Discard any space allocated to find the temp directory. */
494 	if (tmp_free) {
495 		__os_freestr(etmp.db_tmp_dir);
496 		tmp_free = 0;
497 	}
498 
499 	/*
500 	 * If we're opening a data file, see if it exists.  If it does,
501 	 * return it, otherwise, try and find another one to open.
502 	 */
503 	if (data_entry != -1 && __os_exists(start, NULL) != 0) {
504 		__os_freestr(start);
505 		a = b = c = NULL;
506 		goto retry;
507 	}
508 
509 	/* Create the file if so requested. */
510 	if (tmp_create &&
511 	    (ret = __db_tmp_open(dbenv, tmp_oflags, start, fdp)) != 0) {
512 		__os_freestr(start);
513 		return (ret);
514 	}
515 
516 	if (namep == NULL)
517 		__os_freestr(start);
518 	else
519 		*namep = start;
520 	return (0);
521 }
522 
523 /*
524  * __db_home --
525  *	Find the database home.
526  */
527 static int
__db_home(dbenv,db_home,flags)528 __db_home(dbenv, db_home, flags)
529 	DB_ENV *dbenv;
530 	const char *db_home;
531 	u_int32_t flags;
532 {
533 	const char *p;
534 
535 	p = db_home;
536 
537 	/* Use the environment if it's permitted and initialized. */
538 #ifdef HAVE_GETUID
539 	if (LF_ISSET(DB_USE_ENVIRON) ||
540 	    (LF_ISSET(DB_USE_ENVIRON_ROOT) && getuid() == 0)) {
541 #else
542 	if (LF_ISSET(DB_USE_ENVIRON)) {
543 #endif
544 		if ((p = getenv("DB_HOME")) == NULL)
545 			p = db_home;
546 		else if (p[0] == '\0') {
547 			__db_err(dbenv,
548 			    "illegal DB_HOME environment variable");
549 			return (EINVAL);
550 		}
551 	}
552 
553 	if (p == NULL)
554 		return (0);
555 
556 	return (__os_strdup(p, &dbenv->db_home));
557 }
558 
559 /*
560  * __db_parse --
561  *	Parse a single NAME VALUE pair.
562  */
563 static int
__db_parse(dbenv,s)564 __db_parse(dbenv, s)
565 	DB_ENV *dbenv;
566 	char *s;
567 {
568 	int ret;
569 	char *local_s, *name, *value, **p, *tp;
570 
571 	/*
572 	 * We need to strdup the argument in case the caller passed us
573 	 * static data.
574 	 */
575 	if ((ret = __os_strdup(s, &local_s)) != 0)
576 		return (ret);
577 
578 	/*
579 	 * Name/value pairs are parsed as two white-space separated strings.
580 	 * Leading and trailing white-space is trimmed from the value, but
581 	 * it may contain embedded white-space.  Note: we use the isspace(3)
582 	 * macro because it's more portable, but that means that you can use
583 	 * characters like form-feed to separate the strings.
584 	 */
585 	name = local_s;
586 	for (tp = name; *tp != '\0' && !isspace(*tp); ++tp)
587 		;
588 	if (*tp == '\0' || tp == name)
589 		goto illegal;
590 	*tp = '\0';
591 	for (++tp; isspace(*tp); ++tp)
592 		;
593 	if (*tp == '\0')
594 		goto illegal;
595 	value = tp;
596 	for (++tp; *tp != '\0'; ++tp)
597 		;
598 	for (--tp; isspace(*tp); --tp)
599 		;
600 	if (tp == value) {
601 illegal:	ret = EINVAL;
602 		__db_err(dbenv, "illegal name-value pair: %s", s);
603 		goto err;
604 	}
605 	*++tp = '\0';
606 
607 #define	DATA_INIT_CNT	20			/* Start with 20 data slots. */
608 	if (!strcmp(name, "DB_DATA_DIR")) {
609 		if (dbenv->db_data_dir == NULL) {
610 			if ((ret = __os_calloc(DATA_INIT_CNT,
611 			    sizeof(char **), &dbenv->db_data_dir)) != 0)
612 				goto err;
613 			dbenv->data_cnt = DATA_INIT_CNT;
614 		} else if (dbenv->data_next == dbenv->data_cnt - 1) {
615 			dbenv->data_cnt *= 2;
616 			if ((ret = __os_realloc(&dbenv->db_data_dir,
617 			    dbenv->data_cnt * sizeof(char **))) != 0)
618 				goto err;
619 		}
620 		p = &dbenv->db_data_dir[dbenv->data_next++];
621 	} else if (!strcmp(name, "DB_LOG_DIR")) {
622 		if (dbenv->db_log_dir != NULL)
623 			__os_freestr(dbenv->db_log_dir);
624 		p = &dbenv->db_log_dir;
625 	} else if (!strcmp(name, "DB_TMP_DIR")) {
626 		if (dbenv->db_tmp_dir != NULL)
627 			__os_freestr(dbenv->db_tmp_dir);
628 		p = &dbenv->db_tmp_dir;
629 	} else
630 		goto err;
631 
632 	ret = __os_strdup(value, p);
633 
634 err:	__os_freestr(local_s);
635 	return (ret);
636 }
637 
638 /*
639  * __db_tmp_open --
640  *	Create a temporary file.
641  */
642 static int
__db_tmp_open(dbenv,flags,path,fdp)643 __db_tmp_open(dbenv, flags, path, fdp)
644 	DB_ENV *dbenv;
645 	u_int32_t flags;
646 	char *path;
647 	int *fdp;
648 {
649 	u_long pid;
650 	int mode, isdir, ret;
651 	const char *p;
652 	char *trv;
653 
654 	/*
655 	 * Check the target directory; if you have six X's and it doesn't
656 	 * exist, this runs for a *very* long time.
657 	 */
658 	if ((ret = __os_exists(path, &isdir)) != 0) {
659 		__db_err(dbenv, "%s: %s", path, strerror(ret));
660 		return (ret);
661 	}
662 	if (!isdir) {
663 		__db_err(dbenv, "%s: %s", path, strerror(EINVAL));
664 		return (EINVAL);
665 	}
666 
667 	/* Build the path. */
668 	for (trv = path; *trv != '\0'; ++trv)
669 		;
670 	*trv = PATH_SEPARATOR[0];
671 	for (p = DB_TRAIL; (*++trv = *p) != '\0'; ++p)
672 		;
673 
674 	/*
675 	 * Replace the X's with the process ID.  Pid should be a pid_t,
676 	 * but we use unsigned long for portability.
677 	 */
678 	for (pid = getpid(); *--trv == 'X'; pid /= 10)
679 		switch (pid % 10) {
680 		case 0: *trv = '0'; break;
681 		case 1: *trv = '1'; break;
682 		case 2: *trv = '2'; break;
683 		case 3: *trv = '3'; break;
684 		case 4: *trv = '4'; break;
685 		case 5: *trv = '5'; break;
686 		case 6: *trv = '6'; break;
687 		case 7: *trv = '7'; break;
688 		case 8: *trv = '8'; break;
689 		case 9: *trv = '9'; break;
690 		}
691 	++trv;
692 
693 	/* Set up open flags and mode. */
694 	LF_SET(DB_CREATE | DB_EXCL);
695 	mode = __db_omode("rw----");
696 
697 	/* Loop, trying to open a file. */
698 	for (;;) {
699 		if ((ret = __db_open(path, flags, flags, mode, fdp)) == 0)
700 			return (0);
701 
702 		/*
703 		 * XXX:
704 		 * If we don't get an EEXIST error, then there's something
705 		 * seriously wrong.  Unfortunately, if the implementation
706 		 * doesn't return EEXIST for O_CREAT and O_EXCL regardless
707 		 * of other possible errors, we've lost.
708 		 */
709 		if (ret != EEXIST) {
710 			__db_err(dbenv,
711 			    "tmp_open: %s: %s", path, strerror(ret));
712 			return (ret);
713 		}
714 
715 		/*
716 		 * Tricky little algorithm for backward compatibility.
717 		 * Assumes the ASCII ordering of lower-case characters.
718 		 */
719 		for (;;) {
720 			if (*trv == '\0')
721 				return (EINVAL);
722 			if (*trv == 'z')
723 				*trv++ = 'a';
724 			else {
725 				if (isdigit(*trv))
726 					*trv = 'a';
727 				else
728 					++*trv;
729 				break;
730 			}
731 		}
732 	}
733 	/* NOTREACHED */
734 }
735