1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1998,2008 Oracle.  All rights reserved.
5 *
6 * $Id: os_handle.c,v 12.23 2008/01/31 18:40:46 bostic Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12
13/*
14 * __os_openhandle --
15 *	Open a file, using POSIX 1003.1 open flags.
16 *
17 * PUBLIC: int __os_openhandle
18 * PUBLIC:     __P((ENV *, const char *, int, int, DB_FH **));
19 */
20int
21__os_openhandle(env, name, flags, mode, fhpp)
22	ENV *env;
23	const char *name;
24	int flags, mode;
25	DB_FH **fhpp;
26{
27	DB_FH *fhp;
28	u_int nrepeat, retries;
29	int fcntl_flags, ret;
30#ifdef HAVE_VXWORKS
31	int newflags;
32#endif
33	/*
34	 * Allocate the file handle and copy the file name.  We generally only
35	 * use the name for verbose or error messages, but on systems where we
36	 * can't unlink temporary files immediately, we use the name to unlink
37	 * the temporary file when the file handle is closed.
38	 *
39	 * Lock the ENV handle and insert the new file handle on the list.
40	 */
41	if ((ret = __os_calloc(env, 1, sizeof(DB_FH), &fhp)) != 0)
42		return (ret);
43	if ((ret = __os_strdup(env, name, &fhp->name)) != 0)
44		goto err;
45	if (env != NULL) {
46		MUTEX_LOCK(env, env->mtx_env);
47		TAILQ_INSERT_TAIL(&env->fdlist, fhp, q);
48		MUTEX_UNLOCK(env, env->mtx_env);
49		F_SET(fhp, DB_FH_ENVLINK);
50	}
51
52	/* If the application specified an interface, use it. */
53	if (DB_GLOBAL(j_open) != NULL) {
54		if ((fhp->fd = DB_GLOBAL(j_open)(name, flags, mode)) == -1) {
55			ret = __os_posix_err(__os_get_syserr());
56			goto err;
57		}
58		goto done;
59	}
60
61	retries = 0;
62	for (nrepeat = 1; nrepeat < 4; ++nrepeat) {
63		ret = 0;
64#ifdef	HAVE_VXWORKS
65		/*
66		 * VxWorks does not support O_CREAT on open, you have to use
67		 * creat() instead.  (It does not support O_EXCL or O_TRUNC
68		 * either, even though they are defined "for future support".)
69		 * We really want the POSIX behavior that if O_CREAT is set,
70		 * we open if it exists, or create it if it doesn't exist.
71		 * If O_CREAT is specified, single thread and try to open the
72		 * file.  If successful, and O_EXCL return EEXIST.  If
73		 * unsuccessful call creat and then end single threading.
74		 */
75		if (LF_ISSET(O_CREAT)) {
76			DB_BEGIN_SINGLE_THREAD;
77			newflags = flags & ~(O_CREAT | O_EXCL);
78			if ((fhp->fd = open(name, newflags, mode)) != -1) {
79				/*
80				 * We need to mark the file opened at this
81				 * point so that if we get any error below
82				 * we will properly close the fd we just
83				 * opened on the error path.
84				 */
85				F_SET(fhp, DB_FH_OPENED);
86				if (LF_ISSET(O_EXCL)) {
87					/*
88					 * If we get here, want O_EXCL create,
89					 * and the file exists.  Close and
90					 * return EEXISTS.
91					 */
92					DB_END_SINGLE_THREAD;
93					ret = EEXIST;
94					goto err;
95				}
96				/*
97				 * XXX
98				 * Assume any error means non-existence.
99				 * Unfortunately return values (even for
100				 * non-existence) are driver specific so
101				 * there is no single error we can use to
102				 * verify we truly got the equivalent of
103				 * ENOENT.
104				 */
105			} else
106				fhp->fd = creat(name, newflags);
107			DB_END_SINGLE_THREAD;
108		} else
109		/* FALLTHROUGH */
110#endif
111#ifdef __VMS
112		/*
113		 * !!!
114		 * Open with full sharing on VMS.
115		 *
116		 * We use these flags because they are the ones set by the VMS
117		 * CRTL mmap() call when it opens a file, and we have to be
118		 * able to open files that mmap() has previously opened, e.g.,
119		 * when we're joining already existing DB regions.
120		 */
121		fhp->fd = open(name, flags, mode, "shr=get,put,upd,del,upi");
122#else
123		fhp->fd = open(name, flags, mode);
124#endif
125		if (fhp->fd != -1) {
126			ret = 0;
127			break;
128		}
129
130		switch (ret = __os_posix_err(__os_get_syserr())) {
131		case EMFILE:
132		case ENFILE:
133		case ENOSPC:
134			/*
135			 * If it's a "temporary" error, we retry up to 3 times,
136			 * waiting up to 12 seconds.  While it's not a problem
137			 * if we can't open a database, an inability to open a
138			 * log file is cause for serious dismay.
139			 */
140			__os_yield(env, nrepeat * 2, 0);
141			break;
142		case EAGAIN:
143		case EBUSY:
144		case EINTR:
145			/*
146			 * If an EAGAIN, EBUSY or EINTR, retry immediately for
147			 * DB_RETRY times.
148			 */
149			if (++retries < DB_RETRY)
150				--nrepeat;
151			break;
152		default:
153			/* Open is silent on error. */
154			goto err;
155		}
156	}
157
158	if (ret == 0) {
159#if defined(HAVE_FCNTL_F_SETFD)
160		/* Deny file descriptor access to any child process. */
161		if ((fcntl_flags = fcntl(fhp->fd, F_GETFD)) == -1 ||
162		    fcntl(fhp->fd, F_SETFD, fcntl_flags | FD_CLOEXEC) == -1) {
163			ret = __os_get_syserr();
164			__db_syserr(env, ret, "fcntl(F_SETFD)");
165			ret = __os_posix_err(ret);
166			goto err;
167		}
168#else
169		COMPQUIET(fcntl_flags, 0);
170#endif
171
172done:		F_SET(fhp, DB_FH_OPENED);
173		*fhpp = fhp;
174		return (0);
175	}
176
177err:	(void)__os_closehandle(env, fhp);
178	return (ret);
179}
180
181/*
182 * __os_closehandle --
183 *	Close a file.
184 *
185 * PUBLIC: int __os_closehandle __P((ENV *, DB_FH *));
186 */
187int
188__os_closehandle(env, fhp)
189	ENV *env;
190	DB_FH *fhp;
191{
192	DB_ENV *dbenv;
193	int ret;
194
195	ret = 0;
196
197	/*
198	 * If we linked the DB_FH handle into the ENV, it needs to be
199	 * unlinked.
200	 */
201	DB_ASSERT(env, env != NULL || !F_ISSET(fhp, DB_FH_ENVLINK));
202
203	if (env != NULL) {
204		dbenv = env->dbenv;
205		if (fhp->name != NULL && FLD_ISSET(
206		    dbenv->verbose, DB_VERB_FILEOPS | DB_VERB_FILEOPS_ALL))
207			__db_msg(env, "fileops: close %s", fhp->name);
208
209		if (F_ISSET(fhp, DB_FH_ENVLINK)) {
210			/*
211			 * Lock the ENV handle and remove this file
212			 * handle from the list.
213			 */
214			MUTEX_LOCK(env, env->mtx_env);
215			TAILQ_REMOVE(&env->fdlist, fhp, q);
216			MUTEX_UNLOCK(env, env->mtx_env);
217		}
218	}
219
220	/* Discard any underlying system file reference. */
221	if (F_ISSET(fhp, DB_FH_OPENED)) {
222		if (DB_GLOBAL(j_close) != NULL)
223			ret = DB_GLOBAL(j_close)(fhp->fd);
224		else
225			RETRY_CHK((close(fhp->fd)), ret);
226		if (ret != 0) {
227			__db_syserr(env, ret, "close");
228			ret = __os_posix_err(ret);
229		}
230	}
231
232	/* Unlink the file if we haven't already done so. */
233	if (F_ISSET(fhp, DB_FH_UNLINK))
234		(void)__os_unlink(env, fhp->name, 0);
235
236	if (fhp->name != NULL)
237		__os_free(env, fhp->name);
238	__os_free(env, fhp);
239
240	return (ret);
241}
242