view mercurial/osutil.c @ 27895:2d6a89e79b48

scmutil: support background file closing Closing files that have been appended to is relatively slow on Windows/NTFS. This makes several Mercurial operations slower on Windows. The workaround to this issue is conceptually simple: use multiple threads for I/O. Unfortunately, Python doesn't scale well to multiple threads because of the GIL. And, refactoring our code to use threads everywhere would be a huge undertaking. So, we decide to tackle this problem by starting small: establishing a thread pool for closing files. This patch establishes a mechanism for closing file handles on separate threads. The coordinator object is basically a queue of file handles to operate on and a thread pool consuming from the queue. When files are opened through the VFS layer, the caller can specify that delay closing is allowed. A proxy class for file handles has been added. We must use a proxy because it isn't possible to modify __class__ on built-in types. This adds some overhead. But as future patches will show, this overhead is cancelled out by the benefit of closing file handles on background threads.
author Gregory Szorc <gregory.szorc@gmail.com>
date Thu, 14 Jan 2016 13:34:59 -0800
parents f6d1e92fdf8c
children 3d23700cf4a9
line wrap: on
line source

/*
 osutil.c - native operating system services

 Copyright 2007 Matt Mackall and others

 This software may be used and distributed according to the terms of
 the GNU General Public License, incorporated herein by reference.
*/

#define _ATFILE_SOURCE
#include <Python.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#ifdef _WIN32
#include <windows.h>
#include <io.h>
#else
#include <dirent.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif

#ifdef __APPLE__
#include <sys/attr.h>
#include <sys/vnode.h>
#endif

#include "util.h"

/* some platforms lack the PATH_MAX definition (eg. GNU/Hurd) */
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

#ifdef _WIN32
/*
stat struct compatible with hg expectations
Mercurial only uses st_mode, st_size and st_mtime
the rest is kept to minimize changes between implementations
*/
struct hg_stat {
	int st_dev;
	int st_mode;
	int st_nlink;
	__int64 st_size;
	int st_mtime;
	int st_ctime;
};
struct listdir_stat {
	PyObject_HEAD
	struct hg_stat st;
};
#else
struct listdir_stat {
	PyObject_HEAD
	struct stat st;
};
#endif

#define listdir_slot(name) \
	static PyObject *listdir_stat_##name(PyObject *self, void *x) \
	{ \
		return PyInt_FromLong(((struct listdir_stat *)self)->st.name); \
	}

listdir_slot(st_dev)
listdir_slot(st_mode)
listdir_slot(st_nlink)
#ifdef _WIN32
static PyObject *listdir_stat_st_size(PyObject *self, void *x)
{
	return PyLong_FromLongLong(
		(PY_LONG_LONG)((struct listdir_stat *)self)->st.st_size);
}
#else
listdir_slot(st_size)
#endif
listdir_slot(st_mtime)
listdir_slot(st_ctime)

static struct PyGetSetDef listdir_stat_getsets[] = {
	{"st_dev", listdir_stat_st_dev, 0, 0, 0},
	{"st_mode", listdir_stat_st_mode, 0, 0, 0},
	{"st_nlink", listdir_stat_st_nlink, 0, 0, 0},
	{"st_size", listdir_stat_st_size, 0, 0, 0},
	{"st_mtime", listdir_stat_st_mtime, 0, 0, 0},
	{"st_ctime", listdir_stat_st_ctime, 0, 0, 0},
	{0, 0, 0, 0, 0}
};

static PyObject *listdir_stat_new(PyTypeObject *t, PyObject *a, PyObject *k)
{
	return t->tp_alloc(t, 0);
}

static void listdir_stat_dealloc(PyObject *o)
{
	o->ob_type->tp_free(o);
}

