/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1997,2008 Oracle.  All rights reserved.
 *
 * $Id: os_open.c,v 12.29 2008/03/26 04:11:35 david Exp $
 */

#include "db_config.h"

#include "db_int.h"

/*
 * __os_open --
 *	Open a file descriptor (including page size and log size information).
 *
 * PUBLIC: int __os_open __P((ENV *,
 * PUBLIC:     const char *, u_int32_t, u_int32_t, int, DB_FH **));
 */
int
__os_open(env, name, page_size, flags, mode, fhpp)
	ENV *env;
	const char *name;
	u_int32_t page_size, flags;
	int mode;
	DB_FH **fhpp;
{
	DB_ENV *dbenv;
	DB_FH *fhp;
	int oflags, ret;

	COMPQUIET(page_size, 0);

	dbenv = env == NULL ? NULL : env->dbenv;
	*fhpp = NULL;
	oflags = 0;

	if (dbenv != NULL &&
	    FLD_ISSET(dbenv->verbose, DB_VERB_FILEOPS | DB_VERB_FILEOPS_ALL))
		__db_msg(env, "fileops: open %s", name);

#define	OKFLAGS								\
	(DB_OSO_ABSMODE | DB_OSO_CREATE | DB_OSO_DIRECT | DB_OSO_DSYNC |\
	DB_OSO_EXCL | DB_OSO_RDONLY | DB_OSO_REGION | DB_OSO_SEQ |	\
	DB_OSO_TEMP | DB_OSO_TRUNC)
	if ((ret = __db_fchk(env, "__os_open", flags, OKFLAGS)) != 0)
		return (ret);

#if defined(O_BINARY)
	/*
	 * If there's a binary-mode open flag, set it, we never want any
	 * kind of translation.  Some systems do translations by default,
	 * e.g., with Cygwin, the default mode for an open() is set by the
	 * mode of the mount that underlies the file.
	 */
	oflags |= O_BINARY;
#endif

	/*
	 * DB requires the POSIX 1003.1 semantic that two files opened at the
	 * same time with DB_OSO_CREATE/O_CREAT and DB_OSO_EXCL/O_EXCL flags
	 * set return an EEXIST failure in at least one.
	 */
	if (LF_ISSET(DB_OSO_CREATE))
		oflags |= O_CREAT;

	if (LF_ISSET(DB_OSO_EXCL))
		oflags |= O_EXCL;

#if defined(RPM_VENDOR_OPENPKG) /* bdb-use-odirect-on-freebsd-only */
	/* The O_DIRECT feature is fully broken under at least Linux 2.2 and
	   2.4. It is sometimes accepted by open(2) without error and later
	   causes a write(2) to fail with EINVAL -- all depending on the
	   underlying filesystem (usually works on tmpfs and fails on ext3).
	   Sometimes it also causes open(2) to fail with EINVAL. In general,
	   it does especially no "graceful degradation" and so is useless for
	   use on mostly all platforms except for those where we know it works
	   (currently FreeBSD only). Interestingly, RPM works on RedHat, just
	   because RedHat's "kernel" package contains a patch which masks out
	   O_DIRECT usage... ;-) */
#if defined(HAVE_O_DIRECT) && defined(__FreeBSD__)
	if (LF_ISSET(DB_OSO_DIRECT))
		oflags |= O_DIRECT;
#endif
#else
#ifdef HAVE_O_DIRECT
	if (LF_ISSET(DB_OSO_DIRECT))
		oflags |= O_DIRECT;
#endif
#endif
#ifdef O_DSYNC
	if (LF_ISSET(DB_OSO_DSYNC))
		oflags |= O_DSYNC;
#endif

	if (LF_ISSET(DB_OSO_RDONLY))
		oflags |= O_RDONLY;
	else
		oflags |= O_RDWR;

	if (LF_ISSET(DB_OSO_TRUNC))
		oflags |= O_TRUNC;

	/*
	 * Undocumented feature: allow applications to create intermediate
	 * directories whenever a file is opened.
	 */
	if (dbenv != NULL &&
	    env->dir_mode != 0 && LF_ISSET(DB_OSO_CREATE) &&
	    (ret = __db_mkpath(env, name)) != 0)
		return (ret);

	/* Open the file. */
#ifdef HAVE_QNX
	if (LF_ISSET(DB_OSO_REGION))
		ret = __os_qnx_region_open(env, name, oflags, mode, &fhp);
	else
#endif
	ret = __os_openhandle(env, name, oflags, mode, &fhp);
	if (ret != 0)
		return (ret);

	if (LF_ISSET(DB_OSO_REGION))
		F_SET(fhp, DB_FH_REGION);
#ifdef HAVE_FCHMOD
	/*
	 * If the code using Berkeley DB is a library, that code may not be able
	 * to control the application's umask value.  Allow applications to set
	 * absolute file modes.  We can't fix the race between file creation and
	 * the fchmod call -- we can't modify the process' umask here since the
	 * process may be multi-threaded and the umask value is per-process, not
	 * per-thread.
	 */
	if (LF_ISSET(DB_OSO_CREATE) && LF_ISSET(DB_OSO_ABSMODE))
		(void)fchmod(fhp->fd, mode);
#endif

#ifdef O_DSYNC
	/*
	 * If we can configure the file descriptor to flush on write, the
	 * file descriptor does not need to be explicitly sync'd.
	 */
	if (LF_ISSET(DB_OSO_DSYNC))
		F_SET(fhp, DB_FH_NOSYNC);
#endif

#if defined(HAVE_DIRECTIO) && defined(DIRECTIO_ON)
	/*
	 * The Solaris C library includes directio, but you have to set special
	 * compile flags to #define DIRECTIO_ON.  Require both in order to call
	 * directio.
	 */
	if (LF_ISSET(DB_OSO_DIRECT))
		(void)directio(fhp->fd, DIRECTIO_ON);
#endif

	/*
	 * Delete any temporary file.
	 *
	 * !!!
	 * There's a race here, where we've created a file and we crash before
	 * we can unlink it.  Temporary files aren't common in DB, regardless,
	 * it's not a security problem because the file is empty.  There's no
	 * reasonable way to avoid the race (playing signal games isn't worth
	 * the portability nightmare), so we just live with it.
	 */
	if (LF_ISSET(DB_OSO_TEMP)) {
#if defined(HAVE_UNLINK_WITH_OPEN_FAILURE) || defined(CONFIG_TEST)
		F_SET(fhp, DB_FH_UNLINK);
#else
		(void)__os_unlink(env, name, 0);
#endif
	}

	*fhpp = fhp;
	return (0);
}
