1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1996,2008 Oracle.  All rights reserved.
5 *
6 * $Id: env_name.c,v 12.90 2008/01/11 20:49:59 bostic Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12
13static int __db_tmp_open __P((ENV *, u_int32_t, char *, DB_FH **));
14
15#define	DB_ADDSTR(add) {						\
16	/*								\
17	 * The string might be NULL or zero-length, and the p[-1]	\
18	 * might indirect to before the beginning of our buffer.	\
19	 */								\
20	if ((add) != NULL && (add)[0] != '\0') {			\
21		/* If leading slash, start over. */			\
22		if (__os_abspath(add)) {				\
23			p = str;					\
24			slash = 0;					\
25		}							\
26		/* Append to the current string. */			\
27		len = strlen(add);					\
28		if (slash)						\
29			*p++ = PATH_SEPARATOR[0];			\
30		memcpy(p, add, len);					\
31		p += len;						\
32		slash = strchr(PATH_SEPARATOR, p[-1]) == NULL;		\
33	}								\
34}
35
36/*
37 * __db_appname --
38 *	Given an optional DB environment, directory and file name and type
39 *	of call, build a path based on the ENV->open rules, and return
40 *	it in allocated space.
41 *
42 * PUBLIC: int __db_appname __P((ENV *, APPNAME,
43 * PUBLIC:    const char *, u_int32_t, DB_FH **, char **));
44 */
45int
46__db_appname(env, appname, file, tmp_oflags, fhpp, namep)
47	ENV *env;
48	APPNAME appname;
49	const char *file;
50	u_int32_t tmp_oflags;
51	DB_FH **fhpp;
52	char **namep;
53{
54	DB_ENV *dbenv;
55	enum { TRY_NOTSET, TRY_DATA_DIR, TRY_ENV_HOME, TRY_CREATE } try_state;
56	size_t len, str_len;
57	int data_entry, ret, slash, tmp_create;
58	const char *a, *b;
59	char *p, *str;
60
61	dbenv = env->dbenv;
62	try_state = TRY_NOTSET;
63	a = b = NULL;
64	data_entry = 0;
65	tmp_create = 0;
66
67	/*
68	 * We don't return a name when creating temporary files, just a file
69	 * handle.  Default to an error now.
70	 */
71	if (fhpp != NULL)
72		*fhpp = NULL;
73	if (namep != NULL)
74		*namep = NULL;
75
76	/*
77	 * Absolute path names are never modified.  If the file is an absolute
78	 * path, we're done.
79	 */
80	if (file != NULL && __os_abspath(file))
81		return (__os_strdup(env, file, namep));
82
83	/* Everything else is relative to the environment home. */
84	if (env != NULL)
85		a = env->db_home;
86
87retry:	/*
88	 * DB_APP_NONE:
89	 *      DB_HOME/file
90	 * DB_APP_DATA:
91	 *      DB_HOME/DB_DATA_DIR/file
92	 * DB_APP_LOG:
93	 *      DB_HOME/DB_LOG_DIR/file
94	 * DB_APP_TMP:
95	 *      DB_HOME/DB_TMP_DIR/<create>
96	 */
97	switch (appname) {
98	case DB_APP_NONE:
99		break;
100	case DB_APP_DATA:
101		if (env == NULL || dbenv->db_data_dir == NULL) {
102			try_state = TRY_CREATE;
103			break;
104		}
105
106		/*
107		 * First, step through the data_dir entries, if any, looking
108		 * for the file.
109		 */
110		if ((b = dbenv->db_data_dir[data_entry]) != NULL) {
111			++data_entry;
112			try_state = TRY_DATA_DIR;
113			break;
114		}
115
116		/* Second, look in the environment home directory. */
117		if (try_state != TRY_ENV_HOME) {
118			try_state = TRY_ENV_HOME;
119			break;
120		}
121
122		/* Third, try creation in the first data_dir entry. */
123		try_state = TRY_CREATE;
124		b = dbenv->db_data_dir[0];
125		break;
126	case DB_APP_LOG:
127		if (env != NULL)
128			b = dbenv->db_log_dir;
129		break;
130	case DB_APP_TMP:
131		if (env != NULL)
132			b = dbenv->db_tmp_dir;
133		tmp_create = 1;
134		break;
135	}
136
137	len =
138	    (a == NULL ? 0 : strlen(a) + 1) +
139	    (b == NULL ? 0 : strlen(b) + 1) +
140	    (file == NULL ? 0 : strlen(file) + 1);
141
142	/*
143	 * Allocate space to hold the current path information, as well as any
144	 * temporary space that we're going to need to create a temporary file
145	 * name.
146	 */
147#define	DB_TRAIL	"BDBXXXXX"
148	str_len = len + sizeof(DB_TRAIL) + 10;
149	if ((ret = __os_malloc(env, str_len, &str)) != 0)
150		return (ret);
151
152	slash = 0;
153	p = str;
154	DB_ADDSTR(a);
155	DB_ADDSTR(b);
156	DB_ADDSTR(file);
157	*p = '\0';
158
159	/*
160	 * If we're opening a data file, see if it exists.  If it does,
161	 * return it, otherwise, try and find another one to open.
162	 */
163	if (appname == DB_APP_DATA &&
164	    __os_exists(env, str, NULL) != 0 && try_state != TRY_CREATE) {
165		__os_free(env, str);
166		b = NULL;
167		goto retry;
168	}
169
170	/* Create the file if so requested. */
171	if (tmp_create &&
172	    (ret = __db_tmp_open(env, tmp_oflags, str, fhpp)) != 0) {
173		__os_free(env, str);
174		return (ret);
175	}
176
177	if (namep == NULL)
178		__os_free(env, str);
179	else
180		*namep = str;
181	return (0);
182}
183
184/*
185 * __db_tmp_open --
186 *	Create a temporary file.
187 */
188static int
189__db_tmp_open(env, tmp_oflags, path, fhpp)
190	ENV *env;
191	u_int32_t tmp_oflags;
192	char *path;
193	DB_FH **fhpp;
194{
195	pid_t pid;
196	int filenum, i, isdir, ret;
197	char *firstx, *trv;
198
199	/*
200	 * Check the target directory; if you have six X's and it doesn't
201	 * exist, this runs for a *very* long time.
202	 */
203	if ((ret = __os_exists(env, path, &isdir)) != 0) {
204		__db_err(env, ret, "%s", path);
205		return (ret);
206	}
207	if (!isdir) {
208		__db_err(env, EINVAL, "%s", path);
209		return (EINVAL);
210	}
211
212	/* Build the path. */
213	(void)strncat(path, PATH_SEPARATOR, 1);
214	(void)strcat(path, DB_TRAIL);
215
216	/* Replace the X's with the process ID (in decimal). */
217	__os_id(env->dbenv, &pid, NULL);
218	for (trv = path + strlen(path); *--trv == 'X'; pid /= 10)
219		*trv = '0' + (u_char)(pid % 10);
220	firstx = trv + 1;
221
222	/* Loop, trying to open a file. */
223	for (filenum = 1;; filenum++) {
224		if ((ret = __os_open(env, path, 0,
225		    tmp_oflags | DB_OSO_CREATE | DB_OSO_EXCL | DB_OSO_TEMP,
226		    DB_MODE_600, fhpp)) == 0)
227			return (0);
228
229		/*
230		 * !!!:
231		 * If we don't get an EEXIST error, then there's something
232		 * seriously wrong.  Unfortunately, if the implementation
233		 * doesn't return EEXIST for O_CREAT and O_EXCL regardless
234		 * of other possible errors, we've lost.
235		 */
236		if (ret != EEXIST) {
237			__db_err(env, ret, "temporary open: %s", path);
238			return (ret);
239		}
240
241		/*
242		 * Generate temporary file names in a backwards-compatible way.
243		 * If pid == 12345, the result is:
244		 *   <path>/DB12345 (tried above, the first time through).
245		 *   <path>/DBa2345 ...  <path>/DBz2345
246		 *   <path>/DBaa345 ...  <path>/DBaz345
247		 *   <path>/DBba345, and so on.
248		 *
249		 * XXX
250		 * This algorithm is O(n**2) -- that is, creating 100 temporary
251		 * files requires 5,000 opens, creating 1000 files requires
252		 * 500,000.  If applications open a lot of temporary files, we
253		 * could improve performance by switching to timestamp-based
254		 * file names.
255		 */
256		for (i = filenum, trv = firstx; i > 0; i = (i - 1) / 26)
257			if (*trv++ == '\0')
258				return (EINVAL);
259
260		for (i = filenum; i > 0; i = (i - 1) / 26)
261			*--trv = 'a' + ((i - 1) % 26);
262	}
263	/* NOTREACHED */
264}
265