static PyTypeObject listdir_stat_type = {
	PyVarObject_HEAD_INIT(NULL, 0)
	"osutil.stat",             /*tp_name*/
	sizeof(struct listdir_stat), /*tp_basicsize*/
	0,                         /*tp_itemsize*/
	(destructor)listdir_stat_dealloc, /*tp_dealloc*/
	0,                         /*tp_print*/
	0,                         /*tp_getattr*/
	0,                         /*tp_setattr*/
	0,                         /*tp_compare*/
	0,                         /*tp_repr*/
	0,                         /*tp_as_number*/
	0,                         /*tp_as_sequence*/
	0,                         /*tp_as_mapping*/
	0,                         /*tp_hash */
	0,                         /*tp_call*/
	0,                         /*tp_str*/
	0,                         /*tp_getattro*/
	0,                         /*tp_setattro*/
	0,                         /*tp_as_buffer*/
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
	"stat objects",            /* tp_doc */
	0,                         /* tp_traverse */
	0,                         /* tp_clear */
	0,                         /* tp_richcompare */
	0,                         /* tp_weaklistoffset */
	0,                         /* tp_iter */
	0,                         /* tp_iternext */
	0,                         /* tp_methods */
	0,                         /* tp_members */
	listdir_stat_getsets,      /* tp_getset */
	0,                         /* tp_base */
	0,                         /* tp_dict */
	0,                         /* tp_descr_get */
	0,                         /* tp_descr_set */
	0,                         /* tp_dictoffset */
	0,                         /* tp_init */
	0,                         /* tp_alloc */
	listdir_stat_new,          /* tp_new */
};

#ifdef _WIN32

static int to_python_time(const FILETIME *tm)
{
	/* number of seconds between epoch and January 1 1601 */
	const __int64 a0 = (__int64)134774L * (__int64)24L * (__int64)3600L;
	/* conversion factor from 100ns to 1s */
	const __int64 a1 = 10000000;
	/* explicit (int) cast to suspend compiler warnings */
	return (int)((((__int64)tm->dwHighDateTime << 32)
			+ tm->dwLowDateTime) / a1 - a0);
}

static PyObject *make_item(const WIN32_FIND_DATAA *fd, int wantstat)
{
	PyObject *py_st;
	struct hg_stat *stp;

	int kind = (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		? _S_IFDIR : _S_IFREG;

	if (!wantstat)
		return Py_BuildValue("si", fd->cFileName, kind);

	py_st = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
	if (!py_st)
		return NULL;

	stp = &((struct listdir_stat *)py_st)->st;
	/*
	use kind as st_mode
	rwx bits on Win32 are meaningless
	and Hg does not use them anyway
	*/
	stp->st_mode  = kind;
	stp->st_mtime = to_python_time(&fd->ftLastWriteTime);
	stp->st_ctime = to_python_time(&fd->ftCreationTime);
	if (kind == _S_IFREG)
		stp->st_size = ((__int64)fd->nFileSizeHigh << 32)
				+ fd->nFileSizeLow;
	return Py_BuildValue("siN", fd->cFileName,
		kind, py_st);
}

static PyObject *_listdir(char *path, int plen, int wantstat, char *skip)
{
	PyObject *rval = NULL; /* initialize - return value */
	PyObject *list;
	HANDLE fh;
	WIN32_FIND_DATAA fd;
	char *pattern;

	/* build the path + \* pattern string */
	pattern = malloc(plen + 3); /* path + \* + \0 */
	if (!pattern) {
		PyErr_NoMemory();
		goto error_nomem;
	}
	strcpy(pattern, path);

	if (plen > 0) {
		char c = path[plen-1];
		if (c != ':' && c != '/' && c != '\\')
			pattern[plen++] = '\\';
	}
	strcpy(pattern + plen, "*");

	fh = FindFirstFileA(pattern, &fd);
	if (fh == INVALID_HANDLE_VALUE) {
		PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
		goto error_file;
	}

	list = PyList_New(0);
	if (!list)
		goto error_list;

	do {
		PyObject *item;

		if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			if (!strcmp(fd.cFileName, ".")
			|| !strcmp(fd.cFileName, ".."))
				continue;

			if (skip && !strcmp(fd.cFileName, skip)) {
				rval = PyList_New(0);
				goto error;
			}
		}

		item = make_item(&fd, wantstat);
		if (!item)
			goto error;

		if (PyList_Append(list, item)) {
			Py_XDECREF(item);
			goto error;
		}

		Py_XDECREF(item);
	} while (FindNextFileA(fh, &fd));

	if (GetLastError() != ERROR_NO_MORE_FILES) {
		PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
		goto error;
	}

	rval = list;
	Py_XINCREF(rval);
error:
	Py_XDECREF(list);
error_list:
	FindClose(fh);
error_file:
	free(pattern);
error_nomem:
	return rval;
}

