New upstream version 2016.8

This commit is contained in:
Simon McVittie committed 2016-09-06 09:01:54 +01:00
commit ba02f3eb2f
250 files changed
+80671 -11685

No files matched your search

-16
View File
@@ -1,16 +0,0 @@
# A path ostree writes to work around automake bug with
# subdir-objects
Makefile-libglnx.am.inc
# Some standard bits
.deps
.libs
.dirstamp
*.typelib
*.la
*.lo
*.o
*.pyc
*.stamp
*~
+2
View File
@@ -37,6 +37,8 @@ libglnx_la_SOURCES = \
$(libglnx_srcpath)/glnx-lockfile.c \
$(libglnx_srcpath)/glnx-libcontainer.h \
$(libglnx_srcpath)/glnx-libcontainer.c \
$(libglnx_srcpath)/glnx-missing-syscall.h \
$(libglnx_srcpath)/glnx-missing.h \
$(libglnx_srcpath)/glnx-xattrs.h \
$(libglnx_srcpath)/glnx-xattrs.c \
$(libglnx_srcpath)/glnx-shutil.h \
+51
View File
@@ -0,0 +1,51 @@
# Copyright (C) 2015 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 += libglnx/README.md libglnx/COPYING
libglnx_la_SOURCES = \
libglnx/glnx-alloca.h \
libglnx/glnx-backport-autocleanups.h \
libglnx/glnx-backport-autoptr.h \
libglnx/glnx-backports.h \
libglnx/glnx-backports.c \
libglnx/glnx-local-alloc.h \
libglnx/glnx-local-alloc.c \
libglnx/glnx-errors.h \
libglnx/glnx-errors.c \
libglnx/glnx-console.h \
libglnx/glnx-console.c \
libglnx/glnx-dirfd.h \
libglnx/glnx-dirfd.c \
libglnx/glnx-fdio.h \
libglnx/glnx-fdio.c \
libglnx/glnx-lockfile.h \
libglnx/glnx-lockfile.c \
libglnx/glnx-libcontainer.h \
libglnx/glnx-libcontainer.c \
libglnx/glnx-missing-syscall.h \
libglnx/glnx-missing.h \
libglnx/glnx-xattrs.h \
libglnx/glnx-xattrs.c \
libglnx/glnx-shutil.h \
libglnx/glnx-shutil.c \
libglnx/libglnx.h \
$(NULL)
libglnx_la_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags)
libglnx_la_LDFLAGS = -avoid-version -Bsymbolic-functions -export-symbols-regex "^glnx_" -no-undefined -export-dynamic
libglnx_la_LIBADD = $(libglnx_libs)
+4 -2
View File
@@ -221,9 +221,11 @@ text_percent_internal (const char *text,
if (percentage == -1)
{
const guint spacelen = ncolumns - input_textlen;
fwrite (text, 1, input_textlen, stdout);
printpad (spaces, n_spaces, spacelen);
/* Overwrite remaining space, if any */
if (ncolumns > input_textlen)
printpad (spaces, n_spaces, ncolumns - input_textlen);
}
else
{
+277 -35
View File
@@ -37,10 +37,255 @@
#include <glnx-fdio.h>
#include <glnx-dirfd.h>
#include <glnx-alloca.h>
#include <glnx-errors.h>
#include <glnx-xattrs.h>
#include <glnx-backport-autoptr.h>
#include <glnx-local-alloc.h>
#include <glnx-missing.h>
/* Returns the number of chars needed to format variables of the
* specified type as a decimal string. Adds in extra space for a
* negative '-' prefix (hence works correctly on signed
* types). Includes space for the trailing NUL. */
#define DECIMAL_STR_MAX(type) \
(2+(sizeof(type) <= 1 ? 3 : \
sizeof(type) <= 2 ? 5 : \
sizeof(type) <= 4 ? 10 : \
sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)])))
static gboolean
rename_file_noreplace_at (int olddirfd, const char *oldpath,
int newdirfd, const char *newpath,
gboolean ignore_eexist,
GError **error)
{
if (renameat2 (olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) < 0)
{
if (errno == EINVAL || errno == ENOSYS)
{
/* Fall through */
;
}
else if (errno == EEXIST && ignore_eexist)
{
(void) unlinkat (olddirfd, oldpath, 0);
return TRUE;
}
else
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
else
return TRUE;
if (linkat (olddirfd, oldpath, newdirfd, newpath, 0) < 0)
{
if (errno == EEXIST && ignore_eexist)
/* Fall through */
;
else
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
if (unlinkat (olddirfd, oldpath, 0) < 0)
{
glnx_set_error_from_errno (error);
return FALSE;
}
return TRUE;
}
gboolean
glnx_open_tmpfile_linkable_at (int dfd,
const char *subpath,
int flags,
int *out_fd,
char **out_path,
GError **error)
{
glnx_fd_close int fd = -1;
int count;
/* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
g_return_val_if_fail ((flags & O_EXCL) == 0, FALSE);
/* Creates a temporary file, that shall be renamed to "target"
* later. If possible, this uses O_TMPFILE – in which case
* "ret_path" will be returned as NULL. If not possible a the
* tempoary path name used is returned in "ret_path". Use
* link_tmpfile() below to rename the result after writing the file
* in full. */
#if defined(O_TMPFILE) && !defined(DISABLE_OTMPFILE)
fd = openat (dfd, subpath, O_TMPFILE|flags, 0600);
if (fd == -1 && !(errno == ENOSYS || errno == EISDIR || errno == EOPNOTSUPP))
{
glnx_set_prefix_error_from_errno (error, "%s", "open(O_TMPFILE)");
return FALSE;
}
if (fd != -1)
{
*out_fd = fd;
fd = -1;
*out_path = NULL;
return TRUE;
}
/* Fallthrough */
#endif
{ g_autofree char *tmp = g_strconcat (subpath, "/tmp.XXXXXX", NULL);
const guint count_max = 100;
for (count = 0; count < count_max; count++)
{
glnx_gen_temp_name (tmp);
fd = openat (dfd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0600);
if (fd < 0)
{
if (errno == EEXIST)
continue;
else
{
glnx_set_prefix_error_from_errno (error, "%s", "Creating temp file");
return FALSE;
}
}
else
{
*out_fd = fd;
fd = -1;
*out_path = g_steal_pointer (&tmp);
return TRUE;
}
}
}
g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
"Exhausted %u attempts to create temporary file", count);
return FALSE;
}
gboolean
glnx_link_tmpfile_at (int dfd,
GLnxLinkTmpfileReplaceMode mode,
int fd,
const char *tmpfile_path,
int target_dfd,
const char *target,
GError **error)
{
const gboolean replace = (mode == GLNX_LINK_TMPFILE_REPLACE);
const gboolean ignore_eexist = (mode == GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST);
g_return_val_if_fail (fd >= 0, FALSE);
/* Unlike the original systemd code, this function also supports
* replacing existing files.
*/
/* We have `tmpfile_path` for old systems without O_TMPFILE. */
if (tmpfile_path)
{
if (replace)
{
/* We have a regular tempfile, we're overwriting - this is a
* simple renameat().
*/
if (renameat (dfd, tmpfile_path, target_dfd, target) < 0)
{
(void) unlinkat (dfd, tmpfile_path, 0);
glnx_set_error_from_errno (error);
return FALSE;
}
}
else
{
/* We need to use renameat2(..., NOREPLACE) or emulate it */
if (!rename_file_noreplace_at (dfd, tmpfile_path, target_dfd, target,
ignore_eexist,
error))
{
(void) unlinkat (dfd, tmpfile_path, 0);
return FALSE;
}
}
}
else
{
/* This case we have O_TMPFILE, so our reference to it is via /proc/self/fd */
char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
sprintf (proc_fd_path, "/proc/self/fd/%i", fd);
if (replace)
{
/* In this case, we had our temp file atomically hidden, but now
* we need to make it visible in the FS so we can do a rename.
* Ideally, linkat() would gain AT_REPLACE or so.
*/
/* TODO - avoid double alloca, we can just alloca a copy of
* the pathname plus space for tmp.XXXXX */
char *dnbuf = strdupa (target);
const char *dn = dirname (dnbuf);
char *tmpname_buf = glnx_strjoina (dn, "/tmp.XXXXXX");
guint count;
const guint count_max = 100;
for (count = 0; count < count_max; count++)
{
glnx_gen_temp_name (tmpname_buf);
if (linkat (AT_FDCWD, proc_fd_path, target_dfd, tmpname_buf, AT_SYMLINK_FOLLOW) < 0)
{
if (errno == EEXIST)
continue;
else
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
else
break;
}
if (count == count_max)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
"Exhausted %u attempts to create temporary file", count);
}
if (renameat (target_dfd, tmpname_buf, target_dfd, target) < 0)
{
/* This is currently the only case where we need to have
* a cleanup unlinkat() still with O_TMPFILE.
*/
(void) unlinkat (target_dfd, tmpname_buf, 0);
glnx_set_error_from_errno (error);
return FALSE;
}
}
else
{
if (linkat (AT_FDCWD, proc_fd_path, target_dfd, target, AT_SYMLINK_FOLLOW) < 0)
{
if (errno == EEXIST && mode == GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST)
;
else
{
glnx_set_error_from_errno (error);
return FALSE;
}
}
}
}
return TRUE;
}
static guint8*
glnx_fd_readall_malloc (int fd,
@@ -658,38 +903,45 @@ glnx_file_replace_contents_with_perms_at (int dfd,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
int r;
/* We use the /proc/self trick as there's no mkostemp_at() yet */
g_autofree char *tmppath = g_strdup_printf ("/proc/self/fd/%d/.tmpXXXXXX", dfd);
char *dnbuf = strdupa (subpath);
const char *dn = dirname (dnbuf);
g_autofree char *tmpfile_path = NULL;
glnx_fd_close int fd = -1;
dfd = glnx_dirfd_canonicalize (dfd);
if ((fd = g_mkstemp_full (tmppath, O_WRONLY | O_CLOEXEC,
mode == (mode_t) -1 ? 0666 : mode)) == -1)
{
glnx_set_error_from_errno (error);
goto out;
}
/* With O_TMPFILE we can't use umask, and we can't sanely query the
* umask...let's assume something relatively standard.
*/
if (mode == (mode_t) -1)
mode = 0644;
if (!glnx_open_tmpfile_linkable_at (dfd, dn, O_WRONLY | O_CLOEXEC,
&fd, &tmpfile_path,
error))
return FALSE;
if (len == -1)
len = strlen ((char*)buf);
/* Note that posix_fallocate does *not* set errno but returns it. */
r = posix_fallocate (fd, 0, len);
if (r != 0)
if (len > 0)
{
errno = r;
glnx_set_error_from_errno (error);
goto out;
r = posix_fallocate (fd, 0, len);
if (r != 0)
{
errno = r;
glnx_set_error_from_errno (error);
return FALSE;
}
}
if ((r = glnx_loop_write (fd, buf, len)) != 0)
{
errno = -r;
glnx_set_error_from_errno (error);
goto out;
return FALSE;
}
if (!(flags & GLNX_FILE_REPLACE_NODATASYNC))
@@ -702,7 +954,7 @@ glnx_file_replace_contents_with_perms_at (int dfd,
if (errno != ENOENT)
{
glnx_set_error_from_errno (error);
goto out;
return FALSE;
}
do_sync = (flags & GLNX_FILE_REPLACE_DATASYNC_NEW) > 0;
}
@@ -714,7 +966,7 @@ glnx_file_replace_contents_with_perms_at (int dfd,
if (fdatasync (fd) != 0)
{
glnx_set_error_from_errno (error);
goto out;
return FALSE;
}
}
}
@@ -724,31 +976,21 @@ glnx_file_replace_contents_with_perms_at (int dfd,
if (fchown (fd, uid, gid) != 0)
{
glnx_set_error_from_errno (error);
goto out;
return FALSE;
}
}
/* If a mode was forced, override umask */
if (mode != (mode_t) -1)
{
if (fchmod (fd, mode) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
}
if (renameat (dfd, tmppath, dfd, subpath) != 0)
if (fchmod (fd, mode) != 0)
{
glnx_set_error_from_errno (error);
goto out;
return FALSE;
}
ret = TRUE;
out:
if (!ret)
(void) unlink (tmppath);
return ret;
if (!glnx_link_tmpfile_at (dfd, GLNX_LINK_TMPFILE_REPLACE,
fd, tmpfile_path, dfd, subpath, error))
return FALSE;
return TRUE;
}
/**
+23
View File
@@ -46,6 +46,29 @@ const char *glnx_basename (const char *path)
return (basename) (path);
}
gboolean
glnx_open_tmpfile_linkable_at (int dfd,
const char *subpath,
int flags,
int *out_fd,
char **out_path,
GError **error);
typedef enum {
GLNX_LINK_TMPFILE_REPLACE,
GLNX_LINK_TMPFILE_NOREPLACE,
GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST
} GLnxLinkTmpfileReplaceMode;
gboolean
glnx_link_tmpfile_at (int dfd,
GLnxLinkTmpfileReplaceMode flags,
int fd,
const char *tmpfile_path,
int target_dfd,
const char *target,
GError **error);
GBytes *
glnx_fd_readall_bytes (int fd,
GCancellable *cancellable,
+27 -11
View File
@@ -33,6 +33,7 @@
#include <gio/gunixoutputstream.h>
#include "glnx-libcontainer.h"
#include "glnx-dirfd.h"
#include "glnx-backport-autocleanups.h"
#include "glnx-local-alloc.h"
@@ -104,9 +105,9 @@ glnx_libcontainer_bind_mount_readonly (const char *path, GError **error)
}
#endif
/* Based on code from nspawn.c */
/* Based on code from nspawn.c; assumes process cwd is the target */
static int
glnx_libcontainer_make_api_mounts (const char *dest)
glnx_libcontainer_make_api_mounts (void)
{
typedef struct MountPoint {
const char *what;
@@ -134,10 +135,11 @@ glnx_libcontainer_make_api_mounts (const char *dest)
for (k = 0; k < G_N_ELEMENTS(mount_table); k++)
{
g_autofree char *where = NULL;
const char *where = mount_table[k].where;
int t;
where = g_build_filename (dest, mount_table[k].where, NULL);
g_assert (where[0] == '/');
where++;
t = mkdir (where, 0755);
if (t < 0 && errno != EEXIST)
@@ -201,9 +203,9 @@ glnx_libcontainer_prep_dev (const char *dest_devdir)
}
pid_t
glnx_libcontainer_run_chroot_private (const char *dest,
const char *binary,
char **argv)
glnx_libcontainer_run_chroot_at_private (int dfd,
const char *binary,
char **argv)
{
/* Make most new namespaces; note our use of CLONE_NEWNET means we
* have no networking in the container root.
@@ -250,12 +252,12 @@ glnx_libcontainer_run_chroot_private (const char *dest,
}
}
if (chdir (dest) != 0)
_perror_fatal ("chdir: ");
if (fchdir (dfd) != 0)
_perror_fatal ("fchdir: ");
if (!in_container)
{
if (glnx_libcontainer_make_api_mounts (dest) != 0)
if (glnx_libcontainer_make_api_mounts () != 0)
_perror_fatal ("preparing api mounts: ");
if (glnx_libcontainer_prep_dev ("dev") != 0)
@@ -264,7 +266,7 @@ glnx_libcontainer_run_chroot_private (const char *dest,
if (mount (".", ".", NULL, MS_BIND | MS_PRIVATE, NULL) != 0)
_perror_fatal ("mount (MS_BIND)");
if (mount (dest, "/", NULL, MS_MOVE, NULL) != 0)
if (mount (".", "/", NULL, MS_MOVE, NULL) != 0)
_perror_fatal ("mount (MS_MOVE)");
}
@@ -295,3 +297,17 @@ glnx_libcontainer_run_chroot_private (const char *dest,
g_assert_not_reached ();
}
pid_t
glnx_libcontainer_run_chroot_private (const char *dest,
const char *binary,
char **argv)
{
glnx_fd_close int dfd = -1;
dfd = glnx_opendirat_with_errno (AT_FDCWD, dest, TRUE);
if (dfd < 0)
return -1;
return glnx_libcontainer_run_chroot_at_private (dfd, binary, argv);
}
+4
View File
@@ -31,6 +31,10 @@
#include <sys/capability.h>
#include <sched.h>
pid_t glnx_libcontainer_run_chroot_at_private (int root_dfd,
const char *binary,
char **argv);
pid_t glnx_libcontainer_run_chroot_private (const char *dest,
const char *binary,
char **argv);
+2 -1
View File
@@ -187,7 +187,8 @@ void glnx_release_lock_file(GLnxLockFile *f) {
f->path = NULL;
}
(void) close (f->fd);
if (f->fd != -1)
(void) close (f->fd);
f->fd = -1;
f->operation = 0;
}
+54
View File
@@ -0,0 +1,54 @@
/***
This file was originally part of systemd.
Copyright 2010 Lennart Poettering
Copyright 2016 Zbigniew Jędrzejewski-Szmek
systemd 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.1 of the License, or
(at your option) any later version.
systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
***/
/* Missing glibc definitions to access certain kernel APIs */
#if !HAVE_DECL_RENAMEAT2
# ifndef __NR_renameat2
# if defined __x86_64__
# define __NR_renameat2 316
# elif defined __arm__
# define __NR_renameat2 382
# elif defined _MIPS_SIM
# if _MIPS_SIM == _MIPS_SIM_ABI32
# define __NR_renameat2 4351
# endif
# if _MIPS_SIM == _MIPS_SIM_NABI32
# define __NR_renameat2 6315
# endif
# if _MIPS_SIM == _MIPS_SIM_ABI64
# define __NR_renameat2 5311
# endif
# elif defined __i386__
# define __NR_renameat2 353
# else
# warning "__NR_renameat2 unknown for your architecture"
# endif
# endif
static inline int renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) {
# ifdef __NR_renameat2
return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags);
# else
errno = ENOSYS;
return -1;
# endif
}
#endif
+52
View File
@@ -0,0 +1,52 @@
#pragma once
/***
This file was originally part of systemd.
Copyright 2010 Lennart Poettering
systemd 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.1 of the License, or
(at your option) any later version.
systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
***/
/* Missing glibc definitions to access certain kernel APIs */
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <uchar.h>
#include <unistd.h>
#if defined(__i386__) || defined(__x86_64__)
/* The precise definition of __O_TMPFILE is arch specific, so let's
* just define this on x86 where we know the value. */
#ifndef __O_TMPFILE
#define __O_TMPFILE 020000000
#endif
/* a horrid kludge trying to make sure that this will fail on old kernels */
#ifndef O_TMPFILE
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
#endif
#endif
#ifndef RENAME_NOREPLACE
#define RENAME_NOREPLACE (1 << 0)
#endif
#include "glnx-missing-syscall.h"
+1 -40
View File
@@ -26,26 +26,6 @@
#include <glnx-errors.h>
#include <glnx-local-alloc.h>
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
glnx_shutil_rm_rf_children (GLnxDirFdIterator *dfd_iter,
GCancellable *cancellable,
@@ -56,31 +36,12 @@ glnx_shutil_rm_rf_children (GLnxDirFdIterator *dfd_iter,
while (TRUE)
{
if (!glnx_dirfd_iterator_next_dent (dfd_iter, &dent, cancellable, error))
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (dfd_iter, &dent, cancellable, error))
goto out;
if (dent == NULL)
break;
if (dent->d_type == DT_UNKNOWN)
{
struct stat stbuf;
if (fstatat (dfd_iter->fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
{
if (errno == ENOENT)
continue;
else
{
glnx_set_error_from_errno (error);
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 (dent->d_type == DT_DIR)
{
g_auto(GLnxDirFdIterator) child_dfd_iter = { 0, };
-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:gnome="http://api.gnome.org/doap-extensions#"
xmlns="http://usefulinc.com/ns/doap#">
<name>libglnx</name>
<shortname>libglnx</shortname>
<shortdesc xml:lang="en">"Copylib" for system service modules using GLib with Linux</shortdesc>
<description xml:lang="en">This module is intended for use by
infrastructure code using GLib that is also Linux specific, such as
ostree, NetworkManager, and others.
</description>
<license rdf:resource="http://usefulinc.com/doap/licenses/lgpl" />
<mailing-list rdf:resource="mailto:desktop-devel-list@gnome.org" />
<programming-language>C</programming-language>
<maintainer>
<foaf:Person>
<foaf:name>Colin Walters</foaf:name>
<foaf:mbox rdf:resource="mailto:walters@verbum.org"/>
<gnome:userid>walters</gnome:userid>
</foaf:Person>
</maintainer>
</Project>