about summary refs log tree commit diff
path: root/libgsystem
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2014-07-06 02:16:10 +0200
committerMichael Biebl <biebl@debian.org>2014-07-06 02:16:10 +0200
commit33491bc4279481db8ae47213e34a6d695a0e8830 (patch)
tree097d2b0fdff3fae6885381ae5e57a182cd8cbbba /libgsystem
parent59c3714a494c3b3765657c0551ad82842d98a7d2 (diff)
Imported Upstream version 0.9.10.0 upstream/0.9.10.0
Diffstat (limited to 'libgsystem')
-rw-r--r--libgsystem/COPYING481
-rw-r--r--libgsystem/Makefile-libgsystem.am42
-rw-r--r--libgsystem/README10
-rw-r--r--libgsystem/gsystem-console.c443
-rw-r--r--libgsystem/gsystem-console.h59
-rw-r--r--libgsystem/gsystem-file-utils.c1644
-rw-r--r--libgsystem/gsystem-file-utils.h172
-rw-r--r--libgsystem/gsystem-glib-compat.h54
-rw-r--r--libgsystem/gsystem-local-alloc.c72
-rw-r--r--libgsystem/gsystem-local-alloc.h164
-rw-r--r--libgsystem/gsystem-log.c167
-rw-r--r--libgsystem/gsystem-log.h42
-rw-r--r--libgsystem/gsystem-shutil.c460
-rw-r--r--libgsystem/gsystem-shutil.h47
-rw-r--r--libgsystem/gsystem-subprocess-context-private.h65
-rw-r--r--libgsystem/gsystem-subprocess-context.c501
-rw-r--r--libgsystem/gsystem-subprocess-context.h128
-rw-r--r--libgsystem/gsystem-subprocess.c966
-rw-r--r--libgsystem/gsystem-subprocess.h101
-rw-r--r--libgsystem/libgsystem.h49
20 files changed, 5667 insertions, 0 deletions
diff --git a/libgsystem/COPYING b/libgsystem/COPYING
new file mode 100644
index 00000000..5bc8fb2c
--- /dev/null
+++ b/libgsystem/COPYING
@@ -0,0 +1,481 @@
+                  GNU LIBRARY GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL.  It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it.  You can use it for
+your libraries, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library.  If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software.  To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+  Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs.  This
+license, the GNU Library General Public License, applies to certain
+designated libraries.  This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+  The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it.  Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program.  However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+  Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries.  We
+concluded that weaker conditions might promote sharing better.
+
+  However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves.  This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them.  (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.)  The hope is that this
+will lead to faster development of free libraries.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+  Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+                  GNU LIBRARY GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License").  Each licensee is
+addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    c) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    d) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/libgsystem/Makefile-libgsystem.am b/libgsystem/Makefile-libgsystem.am
new file mode 100644
index 00000000..092522c7
--- /dev/null
+++ b/libgsystem/Makefile-libgsystem.am
@@ -0,0 +1,42 @@
+# Copyright (C) 2012 Colin Walters <walters@verbum.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+EXTRA_DIST += $(libgsystem_srcpath)/README $(libgsystem_srcpath)/COPYING
+
+libgsystem_la_SOURCES = \
+	$(libgsystem_srcpath)/gsystem-local-alloc.h \
+	$(libgsystem_srcpath)/gsystem-local-alloc.c \
+	$(libgsystem_srcpath)/gsystem-console.h \
+	$(libgsystem_srcpath)/gsystem-console.c \
+	$(libgsystem_srcpath)/gsystem-file-utils.h \
+	$(libgsystem_srcpath)/gsystem-file-utils.c \
+	$(libgsystem_srcpath)/gsystem-glib-compat.h \
+	$(libgsystem_srcpath)/gsystem-shutil.h \
+	$(libgsystem_srcpath)/gsystem-shutil.c \
+	$(libgsystem_srcpath)/gsystem-log.h \
+	$(libgsystem_srcpath)/gsystem-log.c \
+	$(libgsystem_srcpath)/gsystem-subprocess-context.h \
+	$(libgsystem_srcpath)/gsystem-subprocess-context-private.h \
+	$(libgsystem_srcpath)/gsystem-subprocess-context.c \
+	$(libgsystem_srcpath)/gsystem-subprocess.h \
+	$(libgsystem_srcpath)/gsystem-subprocess.c \
+	$(libgsystem_srcpath)/libgsystem.h \
+	$(NULL)
+
+libgsystem_la_CFLAGS = $(AM_CFLAGS) $(libgsystem_cflags)
+libgsystem_la_LDFLAGS = -avoid-version -Bsymbolic-functions -export-symbols-regex "^gs_" -no-undefined -export-dynamic
+libgsystem_la_LIBADD = $(libgsystem_libs)
diff --git a/libgsystem/README b/libgsystem/README
new file mode 100644
index 00000000..13fa05f2
--- /dev/null
+++ b/libgsystem/README
@@ -0,0 +1,10 @@
+libgsystem is intended to be used as a git external for components
+that depend on GLib, but accept a hard dependency on things which are
+difficult to do in GLib itself.  For example, the local-alloc API
+depends on GCC/clang.
+
+It will also be a useful place to evolve new APIs (e.g. logging)
+before their eventual inclusion in GLib (if ever).
+
+There's no bugzilla component currently, please mail
+patches to: walters@verbum.org
diff --git a/libgsystem/gsystem-console.c b/libgsystem/gsystem-console.c
new file mode 100644
index 00000000..35477eba
--- /dev/null
+++ b/libgsystem/gsystem-console.c
@@ -0,0 +1,443 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+
+/**
+ * SECTION:gsconsole
+ * @title: GSConsole
+ * @short_description: Interact with standard input/output as well as terminal
+ *
+ * First, this class offers API to access the standard input and
+ * output/error, streams as #GInputStream and #GOutputStream
+ * respectively.
+ *
+ * In the case where the process is connected to a controlling
+ * terminal, the gs_console_get() API is available, which exposes a
+ * number of additional features such as no-echo password reading.
+ *
+ * Since: 2.36
+ */
+
+#include "config.h"
+
+#include "gsystem-console.h"
+
+#include <string.h>
+#ifdef G_OS_UNIX
+#include <gio/gunixoutputstream.h>
+#include <gio/gfiledescriptorbased.h>
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+#include <unistd.h>
+#include <termios.h>
+#endif
+#include <fcntl.h>
+
+typedef GObjectClass GSConsoleClass;
+
+struct _GSConsole
+{
+  GObject parent;
+
+  gboolean in_status_line;
+  gssize last_line_written;
+};
+
+G_DEFINE_TYPE (GSConsole, gs_console, G_TYPE_OBJECT);
+
+static void
+gs_console_init (GSConsole  *self)
+{
+  self->last_line_written = -1;
+}
+
+static void
+gs_console_class_init (GSConsoleClass *class)
+{
+}
+
+/**
+ * gs_console_get:
+ *
+ * If the current process has an interactive console, return the
+ * singleton #GSConsole instance.  On Unix, this is equivalent to
+ * isatty().  For all other cases, such as pipes, sockets, /dev/null,
+ * this function will return %NULL.
+ *
+ * Returns: (transfer none): The console instance, or %NULL if not interactive
+ *
+ * Since: 2.36
+ */
+GSConsole *
+gs_console_get (void)
+{
+  static gsize checked = 0;
+  static GSConsole *instance = NULL;
+
+  if (g_once_init_enter (&checked))
+    {
+#ifdef G_OS_UNIX
+      if (isatty (0) && isatty (1))
+        instance = g_object_new (GS_TYPE_CONSOLE, NULL);
+#endif
+      g_once_init_leave (&checked, 1);
+    }
+  
+  return (GSConsole*) instance;
+}
+
+/**
+ * gs_console_get_stdin:
+ *
+ * Returns: (transfer none): The singleton stream connected to standard input
+ */
+GInputStream *
+gs_console_get_stdin (void)
+{
+#ifdef G_OS_UNIX
+  static gsize instance = 0;
+
+  if (g_once_init_enter (&instance))
+    g_once_init_leave (&instance, (gsize) g_unix_input_stream_new (0, FALSE));
+  
+  return (GInputStream*) instance;
+#else
+  g_error ("not implemented");
+#endif
+}
+
+/**
+ * gs_console_get_stdout:
+ *
+ * Returns: (transfer none): The singleton stream connected to standard output
+ */
+GOutputStream *
+gs_console_get_stdout (void)
+{
+#ifdef G_OS_UNIX
+  static gsize instance = 0;
+
+  if (g_once_init_enter (&instance))
+    g_once_init_leave (&instance, (gsize) g_unix_output_stream_new (1, FALSE));
+  
+  return (GOutputStream*) instance;
+#else
+  g_error ("not implemented");
+#endif
+}
+
+/**
+ * gs_console_get_stderr:
+ *
+ * Returns: (transfer none): The singleton stream connected to standard error
+ */
+GOutputStream *
+gs_console_get_stderr (void)
+{
+#ifdef G_OS_UNIX
+  static gsize instance = 0;
+
+  if (g_once_init_enter (&instance))
+    g_once_init_leave (&instance, (gsize) g_unix_output_stream_new (2, FALSE));
+  
+  return (GOutputStream*) instance;
+#else
+  g_error ("not implemented");
+#endif
+}
+
+#ifdef G_OS_UNIX
+static inline void
+_set_error_from_errno (GError **error)
+{
+  int errsv = errno;
+  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                       g_strerror (errsv));
+}
+#endif
+
+/**
+ * gs_console_read_password:
+ * @console: the #GSConsole
+ * @prompt: A string to output before reading the password
+ * @error: a #GError
+ *
+ * Write @prompt to standard output, then switch output echo off, read
+ * a result string, then switch output echo back on.
+ *
+ * Returns: A string, or %NULL on error
+ */
+char *
+gs_console_read_password (GSConsole     *console,
+                          const char    *prompt,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+#ifdef G_OS_UNIX
+  gboolean ret = FALSE;
+  /* This code is modified from that found in
+   * polkit/src/polkittextagentlistener.c, reused under the LGPL v2.1
+   */
+  int res;
+  struct termios ts, ots;
+  GInputStream *in;
+  GOutputStream *out;
+  GString *str = NULL;
+  gsize bytes_written;
+  gboolean reset_terminal = FALSE;
+
+  in = gs_console_get_stdin ();
+  out = gs_console_get_stdout ();
+
+  if (!g_output_stream_write_all (out, prompt, strlen (prompt), &bytes_written,
+                                  cancellable, error))
+    goto out;
+  if (!g_output_stream_flush (out, cancellable, error))
+    goto out;
+
+  /* TODO: We really ought to block SIGINT and STGSTP (and probably
+   *       other signals too) so we can restore the terminal (since we
+   *       turn off echoing). See e.g. Advanced Programming in the
+   *       UNIX Environment 2nd edition (Steves and Rago) section
+   *       18.10, pg 660 where this is suggested. See also various
+   *       getpass(3) implementations
+   *
+   *       However, since we are a library routine the user could have
+   *       multiple threads - in fact, typical usage of
+   *       PolkitAgentTextListener is to run it in a thread. And
+   *       unfortunately threads and POSIX signals is a royal PITA.
+   *
+   *       Maybe we could fork(2) and ask for the password in the
+   *       child and send it back to the parent over a pipe? (we are
+   *       guaranteed that there is only one thread in the child
+   *       process).
+   *
+   *       (Side benefit of doing this in a child process is that we
+   *       could avoid blocking the thread where the
+   *       PolkitAgentTextListener object is being serviced from. But
+   *       since this class is normally used in a dedicated thread
+   *       it doesn't really matter *anyway*.)
+   *
+   *       Anyway, On modern Linux not doing this doesn't seem to be a
+   *       problem - looks like modern shells restore echoing anyway
+   *       on the first input. So maybe it's not even worth solving
+   *       the problem.
+   */
+
+  do
+    res = tcgetattr (1, &ts);
+  while (G_UNLIKELY (res == -1 && errno == EINTR));
+  if (res == -1)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+  ots = ts;
+  ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+  do
+    res = tcsetattr (1, TCSAFLUSH, &ts);
+  while (G_UNLIKELY (res == -1 && errno == EINTR));
+  if (res == -1)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  /* After this point, we'll need to clean up the terminal in case of
+   * error.
+   */
+  reset_terminal = TRUE;
+
+  str = g_string_new (NULL);
+  while (TRUE)
+    {
+      gssize bytes_read;
+      guint8 buf[1];
+
+      /* FIXME - we should probably be converting from the system
+       * codeset, in case it's not UTF-8.
+       */
+      bytes_read = g_input_stream_read (in, buf, sizeof (buf),
+                                        cancellable, error);
+      if (bytes_read < 0)
+        goto out;
+      else if (bytes_read == 0)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
+                       "End of stream while reading password");
+          goto out;
+        }
+      else if (buf[0] == '\n')
+        {
+          break;
+        }
+      else
+        {
+          g_string_append_c (str, buf[0]);
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (reset_terminal)
+    {
+      do
+        res = tcsetattr (1, TCSAFLUSH, &ots);
+      while (G_UNLIKELY (res == -1 && errno == EINTR));
+      if (res == -1)
+        {
+          _set_error_from_errno (error);
+          g_string_free (str, TRUE);
+          return NULL;
+        }
+    }
+
+  if (!ret)
+    {
+      g_string_free (str, TRUE);
+      return NULL;
+    }
+  else
+    {
+      return g_string_free (str, FALSE);
+    }
+#else
+  g_error ("not implemented");
+#endif
+}
+
+/**
+ * gs_console_begin_status_line:
+ * @console: the #GSConsole
+ * @line: String to output
+ *
+ * The primary use case for this function is to output periodic
+ * "status" or "progress" information.  The first time this function
+ * is called, @line will be output normally.  Subsequent invocations
+ * will overwrite the previous.
+ *
+ * You must invoke gs_console_end_status_line() to return the console
+ * to normal mode.  In particular, concurrent use of this function and
+ * the stream returned by gs_console_get_stdout() results in undefined
+ * behavior.
+ */
+gboolean
+gs_console_begin_status_line (GSConsole     *console,
+                              const char    *line,
+                              GCancellable  *cancellable,
+                              GError       **error)
+{
+#ifdef G_OS_UNIX
+  gboolean ret = FALSE;
+  gsize linelen;
+  GOutputStream *out;
+  gsize bytes_written;
+
+  out = gs_console_get_stdout ();
+
+  if (!console->in_status_line)
+    {
+      guint8 buf[3] = { (guint8)'\n', 0x1B, 0x37 };
+      if (!g_output_stream_write_all (out, buf, sizeof (buf), &bytes_written,
+                                      cancellable, error))
+        goto out;
+      console->in_status_line = TRUE;
+      console->last_line_written = -1;
+    }
+
+  {
+    guint8 buf[2] = { 0x1B, 0x38 };
+    if (!g_output_stream_write_all (out, buf, sizeof (buf), &bytes_written,
+                                    cancellable, error))
+      goto out;
+  }
+
+  linelen = strlen (line);
+  if (!g_output_stream_write_all (out, line, linelen, &bytes_written,
+                                  cancellable, error))
+    goto out;
+
+  /* Now we need to pad with spaces enough to overwrite our last line
+   */
+  if (console->last_line_written >= 0
+      && linelen < (gsize) console->last_line_written)
+    {
+      gsize towrite = console->last_line_written - linelen;
+      const char c = ' ';
+      while (towrite > 0)
+        {
+          if (!g_output_stream_write_all (out, &c, 1, &bytes_written,
+                                          cancellable, error))
+            goto out;
+          towrite--;
+        }
+    }
+  
+  console->last_line_written = linelen;
+  
+  ret = TRUE;
+ out:
+  return ret;
+#else
+  g_error ("not implemented");
+#endif
+}
+
+/**
+ * gs_console_end_status_line:
+ * @console: the #GSConsole
+ *
+ * Complete a series of invocations of gs_console_begin_status_line(),
+ * returning the stream to normal mode.  The last printed status line
+ * remains on the console; if this is not desired, print an empty
+ * string to clear it before invoking this function.
+ */
+gboolean
+gs_console_end_status_line (GSConsole     *console,
+                            GCancellable  *cancellable,
+                            GError       **error)
+{
+#ifdef G_OS_UNIX  
+  gboolean ret = FALSE;
+  GOutputStream *out;
+  gsize bytes_written;
+  char c = '\n';
+
+  g_return_val_if_fail (console->in_status_line, FALSE);
+
+  out = gs_console_get_stdout ();
+
+  if (!g_output_stream_write_all (out, &c, 1, &bytes_written,
+                                  cancellable, error))
+    goto out;
+
+  console->in_status_line = FALSE;
+
+  ret = TRUE;
+ out:
+  return ret;
+#else
+  g_error ("not implemented");
+#endif
+}
diff --git a/libgsystem/gsystem-console.h b/libgsystem/gsystem-console.h
new file mode 100644
index 00000000..a22b2d45
--- /dev/null
+++ b/libgsystem/gsystem-console.h
@@ -0,0 +1,59 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSYSTEM_CONSOLE_H__
+#define __GSYSTEM_CONSOLE_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_CONSOLE         (gs_console_get_type ())
+#define GS_CONSOLE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_CONSOLE, GSConsole))
+#define GS_IS_CONSOLE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_CONSOLE))
+
+typedef struct _GSConsole GSConsole;
+
+GType            gs_console_get_type (void) G_GNUC_CONST;
+
+GInputStream *   gs_console_get_stdin (void);
+GOutputStream *  gs_console_get_stdout (void);
+GOutputStream *  gs_console_get_stderr (void);
+
+GSConsole *      gs_console_get (void);
+
+char *           gs_console_read_password (GSConsole     *console,
+                                           const char    *prompt,
+                                           GCancellable  *cancellable,
+                                           GError       **error);
+
+gboolean         gs_console_begin_status_line (GSConsole     *console,
+                                               const char    *line,
+                                               GCancellable  *cancellable,
+                                               GError       **error);
+
+gboolean         gs_console_end_status_line (GSConsole      *console,
+                                             GCancellable   *cancellable,
+                                             GError        **error);
+
+
+G_END_DECLS
+
+#endif
diff --git a/libgsystem/gsystem-file-utils.c b/libgsystem/gsystem-file-utils.c
new file mode 100644
index 00000000..02606035
--- /dev/null
+++ b/libgsystem/gsystem-file-utils.c
@@ -0,0 +1,1644 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 William Jon McCann <mccann@redhat.com>
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <string.h>
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+#include "gsystem-glib-compat.h"
+#include <glib/gstdio.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gfiledescriptorbased.h>
+#include <gio/gunixoutputstream.h>
+#include <glib-unix.h>
+#include <limits.h>
+#include <dirent.h>
+#ifdef GSYSTEM_CONFIG_XATTRS
+#include <attr/xattr.h>
+#endif
+
+static int
+close_nointr (int fd)
+{
+  int res;
+  /* Note this is NOT actually a retry loop.
+   * See: https://bugzilla.gnome.org/show_bug.cgi?id=682819
+   */
+  res = close (fd);
+  /* Just ignore EINTR...on Linux, retrying is wrong. */
+  if (res == EINTR)
+    res = 0;
+  return res;
+}
+
+static void
+close_nointr_noerror (int fd)
+{
+  (void) close_nointr (fd);
+}
+
+static int
+open_nointr (const char *path, int flags, mode_t mode)
+{
+  int res;
+  do
+    res = open (path, flags, mode);
+  while (G_UNLIKELY (res == -1 && errno == EINTR));
+  return res;
+}
+
+static inline void
+_set_error_from_errno (GError **error)
+{
+  int errsv = errno;
+  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                       g_strerror (errsv));
+}
+
+/**
+ * gs_file_openat_noatime:
+ * @dfd: File descriptor for directory
+ * @name: Pathname, relative to @dfd
+ * @ret_fd: (out): Returned file descriptor
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Wrapper for openat() using %O_RDONLY with %O_NOATIME if available.
+ */
+gboolean
+gs_file_openat_noatime (int            dfd,
+                        const char    *name,
+                        int           *ret_fd,
+                        GCancellable  *cancellable,
+                        GError       **error)
+{
+  int fd;
+
+#ifdef O_NOATIME
+  do
+    fd = openat (dfd, name, O_RDONLY | O_NOATIME, 0);
+  while (G_UNLIKELY (fd == -1 && errno == EINTR));
+  /* Only the owner or superuser may use O_NOATIME; so we may get
+   * EPERM.  EINVAL may happen if the kernel is really old...
+   */
+  if (fd == -1 && (errno == EPERM || errno == EINVAL))
+#endif
+    do
+      fd = openat (dfd, name, O_RDONLY, 0);
+    while (G_UNLIKELY (fd == -1 && errno == EINTR));
+  
+  if (fd == -1)
+    {
+      _set_error_from_errno (error);
+      return FALSE;
+    }
+  else
+    {
+      *ret_fd = fd;
+      return TRUE;
+    }
+}
+
+/**
+ * gs_file_read_noatime:
+ * @file: a #GFile
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_file_read(), but try to avoid updating the file's
+ * access time.  This should be used by background scanning
+ * components such as search indexers, antivirus programs, etc.
+ *
+ * Returns: (transfer full): A new input stream, or %NULL on error
+ */
+GInputStream *
+gs_file_read_noatime (GFile         *file,
+                      GCancellable  *cancellable,
+                      GError       **error)
+{
+  const char *path = NULL;
+  int fd;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return NULL;
+
+  path = gs_file_get_path_cached (file);
+  if (path == NULL)
+    {
+      char *uri;
+      uri = g_file_get_uri (file);
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT,
+                   "%s has no associated path", uri);
+      g_free (uri);
+      return NULL;
+    }
+
+  if (!gs_file_openat_noatime (AT_FDCWD, path, &fd, cancellable, error))
+    return NULL;
+
+  return g_unix_input_stream_new (fd, TRUE);
+}
+
+/**
+ * gs_stream_fstat:
+ * @stream: A stream containing a Unix file descriptor
+ * @stbuf: Memory location to write stat buffer
+ * @cancellable:
+ * @error:
+ *
+ * Some streams created via libgsystem are #GUnixInputStream; these do
+ * not support e.g. g_file_input_stream_query_info().  This function
+ * allows dropping to the raw unix fstat() call for these types of
+ * streams, while still conveniently wrapped with the normal GLib
+ * handling of @cancellable and @error.
+ */
+gboolean
+gs_stream_fstat (GFileDescriptorBased *stream,
+                 struct stat          *stbuf,
+                 GCancellable         *cancellable,
+                 GError              **error)
+{
+  gboolean ret = FALSE;
+  int fd;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    goto out;
+
+  fd = g_file_descriptor_based_get_fd (stream);
+
+  if (fstat (fd, stbuf) == -1)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * gs_file_map_noatime: (skip)
+ * @file: a #GFile
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_mapped_file_new(), but try to avoid updating the file's
+ * access time.  This should be used by background scanning
+ * components such as search indexers, antivirus programs, etc.
+ *
+ * Returns: (transfer full): A new mapped file, or %NULL on error
+ */
+GMappedFile *
+gs_file_map_noatime (GFile         *file,
+                     GCancellable  *cancellable,
+                     GError       **error)
+{
+  const char *path;
+  int fd;
+  GMappedFile *ret;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return NULL;
+
+  path = gs_file_get_path_cached (file);
+  if (path == NULL)
+    return NULL;
+
+  if (!gs_file_openat_noatime (AT_FDCWD, path, &fd, cancellable, error))
+    return NULL;
+  
+  ret = g_mapped_file_new_from_fd (fd, FALSE, error);
+  close_nointr_noerror (fd); /* Ignore errors - we always want to close */
+
+  return ret;
+}
+
+#if GLIB_CHECK_VERSION(2,34,0)
+/**
+ * gs_file_map_readonly:
+ * @file: a #GFile
+ * @cancellable:
+ * @error:
+ *
+ * Return a #GBytes which references a readonly view of the contents of
+ * @file.  This function uses #GMappedFile internally.
+ *
+ * Returns: (transfer full): a newly referenced #GBytes
+ */
+GBytes *
+gs_file_map_readonly (GFile         *file,
+                      GCancellable  *cancellable,
+                      GError       **error)
+{
+  GMappedFile *mfile;
+  GBytes *ret;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return NULL;
+
+  mfile = g_mapped_file_new (gs_file_get_path_cached (file), FALSE, error);
+  if (!mfile)
+    return NULL;
+
+  ret = g_mapped_file_get_bytes (mfile);
+  g_mapped_file_unref (mfile);
+  return ret;
+}
+#endif
+
+/**
+ * gs_file_sync_data:
+ * @file: a #GFile
+ * @cancellable:
+ * @error:
+ *
+ * Wraps the UNIX fsync() function (or fdatasync(), if available), which
+ * ensures that the data in @file is on non-volatile storage.
+ */
+gboolean
+gs_file_sync_data (GFile          *file,
+                   GCancellable   *cancellable,
+                   GError        **error)
+{
+  gboolean ret = FALSE;
+  int res;
+  int fd = -1; 
+
+  if (!gs_file_openat_noatime (AT_FDCWD, gs_file_get_path_cached (file), &fd,
+                               cancellable, error))
+    goto out;
+
+  do
+    {
+#ifdef __linux
+      res = fdatasync (fd);
+#else
+      res = fsync (fd);
+#endif
+    }
+  while (G_UNLIKELY (res != 0 && errno == EINTR));
+  if (res != 0)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  res = close_nointr (fd);
+  if (res != 0)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+  fd = -1;
+  
+  ret = TRUE;
+ out:
+  if (fd != -1)
+    close_nointr_noerror (fd);
+  return ret;
+}
+
+/**
+ * gs_file_create:
+ * @file: Path to non-existent file
+ * @mode: Unix access permissions
+ * @out_stream: (out) (transfer full) (allow-none): Newly created output, or %NULL
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_file_create(), except this function allows specifying the
+ * access mode.  This allows atomically creating private files.
+ */
+gboolean
+gs_file_create (GFile          *file,
+                int             mode,
+                GOutputStream **out_stream,
+                GCancellable   *cancellable,
+                GError        **error)
+{
+  gboolean ret = FALSE;
+  int fd;
+  GOutputStream *ret_stream = NULL;
+
+  fd = open_nointr (gs_file_get_path_cached (file), O_WRONLY | O_CREAT | O_EXCL, mode);
+  if (fd < 0)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  if (fchmod (fd, mode) < 0)
+    {
+      close (fd);
+      _set_error_from_errno (error);
+      goto out;
+    }
+  
+  ret_stream = g_unix_output_stream_new (fd, TRUE);
+  
+  ret = TRUE;
+  gs_transfer_out_value (out_stream, &ret_stream);
+ out:
+  g_clear_object (&ret_stream);
+  return ret;
+}
+
+static const char *
+get_default_tmp_prefix (void)
+{
+  static char *tmpprefix = NULL;
+
+  if (g_once_init_enter (&tmpprefix))
+    {
+      const char *prgname = g_get_prgname ();
+      const char *p;
+      char *prefix;
+      char *iter;
+
+      if (prgname)
+        {
+          p = strrchr (prgname, '/');
+          if (p)
+            prgname = p + 1;
+        }
+      else
+        prgname = "";
+          
+      prefix = g_strdup_printf ("tmp-%s%u-", prgname, getuid ());
+      for (iter = prefix; *iter; iter++)
+        {
+          char c = *iter;
+          if (c == ' ')
+            *iter = '_';
+        }
+      
+      g_once_init_leave (&tmpprefix, prefix);
+    }
+
+  return tmpprefix;
+}
+
+/**
+ * gs_fileutil_gen_tmp_name:
+ * @prefix: (allow-none): String prepended to the result
+ * @suffix: (allow-none): String suffixed to the result
+ *
+ * Generate a name suitable for use as a temporary file.  This
+ * function does no I/O; it is not guaranteed that a file with that
+ * name does not exist.
+ */
+char *
+gs_fileutil_gen_tmp_name (const char *prefix,
+                          const char *suffix)
+{
+  static const char table[] = "ABCEDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789";
+  GString *str = g_string_new ("");
+  guint i;
+
+  if (!prefix)
+    prefix = get_default_tmp_prefix ();
+  if (!suffix)
+    suffix = "tmp";
+
+  g_string_append (str, prefix);
+  for (i = 0; i < 8; i++)
+    {
+      int offset = g_random_int_range (0, sizeof (table) - 1);
+      g_string_append_c (str, (guint8)table[offset]);
+    }
+  g_string_append_c (str, '.');
+  g_string_append (str, suffix);
+
+  return g_string_free (str, FALSE);
+}
+
+/**
+ * gs_file_open_dir_fd:
+ * @path: Directory name
+ * @out_fd: (out): File descriptor for directory
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * On success, sets @out_fd to a file descriptor for the directory
+ * that can be used with UNIX functions such as openat().
+ */
+gboolean
+gs_file_open_dir_fd (GFile         *path,
+                     int           *out_fd,
+                     GCancellable  *cancellable,
+                     GError       **error)
+{
+  /* Linux specific probably */
+  *out_fd = open (gs_file_get_path_cached (path), O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC);
+  if (*out_fd == -1)
+    {
+      _set_error_from_errno (error);
+      return FALSE;
+    }
+  return TRUE;
+}
+
+/**
+ * gs_file_open_in_tmpdir_at:
+ * @tmpdir_fd: Directory to place temporary file
+ * @mode: Default mode (will be affected by umask)
+ * @out_name: (out) (transfer full): Newly created file name
+ * @out_stream: (out) (transfer full) (allow-none): Newly created output stream
+ * @cancellable:
+ * @error:
+ *
+ * Like g_file_open_tmp(), except the file will be created in the
+ * provided @tmpdir, and allows specification of the Unix @mode, which
+ * means private files may be created.  Return values will be stored
+ * in @out_name, and optionally @out_stream.
+ */
+gboolean
+gs_file_open_in_tmpdir_at (int                tmpdir_fd,
+                           int                mode,
+                           char             **out_name,
+                           GOutputStream    **out_stream,
+                           GCancellable      *cancellable,
+                           GError           **error)
+{
+  gboolean ret = FALSE;
+  const int max_attempts = 128;
+  int i;
+  char *tmp_name = NULL;
+  int fd;
+
+  /* 128 attempts seems reasonable... */
+  for (i = 0; i < max_attempts; i++)
+    {
+      g_free (tmp_name);
+      tmp_name = gs_fileutil_gen_tmp_name (NULL, NULL);
+
+      do
+        fd = openat (tmpdir_fd, tmp_name, O_WRONLY | O_CREAT | O_EXCL, mode);
+      while (fd == -1 && errno == EINTR);
+      if (fd < 0 && errno != EEXIST)
+        {
+          _set_error_from_errno (error);
+          goto out;
+        }
+      else if (fd != -1)
+        break;
+    }
+  if (i == max_attempts)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Exhausted attempts to open temporary file");
+      goto out;
+    }
+
+  ret = TRUE;
+  gs_transfer_out_value (out_name, &tmp_name);
+  if (out_stream)
+    *out_stream = g_unix_output_stream_new (fd, TRUE);
+  else
+    (void) close (fd);
+ out:
+  g_free (tmp_name);
+  return ret;
+}
+
+/**
+ * gs_file_open_in_tmpdir:
+ * @tmpdir: Directory to place temporary file
+ * @mode: Default mode (will be affected by umask)
+ * @out_file: (out) (transfer full): Newly created file path
+ * @out_stream: (out) (transfer full) (allow-none): Newly created output stream
+ * @cancellable:
+ * @error:
+ *
+ * Like g_file_open_tmp(), except the file will be created in the
+ * provided @tmpdir, and allows specification of the Unix @mode, which
+ * means private files may be created.  Return values will be stored
+ * in @out_file, and optionally @out_stream.
+ */
+gboolean
+gs_file_open_in_tmpdir (GFile             *tmpdir,
+                        int                mode,
+                        GFile            **out_file,
+                        GOutputStream    **out_stream,
+                        GCancellable      *cancellable,
+                        GError           **error)
+{
+  gboolean ret = FALSE;
+  DIR *d = NULL;
+  int dfd = -1;
+  char *tmp_name = NULL;
+  GOutputStream *ret_stream = NULL;
+
+  d = opendir (gs_file_get_path_cached (tmpdir));
+  if (!d)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+  dfd = dirfd (d);
+
+  if (!gs_file_open_in_tmpdir_at (dfd, mode, &tmp_name,
+                                  out_stream ? &ret_stream : NULL,
+                                  cancellable, error))
+    goto out;
+ 
+  ret = TRUE;
+  *out_file = g_file_get_child (tmpdir, tmp_name);
+  gs_transfer_out_value (out_stream, &ret_stream);
+ out:
+  if (d) (void) closedir (d);
+  g_clear_object (&ret_stream);
+  g_free (tmp_name);
+  return ret;
+}
+
+static gboolean
+linkcopy_internal_attempt (GFile          *src,
+                          GFile          *dest,
+                          GFile          *dest_parent,
+                          GFileCopyFlags  flags,
+                          gboolean        sync_data,
+                          gboolean        enable_guestfs_fuse_workaround,
+                          gboolean       *out_try_again,
+                          GCancellable   *cancellable,
+                          GError        **error)
+{
+  gboolean ret = FALSE;
+  int res;
+  char *tmp_name = NULL;
+  GFile *tmp_dest = NULL;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    goto out;
+
+  tmp_name = gs_fileutil_gen_tmp_name (NULL, NULL);
+  tmp_dest = g_file_get_child (dest_parent, tmp_name);
+
+  res = link (gs_file_get_path_cached (src), gs_file_get_path_cached (tmp_dest));
+  if (res == -1)
+    {
+      if (errno == EEXIST)
+        {
+          /* Nothing, fall through */
+          *out_try_again = TRUE;
+          ret = TRUE;
+          goto out;
+        }
+      else if (errno == EXDEV || errno == EMLINK || errno == EPERM
+               || (enable_guestfs_fuse_workaround && errno == ENOENT))
+        {
+          if (!g_file_copy (src, tmp_dest, flags,
+                            cancellable, NULL, NULL, error))
+            goto out;
+        }
+      else
+        {
+          _set_error_from_errno (error);
+          goto out;
+        }
+    }
+      
+  if (sync_data)
+    {
+      /* Now, we need to fsync */
+      if (!gs_file_sync_data (tmp_dest, cancellable, error))
+        goto out;
+    }
+
+  if (!gs_file_rename (tmp_dest, dest, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+  *out_try_again = FALSE;
+ out:
+  g_clear_pointer (&tmp_name, g_free);
+  g_clear_object (&tmp_dest);
+  return ret;
+}
+
+static gboolean
+linkcopy_internal (GFile          *src,
+                   GFile          *dest,
+                   GFileCopyFlags  flags,
+                   gboolean        sync_data,
+                   GCancellable   *cancellable,
+                   GError        **error)
+{
+  gboolean ret = FALSE;
+  gboolean dest_exists;
+  int i;
+  gboolean enable_guestfs_fuse_workaround;
+  struct stat src_stat;
+  struct stat dest_stat;
+  GFile *dest_parent = NULL;
+
+  flags |= G_FILE_COPY_NOFOLLOW_SYMLINKS;
+
+  g_return_val_if_fail ((flags & (G_FILE_COPY_BACKUP | G_FILE_COPY_TARGET_DEFAULT_PERMS)) == 0, FALSE);
+
+  dest_parent = g_file_get_parent (dest);
+
+  if (lstat (gs_file_get_path_cached (src), &src_stat) == -1)
+    {
+      int errsv = errno;
+      g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
+                           g_strerror (errsv));
+      goto out;
+    }
+
+  if (lstat (gs_file_get_path_cached (dest), &dest_stat) == -1)
+    dest_exists = FALSE;
+  else
+    dest_exists = TRUE;
+  
+  if (((flags & G_FILE_COPY_OVERWRITE) == 0) && dest_exists)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
+                           "File exists");
+      goto out;
+    }
+
+  /* Work around the behavior of link() where it's a no-op if src and
+   * dest are the same.
+   */
+  if (dest_exists &&
+      src_stat.st_dev == dest_stat.st_dev &&
+      src_stat.st_ino == dest_stat.st_ino)
+    {
+      ret = TRUE;
+      goto out;
+    }
+
+  enable_guestfs_fuse_workaround = getenv ("LIBGSYSTEM_ENABLE_GUESTFS_FUSE_WORKAROUND") != NULL;
+
+  /* 128 attempts seems reasonable... */
+  for (i = 0; i < 128; i++)
+    {
+      gboolean tryagain = FALSE;
+
+      if (!linkcopy_internal_attempt (src, dest, dest_parent,
+                                      flags, sync_data,
+                                      enable_guestfs_fuse_workaround,
+                                      &tryagain,
+                                      cancellable, error))
+        goto out;
+
+      if (!tryagain)
+        break;
+    }
+
+  ret = TRUE;
+ out:
+  g_clear_object (&dest_parent);
+  return ret;
+
+}
+
+/**
+ * gs_file_linkcopy:
+ * @src: Source file
+ * @dest: Destination file
+ * @flags: flags
+ * @cancellable:
+ * @error:
+ *
+ * First tries to use the UNIX link() call, but if the files are on
+ * separate devices, fall back to copying via g_file_copy().
+ *
+ * The given @flags have different semantics than those documented
+ * when hardlinking is used.  Specifically, both
+ * #G_FILE_COPY_TARGET_DEFAULT_PERMS and #G_FILE_COPY_BACKUP are not
+ * supported.  #G_FILE_COPY_NOFOLLOW_SYMLINKS treated as if it was
+ * always given - if you want to follow symbolic links, you will need
+ * to resolve them manually.
+ *
+ * Beware - do not use this function if @src may be modified, and it's
+ * undesirable for the changes to also be reflected in @dest.  The
+ * best use of this function is in the case where @src and @dest are
+ * read-only, or where @src is a temporary file, and you want to put
+ * it in the final place.
+ */
+gboolean
+gs_file_linkcopy (GFile          *src,
+                  GFile          *dest,
+                  GFileCopyFlags  flags,
+                  GCancellable   *cancellable,
+                  GError        **error)
+{
+  return linkcopy_internal (src, dest, flags, FALSE, cancellable, error);
+}
+
+/**
+ * gs_file_linkcopy_sync_data:
+ * @src: Source file
+ * @dest: Destination file
+ * @flags: flags
+ * @cancellable:
+ * @error:
+ *
+ * This function is similar to gs_file_linkcopy(), except it also uses
+ * gs_file_sync_data() to ensure that @dest is in stable storage
+ * before it is moved into place.
+ */
+gboolean
+gs_file_linkcopy_sync_data (GFile          *src,
+                            GFile          *dest,
+                            GFileCopyFlags  flags,
+                            GCancellable   *cancellable,
+                            GError        **error)
+{
+  return linkcopy_internal (src, dest, flags, TRUE, cancellable, error);
+}
+
+static char *
+gs_file_get_target_path (GFile *file)
+{
+  GFileInfo *info;
+  const char *target;
+  char *path;
+
+  info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+  if (info == NULL)
+    return NULL;
+  target = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
+  path = g_filename_from_uri (target, NULL, NULL);
+  g_object_unref (info);
+
+  return path;
+}
+
+G_LOCK_DEFINE_STATIC (pathname_cache);
+
+/**
+ * gs_file_get_path_cached:
+ *
+ * Like g_file_get_path(), but returns a constant copy so callers
+ * don't need to free the result.
+ */
+const char *
+gs_file_get_path_cached (GFile *file)
+{
+  const char *path;
+  static GQuark _file_path_quark = 0;
+
+  if (G_UNLIKELY (_file_path_quark) == 0)
+    _file_path_quark = g_quark_from_static_string ("gsystem-file-path");
+
+  G_LOCK (pathname_cache);
+
+  path = g_object_get_qdata ((GObject*)file, _file_path_quark);
+  if (!path)
+    {
+      if (g_file_has_uri_scheme (file, "trash") ||
+          g_file_has_uri_scheme (file, "recent"))
+        path = gs_file_get_target_path (file);
+      else
+        path = g_file_get_path (file);
+      if (path == NULL)
+        {
+          G_UNLOCK (pathname_cache);
+          return NULL;
+        }
+      g_object_set_qdata_full ((GObject*)file, _file_path_quark, (char*)path, (GDestroyNotify)g_free);
+    }
+
+  G_UNLOCK (pathname_cache);
+
+  return path;
+}
+
+/**
+ * gs_file_get_basename_cached:
+ *
+ * Like g_file_get_basename(), but returns a constant copy so callers
+ * don't need to free the result.
+ */
+const char *
+gs_file_get_basename_cached (GFile *file)
+{
+  const char *name;
+  static GQuark _file_name_quark = 0;
+
+  if (G_UNLIKELY (_file_name_quark) == 0)
+    _file_name_quark = g_quark_from_static_string ("gsystem-file-name");
+
+  G_LOCK (pathname_cache);
+
+  name = g_object_get_qdata ((GObject*)file, _file_name_quark);
+  if (!name)
+    {
+      name = g_file_get_basename (file);
+      g_object_set_qdata_full ((GObject*)file, _file_name_quark, (char*)name, (GDestroyNotify)g_free);
+    }
+
+  G_UNLOCK (pathname_cache);
+
+  return name;
+}
+
+/**
+ * gs_file_enumerator_iterate:
+ * @direnum: an open #GFileEnumerator
+ * @out_info: (out) (transfer none) (allow-none): Output location for the next #GFileInfo
+ * @out_child: (out) (transfer none) (allow-none): Output location for the next #GFile, or %NULL
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * This is a version of g_file_enumerator_next_file() that's easier to
+ * use correctly from C programs.  With g_file_enumerator_next_file(),
+ * the gboolean return value signifies "end of iteration or error", which
+ * requires allocation of a temporary #GError.
+ *
+ * In contrast, with this function, a %FALSE return from
+ * gs_file_enumerator_iterate() <emphasis>always</emphasis> means
+ * "error".  End of iteration is signaled by @out_info being %NULL.
+ *
+ * Another crucial difference is that the references for @out_info and
+ * @out_child are owned by @direnum (they are cached as hidden
+ * properties).  You must not unref them in your own code.  This makes
+ * memory management significantly easier for C code in combination
+ * with loops.
+ *
+ * Finally, this function optionally allows retrieving a #GFile as
+ * well.
+ *
+ * The code pattern for correctly using gs_file_enumerator_iterate() from C
+ * is:
+ *
+ * |[
+ * direnum = g_file_enumerate_children (file, ...);
+ * while (TRUE)
+ *   {
+ *     GFileInfo *info;
+ *     if (!gs_file_enumerator_iterate (direnum, &info, NULL, cancellable, error))
+ *       goto out;
+ *     if (!info)
+ *       break;
+ *     ... do stuff with "info"; do not unref it! ...
+ *   }
+ * 
+ * out:
+ *   g_object_unref (direnum); // Note: frees the last @info
+ * ]|
+ */
+gboolean
+gs_file_enumerator_iterate (GFileEnumerator  *direnum,
+                            GFileInfo       **out_info,
+                            GFile           **out_child,
+                            GCancellable     *cancellable,
+                            GError          **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+
+  static GQuark cached_info_quark;
+  static GQuark cached_child_quark;
+  static gsize quarks_initialized;
+
+  g_return_val_if_fail (direnum != NULL, FALSE);
+  g_return_val_if_fail (out_info != NULL, FALSE);
+
+  if (g_once_init_enter (&quarks_initialized))
+    {
+      cached_info_quark = g_quark_from_static_string ("gsystem-cached-info");
+      cached_child_quark = g_quark_from_static_string ("gsystem-cached-child");
+      g_once_init_leave (&quarks_initialized, 1);
+    }
+
+  
+  *out_info = g_file_enumerator_next_file (direnum, cancellable, &temp_error);
+  if (out_child)
+    *out_child = NULL;
+  if (temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+  else if (*out_info != NULL)
+    {
+      g_object_set_qdata_full ((GObject*)direnum, cached_info_quark, *out_info, (GDestroyNotify)g_object_unref);
+      if (out_child != NULL)
+        {
+          const char *name = g_file_info_get_name (*out_info);
+          *out_child = g_file_get_child (g_file_enumerator_get_container (direnum), name);
+          g_object_set_qdata_full ((GObject*)direnum, cached_child_quark, *out_child, (GDestroyNotify)g_object_unref);
+        }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * gs_file_rename:
+ * @from: Current path
+ * @to: New path
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * This function wraps the raw Unix function rename().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_rename (GFile          *from,
+                GFile          *to,
+                GCancellable   *cancellable,
+                GError        **error)
+{
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  if (rename (gs_file_get_path_cached (from),
+              gs_file_get_path_cached (to)) < 0)
+    {
+      _set_error_from_errno (error);
+      return FALSE;
+    }
+  return TRUE;
+}
+
+/**
+ * gs_file_unlink:
+ * @path: Path to file
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_file_delete(), except this function does not follow Unix
+ * symbolic links, and will delete a symbolic link even if it's
+ * pointing to a nonexistent file.  In other words, this function
+ * merely wraps the raw Unix function unlink().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_unlink (GFile          *path,
+                GCancellable   *cancellable,
+                GError        **error)
+{
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  if (unlink (gs_file_get_path_cached (path)) < 0)
+    {
+      _set_error_from_errno (error);
+      return FALSE;
+    }
+  return TRUE;
+}
+
+static gboolean
+chown_internal (GFile          *path,
+                gboolean        dereference_links,
+                guint32         owner,
+                guint32         group,
+                GCancellable   *cancellable,
+                GError        **error)
+{
+  gboolean ret = FALSE;
+  int res;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  do
+    if (dereference_links)
+      res = chown (gs_file_get_path_cached (path), owner, group);
+    else
+      res = lchown (gs_file_get_path_cached (path), owner, group);
+  while (G_UNLIKELY (res != 0 && errno == EINTR));
+
+  if (res < 0)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * gs_file_chown:
+ * @path: Path to file
+ * @owner: UNIX owner
+ * @group: UNIX group
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Merely wraps UNIX chown().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_chown (GFile          *path,
+               guint32         owner,
+               guint32         group,
+               GCancellable   *cancellable,
+               GError        **error)
+{
+  return chown_internal (path, TRUE, owner, group, cancellable, error);
+}
+
+/**
+ * gs_file_lchown:
+ * @path: Path to file
+ * @owner: UNIX owner
+ * @group: UNIX group
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Merely wraps UNIX lchown().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_lchown (GFile          *path,
+                guint32         owner,
+                guint32         group,
+                GCancellable   *cancellable,
+                GError        **error)
+{
+  return chown_internal (path, FALSE, owner, group, cancellable, error);
+}
+
+/**
+ * gs_file_chmod:
+ * @path: Path to file
+ * @mode: UNIX mode
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Merely wraps UNIX chmod().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_chmod (GFile          *path,
+               guint           mode,
+               GCancellable   *cancellable,
+               GError        **error)
+{
+  gboolean ret = FALSE;
+  int res;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  do
+    res = chmod (gs_file_get_path_cached (path), mode);
+  while (G_UNLIKELY (res != 0 && errno == EINTR));
+
+  if (res < 0)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * gs_file_ensure_directory:
+ * @dir: Path to create as directory
+ * @with_parents: Also create parent directories
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_file_make_directory(), except does not throw an error if the
+ * directory already exists.
+ */
+gboolean
+gs_file_ensure_directory (GFile         *dir,
+                          gboolean       with_parents, 
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  GFile *parent = NULL;
+
+  if (!g_file_make_directory (dir, cancellable, &temp_error))
+    {
+      if (with_parents &&
+          g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&temp_error);
+
+          parent = g_file_get_parent (dir);
+          if (parent)
+            {
+              if (!gs_file_ensure_directory (parent, TRUE, cancellable, error))
+                goto out;
+            }
+          if (!gs_file_ensure_directory (dir, FALSE, cancellable, error))
+            goto out;
+        }
+      else if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+      else
+        g_clear_error (&temp_error);
+    }
+
+  ret = TRUE;
+ out:
+  g_clear_object (&parent);
+  return ret;
+}
+
+/**
+ * gs_file_ensure_directory_mode:
+ * @dir: Path to create as directory
+ * @mode: Create directory with these permissions
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Wraps UNIX mkdir() function with support for @cancellable, and
+ * uses @error instead of errno.
+ */
+gboolean
+gs_file_ensure_directory_mode (GFile         *dir,
+                               guint          mode,
+                               GCancellable  *cancellable,
+                               GError       **error)
+{
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  if (mkdir (gs_file_get_path_cached (dir), mode) == -1 && errno != EEXIST)
+    {
+      _set_error_from_errno (error);
+      return FALSE;
+    }
+  return TRUE;
+}
+
+/**
+ * gs_file_load_contents_utf8:
+ * @file: Path to file whose contents must be UTF-8
+ * @cancellable:
+ * @error:
+ *
+ * Like g_file_load_contents(), except validates the contents are
+ * UTF-8.
+ */
+gchar *
+gs_file_load_contents_utf8 (GFile         *file,
+                            GCancellable  *cancellable,
+                            GError       **error)
+{
+  gboolean ret = FALSE;
+  gsize len;
+  char *ret_contents = NULL;
+
+  if (!g_file_load_contents (file, cancellable, &ret_contents, &len,
+                             NULL, error))
+    goto out;
+  if (!g_utf8_validate (ret_contents, len, NULL))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_DATA,
+                   "Invalid UTF-8");
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    {
+      g_free (ret_contents);
+      return NULL;
+    }
+  return ret_contents;
+}
+
+static int
+path_common_directory (char *one,
+                       char *two)
+{
+  int dir_index = 0;
+  int i = 0;
+
+  while (*one && *two)
+    {
+      if (*one != *two)
+        break;
+      if (*one == '/')
+        dir_index = i + 1;
+
+      one++;
+      two++;
+      i++;
+    }
+
+  return dir_index;
+}
+
+/**
+ * gs_file_get_relpath:
+ * @one: The first #GFile
+ * @two: The second #GFile
+ *
+ * Like gs_file_get_relative_path(), but does not mandate that
+ * the two files have any parent in common. This function will
+ * instead insert "../" where appropriate.
+ *
+ * Returns: (transfer full): The relative path between the two.
+ */
+gchar *
+gs_file_get_relpath (GFile *one,
+                     GFile *two)
+{
+  gchar *simple_path;
+  gchar *one_path, *one_suffix;
+  gchar *two_path, *two_suffix;
+  GString *path;
+  int i;
+
+  simple_path = g_file_get_relative_path (one, two);
+  if (simple_path)
+    return simple_path;
+
+  one_path = g_file_get_path (one);
+  two_path = g_file_get_path (two);
+
+  i = path_common_directory (one_path, two_path);
+  one_suffix = one_path + i;
+  two_suffix = two_path + i;
+
+  path = g_string_new ("");
+
+  /* For every leftover path segment one has, append "../" so
+   * that we reach the same directory. */
+  while (*one_suffix)
+    {
+      g_string_append (path, "../");
+      one_suffix = strchr (one_suffix, '/');
+      if (one_suffix == NULL)
+        break;
+      one_suffix++;
+    }
+
+  /* And now append the leftover stuff on two's side. */
+  g_string_append (path, two_suffix);
+
+  g_free (one_path);
+  g_free (two_path);
+
+  return g_string_free (path, FALSE);
+}
+
+/**
+ * gs_file_realpath:
+ * @file: A #GFile
+ *
+ * Return a #GFile that contains the same path with symlinks
+ * followed. That is, it's a #GFile whose path is the result
+ * of calling realpath() on @file.
+ *
+ * Returns: (allow-none) (transfer full): A new #GFile or %NULL if @file is invalid
+ */
+GFile *
+gs_file_realpath (GFile *file)
+{
+  gchar *path;
+  gchar path_real[PATH_MAX];
+
+  path = g_file_get_path (file);
+
+  if (realpath ((const char *) path, path_real) == NULL)
+    {
+      g_free (path);
+      return NULL;
+    }
+
+  g_free (path);
+  return g_file_new_for_path (path_real);
+}
+
+#ifdef GSYSTEM_CONFIG_XATTRS
+static char *
+canonicalize_xattrs (char    *xattr_string,
+                     size_t   len)
+{
+  char *p;
+  GSList *xattrs = NULL;
+  GSList *iter;
+  GString *result;
+
+  result = g_string_new (0);
+
+  p = xattr_string;
+  while (p < xattr_string+len)
+    {
+      xattrs = g_slist_prepend (xattrs, p);
+      p += strlen (p) + 1;
+    }
+
+  xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
+  for (iter = xattrs; iter; iter = iter->next) {
+    g_string_append (result, iter->data);
+    g_string_append_c (result, '\0');
+  }
+
+  g_slist_free (xattrs);
+  return g_string_free (result, FALSE);
+}
+
+static GVariant *
+variant_new_ay_bytes (GBytes *bytes)
+{
+  gsize size;
+  gconstpointer data;
+  data = g_bytes_get_data (bytes, &size);
+  g_bytes_ref (bytes);
+  return g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, size,
+                                  TRUE, (GDestroyNotify)g_bytes_unref, bytes);
+}
+
+static gboolean
+read_xattr_name_array (const char *path,
+                       const char *xattrs,
+                       size_t      len,
+                       GVariantBuilder *builder,
+                       GError  **error)
+{
+  gboolean ret = FALSE;
+  const char *p;
+
+  p = xattrs;
+  while (p < xattrs+len)
+    {
+      ssize_t bytes_read;
+      char *buf;
+      GBytes *bytes = NULL;
+
+      bytes_read = lgetxattr (path, p, NULL, 0);
+      if (bytes_read < 0)
+        {
+          _set_error_from_errno (error);
+          g_prefix_error (error, "lgetxattr (%s, %s) failed: ", path, p);
+          goto out;
+        }
+      if (bytes_read == 0)
+        continue;
+
+      buf = g_malloc (bytes_read);
+      bytes = g_bytes_new_take (buf, bytes_read);
+      if (lgetxattr (path, p, buf, bytes_read) < 0)
+        {
+          g_bytes_unref (bytes);
+          _set_error_from_errno (error);
+          g_prefix_error (error, "lgetxattr (%s, %s) failed: ", path, p);
+          goto out;
+        }
+      
+      g_variant_builder_add (builder, "(@ay@ay)",
+                             g_variant_new_bytestring (p),
+                             variant_new_ay_bytes (bytes));
+
+      p = p + strlen (p) + 1;
+      g_bytes_unref (bytes);
+    }
+  
+  ret = TRUE;
+ out:
+  return ret;
+}
+#endif
+
+static gboolean
+get_xattrs_impl (GFile          *f,
+                 GVariantBuilder *builder,
+                 GCancellable   *cancellable,
+                 GError        **error)
+{
+#ifdef GSYSTEM_CONFIG_XATTRS
+  gboolean ret = FALSE;
+  const char *path;
+  ssize_t bytes_read;
+  char *xattr_names = NULL;
+  char *xattr_names_canonical = NULL;
+
+  path = gs_file_get_path_cached (f);
+
+  bytes_read = llistxattr (path, NULL, 0);
+
+  if (bytes_read < 0)
+    {
+      if (errno != ENOTSUP)
+        {
+          _set_error_from_errno (error);
+          g_prefix_error (error, "llistxattr (%s) failed: ", path);
+          goto out;
+        }
+    }
+  else if (bytes_read > 0)
+    {
+      xattr_names = g_malloc (bytes_read);
+      if (llistxattr (path, xattr_names, bytes_read) < 0)
+        {
+          _set_error_from_errno (error);
+          g_prefix_error (error, "llistxattr (%s) failed: ", path);
+          goto out;
+        }
+      xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read);
+      
+      if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, builder, error))
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  g_clear_pointer (&xattr_names, g_free);
+  g_clear_pointer (&xattr_names_canonical, g_free);
+  return ret;
+#else
+  return TRUE;
+#endif
+}
+
+/**
+ * gs_file_get_all_xattrs:
+ * @f: a #GFile
+ * @out_xattrs: (out): A new #GVariant containing the extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Read all extended attributes of @f in a canonical sorted order, and
+ * set @out_xattrs with the result.
+ *
+ * If the filesystem does not support extended attributes, @out_xattrs
+ * will have 0 elements, and this function will return successfully.
+ */
+gboolean
+gs_file_get_all_xattrs (GFile         *f,
+                        GVariant     **out_xattrs,
+                        GCancellable  *cancellable,
+                        GError       **error)
+{
+  gboolean ret = FALSE;
+  GVariantBuilder builder;
+  gboolean builder_initialized = FALSE;
+  GVariant *ret_xattrs = NULL;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
+  builder_initialized = TRUE;
+
+  if (!get_xattrs_impl (f, &builder,
+                        cancellable, error))
+    goto out;
+
+  ret_xattrs = g_variant_builder_end (&builder);
+  builder_initialized = FALSE;
+  g_variant_ref_sink (ret_xattrs);
+  
+  ret = TRUE;
+  gs_transfer_out_value (out_xattrs, &ret_xattrs);
+ out:
+  g_clear_pointer (&ret_xattrs, g_variant_unref);
+  if (!builder_initialized)
+    g_variant_builder_clear (&builder);
+  return ret;
+}
+
+/**
+ * gs_fd_set_all_xattrs:
+ * @fd: File descriptor
+ * @xattrs: Extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * For each attribute in @xattrs, set its value on the file or
+ * directory referred to by @fd.  This function does not remove any
+ * attributes not in @xattrs.
+ */
+gboolean
+gs_fd_set_all_xattrs (int            fd,
+                      GVariant      *xattrs,
+                      GCancellable  *cancellable,
+                      GError       **error)
+{
+#ifdef GSYSTEM_CONFIG_XATTRS
+  gboolean ret = FALSE;
+  int i, n;
+
+  n = g_variant_n_children (xattrs);
+  for (i = 0; i < n; i++)
+    {
+      const guint8* name;
+      const guint8* value_data;
+      GVariant *value = NULL;
+      gsize value_len;
+      int res;
+
+      g_variant_get_child (xattrs, i, "(^&ay@ay)",
+                           &name, &value);
+      value_data = g_variant_get_fixed_array (value, &value_len, 1);
+      
+      do
+        res = fsetxattr (fd, (char*)name, (char*)value_data, value_len, 0);
+      while (G_UNLIKELY (res == -1 && errno == EINTR));
+      g_variant_unref (value);
+      if (G_UNLIKELY (res == -1))
+        {
+          _set_error_from_errno (error);
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+#else
+  return TRUE;
+#endif
+}
+
+/**
+ * gs_file_set_all_xattrs:
+ * @file: File descriptor
+ * @xattrs: Extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * For each attribute in @xattrs, set its value on the file or
+ * directory referred to by @file.  This function does not remove any
+ * attributes not in @xattrs.
+ */
+gboolean
+gs_file_set_all_xattrs (GFile         *file,
+                        GVariant      *xattrs,
+                        GCancellable  *cancellable,
+                        GError       **error)
+{
+#ifdef GSYSTEM_CONFIG_XATTRS
+  gboolean ret = FALSE;
+  const char *path;
+  int i, n;
+
+  path = gs_file_get_path_cached (file);
+
+  n = g_variant_n_children (xattrs);
+  for (i = 0; i < n; i++)
+    {
+      const guint8* name;
+      GVariant *value;
+      const guint8* value_data;
+      gsize value_len;
+      gboolean loop_err;
+
+      g_variant_get_child (xattrs, i, "(^&ay@ay)",
+                           &name, &value);
+      value_data = g_variant_get_fixed_array (value, &value_len, 1);
+      
+      loop_err = lsetxattr (path, (char*)name, (char*)value_data, value_len, 0) < 0;
+      g_clear_pointer (&value, (GDestroyNotify) g_variant_unref);
+      if (loop_err)
+        {
+          _set_error_from_errno (error);
+          g_prefix_error (error, "lsetxattr (%s, %s) failed: ", path, name);
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+#else
+  return TRUE;
+#endif
+}
diff --git a/libgsystem/gsystem-file-utils.h b/libgsystem/gsystem-file-utils.h
new file mode 100644
index 00000000..021aebb7
--- /dev/null
+++ b/libgsystem/gsystem-file-utils.h
@@ -0,0 +1,172 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSYSTEM_FILE_UTILS_H__
+#define __GSYSTEM_FILE_UTILS_H__
+
+#include <gio/gio.h>
+#include <sys/stat.h>
+
+G_BEGIN_DECLS
+
+const char *gs_file_get_path_cached (GFile *file);
+
+const char *gs_file_get_basename_cached (GFile *file);
+
+gboolean gs_file_enumerator_iterate (GFileEnumerator  *direnum,
+                                     GFileInfo       **out_info,
+                                     GFile           **out_child,
+                                     GCancellable     *cancellable,
+                                     GError          **error);
+
+gboolean gs_file_openat_noatime (int            dfd,
+                                 const char    *name,
+                                 int           *ret_fd,
+                                 GCancellable  *cancellable,
+                                 GError       **error);
+
+GInputStream *gs_file_read_noatime (GFile         *file,
+                                    GCancellable  *cancellable,
+                                    GError       **error);
+GMappedFile *gs_file_map_noatime (GFile         *file,
+                                  GCancellable  *cancellable,
+                                  GError       **error);
+
+#ifndef __GI_SCANNER__
+gboolean gs_stream_fstat (GFileDescriptorBased *stream,
+                          struct stat          *out_stbuf,
+                          GCancellable         *cancellable,
+                          GError              **error);
+
+#endif
+
+#if GLIB_CHECK_VERSION(2,34,0)
+GBytes *gs_file_map_readonly (GFile         *file,
+                              GCancellable  *cancellable,
+                              GError       **error);
+#endif
+
+gboolean gs_file_sync_data (GFile          *file,
+                            GCancellable   *cancellable,
+                            GError        **error);
+
+char * gs_fileutil_gen_tmp_name (const char *prefix,
+                                 const char *suffix);
+
+gboolean gs_file_open_dir_fd (GFile         *path,
+                              int           *out_fd,
+                              GCancellable  *cancellable,
+                              GError       **error);
+
+gboolean gs_file_open_in_tmpdir_at (int                tmpdir_fd,
+                                    int                mode,
+                                    char             **out_name,
+                                    GOutputStream    **out_stream,
+                                    GCancellable      *cancellable,
+                                    GError           **error);
+
+gboolean gs_file_open_in_tmpdir (GFile             *tmpdir,
+                                 int                mode,
+                                 GFile            **out_file,
+                                 GOutputStream    **out_stream,
+                                 GCancellable      *cancellable,
+                                 GError           **error);
+
+gboolean gs_file_create (GFile          *file,
+                         int             mode,
+                         GOutputStream **out_stream,
+                         GCancellable   *cancellable,
+                         GError        **error);
+
+gboolean gs_file_linkcopy (GFile          *src,
+                           GFile          *dest,
+                           GFileCopyFlags  flags,
+                           GCancellable   *cancellable,
+                           GError        **error);
+
+gboolean gs_file_linkcopy_sync_data (GFile          *src,
+                                     GFile          *dest,
+                                     GFileCopyFlags  flags,
+                                     GCancellable   *cancellable,
+                                     GError        **error);
+
+gboolean gs_file_rename (GFile          *from,
+                         GFile          *to,
+                         GCancellable   *cancellable,
+                         GError        **error);
+
+gboolean gs_file_unlink (GFile          *path,
+                         GCancellable   *cancellable,
+                         GError        **error);
+
+gboolean gs_file_chown (GFile          *path,
+                        guint32         owner,
+                        guint32         group,
+                        GCancellable   *cancellable,
+                        GError        **error);
+
+gboolean gs_file_lchown (GFile          *path,
+                         guint32         owner,
+                         guint32         group,
+                         GCancellable   *cancellable,
+                         GError        **error);
+
+gboolean gs_file_chmod (GFile          *path,
+                        guint           mode,
+                        GCancellable   *cancellable,
+                        GError        **error);
+
+gboolean gs_file_ensure_directory (GFile          *dir,
+                                   gboolean        with_parents,
+                                   GCancellable   *cancellable,
+                                   GError        **error);
+
+gboolean gs_file_ensure_directory_mode (GFile          *dir,
+                                        guint           mode,
+                                        GCancellable   *cancellable,
+                                        GError        **error);
+
+gchar *gs_file_load_contents_utf8 (GFile         *file,
+                                   GCancellable  *cancellable,
+                                   GError       **error);
+
+gchar *gs_file_get_relpath (GFile *one,
+                            GFile *two);
+
+GFile * gs_file_realpath (GFile *file);
+
+gboolean gs_file_get_all_xattrs (GFile         *f,
+                                 GVariant     **out_xattrs,
+                                 GCancellable  *cancellable,
+                                 GError       **error);
+
+gboolean gs_fd_set_all_xattrs (int            fd,
+                               GVariant      *xattrs,
+                               GCancellable  *cancellable,
+                               GError       **error);
+
+gboolean gs_file_set_all_xattrs (GFile         *file,
+                                 GVariant      *xattrs,
+                                 GCancellable  *cancellable,
+                                 GError       **error);
+
+G_END_DECLS
+
+#endif
diff --git a/libgsystem/gsystem-glib-compat.h b/libgsystem/gsystem-glib-compat.h
new file mode 100644
index 00000000..6fd59c65
--- /dev/null
+++ b/libgsystem/gsystem-glib-compat.h
@@ -0,0 +1,54 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LIBGSYSTEM_GLIB_COMPAT__
+#define __LIBGSYSTEM_GLIB_COMPAT__
+
+#include <gio/gio.h>
+
+#if !GLIB_CHECK_VERSION(2,34,0)
+static inline void
+g_type_ensure (GType type)
+{
+  if (G_UNLIKELY (type == (GType)-1))
+    g_error ("can't happen");
+}
+
+#define g_clear_pointer(pp, destroy)	  \
+	G_STMT_START { \
+		G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer)); \
+		/* Only one access, please */ \
+		gpointer *_pp = (gpointer *) (pp); \
+		gpointer _p; \
+		/* This assignment is needed to avoid a gcc warning */ \
+		GDestroyNotify _destroy = (GDestroyNotify) (destroy); \
+	  \
+		(void) (0 ? (gpointer) *(pp) : 0); \
+		do \
+			_p = g_atomic_pointer_get (_pp); \
+		while G_UNLIKELY (!g_atomic_pointer_compare_and_exchange (_pp, _p, NULL)); \
+	  \
+		if (_p) \
+			_destroy (_p); \
+	} G_STMT_END
+
+#endif
+
+#endif
diff --git a/libgsystem/gsystem-local-alloc.c b/libgsystem/gsystem-local-alloc.c
new file mode 100644
index 00000000..add3fcb3
--- /dev/null
+++ b/libgsystem/gsystem-local-alloc.c
@@ -0,0 +1,72 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "gsystem-local-alloc.h"
+
+/**
+ * SECTION:gslocalalloc
+ * @title: GSystem local allocation
+ * @short_description: Release local variables automatically when they go out of scope
+ *
+ * These macros leverage the GCC extension __attribute__ ((cleanup))
+ * to allow calling a cleanup function such as g_free() when a
+ * variable goes out of scope.  See <ulink
+ * url="http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html">
+ * for more information on the attribute.
+ *
+ * The provided macros make it easy to use the cleanup attribute for
+ * types that come with GLib.  The primary two are #gs_free and
+ * #gs_unref_object, which correspond to g_free() and
+ * g_object_unref(), respectively.
+ *
+ * The rationale behind this is that particularly when handling error
+ * paths, it can be very tricky to ensure the right variables are
+ * freed.  With this, one simply applies gs_lobj to a
+ * locally-allocated #GFile for example, and it will be automatically
+ * unreferenced when it goes out of scope.
+ *
+ * Note - you should only use these macros for <emphasis>stack
+ * allocated</emphasis> variables.  They don't provide garbage
+ * collection or let you avoid freeing things.  They're simply a
+ * compiler assisted deterministic mechanism for calling a cleanup
+ * function when a stack frame ends.
+ *
+ * <example id="gs-lfree"><title>Calling g_free automatically</title>
+ * <programlisting>
+ *
+ * GFile *
+ * create_file (GError **error)
+ * {
+ *   gs_free char *random_id = NULL;
+ *
+ *   if (!prepare_file (error))
+ *     return NULL;
+ *
+ *   random_id = alloc_random_id ();
+ *
+ *   return create_file_real (error);
+ *   // Note that random_id is freed here automatically
+ * }
+ * </programlisting>
+ * </example>
+ *
+ */
diff --git a/libgsystem/gsystem-local-alloc.h b/libgsystem/gsystem-local-alloc.h
new file mode 100644
index 00000000..34db2979
--- /dev/null
+++ b/libgsystem/gsystem-local-alloc.h
@@ -0,0 +1,164 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSYSTEM_LOCAL_ALLOC_H__
+#define __GSYSTEM_LOCAL_ALLOC_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GS_DEFINE_CLEANUP_FUNCTION(Type, name, func) \
+  static inline void name (void *v) \
+  { \
+    func (*(Type*)v); \
+  }
+
+#define GS_DEFINE_CLEANUP_FUNCTION0(Type, name, func) \
+  static inline void name (void *v) \
+  { \
+    if (*(Type*)v) \
+      func (*(Type*)v); \
+  }
+
+/* These functions shouldn't be invoked directly;
+ * they are stubs that:
+ * 1) Take a pointer to the location (typically itself a pointer).
+ * 2) Provide %NULL-safety where it doesn't exist already (e.g. g_object_unref)
+ */
+GS_DEFINE_CLEANUP_FUNCTION0(GArray*, gs_local_array_unref, g_array_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GBytes*, gs_local_bytes_unref, g_bytes_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GChecksum*, gs_local_checksum_free, g_checksum_free)
+GS_DEFINE_CLEANUP_FUNCTION0(GError*, gs_local_free_error, g_error_free)
+GS_DEFINE_CLEANUP_FUNCTION0(GHashTable*, gs_local_hashtable_unref, g_hash_table_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GObject*, gs_local_obj_unref, g_object_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GPtrArray*, gs_local_ptrarray_unref, g_ptr_array_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariant*, gs_local_variant_unref, g_variant_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariantBuilder*, gs_local_variant_builder_unref, g_variant_builder_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariantIter*, gs_local_variant_iter_free, g_variant_iter_free)
+
+GS_DEFINE_CLEANUP_FUNCTION(char**, gs_local_strfreev, g_strfreev)
+GS_DEFINE_CLEANUP_FUNCTION(void*, gs_local_free, g_free)
+
+/**
+ * gs_free:
+ *
+ * Call g_free() on a variable location when it goes out of scope.
+ */
+#define gs_free __attribute__ ((cleanup(gs_local_free)))
+
+/**
+ * gs_unref_object:
+ *
+ * Call g_object_unref() on a variable location when it goes out of
+ * scope.  Note that unlike g_object_unref(), the variable may be
+ * %NULL.
+ */
+#define gs_unref_object __attribute__ ((cleanup(gs_local_obj_unref)))
+
+/**
+ * gs_unref_variant:
+ *
+ * Call g_variant_unref() on a variable location when it goes out of
+ * scope.  Note that unlike g_variant_unref(), the variable may be
+ * %NULL.
+ */
+#define gs_unref_variant __attribute__ ((cleanup(gs_local_variant_unref)))
+
+/**
+ * gs_free_variant_iter:
+ *
+ * Call g_variant_iter_free() on a variable location when it goes out of
+ * scope.
+ */
+#define gs_free_variant_iter __attribute__ ((cleanup(gs_local_variant_iter_free)))
+
+/**
+ * gs_free_variant_builder:
+ *
+ * Call g_variant_builder_unref() on a variable location when it goes out of
+ * scope.
+ */
+#define gs_unref_variant_builder __attribute__ ((cleanup(gs_local_variant_builder_unref)))
+
+/**
+ * gs_unref_array:
+ *
+ * Call g_array_unref() on a variable location when it goes out of
+ * scope.  Note that unlike g_array_unref(), the variable may be
+ * %NULL.
+
+ */
+#define gs_unref_array __attribute__ ((cleanup(gs_local_array_unref)))
+
+/**
+ * gs_unref_ptrarray:
+ *
+ * Call g_ptr_array_unref() on a variable location when it goes out of
+ * scope.  Note that unlike g_ptr_array_unref(), the variable may be
+ * %NULL.
+
+ */
+#define gs_unref_ptrarray __attribute__ ((cleanup(gs_local_ptrarray_unref)))
+
+/**
+ * gs_unref_hashtable:
+ *
+ * Call g_hash_table_unref() on a variable location when it goes out
+ * of scope.  Note that unlike g_hash_table_unref(), the variable may
+ * be %NULL.
+ */
+#define gs_unref_hashtable __attribute__ ((cleanup(gs_local_hashtable_unref)))
+
+/**
+ * gs_free_checksum:
+ *
+ * Call g_checksum_free() on a variable location when it goes out
+ * of scope.  Note that unlike g_checksum_free(), the variable may
+ * be %NULL.
+ */
+#define gs_free_checksum __attribute__ ((cleanup(gs_local_checksum_free)))
+
+/**
+ * gs_unref_bytes:
+ *
+ * Call g_bytes_unref() on a variable location when it goes out
+ * of scope.  Note that unlike g_bytes_unref(), the variable may
+ * be %NULL.
+ */
+#define gs_unref_bytes __attribute__ ((cleanup(gs_local_bytes_unref)))
+
+/**
+ * gs_strfreev:
+ *
+ * Call g_strfreev() on a variable location when it goes out of scope.
+ */
+#define gs_strfreev __attribute__ ((cleanup(gs_local_strfreev)))
+
+/**
+ * gs_free_error:
+ *
+ * Call g_error_free() on a variable location when it goes out of scope.
+ */
+#define gs_free_error __attribute__ ((cleanup(gs_local_free_error)))
+
+G_END_DECLS
+
+#endif
diff --git a/libgsystem/gsystem-log.c b/libgsystem/gsystem-log.c
new file mode 100644
index 00000000..7b031453
--- /dev/null
+++ b/libgsystem/gsystem-log.c
@@ -0,0 +1,167 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 William Jon McCann <mccann@redhat.com>
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#ifdef ENABLE_SYSTEMD_JOURNAL
+#define SD_JOURNAL_SUPPRESS_LOCATION
+#include <systemd/sd-journal.h>
+#endif
+#include <glib-unix.h>
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+
+/**
+ * gs_log_structured:
+ * @message: Text message to send 
+ * @keys: (allow-none) (array zero-terminated=1) (element-type utf8): Optional structured data
+ * 
+ * Log structured data in an operating-system specific fashion.  The
+ * parameter @opts should be an array of UTF-8 KEY=VALUE strings.
+ * This function does not support binary data.  See
+ * http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
+ * for more information about fields that can be used on a systemd
+ * system.
+ */
+void
+gs_log_structured (const char *message,
+                   const char *const *keys)
+{
+#ifdef ENABLE_SYSTEMD_JOURNAL
+    const char *const*iter;
+    char *msgkey;
+    guint i, n_opts;
+    struct iovec *iovs;
+
+    for (n_opts = 0, iter = keys; *iter; iter++, n_opts++)
+        ;
+
+    n_opts++; /* Add one for MESSAGE= */
+    iovs = g_alloca (sizeof (struct iovec) * n_opts);
+    
+    for (i = 0, iter = keys; *iter; iter++, i++) {
+        iovs[i].iov_base = (char*)keys[i];
+        iovs[i].iov_len = strlen (keys[i]);
+    }
+    g_assert(i == n_opts-1);
+    msgkey = g_strconcat ("MESSAGE=", message, NULL);
+    iovs[i].iov_base = msgkey;
+    iovs[i].iov_len = strlen (msgkey);
+    
+    // The code location isn't useful since we're wrapping
+    sd_journal_sendv (iovs, n_opts);
+    
+    g_free (msgkey);
+#else
+    g_print ("%s\n", message);
+#endif
+}
+
+/**
+ * gs_stdout_is_journal:
+ *
+ * Use this function when you want your code to behave differently
+ * depeneding on whether your program was started as a systemd unit,
+ * or e.g. interactively at a terminal.
+ *
+ * Returns: %TRUE if stdout is (probably) connnected to the systemd journal
+ */
+gboolean
+gs_stdout_is_journal (void)
+{
+  static gsize initialized;
+  static gboolean stdout_is_socket;
+
+  if (g_once_init_enter (&initialized))
+    {
+      guint64 pid = (guint64) getpid ();
+      char *fdpath = g_strdup_printf ("/proc/%" G_GUINT64_FORMAT "/fd/1", pid);
+      char buf[1024];
+      ssize_t bytes_read;
+
+      if ((bytes_read = readlink (fdpath, buf, sizeof(buf) - 1)) != -1)
+        {
+          buf[bytes_read] = '\0';
+          stdout_is_socket = g_str_has_prefix (buf, "socket:");
+        }
+      else
+        stdout_is_socket = FALSE;
+
+      g_free (fdpath);
+      g_once_init_leave (&initialized, TRUE);
+    }
+
+  return stdout_is_socket;
+}
+
+/**
+ * gs_log_structured_print:
+ * @message: A message to log
+ * @keys: (allow-none) (array zero-terminated=1) (element-type utf8): Optional structured data
+ *
+ * Like gs_log_structured(), but also print to standard output (if it
+ * is not already connected to the system log).
+ */
+void
+gs_log_structured_print (const char *message,
+                         const char *const *keys)
+{
+  gs_log_structured (message, keys);
+
+#ifdef ENABLE_SYSTEMD_JOURNAL
+  if (!gs_stdout_is_journal ())
+    g_print ("%s\n", message);
+#endif
+}
+
+/**
+ * gs_log_structured_print_id_v:
+ * @message_id: A unique MESSAGE_ID
+ * @format: A format string
+ *
+ * The provided @message_id is a unique MESSAGE_ID (see <ulink url="http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html"> for more information).
+ *
+ * This function otherwise acts as gs_log_structured_print(), taking
+ * @format as a format string.
+ */
+void
+gs_log_structured_print_id_v (const char *message_id,
+                              const char *format,
+                              ...)
+{
+  char *keys[] = { NULL, NULL };
+  char *msg;
+  va_list args;
+
+  va_start (args, format);
+  msg = g_strdup_vprintf (format, args);
+  va_end (args);
+
+  keys[0] = g_strconcat ("MESSAGE_ID=", message_id, NULL);
+  gs_log_structured_print (msg, (const char *const *)keys);
+  g_free (keys[0]);
+  g_free (msg);
+}
diff --git a/libgsystem/gsystem-log.h b/libgsystem/gsystem-log.h
new file mode 100644
index 00000000..80cfc341
--- /dev/null
+++ b/libgsystem/gsystem-log.h
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSYSTEM_LOG_H__
+#define __GSYSTEM_LOG_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+gboolean gs_stdout_is_journal (void);
+
+void gs_log_structured (const char *message,
+                        const char *const *keys);
+
+void gs_log_structured_print (const char *message,
+                              const char *const *keys);
+
+void gs_log_structured_print_id_v (const char *message_id,
+                                   const char *format,
+                                   ...) G_GNUC_PRINTF (2, 3);
+
+G_END_DECLS
+
+#endif
diff --git a/libgsystem/gsystem-shutil.c b/libgsystem/gsystem-shutil.c
new file mode 100644
index 00000000..8029dd9c
--- /dev/null
+++ b/libgsystem/gsystem-shutil.c
@@ -0,0 +1,460 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 William Jon McCann <mccann@redhat.com>
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+#include "gsystem-glib-compat.h"
+#include <glib-unix.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+/* Taken from systemd/src/shared/util.h */
+union dirent_storage {
+        struct dirent dent;
+        guint8 storage[offsetof(struct dirent, d_name) +
+                        ((NAME_MAX + 1 + sizeof(long)) & ~(sizeof(long) - 1))];
+};
+
+static inline void
+_set_error_from_errno (GError **error)
+{
+  int errsv = errno;
+  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                       g_strerror (errsv));
+}
+
+static gboolean
+copy_xattrs_from_file_to_fd (GFile         *src,
+                             int            dest_fd,
+                             GCancellable  *cancellable,
+                             GError       **error)
+{
+  gboolean ret = FALSE;
+  GVariant *src_xattrs = NULL;
+
+  if (!gs_file_get_all_xattrs (src, &src_xattrs, cancellable, error))
+    goto out;
+
+  if (src_xattrs)
+    {
+      if (!gs_fd_set_all_xattrs (dest_fd, src_xattrs, cancellable, error))
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  g_clear_pointer (&src_xattrs, g_variant_unref);
+  return ret;
+}
+
+typedef enum {
+  GS_CP_MODE_NONE,
+  GS_CP_MODE_HARDLINK,
+  GS_CP_MODE_COPY_ALL
+} GsCpMode;
+
+static gboolean
+cp_internal (GFile         *src,
+             GFile         *dest,
+             GsCpMode       mode,
+             GCancellable  *cancellable,
+             GError       **error)
+{
+  gboolean ret = FALSE;
+  GFileEnumerator *enumerator = NULL;
+  GFileInfo *src_info = NULL;
+  GFile *dest_child = NULL;
+  int dest_dfd = -1;
+  int r;
+
+  enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode",
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          cancellable, error);
+  if (!enumerator)
+    goto out;
+
+  src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \
+                                "time::modified,time::modified-usec,time::access,time::access-usec",
+                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                cancellable, error);
+  if (!src_info)
+    goto out;
+
+  do
+    r = mkdir (gs_file_get_path_cached (dest), 0755);
+  while (G_UNLIKELY (r == -1 && errno == EINTR));
+  if (r == -1)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  if (mode != GS_CP_MODE_NONE)
+    {
+      if (!gs_file_open_dir_fd (dest, &dest_dfd,
+                                cancellable, error))
+        goto out;
+
+      do
+        r = fchown (dest_dfd,
+                    g_file_info_get_attribute_uint32 (src_info, "unix::uid"),
+                    g_file_info_get_attribute_uint32 (src_info, "unix::gid"));
+      while (G_UNLIKELY (r == -1 && errno == EINTR));
+      if (r == -1)
+        {
+          _set_error_from_errno (error);
+          goto out;
+        }
+
+      do
+        r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode"));
+      while (G_UNLIKELY (r == -1 && errno == EINTR));
+
+      if (!copy_xattrs_from_file_to_fd (src, dest_dfd, cancellable, error))
+        goto out;
+
+      if (dest_dfd != -1)
+        {
+          (void) close (dest_dfd);
+          dest_dfd = -1;
+        }
+    }
+
+  while (TRUE)
+    {
+      GFileInfo *file_info = NULL;
+      GFile *src_child = NULL;
+
+      if (!gs_file_enumerator_iterate (enumerator, &file_info, &src_child,
+                                       cancellable, error))
+        goto out;
+      if (!file_info)
+        break;
+
+      if (dest_child) g_object_unref (dest_child);
+      dest_child = g_file_get_child (dest, g_file_info_get_name (file_info));
+
+      if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+        {
+          if (!cp_internal (src_child, dest_child, mode,
+                            cancellable, error))
+            goto out;
+        }
+      else
+        {
+          gboolean did_link = FALSE;
+          (void) unlink (gs_file_get_path_cached (dest_child));
+          if (mode == GS_CP_MODE_HARDLINK)
+            {
+              if (link (gs_file_get_path_cached (src_child), gs_file_get_path_cached (dest_child)) == -1)
+                {
+                  if (!(errno == EMLINK || errno == EXDEV))
+                    {
+                      int errsv = errno;
+                      g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                           g_strerror (errsv));
+                      goto out;
+                    }
+                  /* We failed to hardlink; fall back to copying all; this will
+                   * affect subsequent directory copies too.
+                   */
+                  mode = GS_CP_MODE_COPY_ALL;
+                }
+              else
+                did_link = TRUE;
+            }
+          if (!did_link)
+            {
+              GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS;
+              if (mode == GS_CP_MODE_COPY_ALL)
+                copyflags |= G_FILE_COPY_ALL_METADATA;
+              if (!g_file_copy (src_child, dest_child, copyflags,
+                                cancellable, NULL, NULL, error))
+                goto out;
+            }
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (dest_dfd != -1)
+    (void) close (dest_dfd);
+  g_clear_object (&src_info);
+  g_clear_object (&enumerator);
+  g_clear_object (&dest_child);
+  return ret;
+}
+
+/**
+ * gs_shutil_cp_al_or_fallback:
+ * @src: Source path
+ * @dest: Destination path
+ * @cancellable:
+ * @error:
+ *
+ * Recursively copy path @src (which must be a directory) to the
+ * target @dest.  If possible, hardlinks are used; if a hardlink is
+ * not possible, a regular copy is created.  Any existing files are
+ * overwritten.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+gs_shutil_cp_al_or_fallback (GFile         *src,
+                             GFile         *dest,
+                             GCancellable  *cancellable,
+                             GError       **error)
+{
+  return cp_internal (src, dest, GS_CP_MODE_HARDLINK,
+                      cancellable, error);
+}
+
+/**
+ * gs_shutil_cp_a:
+ * @src: Source path
+ * @dest: Destination path
+ * @cancellable:
+ * @error:
+ *
+ * Recursively copy path @src (which must be a directory) to the
+ * target @dest.  Any existing files are overwritten.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+gs_shutil_cp_a (GFile         *src,
+                GFile         *dest,
+                GCancellable  *cancellable,
+                GError       **error)
+{
+  return cp_internal (src, dest, GS_CP_MODE_COPY_ALL,
+                      cancellable, error);
+}
+
+static unsigned char
+struct_stat_to_dt (struct stat *stbuf)
+{
+  if (S_ISDIR (stbuf->st_mode))
+    return DT_DIR;
+  if (S_ISREG (stbuf->st_mode))
+    return DT_REG;
+  if (S_ISCHR (stbuf->st_mode))
+    return DT_CHR;
+  if (S_ISBLK (stbuf->st_mode))
+    return DT_BLK;
+  if (S_ISFIFO (stbuf->st_mode))
+    return DT_FIFO;
+  if (S_ISLNK (stbuf->st_mode))
+    return DT_LNK;
+  if (S_ISSOCK (stbuf->st_mode))
+    return DT_SOCK;
+  return DT_UNKNOWN;
+}
+
+static gboolean
+gs_shutil_rm_rf_children (DIR                *dir,
+                          GCancellable       *cancellable,
+                          GError            **error)
+{
+  gboolean ret = FALSE;
+  int dfd;
+  DIR *child_dir = NULL;
+  struct dirent *dent;
+  union dirent_storage buf;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    goto out;
+
+  dfd = dirfd (dir);
+
+  while (readdir_r (dir, &buf.dent, &dent) == 0)
+    {
+      if (dent == NULL)
+        break;
+      if (dent->d_type == DT_UNKNOWN)
+        {
+          struct stat stbuf;
+          if (fstatat (dfd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
+            {
+              int errsv = errno;
+              if (errsv == ENOENT)
+                continue;
+              else
+                {
+                  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                       g_strerror (errsv));
+                  goto out;
+                }
+            }
+          dent->d_type = struct_stat_to_dt (&stbuf);
+          /* Assume unknown types are just treated like regular files */
+          if (dent->d_type == DT_UNKNOWN)
+            dent->d_type = DT_REG;
+        }
+
+      if (strcmp (dent->d_name, ".") == 0 || strcmp (dent->d_name, "..") == 0)
+        continue;
+          
+      if (dent->d_type == DT_DIR)
+        {
+          int child_dfd = openat (dfd, dent->d_name, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
+
+          if (child_dfd == -1)
+            {
+              if (errno == ENOENT)
+                continue;
+              else
+                {
+                  int errsv = errno;
+                  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                       g_strerror (errsv));
+                  goto out;
+                }
+            }
+
+          child_dir = fdopendir (child_dfd);
+          if (!child_dir)
+            {
+              int errsv = errno;
+              g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                   g_strerror (errsv));
+              goto out;
+            }
+
+          if (!gs_shutil_rm_rf_children (child_dir, cancellable, error))
+            goto out;
+
+          if (unlinkat (dfd, dent->d_name, AT_REMOVEDIR) == -1)
+            {
+              int errsv = errno;
+              g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                   g_strerror (errsv));
+              goto out;
+            }
+
+          (void) closedir (child_dir);
+          child_dir = NULL;
+        }
+      else
+        {
+          if (unlinkat (dfd, dent->d_name, 0) == -1)
+            {
+              int errsv = errno;
+              if (errno != ENOENT)
+                {
+                  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                       g_strerror (errsv));
+                  goto out;
+                }
+            }
+        }
+    }
+  /* Ignore error result from readdir_r, that's what others
+   * seem to do =(
+   */
+
+  ret = TRUE;
+ out:
+  if (child_dir) (void) closedir (child_dir);
+  return ret;
+}
+
+/**
+ * gs_shutil_rm_rf:
+ * @path: A file or directory
+ * @cancellable:
+ * @error:
+ *
+ * Recursively delete the filename referenced by @path; it may be a
+ * file or directory.  No error is thrown if @path does not exist.
+ */
+gboolean
+gs_shutil_rm_rf (GFile        *path,
+                 GCancellable *cancellable,
+                 GError      **error)
+{
+  gboolean ret = FALSE;
+  int dfd = -1;
+  DIR *d = NULL;
+
+  /* With O_NOFOLLOW first */
+  dfd = openat (AT_FDCWD, gs_file_get_path_cached (path),
+                O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
+
+  if (dfd == -1)
+    {
+      int errsv = errno;
+      if (errsv == ENOENT)
+        {
+          ;
+        }
+      else if (errsv == ENOTDIR || errsv == ELOOP)
+        {
+          if (!gs_file_unlink (path, cancellable, error))
+            goto out;
+        }
+      else
+        {
+          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                               g_strerror (errsv));
+          goto out;
+        }
+    }
+  else
+    {
+      d = fdopendir (dfd);
+      if (!d)
+        {
+          int errsv = errno;
+          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                               g_strerror (errsv));
+          goto out;
+        }
+
+      if (!gs_shutil_rm_rf_children (d, cancellable, error))
+        goto out;
+
+      if (rmdir (gs_file_get_path_cached (path)) == -1)
+        {
+          int errsv = errno;
+          if (errsv != ENOENT)
+            {
+              g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                                   g_strerror (errsv));
+              goto out;
+            }
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (d) (void) closedir (d);
+  return ret;
+}
+
diff --git a/libgsystem/gsystem-shutil.h b/libgsystem/gsystem-shutil.h
new file mode 100644
index 00000000..3cdea778
--- /dev/null
+++ b/libgsystem/gsystem-shutil.h
@@ -0,0 +1,47 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSYSTEM_SHUTIL_H__
+#define __GSYSTEM_SHUTIL_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+gboolean
+gs_shutil_cp_al_or_fallback (GFile         *src,
+                             GFile         *dest,
+                             GCancellable  *cancellable,
+                             GError       **error);
+
+gboolean
+gs_shutil_cp_a (GFile         *src,
+                GFile         *dest,
+                GCancellable  *cancellable,
+                GError       **error);
+
+gboolean
+gs_shutil_rm_rf (GFile        *path,
+                 GCancellable *cancellable,
+                 GError      **error);
+
+G_END_DECLS
+
+#endif
diff --git a/libgsystem/gsystem-subprocess-context-private.h b/libgsystem/gsystem-subprocess-context-private.h
new file mode 100644
index 00000000..719df451
--- /dev/null
+++ b/libgsystem/gsystem-subprocess-context-private.h
@@ -0,0 +1,65 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GS_SUBPROCESS_CONTEXT_PRIVATE_H__
+#define __GS_SUBPROCESS_CONTEXT_PRIVATE_H__
+
+#include "gsystem-subprocess-context.h"
+
+G_BEGIN_DECLS
+
+struct _GSSubprocessContext
+{
+  GObject parent;
+
+  GSpawnFlags flags;
+  gchar **argv;
+  gboolean has_argv0;
+  char **envp;
+  char *cwd;
+
+  GSSubprocessStreamDisposition stdin_disposition;
+  GSSubprocessStreamDisposition stdout_disposition;
+  GSSubprocessStreamDisposition stderr_disposition;
+
+  guint keep_descriptors : 1;
+  guint search_path : 1;
+  guint search_path_from_envp : 1;
+  guint unused_flags : 29;
+
+  gint stdin_fd;
+  gchar *stdin_path;
+
+  gint stdout_fd;
+  gchar *stdout_path;
+
+  gint stderr_fd;
+  gchar *stderr_path;
+
+  GArray *postfork_close_fds;
+  GArray *inherit_fds;
+
+  GSpawnChildSetupFunc child_setup_func;
+  gpointer child_setup_data;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/libgsystem/gsystem-subprocess-context.c b/libgsystem/gsystem-subprocess-context.c
new file mode 100644
index 00000000..90ca7167
--- /dev/null
+++ b/libgsystem/gsystem-subprocess-context.c
@@ -0,0 +1,501 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "libgsystem.h"
+
+#if GLIB_CHECK_VERSION(2,34,0)
+
+#ifdef G_OS_UNIX
+#include <gio/gunixoutputstream.h>
+#include <gio/gfiledescriptorbased.h>
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+#endif
+
+/**
+ * SECTION:gssubprocesscontext
+ * @title: GSSubprocess Context
+ * @short_description: Environment options for launching a child process
+ *
+ * This class contains a set of options for launching child processes,
+ * such as where its standard input and output will be directed, the
+ * argument list, the environment, and more.
+ *
+ * While the #GSSubprocess class has high level functions covering
+ * popular cases, use of this class allows access to more advanced
+ * options.  It can also be used to launch multiple subprocesses with
+ * a similar configuration.
+ *
+ * Since: 2.36
+ */
+
+#include "config.h"
+
+#include "gsystem-subprocess-context-private.h"
+#include "gsystem-subprocess.h"
+
+#include <string.h>
+
+typedef GObjectClass GSSubprocessContextClass;
+
+G_DEFINE_TYPE (GSSubprocessContext, gs_subprocess_context, G_TYPE_OBJECT);
+
+enum
+{
+  PROP_0,
+  PROP_ARGV,
+  N_PROPS
+};
+
+static GParamSpec *gs_subprocess_context_pspecs[N_PROPS];
+
+/**
+ * gs_subprocess_context_new:
+ * @argv: Argument list
+ *
+ * Returns: (transfer full): A new instance of a #GSSubprocessContext.
+ */
+GSSubprocessContext *
+gs_subprocess_context_new (gchar           **argv)
+{
+  g_return_val_if_fail (argv != NULL && argv[0] != NULL, NULL);
+
+  return g_object_new (GS_TYPE_SUBPROCESS_CONTEXT,
+		       "argv", argv,
+		       NULL);
+}
+
+GSSubprocessContext *
+gs_subprocess_context_newv (const gchar  *first_arg,
+                           ...)
+{
+  GSSubprocessContext *result;
+  va_list args;
+
+  g_return_val_if_fail (first_arg != NULL, NULL);
+
+  va_start (args, first_arg);
+  result = gs_subprocess_context_newa (first_arg, args);
+  va_end (args);
+  
+  return result;
+}
+
+/**
+ * gs_subprocess_context_newa:
+ * @first_arg: First argument
+ * @args: a va_list
+ *
+ * Returns: (transfer full): A new instance of a #GSSubprocessContext.
+ */
+GSSubprocessContext *
+gs_subprocess_context_newa (const gchar *first_arg,
+                           va_list      args)
+{
+  GSSubprocessContext *result;
+  GPtrArray *argv;
+
+  g_return_val_if_fail (first_arg != NULL, NULL);
+
+  argv = g_ptr_array_new ();
+  do
+    g_ptr_array_add (argv, (gchar*)first_arg);
+  while ((first_arg = va_arg (args, const gchar *)) != NULL);
+  g_ptr_array_add (argv, NULL);
+
+  result = gs_subprocess_context_new ((gchar**)argv->pdata);
+  
+  return result;
+}
+
+#ifdef G_OS_UNIX
+GSSubprocessContext *
+gs_subprocess_context_new_argv0 (const gchar      *argv0,
+                                gchar           **argv)
+{
+  GSSubprocessContext *result;
+  GPtrArray *real_argv;
+  gchar **iter;
+  
+  g_return_val_if_fail (argv0 != NULL, NULL);
+  g_return_val_if_fail (argv != NULL && argv[0] != NULL, NULL);
+  
+  real_argv = g_ptr_array_new ();
+  g_ptr_array_add (real_argv, (gchar*)argv0);
+  for (iter = argv; *iter; iter++)
+    g_ptr_array_add (real_argv, (gchar*) *iter);
+  g_ptr_array_add (real_argv, NULL);
+
+  result = g_object_new (GS_TYPE_SUBPROCESS_CONTEXT,
+                         "argv", real_argv->pdata,
+                         NULL);
+  result->has_argv0 = TRUE;
+
+  return result;
+}
+#endif
+
+static void
+gs_subprocess_context_init (GSSubprocessContext  *self)
+{
+  self->stdin_fd = -1;
+  self->stdout_fd = -1;
+  self->stderr_fd = -1;
+  self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT;
+  self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT;
+  self->postfork_close_fds = g_array_new (FALSE, FALSE, sizeof (int));
+  self->inherit_fds = g_array_new (FALSE, FALSE, sizeof (int));
+}
+
+static void
+gs_subprocess_context_finalize (GObject *object)
+{
+  GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
+
+  g_strfreev (self->argv);
+  g_strfreev (self->envp);
+  g_free (self->cwd);
+
+  g_free (self->stdin_path);
+  g_free (self->stdout_path);
+  g_free (self->stderr_path);
+
+  g_array_unref (self->postfork_close_fds);
+  g_array_unref (self->inherit_fds);
+
+  if (G_OBJECT_CLASS (gs_subprocess_context_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (gs_subprocess_context_parent_class)->finalize (object);
+}
+
+static void
+gs_subprocess_context_set_property (GObject      *object,
+				   guint         prop_id,
+				   const GValue *value,
+				   GParamSpec   *pspec)
+{
+  GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
+
+  switch (prop_id)
+    {
+    case PROP_ARGV:
+      self->argv = (gchar**) g_value_dup_boxed (value);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gs_subprocess_context_get_property (GObject    *object,
+				   guint       prop_id,
+				   GValue     *value,
+				   GParamSpec *pspec)
+{
+  GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
+
+  switch (prop_id)
+    {
+    case PROP_ARGV:
+      g_value_set_boxed (value, self->argv);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gs_subprocess_context_class_init (GSSubprocessContextClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->finalize = gs_subprocess_context_finalize;
+  gobject_class->get_property = gs_subprocess_context_get_property;
+  gobject_class->set_property = gs_subprocess_context_set_property;
+
+  /**
+   * GSSubprocessContext:argv:
+   *
+   * Array of arguments passed to child process; must have at least
+   * one element.  The first element has special handling - if it is
+   * an not absolute path ( as determined by g_path_is_absolute() ),
+   * then the system search path will be used.  See
+   * %G_SPAWN_SEARCH_PATH.
+   * 
+   * Note that in order to use the Unix-specific argv0 functionality,
+   * you must use the setter function
+   * gs_subprocess_context_set_args_and_argv0().  For more information
+   * about this, see %G_SPAWN_FILE_AND_ARGV_ZERO.
+   *
+   * Since: 2.36
+   */
+  gs_subprocess_context_pspecs[PROP_ARGV] = g_param_spec_boxed ("argv", "Arguments", "Arguments for child process", G_TYPE_STRV,
+							       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, gs_subprocess_context_pspecs);
+}
+
+/**
+ * gs_subprocess_context_argv_append:
+ * @self:
+ * @arg: An argument
+ *
+ * Append an argument to the child's argument vector.
+ */
+void
+gs_subprocess_context_argv_append (GSSubprocessContext  *self,
+                                   const gchar          *arg)
+{
+  GPtrArray *new_argv = g_ptr_array_new ();
+  gchar **iter;
+
+  for (iter = self->argv; *iter; iter++)
+    g_ptr_array_add (new_argv, *iter);
+  g_ptr_array_add (new_argv, g_strdup (arg));
+  g_ptr_array_add (new_argv, NULL);
+
+  /* Don't free elements */
+  g_free (self->argv);
+  self->argv = (char**)g_ptr_array_free (new_argv, FALSE);
+}
+
+/* Environment */
+
+/**
+ * gs_subprocess_context_set_environment:
+ * @self:
+ * @environ: (array zero-terminated=1) (element-type utf8): Environment KEY=VALUE pairs
+ *
+ * Replace the environment that will be used for the child process.
+ * The default is to inherit the current process.
+ */
+void
+gs_subprocess_context_set_environment (GSSubprocessContext           *self,
+                                       gchar                        **env)
+{
+  g_strfreev (self->envp);
+  self->envp = g_strdupv (env);
+}
+
+void
+gs_subprocess_context_set_cwd (GSSubprocessContext           *self,
+			      const gchar                  *cwd)
+{
+  g_free (self->cwd);
+  self->cwd = g_strdup (cwd);
+}
+
+void
+gs_subprocess_context_set_keep_descriptors (GSSubprocessContext           *self,
+					   gboolean                      keep_descriptors)
+
+{
+  self->keep_descriptors = keep_descriptors ? 1 : 0;
+}
+
+void
+gs_subprocess_context_set_search_path (GSSubprocessContext           *self,
+				      gboolean                      search_path,
+				      gboolean                      search_path_from_envp)
+{
+  self->search_path = search_path ? 1 : 0;
+  self->search_path_from_envp = search_path_from_envp ? 1 : 0;
+}
+
+void
+gs_subprocess_context_set_stdin_disposition (GSSubprocessContext           *self,
+					    GSSubprocessStreamDisposition  disposition)
+{
+  g_return_if_fail (disposition != GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE);
+  self->stdin_disposition = disposition;
+}
+
+void
+gs_subprocess_context_set_stdout_disposition (GSSubprocessContext           *self,
+					     GSSubprocessStreamDisposition  disposition)
+{
+  g_return_if_fail (disposition != GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE);
+  self->stdout_disposition = disposition;
+}
+
+void
+gs_subprocess_context_set_stderr_disposition (GSSubprocessContext           *self,
+					     GSSubprocessStreamDisposition  disposition)
+{
+  self->stderr_disposition = disposition;
+}
+
+#ifdef G_OS_UNIX
+void
+gs_subprocess_context_set_stdin_file_path (GSSubprocessContext           *self,
+					  const gchar                  *path)
+{
+  self->stdin_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+  g_free (self->stdin_path);
+  self->stdin_path = g_strdup (path);
+}
+
+void
+gs_subprocess_context_set_stdin_fd        (GSSubprocessContext           *self,
+					  gint                          fd)
+{
+  self->stdin_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+  self->stdin_fd = fd;
+}
+
+void
+gs_subprocess_context_set_stdout_file_path (GSSubprocessContext           *self,
+					   const gchar                  *path)
+{
+  self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+  g_free (self->stdout_path);
+  self->stdout_path = g_strdup (path);
+}
+
+void
+gs_subprocess_context_set_stdout_fd (GSSubprocessContext           *self,
+				    gint                          fd)
+{
+  self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+  self->stdout_fd = fd;
+}
+
+void
+gs_subprocess_context_set_stderr_file_path (GSSubprocessContext           *self,
+					   const gchar                  *path)
+{
+  self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+  g_free (self->stderr_path);
+  self->stderr_path = g_strdup (path);
+}
+
+void
+gs_subprocess_context_set_stderr_fd        (GSSubprocessContext           *self,
+					   gint                          fd)
+{
+  self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+  self->stderr_fd = fd;
+}
+#endif
+
+#ifdef G_OS_UNIX
+/**
+ * gs_subprocess_context_set_child_setup: (skip)
+ * @self:
+ * @child_setup: Function to call in the newly forked child, before execve()
+ * @user_data: Data passed to child
+ *
+ * FIXME - note extensive restricitons on GSpawnChildSetupFunc here
+ */
+void
+gs_subprocess_context_set_child_setup (GSSubprocessContext           *self,
+				      GSpawnChildSetupFunc          child_setup,
+				      gpointer                      user_data)
+{
+  self->child_setup_func = child_setup;
+  self->child_setup_data = user_data;
+}
+
+static gboolean
+open_pipe_internal (GSSubprocessContext         *self,
+                    gboolean                     for_read,
+                    void                       **out_stream,
+                    gint                        *out_fdno,
+                    GError                     **error)
+{
+  int pipefds[2];
+
+  g_return_val_if_fail (out_stream != NULL, FALSE);
+  g_return_val_if_fail (out_fdno != NULL, FALSE);
+
+  if (!g_unix_open_pipe (pipefds, FD_CLOEXEC, error))
+    return FALSE;
+
+  if (for_read)
+    {
+      *out_stream = g_unix_input_stream_new (pipefds[0], TRUE);
+      *out_fdno = pipefds[1];
+    }
+  else
+    {
+      *out_stream = g_unix_output_stream_new (pipefds[1], TRUE);
+      *out_fdno = pipefds[0];
+    }
+  g_array_append_val (self->inherit_fds, *out_fdno);
+  g_array_append_val (self->postfork_close_fds, *out_fdno);
+
+  return TRUE;
+}
+
+/**
+ * gs_subprocess_context_open_pipe_read:
+ * @self:
+ * @out_stream: (out) (transfer full): A newly referenced output stream
+ * @out_fdno: (out): File descriptor number for the subprocess side of the pipe
+ *
+ * This allows you to open a pipe between the parent and child
+ * processes, independent of the standard streams.  For this function,
+ * the pipe is set up so that the parent can read, and the child can
+ * write.  For the opposite version, see
+ * gs_subprocess_context_open_pipe_write().
+ *
+ * The returned @out_fdno is the file descriptor number that the child
+ * will see; you need to communicate this number via a separate
+ * channel, such as the argument list.  For example, if you're using
+ * this pipe to send a password, provide
+ * <literal>--password-fd=&lt;fdno string&gt;</literal>.
+ *
+ * Returns: %TRUE on success, %FALSE on error (and @error will be set)
+ */
+gboolean
+gs_subprocess_context_open_pipe_read (GSSubprocessContext         *self,
+                                      GInputStream               **out_stream,
+                                      gint                        *out_fdno,
+                                      GError                     **error)
+{
+  return open_pipe_internal (self, TRUE, (void**)out_stream, out_fdno, error);
+}
+
+/**
+ * gs_subprocess_context_open_pipe_write:
+ * @self:
+ * @out_stream: (out) (transfer full): A newly referenced stream
+ * @out_fdno: (out): File descriptor number for the subprocess side of the pipe
+ *
+ * Like gs_subprocess_context_open_pipe_read(), but returns a writable
+ * channel from which the child process can read.
+ *
+ * Returns: %TRUE on success, %FALSE on error (and @error will be set)
+ */
+gboolean
+gs_subprocess_context_open_pipe_write (GSSubprocessContext         *self,
+                                       GOutputStream              **out_stream,
+                                       gint                        *out_fdno,
+                                       GError                     **error)
+{
+  return open_pipe_internal (self, FALSE, (void**)out_stream, out_fdno, error);
+}
+
+#endif
+
+#endif
diff --git a/libgsystem/gsystem-subprocess-context.h b/libgsystem/gsystem-subprocess-context.h
new file mode 100644
index 00000000..b8a24012
--- /dev/null
+++ b/libgsystem/gsystem-subprocess-context.h
@@ -0,0 +1,128 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSYSTEM_SUBPROCESS_CONTEXT_H__
+#define __GSYSTEM_SUBPROCESS_CONTEXT_H__
+
+#include <gio/gio.h>
+
+#if GLIB_CHECK_VERSION(2,34,0)
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_SUBPROCESS_CONTEXT         (gs_subprocess_context_get_type ())
+#define GS_SUBPROCESS_CONTEXT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_SUBPROCESS_CONTEXT, GSSubprocessContext))
+#define GS_IS_SUBPROCESS_CONTEXT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_SUBPROCESS_CONTEXT))
+
+typedef struct _GSSubprocessContext GSSubprocessContext;
+
+/**
+ * GSSubprocessStreamDisposition:
+ * @GS_SUBPROCESS_STREAM_DISPOSITION_NULL: Redirect to operating system's null output stream
+ * @GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT: Keep the stream from the parent process
+ * @GS_SUBPROCESS_STREAM_DISPOSITION_PIPE: Open a private unidirectional channel between the processes
+ * @GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE: Only applicable to standard error; causes it to be merged with standard output
+ *
+ * Flags to define the behaviour of the standard input/output/error of
+ * a #GSSubprocess.
+ *
+ * Since: 2.36
+ **/
+typedef enum {
+  GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
+  GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
+  GS_SUBPROCESS_STREAM_DISPOSITION_PIPE,
+  GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE
+} GSSubprocessStreamDisposition;
+
+GType            gs_subprocess_context_get_type (void) G_GNUC_CONST;
+
+GSSubprocessContext * gs_subprocess_context_new (gchar           **argv);
+GSSubprocessContext * gs_subprocess_context_newv (const gchar  *first_arg,
+                                                ...);
+GSSubprocessContext * gs_subprocess_context_newa (const gchar  *first_arg,
+                                                va_list       args);
+
+#ifdef G_OS_UNIX
+GSSubprocessContext * gs_subprocess_context_new_argv0 (const gchar   *argv0,
+                                                     gchar        **argv);
+#endif
+
+void             gs_subprocess_context_argv_append (GSSubprocessContext  *self,
+                                                    const gchar          *arg);
+
+/* Environment */
+
+void             gs_subprocess_context_set_environment (GSSubprocessContext           *self,
+						       gchar                       **environ);
+void             gs_subprocess_context_set_cwd (GSSubprocessContext           *self,
+					       const gchar                  *cwd);
+void             gs_subprocess_context_set_keep_descriptors (GSSubprocessContext           *self,
+							    gboolean                      keep_descriptors);
+void             gs_subprocess_context_set_search_path (GSSubprocessContext           *self,
+						       gboolean                      search_path,
+						       gboolean                      search_path_from_envp);
+
+/* Basic I/O control */
+
+void             gs_subprocess_context_set_stdin_disposition (GSSubprocessContext           *self,
+							     GSSubprocessStreamDisposition  disposition);
+void             gs_subprocess_context_set_stdout_disposition (GSSubprocessContext           *self,
+							      GSSubprocessStreamDisposition  disposition);
+void             gs_subprocess_context_set_stderr_disposition (GSSubprocessContext           *self,
+							      GSSubprocessStreamDisposition  disposition);
+
+/* Extended I/O control, only available on UNIX */
+
+#ifdef G_OS_UNIX
+void             gs_subprocess_context_set_stdin_file_path (GSSubprocessContext           *self,
+							   const gchar                  *path);
+void             gs_subprocess_context_set_stdin_fd        (GSSubprocessContext           *self,
+							   gint                          fd);
+void             gs_subprocess_context_set_stdout_file_path (GSSubprocessContext           *self,
+							    const gchar                  *path);
+void             gs_subprocess_context_set_stdout_fd        (GSSubprocessContext           *self,
+							    gint                          fd);
+void             gs_subprocess_context_set_stderr_file_path (GSSubprocessContext           *self,
+							    const gchar                  *path);
+void             gs_subprocess_context_set_stderr_fd        (GSSubprocessContext           *self,
+							    gint                          fd);
+
+gboolean         gs_subprocess_context_open_pipe_read       (GSSubprocessContext         *self,
+                                                             GInputStream               **out_stream,
+                                                             gint                        *out_fdno,
+                                                             GError                     **error);
+gboolean         gs_subprocess_context_open_pipe_write      (GSSubprocessContext         *self,
+                                                             GOutputStream              **out_stream,
+                                                             gint                        *out_fdno,
+                                                             GError                     **error);
+#endif
+
+/* Child setup, only available on UNIX */
+#ifdef G_OS_UNIX
+void             gs_subprocess_context_set_child_setup        (GSSubprocessContext           *self,
+							      GSpawnChildSetupFunc          child_setup,
+							      gpointer                      user_data);
+#endif
+
+G_END_DECLS
+
+#endif
+#endif
diff --git a/libgsystem/gsystem-subprocess.c b/libgsystem/gsystem-subprocess.c
new file mode 100644
index 00000000..4ec680f8
--- /dev/null
+++ b/libgsystem/gsystem-subprocess.c
@@ -0,0 +1,966 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2012 Red Hat, Inc.
+ * Copyright © 2012 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: Colin Walters <walters@verbum.org>
+ *          Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+
+#if GLIB_CHECK_VERSION(2,34,0)
+
+/**
+ * SECTION:gssubprocess
+ * @title: GSSubprocess
+ * @short_description: Create child processes and monitor their status
+ *
+ * This class wraps the lower-level g_spawn_async_with_pipes() API,
+ * providing a more modern GIO-style API, such as returning
+ * #GInputStream objects for child output pipes.
+ *
+ * One major advantage that GIO brings over the core GLib library is
+ * comprehensive API for asynchronous I/O, such
+ * g_output_stream_splice_async().  This makes GSubprocess
+ * significantly more powerful and flexible than equivalent APIs in
+ * some other languages such as the <literal>subprocess.py</literal>
+ * included with Python.  For example, using #GSubprocess one could
+ * create two child processes, reading standard output from the first,
+ * processing it, and writing to the input stream of the second, all
+ * without blocking the main loop.
+ *
+ * Since: 2.36
+ */
+
+#include "config.h"
+
+#include "gsystem-subprocess.h"
+#include "gsystem-subprocess-context-private.h"
+
+#include <string.h>
+#ifdef G_OS_UNIX
+#include <gio/gunixoutputstream.h>
+#include <gio/gfiledescriptorbased.h>
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+#endif
+#include <fcntl.h>
+#ifdef G_OS_WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#include "giowin32-priv.h"
+#endif
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+static void initable_iface_init (GInitableIface         *initable_iface);
+
+typedef GObjectClass GSSubprocessClass;
+
+#ifdef G_OS_UNIX
+static void
+gs_subprocess_unix_queue_waitpid (GSSubprocess  *self);
+#endif
+
+struct _GSSubprocess
+{
+  GObject parent;
+
+  GSSubprocessContext *context;
+  GPid pid;
+
+  guint pid_valid : 1;
+  guint reaped_child : 1;
+  guint unused : 30;
+
+  /* These are the streams created if a pipe is requested via flags. */
+  GOutputStream *stdin_pipe;
+  GInputStream  *stdout_pipe;
+  GInputStream  *stderr_pipe;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GSSubprocess, gs_subprocess, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
+
+enum
+{
+  PROP_0,
+  PROP_CONTEXT,
+  N_PROPS
+};
+
+static GParamSpec *gs_subprocess_pspecs[N_PROPS];
+
+static void
+gs_subprocess_init (GSSubprocess  *self)
+{
+}
+
+static void
+gs_subprocess_finalize (GObject *object)
+{
+  GSSubprocess *self = GS_SUBPROCESS (object);
+
+  if (self->pid_valid)
+    {
+#ifdef G_OS_UNIX
+      /* Here we need to actually call waitpid() to clean up the
+       * zombie.  In case the child hasn't actually exited, defer this
+       * cleanup to the worker thread.
+       */
+      if (!self->reaped_child)
+        gs_subprocess_unix_queue_waitpid (self);
+#endif
+      g_spawn_close_pid (self->pid);
+    }
+
+  g_clear_object (&self->stdin_pipe);
+  g_clear_object (&self->stdout_pipe);
+  g_clear_object (&self->stderr_pipe);
+
+  if (G_OBJECT_CLASS (gs_subprocess_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (gs_subprocess_parent_class)->finalize (object);
+}
+
+static void
+gs_subprocess_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  GSSubprocess *self = GS_SUBPROCESS (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      self->context = g_value_dup_object (value);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gs_subprocess_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  GSSubprocess *self = GS_SUBPROCESS (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      g_value_set_object (value, self->context);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static void
+gs_subprocess_class_init (GSSubprocessClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->finalize = gs_subprocess_finalize;
+  gobject_class->get_property = gs_subprocess_get_property;
+  gobject_class->set_property = gs_subprocess_set_property;
+
+  /**
+   * GSSubprocess:context:
+   *
+   *
+   * Since: 2.36
+   */
+  gs_subprocess_pspecs[PROP_CONTEXT] = g_param_spec_object ("context", "Context", "Subprocess options", GS_TYPE_SUBPROCESS_CONTEXT,
+							   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+							   G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, gs_subprocess_pspecs);
+}
+
+#ifdef G_OS_UNIX
+
+static gboolean
+gs_subprocess_unix_waitpid_dummy (gpointer data)
+{
+  return FALSE;
+}
+
+static void
+gs_subprocess_unix_queue_waitpid (GSSubprocess  *self)
+{
+  GMainContext *worker_context;
+  GSource *waitpid_source;
+
+#ifdef GLIB_COMPILATION
+  worker_context = GLIB_PRIVATE_CALL (g_get_worker_context) ();
+#else
+  worker_context = g_main_context_get_thread_default ();
+#endif
+  waitpid_source = g_child_watch_source_new (self->pid); 
+  g_source_set_callback (waitpid_source, gs_subprocess_unix_waitpid_dummy, NULL, NULL);
+  g_source_attach (waitpid_source, worker_context);
+  g_source_unref (waitpid_source);
+}
+
+#endif
+
+static GInputStream *
+platform_input_stream_from_spawn_fd (gint fd)
+{
+  if (fd < 0)
+    return NULL;
+
+#ifdef G_OS_UNIX
+  return g_unix_input_stream_new (fd, TRUE);
+#else
+  return g_win32_input_stream_new_from_fd (fd, TRUE);
+#endif
+}
+
+static GOutputStream *
+platform_output_stream_from_spawn_fd (gint fd)
+{
+  if (fd < 0)
+    return NULL;
+
+#ifdef G_OS_UNIX
+  return g_unix_output_stream_new (fd, TRUE);
+#else
+  return g_win32_output_stream_new_from_fd (fd, TRUE);
+#endif
+}
+
+#ifdef G_OS_UNIX
+static gint
+unix_open_file (const char  *filename,
+                gint         mode,
+                GError     **error)
+{
+  gint my_fd;
+
+  do
+    my_fd = open (filename, mode | O_BINARY | O_CLOEXEC, 0666);
+  while (my_fd == -1 && errno == EINTR);
+
+  /* If we return -1 we should also set the error */
+  if (my_fd < 0)
+    {
+      gint saved_errno = errno;
+      char *display_name;
+
+      display_name = g_filename_display_name (filename);
+      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno),
+                   "Error opening file '%s': %s", display_name,
+                   g_strerror (saved_errno));
+      g_free (display_name);
+      /* fall through... */
+    }
+
+  return my_fd;
+}
+#endif
+
+typedef struct
+{
+  gint                   fds[3];
+  GArray                *inherit_fds;
+  GSpawnChildSetupFunc   child_setup_func;
+  gpointer               child_setup_data;
+} ChildData;
+
+static void
+child_setup (gpointer user_data)
+{
+  ChildData *child_data = user_data;
+  guint i;
+  gint result;
+
+  /* We're on the child side now.  "Rename" the file descriptors in
+   * child_data.fds[] to stdin/stdout/stderr.
+   *
+   * We don't close the originals.  It's possible that the originals
+   * should not be closed and if they should be closed then they should
+   * have been created O_CLOEXEC.
+   */
+  for (i = 0; i < 3; i++)
+    {
+      if (child_data->fds[i] != -1 && child_data->fds[i] != (int) i)
+        {
+          do
+            result = dup2 (child_data->fds[i], i);
+          while (G_UNLIKELY (result == -1 && errno == EINTR));
+        }
+    }
+
+  /* Unset the CLOEXEC flag for the child *should* inherit */
+  for (i = 0; i < child_data->inherit_fds->len; i++)
+    {
+      int fd = g_array_index (child_data->inherit_fds, int, i);
+      int flags;
+
+      do
+        flags = fcntl (fd, F_GETFL);
+      while (G_UNLIKELY (flags == -1 && errno == EINTR));
+
+      flags &= ~FD_CLOEXEC;
+      
+      do
+        result = fcntl (fd, F_SETFD, flags);
+      while (G_UNLIKELY (result == -1 && errno == EINTR));
+    }
+
+  if (child_data->child_setup_func)
+    child_data->child_setup_func (child_data->child_setup_data);
+}
+
+static gboolean
+initable_init (GInitable     *initable,
+               GCancellable  *cancellable,
+               GError       **error)
+{
+  GSSubprocess *self = GS_SUBPROCESS (initable);
+  ChildData child_data = { { -1, -1, -1 } };
+  gint *pipe_ptrs[3] = { NULL, NULL, NULL };
+  gint pipe_fds[3] = { -1, -1, -1 };
+  gint close_fds[3] = { -1, -1, -1 };
+  GSpawnFlags spawn_flags = 0;
+  gboolean success = FALSE;
+  guint i;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  /* We must setup the three fds that will end up in the child as stdin,
+   * stdout and stderr.
+   *
+   * First, stdin.
+   */
+#ifdef G_OS_UNIX
+  if (self->context->stdin_fd != -1)
+    child_data.fds[0] = self->context->stdin_fd;
+  else if (self->context->stdin_path != NULL)
+    {
+      child_data.fds[0] = close_fds[0] = unix_open_file (self->context->stdin_path, 
+							 O_RDONLY, error);
+      if (child_data.fds[0] == -1)
+	goto out;
+    }
+  else
+#endif
+  if (self->context->stdin_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_NULL)
+    ; /* nothing */
+  else if (self->context->stdin_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
+    spawn_flags |= G_SPAWN_CHILD_INHERITS_STDIN;
+  else if (self->context->stdin_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_PIPE)
+    pipe_ptrs[0] = &pipe_fds[0];
+  else
+    g_assert_not_reached ();
+
+  /* Next, stdout. */
+#ifdef G_OS_UNIX
+  if (self->context->stdout_fd != -1)
+    child_data.fds[1] = self->context->stdout_fd;
+  else if (self->context->stdout_path != NULL)
+    {
+      child_data.fds[1] = close_fds[1] = unix_open_file (self->context->stdout_path, 
+							 O_CREAT | O_WRONLY, error);
+      if (child_data.fds[1] == -1)
+	goto out;
+    }
+  else
+#endif
+  if (self->context->stdout_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_NULL)
+    spawn_flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
+  else if (self->context->stdout_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
+    ; /* Nothing */
+  else if (self->context->stdout_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_PIPE)
+    pipe_ptrs[1] = &pipe_fds[1];
+  else
+    g_assert_not_reached ();
+
+  /* Finally, stderr. */
+#ifdef G_OS_UNIX
+  if (self->context->stderr_fd != -1)
+    child_data.fds[2] = self->context->stderr_fd;
+  else if (self->context->stderr_path != NULL)
+    {
+      child_data.fds[2] = close_fds[2] = unix_open_file (self->context->stderr_path, 
+							 O_CREAT | O_WRONLY, error);
+      if (child_data.fds[2] == -1)
+	goto out;
+    }
+  else
+#endif
+  if (self->context->stderr_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_NULL)
+    spawn_flags |= G_SPAWN_STDERR_TO_DEV_NULL;
+  else if (self->context->stderr_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
+    ; /* Nothing */
+  else if (self->context->stderr_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_PIPE)
+    pipe_ptrs[2] = &pipe_fds[2];
+  else if (self->context->stderr_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE)
+    /* This will work because stderr gets setup after stdout. */
+    child_data.fds[2] = 1;
+  else
+    g_assert_not_reached ();
+
+  child_data.inherit_fds = self->context->inherit_fds;
+
+  if (self->context->keep_descriptors)
+    spawn_flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
+
+  if (self->context->search_path)
+    spawn_flags |= G_SPAWN_SEARCH_PATH;
+  else if (self->context->search_path_from_envp)
+    spawn_flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
+  else if (!g_path_is_absolute (((gchar**)self->context->argv)[0]))
+    spawn_flags |= G_SPAWN_SEARCH_PATH;
+
+  if (self->context->has_argv0)
+    spawn_flags |= G_SPAWN_FILE_AND_ARGV_ZERO;
+
+  spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
+#ifdef GLIB_COMPILATION
+  spawn_flags |= G_SPAWN_CLOEXEC_PIPES;
+#endif
+
+  child_data.child_setup_func = self->context->child_setup_func;
+  child_data.child_setup_data = self->context->child_setup_data;
+  success = g_spawn_async_with_pipes (self->context->cwd,
+				      (char**)self->context->argv,
+				      self->context->envp,
+                                      spawn_flags,
+                                      child_setup, &child_data,
+                                      &self->pid,
+                                      pipe_ptrs[0], pipe_ptrs[1], pipe_ptrs[2],
+                                      error);
+  if (success)
+    self->pid_valid = TRUE;
+
+out:
+  for (i = 0; i < 3; i++)
+    if (close_fds[i] != -1)
+      close (close_fds[i]);
+
+  for (i = 0; i < self->context->postfork_close_fds->len; i++)
+    (void) close (g_array_index (self->context->postfork_close_fds, int, i));
+
+  self->stdin_pipe = platform_output_stream_from_spawn_fd (pipe_fds[0]);
+  self->stdout_pipe = platform_input_stream_from_spawn_fd (pipe_fds[1]);
+  self->stderr_pipe = platform_input_stream_from_spawn_fd (pipe_fds[2]);
+
+  return success;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+  initable_iface->init = initable_init;
+}
+
+/**
+ * gs_subprocess_new:
+ *
+ * Create a new process, using the parameters specified by
+ * GSSubprocessContext.
+ *
+ * Returns: (transfer full): A newly created %GSSubprocess, or %NULL on error (and @error will be set)
+ *
+ * Since: 2.36
+ */
+GSSubprocess *
+gs_subprocess_new (GSSubprocessContext   *context,
+                   GCancellable          *cancellable,
+                   GError               **error)
+{
+  return g_initable_new (GS_TYPE_SUBPROCESS,
+                         cancellable, error,
+                         "context", context,
+                         NULL);
+}
+
+/**
+ * gs_subprocess_get_pid:
+ * @self: a #GSSubprocess
+ *
+ * The identifier for this child process; it is valid as long as the
+ * process @self is referenced.  In particular, do
+ * <emphasis>not</emphasis> call g_spawn_close_pid() on this value;
+ * that is handled internally.
+ *
+ * On some Unix versions, it is possible for there to be a race
+ * condition where waitpid() may have been called to collect the child
+ * before any watches (such as that installed by
+ * gs_subprocess_add_watch()) have fired.  If you are planning to use
+ * native functions such as kill() on the pid, your program should
+ * gracefully handle an %ESRCH result to mitigate this.
+ *
+ * If you want to request process termination, using the high level
+ * gs_subprocess_request_exit() and gs_subprocess_force_exit() API is
+ * recommended.
+ *
+ * Returns: Operating-system specific identifier for child process
+ *
+ * Since: 2.36
+ */
+GPid
+gs_subprocess_get_pid (GSSubprocess     *self)
+{
+  g_return_val_if_fail (GS_IS_SUBPROCESS (self), 0);
+
+  return self->pid;
+}
+
+/**
+ * gs_subprocess_get_stdin_pipe:
+ *
+ * Returns: (transfer none): Pipe
+ */
+GOutputStream *
+gs_subprocess_get_stdin_pipe (GSSubprocess       *self)
+{
+  g_return_val_if_fail (GS_IS_SUBPROCESS (self), NULL);
+  g_return_val_if_fail (self->stdin_pipe, NULL);
+
+  return self->stdin_pipe;
+}
+
+/**
+ * gs_subprocess_get_stdout_pipe:
+ *
+ * Returns: (transfer none): Pipe
+ */
+GInputStream *
+gs_subprocess_get_stdout_pipe (GSSubprocess      *self)
+{
+  g_return_val_if_fail (GS_IS_SUBPROCESS (self), NULL);
+  g_return_val_if_fail (self->stdout_pipe, NULL);
+
+  return self->stdout_pipe;
+}
+
+/**
+ * gs_subprocess_get_stderr_pipe:
+ *
+ * Returns: (transfer none): Pipe
+ */
+GInputStream *
+gs_subprocess_get_stderr_pipe (GSSubprocess      *self)
+{
+  g_return_val_if_fail (GS_IS_SUBPROCESS (self), NULL);
+  g_return_val_if_fail (self->stderr_pipe, NULL);
+
+  return self->stderr_pipe;
+}
+
+typedef struct {
+  GSSubprocess *self;
+  GCancellable *cancellable;
+  GSimpleAsyncResult *result;
+} GSSubprocessWatchData;
+
+static gboolean
+gs_subprocess_on_child_exited (GPid       pid,
+			      gint       status_code,
+			      gpointer   user_data)
+{
+  GSSubprocessWatchData *data = user_data;
+  GError *error = NULL;
+  
+  if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
+    {
+      g_simple_async_result_take_error (data->result, error);
+    }
+  else
+    {
+      data->self->reaped_child = TRUE;
+
+      g_simple_async_result_set_op_res_gssize (data->result, status_code);
+    }
+
+  g_simple_async_result_complete (data->result);
+
+  g_object_unref (data->result);
+  g_object_unref (data->self);
+  g_free (data);
+
+  return FALSE;
+}
+
+/**
+ * gs_subprocess_wait:
+ * @self: a #GSSubprocess
+ * @cancellable: a #GCancellable
+ * @callback: Invoked when process exits, or @cancellable is cancelled
+ * @user_data: Data for @callback
+ *
+ * Start an asynchronous wait for the subprocess @self to exit.
+ *
+ * Since: 2.36
+ */
+void
+gs_subprocess_wait (GSSubprocess                *self,
+		   GCancellable               *cancellable,
+		   GAsyncReadyCallback         callback,
+		   gpointer                    user_data)
+{
+  GSource *source;
+  GSSubprocessWatchData *data;
+
+  data = g_new0 (GSSubprocessWatchData, 1);
+
+  data->self = g_object_ref (self);
+  data->result = g_simple_async_result_new ((GObject*)self, callback, user_data,
+					    gs_subprocess_wait);
+
+  source = g_child_watch_source_new (self->pid);
+
+  g_source_set_callback (source, (GSourceFunc)gs_subprocess_on_child_exited,
+			 data, NULL);
+  if (cancellable)
+    {
+      GSource *cancellable_source;
+
+      data->cancellable = g_object_ref (cancellable);
+
+      cancellable_source = g_cancellable_source_new (cancellable);
+      g_source_add_child_source (source, cancellable_source);
+      g_source_unref (cancellable_source);
+    }
+
+  g_source_attach (source, g_main_context_get_thread_default ());
+  g_source_unref (source);
+}
+
+/**
+ * gs_subprocess_wait_finish:
+ * @self: a #GSSubprocess
+ * @result: a #GAsyncResult
+ * @out_exit_status: (out): Exit status of the process encoded in platform-specific way
+ * @error: a #GError
+ *
+ * The exit status of the process will be stored in @out_exit_status.
+ * See the documentation of g_spawn_check_exit_status() for more
+ * details.
+ *
+ * Note that @error is not set if the process exits abnormally; you
+ * must use g_spawn_check_exit_status() for that.
+ *
+ * Since: 2.36
+ */
+gboolean
+gs_subprocess_wait_finish (GSSubprocess                *self,
+			  GAsyncResult               *result,
+			  int                        *out_exit_status,
+			  GError                    **error)
+{
+  GSimpleAsyncResult *simple;
+
+  simple = G_SIMPLE_ASYNC_RESULT (result);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    return FALSE;
+
+  *out_exit_status = g_simple_async_result_get_op_res_gssize (simple);
+  
+  return TRUE;
+}
+
+typedef struct {
+  GMainLoop *loop;
+  gint *exit_status_ptr;
+  gboolean caught_error;
+  GError **error;
+} GSSubprocessSyncWaitData;
+
+static void
+gs_subprocess_on_sync_wait_complete (GObject       *object,
+				    GAsyncResult  *result,
+				    gpointer       user_data)
+{
+  GSSubprocessSyncWaitData *data = user_data;
+
+  if (!gs_subprocess_wait_finish ((GSSubprocess*)object, result, 
+				 data->exit_status_ptr, data->error))
+    data->caught_error = TRUE;
+
+  g_main_loop_quit (data->loop);
+}
+
+/**
+ * gs_subprocess_wait_sync:
+ * @self: a #GSSubprocess
+ * @out_exit_status: (out): Platform-specific exit code
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Synchronously wait for the subprocess to terminate, returning the
+ * status code in @out_exit_status.  See the documentation of
+ * g_spawn_check_exit_status() for how to interpret it.  Note that if
+ * @error is set, then @out_exit_status will be left uninitialized.
+ * 
+ * Returns: %TRUE on success, %FALSE if @cancellable was cancelled
+ *
+ * Since: 2.36
+ */
+gboolean
+gs_subprocess_wait_sync (GSSubprocess        *self,
+			int                *out_exit_status,
+			GCancellable       *cancellable,
+			GError            **error)
+{
+  gboolean ret = FALSE;
+  gboolean pushed_thread_default = FALSE;
+  GMainContext *context = NULL;
+  GSSubprocessSyncWaitData data;
+
+  memset (&data, 0, sizeof (data));
+
+  g_return_val_if_fail (GS_IS_SUBPROCESS (self), FALSE);
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  context = g_main_context_new ();
+  g_main_context_push_thread_default (context);
+  pushed_thread_default = TRUE;
+
+  data.exit_status_ptr = out_exit_status;
+  data.loop = g_main_loop_new (context, TRUE);
+  data.error = error;
+
+  gs_subprocess_wait (self, cancellable,
+		     gs_subprocess_on_sync_wait_complete, &data);
+
+  g_main_loop_run (data.loop);
+
+  if (data.caught_error)
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (pushed_thread_default)
+    g_main_context_pop_thread_default (context);
+  if (context)
+    g_main_context_unref (context);
+  if (data.loop)
+    g_main_loop_unref (data.loop);
+
+  return ret;
+}
+
+/**
+ * gs_subprocess_wait_sync_check:
+ * @self: a #GSSubprocess
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Combines gs_subprocess_wait_sync() with g_spawn_check_exit_status().
+ * 
+ * Returns: %TRUE on success, %FALSE if process exited abnormally, or @cancellable was cancelled
+ *
+ * Since: 2.36
+ */
+gboolean
+gs_subprocess_wait_sync_check (GSSubprocess        *self,
+			      GCancellable       *cancellable,
+			      GError            **error)
+{
+  gboolean ret = FALSE;
+  int exit_status;
+
+  if (!gs_subprocess_wait_sync (self, &exit_status, cancellable, error))
+    goto out;
+
+  if (!g_spawn_check_exit_status (exit_status, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * gs_subprocess_request_exit:
+ * @self: a #GSSubprocess
+ *
+ * This API uses an operating-system specific mechanism to request
+ * that the subprocess gracefully exit.  This API is not available on
+ * all operating systems; for those not supported, it will do nothing
+ * and return %FALSE.  Portable code should handle this situation
+ * gracefully.  For example, if you are communicating via input or
+ * output pipe with the child, many programs will automatically exit
+ * when one of their standard input or output are closed.
+ *
+ * On Unix, this API sends %SIGTERM.
+ *
+ * A %TRUE return value does <emphasis>not</emphasis> mean the
+ * subprocess has exited, merely that an exit request was initiated.
+ * You can use gs_subprocess_add_watch() to monitor the status of the
+ * process after calling this function.
+ *
+ * This function returns %TRUE if the process has already exited.
+ *
+ * Returns: %TRUE if the operation is supported, %FALSE otherwise.
+ *
+ * Since: 2.36
+ */
+gboolean
+gs_subprocess_request_exit (GSSubprocess *self)
+{
+  g_return_val_if_fail (GS_IS_SUBPROCESS (self), FALSE);
+
+#ifdef G_OS_UNIX
+  (void) kill (self->pid, SIGTERM);
+  return TRUE;
+#else
+  return FALSE;
+#endif
+}
+
+/**
+ * gs_subprocess_force_exit:
+ * @self: a #GSSubprocess
+ *
+ * Use an operating-system specific method to attempt an immediate,
+ * forceful termination of the process.  There is no mechanism to
+ * determine whether or not the request itself was successful;
+ * however, you can use gs_subprocess_wait() to monitor the status of
+ * the process after calling this function.
+ *
+ * On Unix, this function sends %SIGKILL.
+ */
+void
+gs_subprocess_force_exit (GSSubprocess *self)
+{
+  g_return_if_fail (GS_IS_SUBPROCESS (self));
+
+#if !defined(GLIB_COMPIATION)
+  {
+    int ret;
+    do
+      ret = kill (self->pid, SIGKILL);
+    while (ret == -1 && errno == EINTR);
+  }
+#elif defined(G_OS_UNIX)
+  GLIB_PRIVATE_CALL (g_main_send_signal) (self->pid, SIGKILL);
+#else
+  TerminateProcess (self->pid, 1);
+#endif
+}
+
+GSSubprocess *
+gs_subprocess_new_simple_argl (GSSubprocessStreamDisposition stdout_disposition,
+			      GSSubprocessStreamDisposition  stderr_disposition,
+                               GCancellable                 *cancellable,
+			      GError                       **error,
+			      const gchar                   *first_arg,
+			      ...)
+{
+  va_list args;
+  GSSubprocess *result;
+  GSSubprocessContext *context;
+
+  va_start (args, first_arg);
+  context = gs_subprocess_context_newa (first_arg, args);
+  va_end (args);
+  result = gs_subprocess_new (context, cancellable, error);
+  g_object_unref (context);
+  
+  return result;
+}
+
+/**
+ * gs_subprocess_new_simple_argv:
+ * @argv: (array zero-terminated=1) (element-type utf8): Argument array
+ * @stdout_disposition: Where to redirect stdout
+ * @stderr_disposition: Where to redirect stdout
+ * @error: a #GError
+ *
+ * Create a new subprocess using the provided argument array and
+ * stream dispositions.
+ */
+GSSubprocess *
+gs_subprocess_new_simple_argv (gchar                       **argv,
+			      GSSubprocessStreamDisposition  stdout_disposition,
+			      GSSubprocessStreamDisposition  stderr_disposition,
+                               GCancellable                 *cancellable,
+			      GError                      **error)
+{
+  GSSubprocessContext *context;
+  GSSubprocess *result;
+
+  context = gs_subprocess_context_new (argv);
+  gs_subprocess_context_set_stdout_disposition (context, stdout_disposition);
+  gs_subprocess_context_set_stderr_disposition (context, stderr_disposition);
+
+  result = gs_subprocess_new (context, cancellable, error);
+  g_object_unref (context);
+
+  return result;
+}
+
+/**
+ * gs_subprocess_simple_run_sync:
+ * @cwd: Current working directory
+ * @stdin_disposition: What to do with standard input
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ * @first_arg: First argument
+ * @...: Remaining arguments, %NULL terminated
+ *
+ * Run a process synchronously, throw an error if it fails.
+ */
+gboolean
+gs_subprocess_simple_run_sync (const char                    *cwd,
+                               GSSubprocessStreamDisposition  stdin_disposition,
+                               GCancellable                  *cancellable,
+                               GError                       **error,
+                               const char                    *first_arg,
+                               ...)
+{
+  gboolean ret = FALSE;
+  va_list args;
+  GSSubprocess *proc = NULL;
+  GSSubprocessContext *context = NULL;
+
+  va_start (args, first_arg);
+  context = gs_subprocess_context_newa (first_arg, args);
+  va_end (args);
+  gs_subprocess_context_set_stdin_disposition (context, stdin_disposition);
+  gs_subprocess_context_set_cwd (context, cwd);
+  proc = gs_subprocess_new (context, cancellable, error);
+  if (!proc)
+    goto out;
+
+  if (!gs_subprocess_wait_sync_check (proc, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_object_unref (context);
+  if (proc)
+    g_object_unref (proc);
+  return ret;
+}
+
+#endif
diff --git a/libgsystem/gsystem-subprocess.h b/libgsystem/gsystem-subprocess.h
new file mode 100644
index 00000000..f248f7ba
--- /dev/null
+++ b/libgsystem/gsystem-subprocess.h
@@ -0,0 +1,101 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSYSTEM_SUBPROCESS_H__
+#define __GSYSTEM_SUBPROCESS_H__
+
+#include <gio/gio.h>
+
+#if GLIB_CHECK_VERSION(2,34,0)
+
+#include "gsystem-subprocess-context.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_SUBPROCESS         (gs_subprocess_get_type ())
+#define GS_SUBPROCESS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_SUBPROCESS, GSSubprocess))
+#define GS_IS_SUBPROCESS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_SUBPROCESS))
+
+typedef struct _GSSubprocess GSSubprocess;
+
+GType            gs_subprocess_get_type (void) G_GNUC_CONST;
+
+/**** Core API ****/
+
+GSSubprocess *    gs_subprocess_new (GSSubprocessContext   *context,
+                                     GCancellable          *cancellable,
+                                     GError               **error);
+
+GOutputStream *  gs_subprocess_get_stdin_pipe (GSSubprocess       *self);
+
+GInputStream *   gs_subprocess_get_stdout_pipe (GSSubprocess      *self);
+
+GInputStream *   gs_subprocess_get_stderr_pipe (GSSubprocess      *self);
+
+void             gs_subprocess_wait (GSSubprocess                *self,
+				    GCancellable               *cancellable,
+				    GAsyncReadyCallback         callback,
+				    gpointer                    user_data);
+
+gboolean         gs_subprocess_wait_finish (GSSubprocess                *self,
+					   GAsyncResult               *result,
+					   int                        *out_exit_status,
+					   GError                    **error);
+
+gboolean         gs_subprocess_wait_sync (GSSubprocess   *self,
+					 int           *out_exit_status,
+					 GCancellable  *cancellable,
+					 GError       **error);
+
+gboolean         gs_subprocess_wait_sync_check (GSSubprocess   *self,
+					       GCancellable  *cancellable,
+					       GError       **error);
+
+GPid             gs_subprocess_get_pid (GSSubprocess     *self);
+
+gboolean         gs_subprocess_request_exit (GSSubprocess       *self);
+
+void             gs_subprocess_force_exit (GSSubprocess       *self);
+
+/**** High level helpers ****/
+
+GSSubprocess *    gs_subprocess_new_simple_argl (GSSubprocessStreamDisposition   stdout_disposition,
+                                                 GSSubprocessStreamDisposition   stderr_disposition,
+                                                 GCancellable                   *cancellable,
+                                                 GError                        **error,
+                                                 const char                     *first_arg,
+                                                 ...) G_GNUC_NULL_TERMINATED;
+GSSubprocess *    gs_subprocess_new_simple_argv (char                         **argv,
+                                                 GSSubprocessStreamDisposition  stdout_disposition,
+                                                 GSSubprocessStreamDisposition  stderr_disposition,
+                                                 GCancellable                  *cancellable,
+                                                 GError                       **error);
+
+gboolean          gs_subprocess_simple_run_sync (const char                    *cwd,
+                                                 GSSubprocessStreamDisposition  stdin_disposition,
+                                                 GCancellable                  *cancellable,
+                                                 GError                       **error,
+                                                 const char                    *first_arg,
+                                                 ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif
+#endif
diff --git a/libgsystem/libgsystem.h b/libgsystem/libgsystem.h
new file mode 100644
index 00000000..60884b65
--- /dev/null
+++ b/libgsystem/libgsystem.h
@@ -0,0 +1,49 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LIBGSYSTEM__
+#define __LIBGSYSTEM__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define gs_transfer_out_value(outp, srcp) G_STMT_START {   \
+  if (outp)                                                \
+    {                                                      \
+      *(outp) = *(srcp);                                   \
+      *(srcp) = NULL;                                      \
+    }                                                      \
+  } G_STMT_END;
+
+#include <gsystem-console.h>
+#include <gsystem-file-utils.h>
+#include <gsystem-shutil.h>
+#if GLIB_CHECK_VERSION(2,34,0)
+#include <gsystem-subprocess.h>
+#endif
+#include <gsystem-log.h>
+#ifndef _GSYSTEM_NO_LOCAL_ALLOC
+#include <gsystem-local-alloc.h>
+#endif
+
+G_END_DECLS
+
+#endif