#else

int entkind(struct dirent *ent)
{
#ifdef DT_REG
	switch (ent->d_type) {
	case DT_REG: return S_IFREG;
	case DT_DIR: return S_IFDIR;
	case DT_LNK: return S_IFLNK;
	case DT_BLK: return S_IFBLK;
	case DT_CHR: return S_IFCHR;
	case DT_FIFO: return S_IFIFO;
	case DT_SOCK: return S_IFSOCK;
	}
#endif
	return -1;
}

static PyObject *makestat(const struct stat *st)
{
	PyObject *stat;

	stat = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
	if (stat)
		memcpy(&((struct listdir_stat *)stat)->st, st, sizeof(*st));
	return stat;
}

static PyObject *_listdir_stat(char *path, int pathlen, int keepstat,
			       char *skip)
{
	PyObject *list, *elem, *stat = NULL, *ret = NULL;
	char fullpath[PATH_MAX + 10];
	int kind, err;
	struct stat st;
	struct dirent *ent;
	DIR *dir;
#ifdef AT_SYMLINK_NOFOLLOW
	int dfd = -1;
#endif

	if (pathlen >= PATH_MAX) {
		errno = ENAMETOOLONG;
		PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
		goto error_value;
	}
	strncpy(fullpath, path, PATH_MAX);
	fullpath[pathlen] = '/';

#ifdef AT_SYMLINK_NOFOLLOW
	dfd = open(path, O_RDONLY);
	if (dfd == -1) {
		PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
		goto error_value;
	}
	dir = fdopendir(dfd);
#else
	dir = opendir(path);
#endif
	if (!dir) {
		PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
		goto error_dir;
	}

	list = PyList_New(0);
	if (!list)
		goto error_list;

	while ((ent = readdir(dir))) {
		if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
			continue;

		kind = entkind(ent);
		if (kind == -1 || keepstat) {
#ifdef AT_SYMLINK_NOFOLLOW
			err = fstatat(dfd, ent->d_name, &st,
				      AT_SYMLINK_NOFOLLOW);
#else
			strncpy(fullpath + pathlen + 1, ent->d_name,
				PATH_MAX - pathlen);
			fullpath[PATH_MAX] = '\0';
			err = lstat(fullpath, &st);
#endif
			if (err == -1) {
				/* race with file deletion? */
				if (errno == ENOENT)
					continue;
				strncpy(fullpath + pathlen + 1, ent->d_name,
					PATH_MAX - pathlen);
				fullpath[PATH_MAX] = 0;
				PyErr_SetFromErrnoWithFilename(PyExc_OSError,
							       fullpath);
				goto error;
			}
			kind = st.st_mode & S_IFMT;
		}

		/* quit early? */
		if (skip && kind == S_IFDIR && !strcmp(ent->d_name, skip)) {
			ret = PyList_New(0);
			goto error;
		}

		if (keepstat) {
			stat = makestat(&st);
			if (!stat)
				goto error;
			elem = Py_BuildValue("siN", ent->d_name, kind, stat);
		} else
			elem = Py_BuildValue("si", ent->d_name, kind);
		if (!elem)
			goto error;
		stat = NULL;

		PyList_Append(list, elem);
		Py_DECREF(elem);
	}

	ret = list;
	Py_INCREF(ret);

error:
	Py_DECREF(list);
	Py_XDECREF(stat);
error_list:
	closedir(dir);
error_dir:
#ifdef AT_SYMLINK_NOFOLLOW
	close(dfd);
#endif
error_value:
	return ret;
}

