Commits

John Mulligan committed 0727d07

public history

  • Participants

Comments (0)

Files changed (17)

+^dist/
+^build/
+\.swp$
+\.py[co]$
+\.o$
+\.so$
+doc/build/
+41b02ad950878b67388b786e4e788e1b789a1b91 rpmbuild-0.1-0.1
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+include fsnix/*.py
+include fsnix/*.c
+include tests/*.py
+include *.txt
+include test/*.py
+include doc/*
+include doc/source/*
+
+build:
+	python setup.py build
+
+
+dist: clean
+	$(MAKE) sdist
+
+
+sdist:
+	python setup.py sdist
+
+
+local:
+	python setup.py build_py -d . -c
+	python setup.py build_ext -i
+
+
+test: local
+	PYTHONPATH=$$PYTHONPATH:. python tests/test_fslib.py
+	PYTHONPATH=$$PYTHONPATH:. python tests/test_fswlib.py
+	PYTHONPATH=$$PYTHONPATH:. python tests/test_util.py
+
+
+doc: local
+	sphinx-build doc/source  build/doc
+
+
+docserve: doc
+	cd build/doc && python -m SimpleHTTPServer 8888
+
+
+clean:
+	python setup.py clean -a
+	$(RM) -r dist build
+	$(RM) MANIFEST
+	$(RM) fsnix/*.so  fsnix/*.pyc
+
+=======================================
+fsnix - Modern File System APIs & Such
+=======================================
+
+This library is designed to expose various file and file system related
+APIs for modern POSIX compatible systems to Python. This includes
+functions that wrap openat and other *at type calls (from POSIX.1-2008)
+that are not available for Python 2.x. In addition it provides a stdlib
+compatible listdir call which can also be used to return d_type values
+from a directory entry on file systems that support that feature.
+
+This package provides 'fsnix.fslib' which is the compiled module that
+provides the "full" low-level api. The module 'fsnix.fswlib' is a
+ctypes wrapper that tracks fslib where possible and can be used where
+you can not use a C compiler. Finally 'fslib.util' provides some
+convenient, higher-level, more pythonic interfaces that use the other
+modules.
+
+
+
+* Current Version: 0.1
+* License: MIT License
+
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/fsnix.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/fsnix.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/fsnix"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/fsnix"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	make -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."

doc/source/conf.py

+# -*- coding: utf-8 -*-
+#
+# fsnix documentation build configuration file, created by
+# sphinx-quickstart on Fri Oct  7 13:03:20 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.doctest']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'fsnix'
+copyright = u'2012, Nasuni Corporation'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'fsnixdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'fsnix.tex', u'fsnix Documentation',
+   u'John Mulligan', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'fsnix', u'fsnix Documentation',
+     [u'John Mulligan'], 1)
+]

doc/source/index.rst

+.. fsnix documentation master file, created by
+   sphinx-quickstart on Fri Oct  7 13:03:20 2011.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+=====================================
+fsnix: file system API for Python 2.x
+=====================================
+
+Introduction
+-------------
+
+This library provides modules for Python 2.x that expose some of the
+more recent File System related API calls in POSIX.1-2008. This includes
+the functions openat, mkdirat, unlinkat, and other similar calls suffixed
+with "at". These calls exist to help defend against race conditions and
+support thread-level working directories. Typically these functions take
+one or more directory file descriptor and a relative path as arguments.
+
+Additionally, this library provides a util module that has a higher level
+interface for working with open directories and other functions that
+use the lower level library calls.
+
+The api for these functions should be compatible with the functions
+that will be available in Python 3.3 and above, if said function is
+present in the posix module.
+
+
+Examples
+-----------
+
+Open a directory, and create a file in that directory, then remove it.
+
+>>> import tempfile, os
+>>> from fsnix import fs
+>>> root = tempfile.mkdtemp()
+>>> dirfd = os.open(root, os.O_RDONLY)
+>>> fd = fs.openat(dirfd, "foobar.txt", os.O_CREAT | os.O_RDWR)
+>>> os.write(fd, "Hello World\n")
+12
+>>> os.close(fd)
+>>> os.listdir(root)
+['foobar.txt']
+>>> fs.unlinkat(dirfd, 'foobar.txt')
+>>> os.close(dirfd)
+>>> os.listdir(root)
+[]
+
+
+This example shows how to create a subdir, write a file into that and then
+rename the directory, all using the low-level apis.
+
+>>> import tempfile, os
+>>> from fsnix import fs
+>>> root = tempfile.mkdtemp()
+>>> dirfd = os.open(root, os.O_RDONLY)
+>>> fs.mkdirat(dirfd, 'ham')
+>>> dirfd2 = fs.openat(dirfd, 'ham', os.O_RDONLY)
+>>> os.close(fs.openat(dirfd2, "foobar.txt", os.O_CREAT | os.O_RDWR))
+>>> os.close(dirfd2)
+>>> fs.renameat(dirfd, 'ham', dirfd, 'bacon')
+>>> os.close(dirfd)
+>>> os.listdir(os.path.join(root, 'bacon'))
+['foobar.txt']
+
+The util module aims at providing a higher level interface to make some
+common actions simpler. This example tries to wipe out any empty dirs
+in the example directory.
+
+>>> import tempfile, os, errno
+>>> from fsnix import fs
+>>> from fsnix import util
+>>> root = tempfile.mkdtemp()
+>>> for i in range(0, 19):
+...     if i & 1:
+...         open(os.path.join(root, str(i)), 'w').close()
+...     else:
+...         os.mkdir(os.path.join(root, str(i)))
+>>> open(os.path.join(root, '0', 'foo'), 'w').close()
+>>> with util.opendir(root) as dh:
+...     for name in dh.listdir():
+...         try:
+...             fs.unlinkat(dh.fileno(), name, fs.AT_REMOVEDIR)
+...         except OSError, e:
+...             if e.errno != errno.ENOTDIR and e.errno != errno.ENOTEMPTY:
+...                 raise
+>>> sorted(os.listdir(root), key=int)
+['0', '1', '3', '5', '7', '9', '11', '13', '15', '17']
+
+
+
+
+
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+

fsnix/__init__.py

+#!/usr/bin/env python
+# 
+# Copyright (c) 2011, 2012 Nasuni Corporation  http://www.nasuni.com/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+#
+"""fsnix - utility library for file system APIs
+
+There are two modules within this library called fslib and fswlib,
+the former uses the Python C-API and the latter uses ctypes. The
+first one available will be aliased in under the name 'fs'.
+
+fslib is more complete but requires compilation. fswlib is less complete
+(see documenation for details) but works w/o a compiler available.
+"""
+
+
+USING_LIB = None
+try:
+    from fsnix import fslib as fs
+    USING_LIB = 'fslib'
+except ImportError:
+    pass
+
+if not USING_LIB:
+    try:
+        from fsnix import fswlib as fs
+        USING_LIB = 'fswlib'
+    except ImportError:
+        pass
+/*************************************************************************
+ * fsnxi/fslib.c : posix *at and other file system functions
+ *
+ * Exposes posix functions like openat, unlinkat and such to
+ * python. These newer posix functions are not exposed by
+ * the python standard library in python 2.x.
+ *
+ * 
+ * Copyright (c) 2011, 2012 Nasuni Corporation  http://www.nasuni.com/
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+
+#include <Python.h>
+#include <structseq.h>
+
+/* hack to compile both on linux and freebsd;
+   on freebsd python's header is restricting me 
+   to 200112L, but I need 2008 for O_CLOEXEC. Also
+   the python devs yell at anyone who wants to customize
+   this by setting something before Python.h is included
+*/
+#if _POSIX_C_SOURCE < 200809
+#undef __POSIX_VISIBLE
+#define __POSIX_VISIBLE 200809
+#endif
+
+#include <fcntl.h>
+#include <dirent.h>
+
+#define MODULE_NAME "fslib"
+
+/* define MAXPATHLEN like python's posix module */
+#ifndef MAXPATHLEN
+#if defined(PATH_MAX) && PATH_MAX > 1024
+#define MAXPATHLEN PATH_MAX
+#else
+#define MAXPATHLEN 1024
+#endif
+#endif
+
+/* internal flags */
+#define INCL_DTYPE 1
+
+/* force values to HAVE_UTIMENSAT to 0/1 */
+#ifdef HAVE_UTIMENSAT
+#define HAVE_UTIMENSAT 1
+#else
+#define HAVE_UTIMENSAT 0
+#endif
+
+
+static PyObject * bail_like_posix(char * name)
+{
+    PyObject *rc = PyErr_SetFromErrnoWithFilename(PyExc_OSError, name);
+    PyMem_Free(name);
+    return rc;
+}
+
+PyDoc_STRVAR(fslib_openat__doc__,
+"openat(dirfd, pathname [, flags] [, mode=0600]) -> int\n\n"
+"Opens a file descriptor in the same manner as os.open, but\n"
+"takes an initial file descriptor argument. If pathname is\n"
+"relative then the path will be relative to the directory\n"
+"referred to by the dirfd."
+);
+
+static PyObject * fslib_openat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    int fd;
+    char * path = NULL;
+    int flags = 0;
+    mode_t mode = 0600;
+
+    if (!PyArg_ParseTuple(args, "iet|ii:openat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &flags, &mode)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    fd = openat(dirfd, path, flags, mode);
+    Py_END_ALLOW_THREADS
+
+    if (fd < 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    return PyInt_FromLong((long)fd);
+}
+
+
+PyDoc_STRVAR(fslib_unlinkat__doc__,
+"unlinkat(dirfd, pathname [, flags]) -> None\n\n"
+"Unlinks a file in a manner similar to os.unlink, but\n"
+"takes an initial file descriptor argument. If pathname is\n"
+"relative then the path will be relative to the directory\n"
+"referred to by the dirfd.\n"
+"If the flags argument contains the AT_REMOVEDIR flag, then\n"
+"unlink at removes directories instead of files."
+);
+
+static PyObject * fslib_unlinkat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    int flags = 0;
+    int sts = 0;
+
+    if (!PyArg_ParseTuple(args, "iet|i:unlinkat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &flags)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = unlinkat(dirfd, path, flags);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_mkfifoat__doc__,
+"mkfifoat(dfd, pathname [, mode=0666])\n\n"
+"Creates a FIFO (named pipe) like os.mkfifo().\n"
+"If pathname is relative then the FIFO is created relative to\n"
+"the directory referred to by the directory file descriptor dfd."
+);
+
+static PyObject * fslib_mkfifoat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    mode_t mode = 0666;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "iet|i:mkfifoat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &mode)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = mkfifoat(dirfd, path, mode);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_mkdirat__doc__,
+"mkdirat(dirfd, pathname [, mode=0777])\n\n"
+"Create a directory, like os.mkdir.\n"
+"If pathname is relative then the file is created relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+);
+
+static PyObject * fslib_mkdirat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    mode_t mode = 0777;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "iet|i:mkdirat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &mode)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = mkdirat(dirfd, path, mode);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_mknodat__doc__,
+"mknodat(dirfd, pathname [, mode=0600, device])\n\n"
+"Create a filesystem node, like os.mknod.\n"
+"If pathname is relative then the file is created relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+"See the docs for os.mknod for details about the mode and device args.\n"
+);
+
+static PyObject * fslib_mknodat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    mode_t mode = 0600;
+    dev_t device = 0;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "iet|ii:mknodat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &mode, &device)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = mknodat(dirfd, path, mode, device);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_fchownat__doc__,
+"fchownat(dirfd, pathname, uid, gid, [, flags])\n\n"
+"Change the user and group ownership of a file, like os.chown.\n"
+"The uid and gid parameters must be integers.\n"
+"If pathname is relative then the file is modified relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+"Specifying the AT_SYMLINK_NOFOLLOW flag will cause the function\n"
+"to operate on symlinks rather than the file it points to.\n"
+);
+
+static PyObject * fslib_fchownat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    int flags = 0;
+    long uid = -1;
+    long gid = -1;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "ietll|i:fchownat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &uid, &gid, &flags)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = fchownat(dirfd, path, (uid_t)uid, (gid_t)gid, flags);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_fchmodat__doc__,
+"fchmodat(dirfd, pathname, mode, [, flags])\n\n"
+"Change the access permissions of a file, like os.chmod.\n"
+"If pathname is relative then the file is modified relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+"Specifying the AT_SYMLINK_NOFOLLOW flag will cause the function\n"
+"to operate on symlinks rather than the file it points to.\n"
+);
+
+static PyObject * fslib_fchmodat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    mode_t mode = 0;
+    int flags = 0;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "ieti|i:fchmodat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &mode, &flags)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = fchmodat(dirfd, path, mode, flags);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_faccessat__doc__,
+"faccessat(dirfd, pathname, mode) -> bool\n\n"
+"Test for access permissions in the same manner as os.access.\n"
+"If pathname is relative then the file is created relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+"Currently, there is no mechanism to get the errno.\n"
+);
+
+static PyObject * fslib_faccessat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    mode_t mode = 0;
+    int flags = 0;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "ieti|i:faccessat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &mode, &flags)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = faccessat(dirfd, path, mode, flags);
+    Py_END_ALLOW_THREADS
+
+    PyMem_Free(path);
+    return PyBool_FromLong(sts == 0);
+}
+
+
+PyDoc_STRVAR(fslib_readlinkat__doc__,
+"readlinkat(dirfd, pathname) -> path\n\n"
+"Read the path a symlink points to, like os.readlink.\n"
+"If pathname is relative then the symlink is accessed relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+);
+
+static PyObject * fslib_readlinkat(PyObject *self, PyObject *args)
+{
+    PyObject *linkpath;
+    char buf[MAXPATHLEN];
+    int dirfd;
+    char * path = NULL;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "iet:readlinkat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path)) {
+        return NULL;
+    }
+
+    /* Note: Not implementing Unicode output for now, for
+     * simplicity. In the future we can copy what os.readlink does.
+     */
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = readlinkat(dirfd, path, buf, sizeof(buf));
+    Py_END_ALLOW_THREADS
+
+    if (sts < 0) {
+        return bail_like_posix(path);
+    }
+    linkpath = PyString_FromStringAndSize(buf, sts);
+    PyMem_Free(path);
+    return linkpath;
+}
+
+
+PyDoc_STRVAR(fslib_symlinkat__doc__,
+"symlinkat(src, dirfd, pathname)\n\n"
+"Create a symlink pointing to src, like os.symlink.\n"
+"If pathname is relative then the symlink is created relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+);
+
+static PyObject * fslib_symlinkat(PyObject *self, PyObject *args)
+{
+    char * srcpath = NULL;
+    int dirfd;
+    char * sinkpath = NULL;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "etiet:symlinkat",
+                          Py_FileSystemDefaultEncoding, &srcpath,
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &sinkpath)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = symlinkat(srcpath, dirfd, sinkpath);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, sinkpath);
+        PyMem_Free(srcpath);
+        PyMem_Free(sinkpath);
+        return NULL;
+    }
+    PyMem_Free(srcpath);
+    PyMem_Free(sinkpath);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_renameat__doc__,
+"renameat(olddfd, oldpath, newfd, newpath)\n\n"
+"Rename a file in a manner similar to os.rename\n"
+"If either oldpath or newpath is relative then the file being renamed\n"
+"is relative the directory referred to by the the directory file\n"
+"descriptor preceding it.\n"
+);
+
+static PyObject * fslib_renameat(PyObject *self, PyObject *args)
+{
+    int olddirfd;
+    char * oldpath = NULL;
+    int newdirfd;
+    char * newpath = NULL;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "ietiet:renameat",
+                          &olddirfd,
+                          Py_FileSystemDefaultEncoding, &oldpath,
+                          &newdirfd,
+                          Py_FileSystemDefaultEncoding, &newpath)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = renameat(olddirfd, oldpath, newdirfd, newpath);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, newpath);
+        PyMem_Free(oldpath);
+        PyMem_Free(newpath);
+        return NULL;
+    }
+    PyMem_Free(oldpath);
+    PyMem_Free(newpath);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_linkat__doc__,
+"renameat(olddfd, oldpath, newfd, newpath)\n\n"
+"Create a file link (aka hardlink), like os.link\n"
+"If either oldpath or newpath is relative then the file being renamed\n"
+"is relative the directory referred to by the the directory file\n"
+"descriptor preceding it.\n"
+);
+
+static PyObject * fslib_linkat(PyObject *self, PyObject *args)
+{
+    int olddirfd;
+    char * oldpath = NULL;
+    int newdirfd;
+    char * newpath = NULL;
+    int flags = 0;
+    int sts;
+
+    if (!PyArg_ParseTuple(args, "ietiet:linkat",
+                          &olddirfd,
+                          Py_FileSystemDefaultEncoding, &oldpath,
+                          &newdirfd,
+                          Py_FileSystemDefaultEncoding, &newpath,
+                          &flags)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = linkat(olddirfd, oldpath, newdirfd, newpath, flags);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError, newpath);
+        PyMem_Free(oldpath);
+        PyMem_Free(newpath);
+        return NULL;
+    }
+    PyMem_Free(oldpath);
+    PyMem_Free(newpath);
+    Py_RETURN_NONE;
+}
+
+
+PyDoc_STRVAR(fslib_stat_obj__doc__,
+MODULE_NAME ".stat_obj: wrapper for stat struct"
+);
+
+
+static PyStructSequence_Field fslib_stat_obj_fields[] = {
+    {"st_mode", "protection bits"},
+    {"st_ino", "inode"},
+    {"st_dev", "device"},
+    {"st_nlink", "number of hard links"},
+    {"st_uid", "user ID of owner"},
+    {"st_gid", "group ID of owner"},
+    {"st_size", "total size, in bytes"},
+    {"st_atime", "time of last access"},
+    {"st_mtime", "time of last modification"},
+    {"st_ctime", "time of last change"},
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+    {"st_blksize", "block size"},
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+    {"st_blocks", "number of blocks"},
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+    {"st_rdev", "device type"},
+#endif
+    {0}
+};
+
+
+static PyStructSequence_Desc fslib_stat_obj_desc = {
+    "fslib_stat_obj",
+    fslib_stat_obj__doc__,
+    fslib_stat_obj_fields,
+    10
+};
+
+
+static PyTypeObject StatObjType;
+
+
+#ifdef HAVE_LONG_LONG
+#define Py_ST_DEV PyLong_FromLongLong
+#define C_ST_DEV PY_LONG_LONG
+#else
+#define Py_ST_DEV PyInt_FromLong
+#define C_ST_DEV long
+#endif
+
+#ifdef HAVE_LARGEFILE_SUPPORT
+#define Py_ST_INT PyLong_FromLongLong
+#define C_ST_INT PY_LONG_LONG
+#else
+#define Py_ST_INT PyInt_FromLong
+#define C_ST_INT long
+#endif
+
+
+static PyObject *convert_stat_obj(struct stat *stp)
+{
+    int pos = 0;
+    PyObject *res = PyStructSequence_New(&StatObjType);
+    if (!res) return NULL;
+
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyInt_FromLong((long)stp->st_mode));
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              Py_ST_INT((C_ST_INT)stp->st_ino));
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              Py_ST_DEV((C_ST_DEV)stp->st_dev));
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyInt_FromLong((long)stp->st_nlink));
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyInt_FromLong((long)stp->st_uid));
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyInt_FromLong((long)stp->st_gid));
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              Py_ST_INT((C_ST_INT)stp->st_size));
+
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyFloat_FromDouble((double)stp->st_atime));
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyFloat_FromDouble((double)stp->st_mtime));
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyFloat_FromDouble((double)stp->st_ctime));
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyInt_FromLong((long)stp->st_blksize));
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyInt_FromLong((long)stp->st_blocks));
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+    PyStructSequence_SET_ITEM(res, (pos++),
+                              PyInt_FromLong((long)stp->st_rdev));
+#endif
+    return res;
+}
+
+
+PyDoc_STRVAR(fslib_fstatat__doc__,
+"fstatat(dirfd, pathname [, flags]) -> stat data\n\n"
+"Retrieve the stat data for a file, like os.stat.\n"
+"If pathname is relative then the file is accessed relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+"Returns a dict containing the stat fields for this platform.\n"
+);
+
+static PyObject * fslib_fstatat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    int flags = 0;
+    int sts;
+    struct stat statbuf;
+
+    if (!PyArg_ParseTuple(args, "iet|i:fstatat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &flags)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    sts = fstatat(dirfd, path, &statbuf, flags);
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    return convert_stat_obj(&statbuf);
+}
+
+/* define my own time pair type
+ * this makes it easier to convert to other time structs
+ */
+struct timepair {
+    time_t seconds;
+    long fraction;
+    double scale;
+};
+
+
+static int store_time_arg(struct timepair times[], int pos,
+                          PyObject *ptime)
+{
+    long wholesec;
+    double fracsec;
+    if (PyFloat_Check(ptime)) {
+        wholesec = PyInt_AsLong(ptime);
+        fracsec = PyFloat_AsDouble(ptime);
+        times[pos].seconds = wholesec;
+        times[pos].fraction = (long)((fracsec - wholesec) * times[0].scale);
+        if (times[pos].fraction < 0) {
+            /* not allowed to be negative */
+            times[pos].fraction = 0;
+        }
+        return 0;
+    }
+    if (PyInt_Check(ptime)) {
+        times[pos].seconds = PyInt_AsLong(ptime);
+        times[pos].fraction = 0;
+        return 0;
+    }
+    if (PyTuple_Check(ptime) && PyTuple_Size(ptime) == 2
+        && PyInt_Check(PyTuple_GET_ITEM(ptime, 0))
+        && PyInt_Check(PyTuple_GET_ITEM(ptime, 1))) {
+        times[pos].seconds = (time_t)PyInt_AsLong(PyTuple_GET_ITEM(ptime, 0));
+        times[pos].fraction = PyInt_AsLong(PyTuple_GET_ITEM(ptime, 1));
+        return 0;
+    }
+    PyErr_SetString(PyExc_TypeError,
+                    (pos==0)?
+                        ("atime must be a tuple (sec, nsec) or number"):
+                        ("mtime must be a tuple (sec, nsec) or number"));
+    return -1;
+}
+
+static int pyarg_to_timespec(struct timepair times[], PyObject *timearg)
+{
+    PyObject * atime;
+    PyObject * mtime;
+
+    if (timearg == Py_None) {
+        return 0;
+    }
+    if (PyTuple_Check(timearg) && PyTuple_Size(timearg) == 2) {
+        atime = PyTuple_GET_ITEM(timearg, 0);
+        mtime = PyTuple_GET_ITEM(timearg, 1);
+        if (store_time_arg(times, 0, atime) == -1) {
+            return -1;
+        }
+        if (store_time_arg(times, 1, mtime) == -1) {
+            return -1;
+        }
+        return 1;
+    }
+    PyErr_SetString(PyExc_TypeError, "arg 2 must be a tuple (atime, mtime)");
+    return -1;
+}
+
+#if HAVE_UTIMENSAT
+
+PyDoc_STRVAR(fslib_utimensat__doc__,
+"utimensat(dirfd, pathname, (atime, mtime))\n\n"
+"Set the access and modified times on a file, like os.utime.\n"
+"The time tuple may be given as a single None value to set both\n"
+"atime and mtime to the current time. Otherwise, atime and mtime\n"
+"must both be either an integer a float or a two-tuple containing\n"
+"the time value in seconds followed by a value in nanoseconds.\n"
+"If pathname is relative then the file is accessed relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+);
+
+static PyObject * fslib_utimensat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    PyObject *timearg;
+    int flags = 0;
+    int sts;
+    struct timepair timetemp[2];
+    struct timespec times[2];
+
+
+    if (!PyArg_ParseTuple(args, "ietO|i:utimensat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &timearg,
+                          &flags)) {
+        return NULL;
+    }
+
+    timetemp[0].scale = 1e9;
+    timetemp[1].scale = 1e9;
+    sts = pyarg_to_timespec(timetemp, timearg);
+    if (sts == -1) {
+        PyMem_Free(path);
+        return NULL;
+    }
+    times[0].tv_sec = timetemp[0].seconds;
+    times[0].tv_nsec = timetemp[0].fraction;
+    times[1].tv_sec = timetemp[1].seconds;
+    times[1].tv_nsec = timetemp[1].fraction;
+
+    Py_BEGIN_ALLOW_THREADS
+    if (sts == 0) {
+        sts = utimensat(dirfd, path, NULL, flags);
+    } else {
+        sts = utimensat(dirfd, path, times, flags);
+    }
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    Py_RETURN_NONE;
+}
+
+#endif /* HAVE_UTIMENSAT */
+
+
+#if defined(HAVE_FUTIMESAT)
+
+PyDoc_STRVAR(fslib_futimesat__doc__,
+"futimesat(dirfd, pathname, (atime, mtime))\n\n"
+"Set the access and modified times on a file, like os.utime.\n"
+"The time tuple may be given as a single None value to set both\n"
+"atime and mtime to the current time. Otherwise, atime and mtime\n"
+"must both be either an integer a float or a two-tuple containing\n"
+"the time value in seconds followed by a value in nanoseconds.\n"
+"If pathname is relative then the file is accessed relative to\n"
+"the directory referred to by the directory file descriptor dfd.\n"
+);
+
+static PyObject * fslib_futimesat(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    char * path = NULL;
+    PyObject *timearg;
+    int sts;
+    struct timepair timetemp[2];
+    struct timeval times[2];
+
+
+    if (!PyArg_ParseTuple(args, "ietO|i:futimesat",
+                          &dirfd,
+                          Py_FileSystemDefaultEncoding, &path,
+                          &timearg)) {
+        return NULL;
+    }
+
+    timetemp[0].scale = 1e6;
+    timetemp[1].scale = 1e6;
+    sts = pyarg_to_timespec(timetemp, timearg);
+    if (sts == -1) {
+        PyMem_Free(path);
+        return NULL;
+    }
+    times[0].tv_sec = timetemp[0].seconds;
+    times[0].tv_usec = timetemp[0].fraction;
+    times[1].tv_sec = timetemp[1].seconds;
+    times[1].tv_usec = timetemp[1].fraction;
+
+    Py_BEGIN_ALLOW_THREADS
+    if (sts == 0) {
+        sts = futimesat(dirfd, path, NULL);
+    } else {
+        sts = futimesat(dirfd, path, times);
+    }
+    Py_END_ALLOW_THREADS
+
+    if (sts != 0) {
+        return bail_like_posix(path);
+    }
+    PyMem_Free(path);
+    Py_RETURN_NONE;
+}
+
+#endif /* defined(HAVE_FUTIMESAT) */
+
+/*
+ * _append_dir_entries is used to append ``max`` directory entries to
+ * the list ``pylist`` by reading them out of ``dirh``. The number
+ * of items read will be placed into ``count``. ``flags`` are used to
+ * control the behavior of the listing (currently only including DTYPE).
+ *
+ * The behavior of this function should be pretty similar to the behavior
+ * of posixmodule.c in python stdlib. The main differences are the adding
+ * to an existing list, and the control flags.
+ */
+int _append_dir_entries(DIR *dirh, PyObject *pylist, unsigned int max,
+                        unsigned int flags, unsigned int *count)
+{
+    int failure = 0;
+    unsigned int curr = 0;
+    struct dirent *dent;
+    size_t slen;
+    PyObject *fn;
+
+    for (;;) {
+        if (max && curr>=max) {
+            break; /* over max */
+        }
+        errno = 0;
+        Py_BEGIN_ALLOW_THREADS
+        dent = readdir(dirh);
+        Py_END_ALLOW_THREADS
+
+        if (dent == NULL && errno == 0) {
+            break; /* end of the dir stream */
+        }
+        if (dent == NULL) {
+            failure = errno;
+            break; /* error condition */
+        }
+
+        slen = strlen(dent->d_name);
+        if ((slen == 1 && dent->d_name[0] == '.') ||
+            (slen == 2 && dent->d_name[0] == '.' && dent->d_name[1] == '.')) {
+            continue;
+        }
+        fn = PyString_FromStringAndSize(dent->d_name, slen);
+        if (fn == NULL ) {
+            failure = -1;
+            break; /* error condition */
+        }
+        if (flags & INCL_DTYPE) {
+            fn = Py_BuildValue("NB", fn, dent->d_type);
+            if (fn == NULL ) {
+                failure = -1;
+                break; /* error condition */
+            }
+        }
+        if (PyList_Append(pylist, fn) != 0) {
+            Py_XDECREF(fn);
+            failure = -1;
+            break; /* error condition */
+        } else {
+            Py_XDECREF(fn);
+        }
+    }
+
+    (*count) = curr;
+    return failure;
+}
+
+
+PyDoc_STRVAR(fslib_fdlistdir__doc__,
+"fdlistdir(fd, flags=0) -> list of strings\n\n"
+"Return a list of the names of the entries in the directory.");
+
+static PyObject * fslib_fdlistdir(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    DIR *dirh;
+    PyObject *reslist;
+    int res;
+    unsigned int count;
+    unsigned int flags = 0;
+
+    if (!PyArg_ParseTuple(args, "i|i:fdlistdir", &dirfd, &flags)) {
+        return NULL;
+    }
+
+    reslist = PyList_New(0);
+    if (reslist == NULL) return NULL;
+
+    dirh = fdopendir(dirfd);
+    if (dirh == NULL) {
+        Py_XDECREF(reslist);
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+    rewinddir(dirh);
+
+    res = _append_dir_entries(dirh, reslist, 0, flags, &count);
+    if (res > 0) {
+        Py_XDECREF(reslist);
+        closedir(dirh);
+        errno = res;
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+    if (res != 0) {
+        Py_XDECREF(reslist);
+        closedir(dirh);
+        PyErr_SetString(PyExc_MemoryError, "could not update dir list");
+        return NULL;
+    }
+
+    closedir(dirh);
+    return reslist;
+}
+
+
+PyDoc_STRVAR(fslib_listdir__doc__,
+"listdir(path, flags=0) -> list of strings\n\n"
+"Return a list of the names of the entries in the directory.");
+
+static PyObject * fslib_listdir(PyObject *self, PyObject *args)
+{
+    char *path = NULL;
+    DIR *dirh;
+    PyObject *reslist = NULL;
+    int res;
+    unsigned int count;
+    unsigned int flags = 0;
+
+    if (!PyArg_ParseTuple(args, "et|i:fdlistdir",
+                          Py_FileSystemDefaultEncoding, &path,
+                          &flags)) {
+        return NULL;
+    }
+
+    reslist = PyList_New(0);
+    if (reslist == NULL)
+      goto fail;
+
+    dirh = opendir(path);
+    if (dirh == NULL) {
+        Py_XDECREF(reslist);
+        bail_like_posix(path);
+        return NULL;
+    }
+    rewinddir(dirh);
+
+    res = _append_dir_entries(dirh, reslist, 0, flags, &count);
+    if (res > 0) {
+        Py_XDECREF(reslist);
+        closedir(dirh);
+        errno = res;
+        bail_like_posix(path);
+        return NULL;
+    }
+    if (res != 0) {
+        Py_XDECREF(reslist);
+        closedir(dirh);
+        PyErr_SetString(PyExc_MemoryError, "could not update dir list");
+        reslist = NULL;
+        goto fail;
+    }
+
+    closedir(dirh);
+
+fail:
+    PyMem_Free(path);
+    return reslist;
+}
+
+/*
+ * Implement the iterator object returned by fditerdir
+ */
+
+typedef struct {
+    PyObject_HEAD
+    /* object specific fields */
+    DIR *dirh;
+    PyObject *entries;
+    unsigned int groupsize;
+    unsigned int flags;
+} fslib_diriter;
+
+
+static void fslib_diriter_dealloc(fslib_diriter *self)
+{
+    Py_XDECREF(self->entries);
+    if (self->dirh != NULL) {
+        closedir(self->dirh);
+        self->dirh = NULL;
+    }
+    self->ob_type->tp_free((PyObject*)self);
+}
+
+
+static PyObject * fslib_diriter_new(PyTypeObject *type,
+                                   PyObject *args,
+                                   PyObject *kwds)
+{
+    fslib_diriter *self;
+
+    self = (fslib_diriter *)type->tp_alloc(type, 0);
+    self->entries = PyList_New(0);
+    if (self->entries == NULL) {
+        PyErr_SetString(PyExc_MemoryError, "failed allocation");
+        return NULL;
+    }
+    self->dirh = NULL;
+    return (PyObject *)self;
+}
+
+
+static PyTypeObject fslib_diriter_type = {
+    PyObject_HEAD_INIT(NULL)
+    0,                         /*ob_size*/
+    "fslib.DirIter",           /*tp_name*/
+    sizeof(fslib_diriter),     /*tp_basicsize*/
+    0,                         /*tp_itemsize*/
+    (destructor)fslib_diriter_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,        /*tp_flags*/
+    "directory iter",          /* tp_doc */
+
+};
+
+
+static PyObject *fslib_diriter_iter(fslib_diriter *self)
+{
+    Py_XINCREF((PyObject *)self);
+    return (PyObject*)self;
+}
+
+
+static PyObject *fslib_diriter_next(fslib_diriter *self)
+{
+    int res;
+    unsigned int count = 0;
+    PyObject *result = NULL;
+
+    if (self->dirh == NULL) {
+        PyErr_SetString(PyExc_StopIteration, "");
+        return NULL;
+    }
+
+    if (PyList_Size(self->entries) < 1) {
+        res = _append_dir_entries(self->dirh, self->entries,
+                                  self->groupsize, self->flags, &count);
+        if (res > 0) {
+            errno = res;
+            PyErr_SetFromErrno(PyExc_OSError);
+            return NULL;
+        }
+        if (res != 0) {
+            PyErr_SetString(PyExc_MemoryError, "could not update dir list");
+            return NULL;
+        }
+        if (PyList_Size(self->entries) == 0) {
+            if (closedir(self->dirh) != 0) {
+                PyErr_SetFromErrno(PyExc_OSError);
+            } else {
+                PyErr_SetString(PyExc_StopIteration, "");
+            }
+            self->dirh = NULL;
+            return NULL;
+        }
+    }
+
+    result = PySequence_GetItem(self->entries, 0);
+    if (result == NULL || PySequence_DelItem(self->entries, 0) == -1) {
+        return NULL;
+    }
+
+    return result;
+}
+
+
+static PyObject *fslib_diriter_enter(fslib_diriter *self, PyObject *args)
+{
+    /* guess what happens if this incref isn't here - oh yeah! */
+    Py_XINCREF((PyObject *)self);
+    return (PyObject *)self;
+}
+
+
+static PyObject *fslib_diriter_exit(fslib_diriter *self, PyObject *args)
+{
+    if (self->dirh != NULL) {
+        closedir(self->dirh);
+        self->dirh = NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+
+static PyObject *fslib_diriter_close(fslib_diriter *self, PyObject *args)
+{
+    if (self->dirh != NULL) {
+        closedir(self->dirh);
+        self->dirh = NULL;
+    }
+    Py_RETURN_NONE;
+}
+
+
+static PyMethodDef fslib_diriter_methods[] = {
+    {"__iter__", (PyCFunction)fslib_diriter_iter, METH_NOARGS,
+            "return the iterator"},
+    {"next", (PyCFunction)fslib_diriter_next, METH_NOARGS,
+            "return next item"},
+    {"close", (PyCFunction)fslib_diriter_close, METH_NOARGS,
+            "close directory pointer"},
+    {"__enter__", (PyCFunction)fslib_diriter_enter, METH_NOARGS,
+            "enter a context manager"},
+    {"__exit__", (PyCFunction)fslib_diriter_exit, METH_VARARGS,
+            "exit the context manager"},
+
+    {NULL}
+};
+
+
+PyDoc_STRVAR(fslib_fditerdir__doc__,
+"fditerdir(dirfd) -> iterator\n\n"
+"Return an iterator that yields the names of the entries in the directory.");
+
+
+PyObject *fslib_fditerdir(PyObject *self, PyObject *args)
+{
+    int dirfd;
+    int groupsize = 32;
+    DIR *temp;
+    int flags = 0;
+    fslib_diriter *obj;
+
+    if (!PyArg_ParseTuple(args, "i|ii:fditerdir", &dirfd, &groupsize, &flags)) {
+        return NULL;
+    }
+
+    Py_BEGIN_ALLOW_THREADS
+    temp = fdopendir(dirfd);
+    Py_END_ALLOW_THREADS
+    if (temp == NULL) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+    rewinddir(temp);
+
+    obj = (fslib_diriter*)fslib_diriter_new(&fslib_diriter_type, NULL, NULL);
+    if (obj == NULL) {
+        closedir(temp);
+        return NULL;
+    } else {
+        obj->dirh = temp;
+    }
+
+    obj->flags = flags;
+    obj->groupsize = groupsize;
+    return (PyObject*)obj;
+}
+
+
+static PyMethodDef Methods[] = {
+    {"openat", fslib_openat, METH_VARARGS, fslib_openat__doc__},
+    {"unlinkat", fslib_unlinkat, METH_VARARGS, fslib_unlinkat__doc__},
+    {"mkfifoat", fslib_mkfifoat, METH_VARARGS, fslib_mkfifoat__doc__},
+    {"mkdirat", fslib_mkdirat, METH_VARARGS, fslib_mkdirat__doc__},
+    {"mknodat", fslib_mknodat, METH_VARARGS, fslib_mknodat__doc__},
+    {"fchownat", fslib_fchownat, METH_VARARGS, fslib_fchownat__doc__},
+    {"fchmodat", fslib_fchmodat, METH_VARARGS, fslib_fchmodat__doc__},
+    {"faccessat", fslib_faccessat, METH_VARARGS, fslib_faccessat__doc__},
+    {"readlinkat", fslib_readlinkat, METH_VARARGS, fslib_readlinkat__doc__},
+    {"symlinkat", fslib_symlinkat, METH_VARARGS, fslib_symlinkat__doc__},
+    {"renameat", fslib_renameat, METH_VARARGS, fslib_renameat__doc__},
+    {"linkat", fslib_linkat, METH_VARARGS, fslib_linkat__doc__},
+    {"fstatat", fslib_fstatat, METH_VARARGS, fslib_fstatat__doc__},
+#if HAVE_UTIMENSAT
+    {"utimensat", fslib_utimensat, METH_VARARGS, fslib_utimensat__doc__},
+#endif
+#if defined(HAVE_FUTIMESAT)
+    {"futimesat", fslib_futimesat, METH_VARARGS, fslib_futimesat__doc__},
+#endif
+
+    {"fdlistdir", fslib_fdlistdir, METH_VARARGS, fslib_fdlistdir__doc__},
+    {"listdir", fslib_listdir, METH_VARARGS, fslib_listdir__doc__},
+    {"fditerdir", fslib_fditerdir, METH_VARARGS, fslib_fditerdir__doc__},
+
+    {NULL, NULL, 0, NULL}
+};
+
+
+PyMODINIT_FUNC initfslib(void)
+{
+    PyObject *mod;
+    mod = Py_InitModule(MODULE_NAME, Methods);
+    int res;
+
+    /* flag constants */
+    res = PyModule_AddIntConstant(mod, "AT_FDCWD", AT_FDCWD);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "AT_REMOVEDIR", AT_REMOVEDIR);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "AT_EACCESS", AT_EACCESS);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod,
+                                  "AT_SYMLINK_NOFOLLOW",
+                                  AT_SYMLINK_NOFOLLOW);
+    if (res) return;
+
+    /* misc flags */
+    res = PyModule_AddIntConstant(mod, "O_CLOEXEC", O_CLOEXEC);
+    if (res) return;
+    /* directory entry flags */
+    res = PyModule_AddIntConstant(mod, "DT_UNKNOWN", DT_UNKNOWN);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "DT_BLK", DT_BLK);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "DT_CHR", DT_CHR);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "DT_DIR", DT_DIR);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "DT_FIFO", DT_FIFO);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "DT_LNK", DT_LNK);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "DT_REG", DT_REG);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "DT_SOCK", DT_SOCK);
+    if (res) return;
+
+
+    /* configuration constants */
+    res = PyModule_AddIntConstant(mod, "HAVE_UTIMENSAT", HAVE_UTIMENSAT);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "HAVE_FUTIMESAT", HAVE_FUTIMESAT);
+    if (res) return;
+    res = PyModule_AddIntConstant(mod, "FSLIB_INCL_DTYPE", INCL_DTYPE);
+    if (res) return;
+
+    fslib_diriter_type.tp_new = fslib_diriter_new;
+    fslib_diriter_type.tp_methods = fslib_diriter_methods;
+    if (PyType_Ready(&fslib_diriter_type) < 0) {
+        return;
+    }
+    Py_INCREF(&fslib_diriter_type);
+    PyModule_AddObject(mod, "DirIter",
+                      (PyObject *)&fslib_diriter_type);
+
+    PyStructSequence_InitType(&StatObjType, &fslib_stat_obj_desc);
+    Py_INCREF((PyObject*) &StatObjType);
+    PyModule_AddObject(mod, "stat_obj", (PyObject*) &StatObjType);
+}
+#!/usr/bin/env python
+# 
+# Copyright (c) 2011, 2012 Nasuni Corporation  http://www.nasuni.com/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+#
+"""fswlib - Ctypes wrappers for posix.1-2008 and other system calls
+
+Exposes posix functions like openat, unlinkat and such to
+python. These newer posix functions are not exposed by
+the python standard library in python 2.x.
+"""
+
+import ctypes
+import ctypes.util
+import sys
+import os
+import collections
+
+from ctypes import c_int, c_char_p
+
+__all__ = [
+    'HAVE_FUTIMESAT',
+    'HAVE_UTIMENSAT',
+    'AT_FDCWD',
+    'AT_SYMLINK_NOFOLLOW',
+    'AT_EACCESS',
+    'AT_REMOVEDIR',
+    'faccessat',
+    'fchmodat',
+    'fchownat',
+    'fditerdir',
+    'fdlistdir',
+    'fstatat',
+    'futimesat',
+    'linkat',
+    'mkdirat',
+    'mkfifoat',
+    'mknodat',
+    'openat',
+    'readlinkat',
+    'renameat',
+    'symlinkat',
+    'unlinkat',
+    'utimensat',
+    ]
+
+
+try:
+    _libcpath = ctypes.util.find_library('c')
+except Exception:
+    _libcpath = None
+if not _libcpath:
+    raise ImportError('can not find libc')
+
+
+_libc = ctypes.CDLL(_libcpath, use_errno=True)
+
+
+HAVE_UTIMENSAT = bool(getattr(_libc, 'utimensat', None))
+HAVE_FUTIMESAT = bool(getattr(_libc, 'futimesat', None))
+try:
+    MAXPATH = os.pathconf('/', 'PC_PATH_MAX')
+except Exception:
+    MAXPATH = 1024
+
+
+_libc.rewinddir.argtypes = (ctypes.c_void_p,)
+
+
+def _exc_from_errno(ecls, msg=None, path=None):
+    eno = ctypes.get_errno()
+    if not msg:
+        msg = os.strerror(eno)
+    if path:
+        return ecls(eno, msg, path)
+    else:
+        return ecls(eno, msg)
+
+
+def openat(fd, pathname, flags=0, mode=0600):
+    try:
+        _openat = _libc.openat64
+    except AttributeError:
+        # fall back to openat if openat64 does not exist
+        _openat = _libc.openat
+    _openat.argtypes = (c_int, c_char_p, c_int, c_int)
+    _openat.restype = c_int
+    res = _openat(fd, pathname, int(flags), int(mode))
+    if res == -1:
+        raise _exc_from_errno(IOError, path=pathname)
+    return res
+
+
+def renameat(oldfd, oldpath, newfd, newpath):
+    """renameat
+    """
+    _renameat = _libc.renameat
+    _renameat.argtypes = (c_int, c_char_p, c_int, c_char_p)
+    _renameat.restype = c_int
+    res = _renameat(oldfd, oldpath, newfd, newpath)
+    if res == -1:
+        raise _exc_from_errno(OSError, path=newpath)
+    return res
+
+
+def mkdirat(fd, path, mode=0777):
+    """mkdirat
+    """
+    _mkdirat = _libc.mkdirat
+    _mkdirat.argtypes = (c_int, c_char_p, c_int)
+    _mkdirat.restype = c_int
+    res = _mkdirat(fd, path, int(mode))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=path)
+    return res
+
+
+def mkfifoat(fd, path, mode=0666):
+    _mkfifoat = _libc.mkfifoat
+    _mkfifoat.argtypes = (c_int, c_char_p, c_int)
+    _mkfifoat.restype = c_int
+    res = _mkfifoat(fd, path, int(mode))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=path)
+    return res
+
+
+def mknodat(fd, path, mode=0600, device=0):
+    _mknodat = _libc.mknodat
+    _mknodat.argtypes = (c_int, c_char_p, c_int, c_int)
+    _mknodat.restype = c_int
+    res = _mknodat(fd, path, int(mode), int(device))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=path)
+    return res
+
+
+def linkat(oldfd, oldpath, newfd, newpath, flags=0):
+    _linkat = _libc.linkat
+    _linkat.argtypes = (c_int, c_char_p, c_int, c_char_p, c_int)
+    _linkat.restype = c_int
+    res = _linkat(oldfd, oldpath, newfd, newpath, int(flags))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=newpath)
+    return res
+
+
+def symlinkat(oldname, fd, name):
+    """create a symlink to `oldname` at name
+    """
+    _symlinkat = _libc.symlinkat
+    _symlinkat.argtypes = (c_char_p, c_int, c_char_p)
+    _symlinkat.restype = c_int
+    res = _symlinkat(oldname, fd, name)
+    if res == -1:
+        raise _exc_from_errno(OSError, path=name)
+    return res
+
+
+def unlinkat(fd, path, flags=0):
+    """unlinkat
+    """
+    _unlinkat = _libc.unlinkat
+    _unlinkat.argtypes = (c_int, c_char_p, c_int)
+    _unlinkat.restype = c_int
+    res = _unlinkat(fd, path, int(flags))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=path)
+    return res
+
+
+def readlinkat(fd, path):
+    """readlinkat
+    """
+    _readlinkat = _libc.readlinkat
+    _readlinkat.argtypes = (c_int, c_char_p, c_char_p, ctypes.c_size_t)
+    _readlinkat.restype = c_int
+    obuf = ctypes.create_string_buffer(MAXPATH)
+    res = _readlinkat(fd, path, obuf, ctypes.sizeof(obuf))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=path)
+    return obuf.value
+
+
+def fchownat(fd, path, uid, gid, flags=0):
+    _fchownat = _libc.fchownat
+    _fchownat.argtypes = (c_int, c_char_p, c_int, c_int, c_int)
+    _fchownat.restype = c_int
+    res = _fchownat(fd, path, int(uid), int(gid), int(flags))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=path)
+    return res
+
+
+def fchmodat(fd, path, mode, flags=0):
+    _fchmodat = _libc.fchmodat
+    _fchmodat.argtypes = (c_int, c_char_p, c_int, c_int)
+    _fchmodat.restype = c_int
+    res = _fchmodat(fd, path, int(mode), int(flags))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=path)
+    return res
+
+
+class _stat(ctypes.Structure):
+    """
+    Currently Linux Only -- not checked for portability
+
+           struct stat {
+               dev_t     st_dev;     /* ID of device containing file */
+               ino_t     st_ino;     /* inode number */
+               mode_t    st_mode;    /* protection */
+               nlink_t   st_nlink;   /* number of hard links */
+               uid_t     st_uid;     /* user ID of owner */
+               gid_t     st_gid;     /* group ID of owner */
+               dev_t     st_rdev;    /* device ID (if special file) */
+               off_t     st_size;    /* total size, in bytes */
+               blksize_t st_blksize; /* blocksize for file system I/O */
+               blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
+               time_t    st_atime;   /* time of last access */
+               time_t    st_mtime;   /* time of last modification */
+               time_t    st_ctime;   /* time of last status change */
+           };
+    """
+    _fields_ = [
+        ('st_dev', ctypes.c_ulong),
+        ('st_ino', ctypes.c_ulong),
+        ('st_mode', ctypes.c_uint),
+        ('st_nlink', ctypes.c_ulong),
+        ('st_uid', ctypes.c_uint),
+        ('st_gid', ctypes.c_uint),
+        ('st_rdev', ctypes.c_ulong),
+        ('st_size', ctypes.c_ulong),
+        ('st_blksize', ctypes.c_ulong),
+        ('st_blocks', ctypes.c_ulong),
+        ('st_atime', ctypes.c_long),
+        ('st_mtime', ctypes.c_long),
+        ('st_ctime', ctypes.c_long),
+        ]
+
+_stat_p = ctypes.POINTER(_stat)
+
+statresult = collections.namedtuple('statresult',
+        'st_mode st_ino st_dev st_nlink st_uid st_gid st_size'
+        ' st_atime st_mtime st_ctime st_blksize st_blocks st_rdev')
+
+
+def fstatat(fd, path, flags=0):
+    raise NotImplementedError()
+    """
+    _fstatat = _libc.__fxstatat64
+    _fstatat.argtypes = (c_int, c_int, c_char_p, ctypes.c_void_p, c_int)
+    _fstatat.restype = c_int
+    s = _stat()
+    res = _fstatat(1, fd, path, ctypes.byref(x), int(flags))
+    if res == -1:
+        raise _exc_from_errno(OSError, path=path)
+    return None
+    return ctypes.cast(x, _stat)
+    return _stat_tuple(s)
+    """
+
+
+def _stat_tuple(stat_struct):
+    return statresult(**dict((k, getattr(stat_struct, k))
+                              for k in statresult._fields))
+
+
+def faccessat(fd, path, mode, flags=0):
+    _faccessat = _libc.faccessat
+    _faccessat.argtypes = (c_int, c_char_p, c_int, c_int)
+    _faccessat.restype = c_int
+    res = _faccessat(fd, path, mode, flags)
+    return (res == 0)
+
+
+def futimesat(fd, path, times):
+    #futimesat(dirfd, pathname, (atime, mtime))