/*- * See the file LICENSE for redistribution information. * * Copyright (c) 1996-2009 Oracle. All rights reserved. * * $Id$ */ #include "db_config.h" #include "db_int.h" static int __db_fullpath __P((ENV *, const char *, const char *, int, int, char **)); #define DB_ADDSTR(add) { \ /* \ * The string might be NULL or zero-length, and the p[-1] \ * might indirect to before the beginning of our buffer. \ */ \ if ((add) != NULL && (add)[0] != '\0') { \ /* If leading slash, start over. */ \ if (__os_abspath(add)) { \ p = str; \ slash = 0; \ } \ /* Append to the current string. */ \ len = strlen(add); \ if (slash) \ *p++ = PATH_SEPARATOR[0]; \ memcpy(p, add, len); \ p += len; \ slash = strchr(PATH_SEPARATOR, p[-1]) == NULL; \ } \ } /* * __db_fullpath -- * Constructs a path name relative to the environment home, and optionally * checks whether the file or directory exist. */ static int __db_fullpath(env, dir, file, check_file, check_dir, namep) ENV *env; const char *dir; const char *file; int check_file; int check_dir; char **namep; { size_t len; const char *home; char *p, *str; int isdir, ret, slash; /* All paths are relative to the environment home. */ home = (env == NULL) ? NULL : env->db_home; len = (home == NULL ? 0 : strlen(home) + 1) + (dir == NULL ? 0 : strlen(dir) + 1) + (file == NULL ? 0 : strlen(file) + 1); if ((ret = __os_malloc(env, len, &str)) != 0) return (ret); slash = 0; p = str; DB_ADDSTR(home); DB_ADDSTR(dir); *p = '\0'; if (check_dir && (__os_exists(env, str, &isdir) != 0 || !isdir)) { __os_free(env, str); return (ENOENT); } DB_ADDSTR(file); *p = '\0'; /* * If we're opening a data file, see if it exists. If not, keep * trying. */ if (check_file && __os_exists(env, str, NULL) != 0) { __os_free(env, str); return (ENOENT); } if (namep == NULL) __os_free(env, str); else *namep = str; return (0); } #define DB_CHECKFILE(file, dir, check_file, check_dir, namep, ret_dir) do { \ ret = __db_fullpath(env, dir, file, \ check_file, check_dir, namep); \ if (ret == 0 && (ret_dir) != NULL) \ *(ret_dir) = (dir); \ if (ret != ENOENT) \ return (ret); \ } while (0) /* * __db_appname -- * Given an optional DB environment, directory and file name and type * of call, build a path based on the ENV->open rules, and return * it in allocated space. Dirp can be used to specify a data directory * to use. If not and one is used then drip will contain a pointer * to the directory name. * * PUBLIC: int __db_appname __P((ENV *, APPNAME, * PUBLIC: const char *, const char **, char **)); */ int __db_appname(env, appname, file, dirp, namep) ENV *env; APPNAME appname; const char *file; const char **dirp; char **namep; { DB_ENV *dbenv; char **ddp; const char *dir; int ret; dbenv = env->dbenv; dir = NULL; if (namep != NULL) *namep = NULL; /* * Absolute path names are never modified. If the file is an absolute * path, we're done. */ if (file != NULL && __os_abspath(file)) return (__os_strdup(env, file, namep)); /* * DB_APP_NONE: * DB_HOME/file * DB_APP_DATA: * DB_HOME/DB_DATA_DIR/file * DB_APP_LOG: * DB_HOME/DB_LOG_DIR/file * DB_APP_TMP: * DB_HOME/DB_TMP_DIR/ */ switch (appname) { case DB_APP_NONE: break; case DB_APP_RECOVER: case DB_APP_DATA: /* * First, step through the data_dir entries, if any, looking * for the file. */ if (dbenv != NULL && dbenv->db_data_dir != NULL) for (ddp = dbenv->db_data_dir; *ddp != NULL; ddp++) DB_CHECKFILE(file, *ddp, 1, 0, namep, dirp); /* Second, look in the environment home directory. */ DB_CHECKFILE(file, NULL, 1, 0, namep, dirp); /* * Otherwise, we're going to create. Use the specified * directory unless we're in recovery and it doesn't exist. */ if (dirp != NULL && *dirp != NULL) DB_CHECKFILE(file, *dirp, 0, appname == DB_APP_RECOVER, namep, dirp); /* Finally, use the create directory, if set. */ if (dbenv != NULL && dbenv->db_create_dir != NULL) dir = dbenv->db_create_dir; break; case DB_APP_LOG: if (dbenv != NULL) dir = dbenv->db_log_dir; break; case DB_APP_TMP: if (dbenv != NULL) dir = dbenv->db_tmp_dir; break; } /* * Construct the full path. For temporary files, it is an error if the * directory does not exist: if it doesn't, checking whether millions * of temporary files exist inside it takes a *very* long time. */ DB_CHECKFILE(file, dir, 0, appname == DB_APP_TMP, namep, dirp); return (ret); } /* * __db_tmp_open -- * Create a temporary file. * * PUBLIC: int __db_tmp_open __P((ENV *, u_int32_t, DB_FH **)); */ int __db_tmp_open(env, oflags, fhpp) ENV *env; u_int32_t oflags; DB_FH **fhpp; { pid_t pid; int filenum, i, ret; char *path; char *firstx, *trv; DB_ASSERT(env, fhpp != NULL); *fhpp = NULL; #define DB_TRAIL "BDBXXXXX" if ((ret = __db_appname(env, DB_APP_TMP, DB_TRAIL, NULL, &path)) != 0) goto done; /* Replace the X's with the process ID (in decimal). */ __os_id(env->dbenv, &pid, NULL); for (trv = path + strlen(path); *--trv == 'X'; pid /= 10) *trv = '0' + (u_char)(pid % 10); firstx = trv + 1; /* Loop, trying to open a file. */ for (filenum = 1;; filenum++) { if ((ret = __os_open(env, path, 0, oflags | DB_OSO_CREATE | DB_OSO_EXCL | DB_OSO_TEMP, DB_MODE_600, fhpp)) == 0) { ret = 0; goto done; } /* * !!!: * If we don't get an EEXIST error, then there's something * seriously wrong. Unfortunately, if the implementation * doesn't return EEXIST for O_CREAT and O_EXCL regardless * of other possible errors, we've lost. */ if (ret != EEXIST) { __db_err(env, ret, "temporary open: %s", path); goto done; } /* * Generate temporary file names in a backwards-compatible way. * If pid == 12345, the result is: * /DB12345 (tried above, the first time through). * /DBa2345 ... /DBz2345 * /DBaa345 ... /DBaz345 * /DBba345, and so on. * * XXX * This algorithm is O(n**2) -- that is, creating 100 temporary * files requires 5,000 opens, creating 1000 files requires * 500,000. If applications open a lot of temporary files, we * could improve performance by switching to timestamp-based * file names. */ for (i = filenum, trv = firstx; i > 0; i = (i - 1) / 26) if (*trv++ == '\0') { ret = EINVAL; goto done; } for (i = filenum; i > 0; i = (i - 1) / 26) *--trv = 'a' + ((i - 1) % 26); } done: __os_free(env, path); return (ret); }