#ifdef __APPLE__

typedef struct {
	u_int32_t length;
	attrreference_t name;
	fsobj_type_t obj_type;
	struct timespec mtime;
#if __LITTLE_ENDIAN__
	mode_t access_mask;
	uint16_t padding;
#else
	uint16_t padding;
	mode_t access_mask;
#endif
	off_t size;
} __attribute__((packed)) attrbuf_entry;

int attrkind(attrbuf_entry *entry)
{
	switch (entry->obj_type) {
	case VREG: return S_IFREG;
	case VDIR: return S_IFDIR;
	case VLNK: return S_IFLNK;
	case VBLK: return S_IFBLK;
	case VCHR: return S_IFCHR;
	case VFIFO: return S_IFIFO;
	case VSOCK: return S_IFSOCK;
	}
	return -1;
}

/* get these many entries at a time */
#define LISTDIR_BATCH_SIZE 50

static PyObject *_listdir_batch(char *path, int pathlen, int keepstat,
				char *skip, bool *fallback)
{
	PyObject *list, *elem, *stat = NULL, *ret = NULL;
	int kind, err;
	unsigned long index;
	unsigned int count, old_state, new_state;
	bool state_seen = false;
	attrbuf_entry *entry;
	/* from the getattrlist(2) man page: a path can be no longer than
	   (NAME_MAX * 3 + 1) bytes. Also, "The getattrlist() function will
	   silently truncate attribute data if attrBufSize is too small." So
	   pass in a buffer big enough for the worst case. */
	char attrbuf[LISTDIR_BATCH_SIZE * (sizeof(attrbuf_entry) + NAME_MAX * 3 + 1)];
	unsigned int basep_unused;

	struct stat st;
	int dfd = -1;

	/* these must match the attrbuf_entry struct, otherwise you'll end up
	   with garbage */
	struct attrlist requested_attr = {0};
	requested_attr.bitmapcount = ATTR_BIT_MAP_COUNT;
	requested_attr.commonattr = (ATTR_CMN_NAME | ATTR_CMN_OBJTYPE |
				     ATTR_CMN_MODTIME | ATTR_CMN_ACCESSMASK);
	requested_attr.fileattr = ATTR_FILE_DATALENGTH;

	*fallback = false;

	if (pathlen >= PATH_MAX) {
		errno = ENAMETOOLONG;
		PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
		goto error_value;
	}

	dfd = open(path, O_RDONLY);
	if (dfd == -1) {
		PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
		goto error_value;
	}

	list = PyList_New(0);
	if (!list)
		goto error_dir;

	do {
		count = LISTDIR_BATCH_SIZE;
		err = getdirentriesattr(dfd, &requested_attr, &attrbuf,
					sizeof(attrbuf), &count, &basep_unused,
					&new_state, 0);
		if (err < 0) {
			if (errno == ENOTSUP) {
				/* We're on a filesystem that doesn't support
				   getdirentriesattr. Fall back to the
				   stat-based implementation. */
				*fallback = true;
			} else
				PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
			goto error;
		}

		if (!state_seen) {
			old_state = new_state;
			state_seen = true;
		} else if (old_state != new_state) {
			/* There's an edge case with getdirentriesattr. Consider
			   the following initial list of files:

			   a
			   b
			   <--
			   c
			   d

			   If the iteration is paused at the arrow, and b is
			   deleted before it is resumed, getdirentriesattr will
			   not return d at all!  Ordinarily we're expected to
			   restart the iteration from the beginning. To avoid
			   getting stuck in a retry loop here, fall back to
			   stat. */
			*fallback = true;
			goto error;
		}

		entry = (attrbuf_entry *)attrbuf;

		for (index = 0; index < count; index++) {
			char *filename = ((char *)&entry->name) +
				entry->name.attr_dataoffset;

			if (!strcmp(filename, ".") || !strcmp(filename, ".."))
				continue;

			kind = attrkind(entry);
			if (kind == -1) {
				PyErr_Format(PyExc_OSError,
					     "unknown object type %u for file "
					     "%s%s!",
					     entry->obj_type, path, filename);
				goto error;
			}

			/* quit early? */
			if (skip && kind == S_IFDIR && !strcmp(filename, skip)) {
				ret = PyList_New(0);
				goto error;
			}

			if (keepstat) {
				/* from the getattrlist(2) man page: "Only the
				   permission bits ... are valid". */
				st.st_mode = (entry->access_mask & ~S_IFMT) | kind;
				st.st_mtime = entry->mtime.tv_sec;
				st.st_size = entry->size;
				stat = makestat(&st);
				if (!stat)
					goto error;
				elem = Py_BuildValue("siN", filename, kind, stat);
			} else
				elem = Py_BuildValue("si", filename, kind);
			if (!elem)
				goto error;
			stat = NULL;

			PyList_Append(list, elem);
			Py_DECREF(elem);

			entry = (attrbuf_entry *)((char *)entry + entry->length);
		}
	} while (err == 0);

	ret = list;
	Py_INCREF(ret);

error:
	Py_DECREF(list);
	Py_XDECREF(stat);
error_dir:
	close(dfd);
error_value:
	return ret;
}

#endif /* __APPLE__ */

static PyObject *_listdir(char *path, int pathlen, int keepstat, char *skip)
{
#ifdef __APPLE__
	PyObject *ret;
	bool fallback = false;

	ret = _listdir_batch(path, pathlen, keepstat, skip, &fallback);
	if (ret != NULL || !fallback)
		return ret;
#endif
	return _listdir_stat(path, pathlen, keepstat, skip);
}

static PyObject *statfiles(PyObject *self, PyObject *args)
{
	PyObject *names, *stats;
	Py_ssize_t i, count;

	if (!PyArg_ParseTuple(args, "O:statfiles", &names))
		return NULL;

	count = PySequence_Length(names);
	if (count == -1) {
		PyErr_SetString(PyExc_TypeError, "not a sequence");
		return NULL;
	}

	stats = PyList_New(count);
	if (stats == NULL)
		return NULL;

	for (i = 0; i < count; i++) {
		PyObject *stat, *pypath;
		struct stat st;
		int ret, kind;
		char *path;

		/* With a large file count or on a slow filesystem,
		   don't block signals for long (issue4878). */
		if ((i % 1000) == 999 && PyErr_CheckSignals() == -1)
			goto bail;

		pypath = PySequence_GetItem(names, i);
		if (!pypath)
			goto bail;
		path = PyString_AsString(pypath);
		if (path == NULL) {
			Py_DECREF(pypath);
			PyErr_SetString(PyExc_TypeError, "not a string");
			goto bail;
		}
		ret = lstat(path, &st);
		Py_DECREF(pypath);
		kind = st.st_mode & S_IFMT;
		if (ret != -1 && (kind == S_IFREG || kind == S_IFLNK)) {
			stat = makestat(&st);
			if (stat == NULL)
				goto bail;
			PyList_SET_ITEM(stats, i, stat);
		} else {
			Py_INCREF(Py_None);
			PyList_SET_ITEM(stats, i, Py_None);
		}
	}

	return stats;

bail:
	Py_DECREF(stats);
	return NULL;
}

/*
 * recvfds() simply does not release GIL during blocking io operation because
 * command server is known to be single-threaded.
 */

static ssize_t recvfdstobuf(int sockfd, int **rfds, void *cbuf, size_t cbufsize)
{
	char dummy[1];
	struct iovec iov = {dummy, sizeof(dummy)};
	struct msghdr msgh = {0};
	struct cmsghdr *cmsg;

	msgh.msg_iov = &iov;
	msgh.msg_iovlen = 1;
	msgh.msg_control = cbuf;
	msgh.msg_controllen = (socklen_t)cbufsize;
	if (recvmsg(sockfd, &msgh, 0) < 0)
		return -1;

	for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg;
	     cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
		if (cmsg->cmsg_level != SOL_SOCKET ||
		    cmsg->cmsg_type != SCM_RIGHTS)
			continue;
		*rfds = (int *)CMSG_DATA(cmsg);
		return (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
	}

	*rfds = cbuf;
	return 0;
}

static PyObject *recvfds(PyObject *self, PyObject *args)
{
	int sockfd;
	int *rfds = NULL;
	ssize_t rfdscount, i;
	char cbuf[256];
	PyObject *rfdslist = NULL;

	if (!PyArg_ParseTuple(args, "i", &sockfd))
		return NULL;

	rfdscount = recvfdstobuf(sockfd, &rfds, cbuf, sizeof(cbuf));
	if (rfdscount < 0)
		return PyErr_SetFromErrno(PyExc_OSError);

	rfdslist = PyList_New(rfdscount);
	if (!rfdslist)
		goto bail;
	for (i = 0; i < rfdscount; i++) {
		PyObject *obj = PyInt_FromLong(rfds[i]);
		if (!obj)
			goto bail;
		PyList_SET_ITEM(rfdslist, i, obj);
	}
	return rfdslist;

bail:
	Py_XDECREF(rfdslist);
	return NULL;
}

#endif /* ndef _WIN32 */

static PyObject *listdir(PyObject *self, PyObject *args, PyObject *kwargs)
{
	PyObject *statobj = NULL; /* initialize - optional arg */
	PyObject *skipobj = NULL; /* initialize - optional arg */
	char *path, *skip = NULL;
	int wantstat, plen;

	static char *kwlist[] = {"path", "stat", "skip", NULL};

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|OO:listdir",
			kwlist, &path, &plen, &statobj, &skipobj))
		return NULL;

	wantstat = statobj && PyObject_IsTrue(statobj);

	if (skipobj && skipobj != Py_None) {
		skip = PyBytes_AsString(skipobj);
		if (!skip)
			return NULL;
	}

	return _listdir(path, plen, wantstat, skip);
}

#ifdef _WIN32
static PyObject *posixfile(PyObject *self, PyObject *args, PyObject *kwds)
{
	static char *kwlist[] = {"name", "mode", "buffering", NULL};
	PyObject *file_obj = NULL;
	char *name = NULL;
	char *mode = "rb";
	DWORD access = 0;
	DWORD creation;
	HANDLE handle;
	int fd, flags = 0;
	int bufsize = -1;
	char m0, m1, m2;
	char fpmode[4];
	int fppos = 0;
	int plus;
	FILE *fp;

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|si:posixfile", kwlist,
					 Py_FileSystemDefaultEncoding,
					 &name, &mode, &bufsize))
		return NULL;

	m0 = mode[0];
	m1 = m0 ? mode[1] : '\0';
	m2 = m1 ? mode[2] : '\0';
	plus = m1 == '+' || m2 == '+';

	fpmode[fppos++] = m0;
	if (m1 == 'b' || m2 == 'b') {
		flags = _O_BINARY;
		fpmode[fppos++] = 'b';
	}
	else
		flags = _O_TEXT;
	if (m0 == 'r' && !plus) {
		flags |= _O_RDONLY;
		access = GENERIC_READ;
	} else {
		/*
		work around http://support.microsoft.com/kb/899149 and
		set _O_RDWR for 'w' and 'a', even if mode has no '+'
		*/
		flags |= _O_RDWR;
		access = GENERIC_READ | GENERIC_WRITE;
		fpmode[fppos++] = '+';
	}
	fpmode[fppos++] = '\0';

	switch (m0) {
	case 'r':
		creation = OPEN_EXISTING;
		break;
	case 'w':
		creation = CREATE_ALWAYS;
		break;
	case 'a':
		creation = OPEN_ALWAYS;
		flags |= _O_APPEND;
		break;
	default:
		PyErr_Format(PyExc_ValueError,
			     "mode string must begin with one of 'r', 'w', "
			     "or 'a', not '%c'", m0);
		goto bail;
	}

	handle = CreateFile(name, access,
			    FILE_SHARE_READ | FILE_SHARE_WRITE |
			    FILE_SHARE_DELETE,
			    NULL,
			    creation,
			    FILE_ATTRIBUTE_NORMAL,
			    0);

	if (handle == INVALID_HANDLE_VALUE) {
		PyErr_SetFromWindowsErrWithFilename(GetLastError(), name);
		goto bail;
	}

	fd = _open_osfhandle((intptr_t)handle, flags);

	if (fd == -1) {
		CloseHandle(handle);
		PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
		goto bail;
	}
#ifndef IS_PY3K
	fp = _fdopen(fd, fpmode);
	if (fp == NULL) {
		_close(fd);
		PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
		goto bail;
	}

	file_obj = PyFile_FromFile(fp, name, mode, fclose);
	if (file_obj == NULL) {
		fclose(fp);
		goto bail;
	}

	PyFile_SetBufSize(file_obj, bufsize);
#else
	file_obj = PyFile_FromFd(fd, name, mode, bufsize, NULL, NULL, NULL, 1);
	if (file_obj == NULL)
		goto bail;
#endif
bail:
	PyMem_Free(name);
	return file_obj;
}
#endif

#ifdef __APPLE__
#include <ApplicationServices/ApplicationServices.h>

static PyObject *isgui(PyObject *self)
{
	CFDictionaryRef dict = CGSessionCopyCurrentDictionary();

	if (dict != NULL) {
		CFRelease(dict);
		Py_RETURN_TRUE;
	} else {
		Py_RETURN_FALSE;
	}
}
#endif

static char osutil_doc[] = "Native operating system services.";

static PyMethodDef methods[] = {
	{"listdir", (PyCFunction)listdir, METH_VARARGS | METH_KEYWORDS,
	 "list a directory\n"},
#ifdef _WIN32
	{"posixfile", (PyCFunction)posixfile, METH_VARARGS | METH_KEYWORDS,
	 "Open a file with POSIX-like semantics.\n"
"On error, this function may raise either a WindowsError or an IOError."},
#else
	{"statfiles", (PyCFunction)statfiles, METH_VARARGS | METH_KEYWORDS,
	 "stat a series of files or symlinks\n"
"Returns None for non-existent entries and entries of other types.\n"},
	{"recvfds", (PyCFunction)recvfds, METH_VARARGS,
	 "receive list of file descriptors via socket\n"},
#endif
#ifdef __APPLE__
	{
		"isgui", (PyCFunction)isgui, METH_NOARGS,
		"Is a CoreGraphics session available?"
	},
#endif
	{NULL, NULL}
};

#ifdef IS_PY3K
static struct PyModuleDef osutil_module = {
	PyModuleDef_HEAD_INIT,
	"osutil",
	osutil_doc,
	-1,
	methods
};

PyMODINIT_FUNC PyInit_osutil(void)
{
	if (PyType_Ready(&listdir_stat_type) < 0)
		return NULL;

	return PyModule_Create(&osutil_module);
}
#else
PyMODINIT_FUNC initosutil(void)
{
	if (PyType_Ready(&listdir_stat_type) == -1)
		return;

	Py_InitModule3("osutil", methods, osutil_doc);
}
#endif