summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Shadura <andrewsh@debian.org>2015-11-22 21:25:49 +0100
committerAndrew Shadura <andrewsh@debian.org>2015-11-22 21:25:49 +0100
commit6e7205c8f65c8bc03af8619c04b7179218eb1520 (patch)
tree0957dfac7dbf4a5a39adfe2777666037b7bdd0f3
pseudo (1.7.4-1) unstable; urgency=low
* Initial release (Closes: #796973). # imported from the archive
-rw-r--r--.gitignore16
-rw-r--r--COPYING504
-rw-r--r--ChangeLog.txt664
-rw-r--r--Futures.txt54
-rw-r--r--Makefile.in213
-rw-r--r--README87
-rwxr-xr-xconfigure341
-rw-r--r--debian/README.Debian6
-rw-r--r--debian/changelog5
-rw-r--r--debian/clean2
-rw-r--r--debian/compat1
-rw-r--r--debian/control23
-rw-r--r--debian/copyright47
-rw-r--r--debian/docs7
-rwxr-xr-xdebian/fakeroot-pseudo129
-rw-r--r--debian/fakeroot-pseudo.1163
-rw-r--r--debian/install1
-rw-r--r--debian/manpages3
-rw-r--r--debian/patches/manpage.patch28
-rw-r--r--debian/patches/series1
-rwxr-xr-xdebian/postinst26
-rwxr-xr-xdebian/prerm23
-rwxr-xr-xdebian/rules20
-rw-r--r--debian/source/format1
-rw-r--r--debian/watch1
-rw-r--r--doc/chroot31
-rw-r--r--doc/database120
-rw-r--r--doc/overview94
-rw-r--r--doc/passwd27
-rw-r--r--doc/perftest8
-rw-r--r--doc/ports23
-rw-r--r--doc/program_flow84
-rw-r--r--doc/pseudo_ipc78
-rw-r--r--doc/utils33
-rw-r--r--enums/debug_type.in30
-rw-r--r--enums/msg_type.in7
-rw-r--r--enums/op.in29
-rw-r--r--enums/query_field.in28
-rw-r--r--enums/query_type.in9
-rw-r--r--enums/res.in4
-rw-r--r--enums/sev.in6
-rw-r--r--guts/COPYRIGHT17
-rw-r--r--guts/README220
-rw-r--r--makedata.c32
-rwxr-xr-xmaketables275
-rwxr-xr-xmakewrappers638
-rw-r--r--offsets.c56
-rwxr-xr-xperftest57
-rw-r--r--ports/common/guts/execl.c18
-rw-r--r--ports/common/guts/execle.c18
-rw-r--r--ports/common/guts/execlp.c18
-rw-r--r--ports/common/guts/execv.c32
-rw-r--r--ports/common/guts/execve.c33
-rw-r--r--ports/common/guts/execvp.c33
-rw-r--r--ports/common/guts/fork.c23
-rw-r--r--ports/common/pseudo_wrappers.c408
-rwxr-xr-xports/common/subports8
-rw-r--r--ports/common/wrapfuncs.in7
-rw-r--r--ports/darwin/guts/COPYRIGHT17
-rw-r--r--ports/darwin/guts/fcntl.c44
-rw-r--r--ports/darwin/guts/fgetgrent_r.c13
-rw-r--r--ports/darwin/guts/fgetpwent_r.c13
-rw-r--r--ports/darwin/guts/fgetxattr.c13
-rw-r--r--ports/darwin/guts/flistxattr.c13
-rw-r--r--ports/darwin/guts/fremovexattr.c13
-rw-r--r--ports/darwin/guts/fsetxattr.c13
-rw-r--r--ports/darwin/guts/fstat.c27
-rw-r--r--ports/darwin/guts/getgrent_r.c13
-rw-r--r--ports/darwin/guts/getgrouplist.c42
-rw-r--r--ports/darwin/guts/getgroups.c22
-rw-r--r--ports/darwin/guts/getpwent_r.c13
-rw-r--r--ports/darwin/guts/getxattr.c13
-rw-r--r--ports/darwin/guts/listxattr.c13
-rw-r--r--ports/darwin/guts/lstat.c27
-rw-r--r--ports/darwin/guts/open.c58
-rw-r--r--ports/darwin/guts/removexattr.c13
-rw-r--r--ports/darwin/guts/scandir.c14
-rw-r--r--ports/darwin/guts/setxattr.c13
-rw-r--r--ports/darwin/guts/stat.c31
-rw-r--r--ports/darwin/guts/sync_file_range.c13
-rw-r--r--ports/darwin/portdefs.h13
-rwxr-xr-xports/darwin/preports2
-rw-r--r--ports/darwin/pseudo_wrappers.c606
-rwxr-xr-xports/darwin/subports3
-rw-r--r--ports/darwin/wrapfuncs.in27
-rw-r--r--ports/linux/guts/COPYRIGHT17
-rw-r--r--ports/linux/guts/__fxstat.c20
-rw-r--r--ports/linux/guts/__fxstat64.c30
-rw-r--r--ports/linux/guts/__fxstatat.c32
-rw-r--r--ports/linux/guts/__fxstatat64.c58
-rw-r--r--ports/linux/guts/__lxstat.c14
-rw-r--r--ports/linux/guts/__lxstat64.c14
-rw-r--r--ports/linux/guts/__openat64_2.c14
-rw-r--r--ports/linux/guts/__openat_2.c14
-rw-r--r--ports/linux/guts/__xmknod.c14
-rw-r--r--ports/linux/guts/__xmknodat.c78
-rw-r--r--ports/linux/guts/__xstat.c14
-rw-r--r--ports/linux/guts/__xstat64.c13
-rw-r--r--ports/linux/guts/canonicalize_file_name.c14
-rw-r--r--ports/linux/guts/creat64.c14
-rw-r--r--ports/linux/guts/eaccess.c14
-rw-r--r--ports/linux/guts/euidaccess.c14
-rw-r--r--ports/linux/guts/fcntl.c71
-rw-r--r--ports/linux/guts/fopen64.c37
-rw-r--r--ports/linux/guts/freopen64.c36
-rw-r--r--ports/linux/guts/fstat.c13
-rw-r--r--ports/linux/guts/fstat64.c13
-rw-r--r--ports/linux/guts/ftw64.c14
-rw-r--r--ports/linux/guts/get_current_dir_name.c17
-rw-r--r--ports/linux/guts/getgrent_r.c21
-rw-r--r--ports/linux/guts/getgrouplist.c42
-rw-r--r--ports/linux/guts/getgroups.c22
-rw-r--r--ports/linux/guts/getpw.c33
-rw-r--r--ports/linux/guts/getpwent_r.c21
-rw-r--r--ports/linux/guts/getresgid.c23
-rw-r--r--ports/linux/guts/getresuid.c23
-rw-r--r--ports/linux/guts/glob64.c36
-rw-r--r--ports/linux/guts/lchown.c13
-rw-r--r--ports/linux/guts/lckpwdf.c32
-rw-r--r--ports/linux/guts/lstat.c13
-rw-r--r--ports/linux/guts/lstat64.c13
-rw-r--r--ports/linux/guts/mkstemp64.c47
-rw-r--r--ports/linux/guts/nftw64.c14
-rw-r--r--ports/linux/guts/open.c14
-rw-r--r--ports/linux/guts/open64.c14
-rw-r--r--ports/linux/guts/openat.c92
-rw-r--r--ports/linux/guts/openat64.c14
-rw-r--r--ports/linux/guts/scandir.c14
-rw-r--r--ports/linux/guts/scandir64.c14
-rw-r--r--ports/linux/guts/setfsgid.c19
-rw-r--r--ports/linux/guts/setfsuid.c19
-rw-r--r--ports/linux/guts/setgroups.c18
-rw-r--r--ports/linux/guts/setresgid.c37
-rw-r--r--ports/linux/guts/setresuid.c37
-rw-r--r--ports/linux/guts/stat.c13
-rw-r--r--ports/linux/guts/stat64.c13
-rw-r--r--ports/linux/guts/truncate64.c14
-rw-r--r--ports/linux/guts/ulckpwdf.c15
-rw-r--r--ports/linux/newclone/guts/clone.c31
-rw-r--r--ports/linux/newclone/pseudo_wrappers.c92
-rw-r--r--ports/linux/newclone/wrapfuncs.in1
-rw-r--r--ports/linux/noxattr/guts/fgetxattr.c18
-rw-r--r--ports/linux/noxattr/guts/flistxattr.c17
-rw-r--r--ports/linux/noxattr/guts/fremovexattr.c16
-rw-r--r--ports/linux/noxattr/guts/fsetxattr.c19
-rw-r--r--ports/linux/noxattr/guts/getxattr.c18
-rw-r--r--ports/linux/noxattr/guts/lgetxattr.c18
-rw-r--r--ports/linux/noxattr/guts/listxattr.c17
-rw-r--r--ports/linux/noxattr/guts/llistxattr.c17
-rw-r--r--ports/linux/noxattr/guts/lremovexattr.c16
-rw-r--r--ports/linux/noxattr/guts/lsetxattr.c19
-rw-r--r--ports/linux/noxattr/guts/removexattr.c16
-rw-r--r--ports/linux/noxattr/guts/setxattr.c19
-rw-r--r--ports/linux/noxattr/wrapfuncs.in14
-rw-r--r--ports/linux/oldclone/guts/clone.c31
-rw-r--r--ports/linux/oldclone/pseudo_wrappers.c76
-rw-r--r--ports/linux/oldclone/wrapfuncs.in1
-rw-r--r--ports/linux/portdefs.h27
-rwxr-xr-xports/linux/preports2
-rw-r--r--ports/linux/pseudo_wrappers.c33
-rwxr-xr-xports/linux/subports43
-rw-r--r--ports/linux/wrapfuncs.in53
-rw-r--r--ports/linux/xattr/guts/fgetxattr.c12
-rw-r--r--ports/linux/xattr/guts/flistxattr.c12
-rw-r--r--ports/linux/xattr/guts/fremovexattr.c12
-rw-r--r--ports/linux/xattr/guts/fsetxattr.c12
-rw-r--r--ports/linux/xattr/guts/getxattr.c12
-rw-r--r--ports/linux/xattr/guts/lgetxattr.c12
-rw-r--r--ports/linux/xattr/guts/listxattr.c12
-rw-r--r--ports/linux/xattr/guts/llistxattr.c12
-rw-r--r--ports/linux/xattr/guts/lremovexattr.c12
-rw-r--r--ports/linux/xattr/guts/lsetxattr.c12
-rw-r--r--ports/linux/xattr/guts/removexattr.c12
-rw-r--r--ports/linux/xattr/guts/setxattr.c12
-rw-r--r--ports/linux/xattr/portdefs.h2
-rw-r--r--ports/linux/xattr/pseudo_wrappers.c233
-rw-r--r--ports/linux/xattr/wrapfuncs.in12
-rw-r--r--ports/uids_generic/guts/COPYRIGHT17
-rw-r--r--ports/uids_generic/guts/endgrent.c14
-rw-r--r--ports/uids_generic/guts/endpwent.c14
-rw-r--r--ports/uids_generic/guts/getegid.c14
-rw-r--r--ports/uids_generic/guts/geteuid.c14
-rw-r--r--ports/uids_generic/guts/getgid.c14
-rw-r--r--ports/uids_generic/guts/getgrent.c21
-rw-r--r--ports/uids_generic/guts/getgrgid.c21
-rw-r--r--ports/uids_generic/guts/getgrgid_r.c30
-rw-r--r--ports/uids_generic/guts/getgrnam.c23
-rw-r--r--ports/uids_generic/guts/getgrnam_r.c29
-rw-r--r--ports/uids_generic/guts/getpwent.c21
-rw-r--r--ports/uids_generic/guts/getpwnam.c21
-rw-r--r--ports/uids_generic/guts/getpwnam_r.c28
-rw-r--r--ports/uids_generic/guts/getpwuid.c21
-rw-r--r--ports/uids_generic/guts/getpwuid_r.c28
-rw-r--r--ports/uids_generic/guts/getuid.c14
-rw-r--r--ports/uids_generic/guts/setegid.c20
-rw-r--r--ports/uids_generic/guts/seteuid.c20
-rw-r--r--ports/uids_generic/guts/setgid.c27
-rw-r--r--ports/uids_generic/guts/setgrent.c14
-rw-r--r--ports/uids_generic/guts/setpwent.c14
-rw-r--r--ports/uids_generic/guts/setregid.c30
-rw-r--r--ports/uids_generic/guts/setreuid.c30
-rw-r--r--ports/uids_generic/guts/setuid.c27
-rw-r--r--ports/uids_generic/wrapfuncs.in25
-rw-r--r--ports/unix/guts/COPYRIGHT17
-rw-r--r--ports/unix/guts/access.c31
-rw-r--r--ports/unix/guts/acct.c14
-rw-r--r--ports/unix/guts/chdir.c24
-rw-r--r--ports/unix/guts/chmod.c14
-rw-r--r--ports/unix/guts/chown.c14
-rw-r--r--ports/unix/guts/chroot.c19
-rw-r--r--ports/unix/guts/close.c17
-rw-r--r--ports/unix/guts/closedir.c20
-rw-r--r--ports/unix/guts/creat.c14
-rw-r--r--ports/unix/guts/dup.c19
-rw-r--r--ports/unix/guts/dup2.c22
-rw-r--r--ports/unix/guts/fchdir.c18
-rw-r--r--ports/unix/guts/fchmod.c25
-rw-r--r--ports/unix/guts/fchmodat.c76
-rw-r--r--ports/unix/guts/fchown.c46
-rw-r--r--ports/unix/guts/fchownat.c56
-rw-r--r--ports/unix/guts/fclose.c20
-rw-r--r--ports/unix/guts/fdatasync.c16
-rw-r--r--ports/unix/guts/fopen.c36
-rw-r--r--ports/unix/guts/freopen.c36
-rw-r--r--ports/unix/guts/fsync.c16
-rw-r--r--ports/unix/guts/fts_open.c48
-rw-r--r--ports/unix/guts/ftw.c14
-rw-r--r--ports/unix/guts/getcwd.c67
-rw-r--r--ports/unix/guts/getwd.c21
-rw-r--r--ports/unix/guts/glob.c36
-rw-r--r--ports/unix/guts/lchown.c13
-rw-r--r--ports/unix/guts/link.c20
-rw-r--r--ports/unix/guts/linkat.c70
-rw-r--r--ports/unix/guts/lutimes.c14
-rw-r--r--ports/unix/guts/mkdir.c14
-rw-r--r--ports/unix/guts/mkdirat.c48
-rw-r--r--ports/unix/guts/mkdtemp.c45
-rw-r--r--ports/unix/guts/mkfifo.c14
-rw-r--r--ports/unix/guts/mkfifoat.c78
-rw-r--r--ports/unix/guts/mknod.c13
-rw-r--r--ports/unix/guts/mknodat.c73
-rw-r--r--ports/unix/guts/mkstemp.c47
-rw-r--r--ports/unix/guts/mktemp.c35
-rw-r--r--ports/unix/guts/msync.c16
-rw-r--r--ports/unix/guts/nftw.c14
-rw-r--r--ports/unix/guts/opendir.c31
-rw-r--r--ports/unix/guts/pathconf.c14
-rw-r--r--ports/unix/guts/popen.c19
-rw-r--r--ports/unix/guts/readlink.c14
-rw-r--r--ports/unix/guts/readlinkat.c25
-rw-r--r--ports/unix/guts/realpath.c28
-rw-r--r--ports/unix/guts/remove.c22
-rw-r--r--ports/unix/guts/rename.c100
-rw-r--r--ports/unix/guts/renameat.c111
-rw-r--r--ports/unix/guts/rmdir.c36
-rw-r--r--ports/unix/guts/symlink.c14
-rw-r--r--ports/unix/guts/symlinkat.c52
-rw-r--r--ports/unix/guts/sync.c16
-rw-r--r--ports/unix/guts/sync_file_range.c13
-rw-r--r--ports/unix/guts/syncfs.c13
-rw-r--r--ports/unix/guts/system.c19
-rw-r--r--ports/unix/guts/tempnam.c18
-rw-r--r--ports/unix/guts/tmpnam.c18
-rw-r--r--ports/unix/guts/truncate.c14
-rw-r--r--ports/unix/guts/umask.c14
-rw-r--r--ports/unix/guts/unlink.c14
-rw-r--r--ports/unix/guts/unlinkat.c61
-rw-r--r--ports/unix/guts/utime.c13
-rw-r--r--ports/unix/guts/utimes.c13
-rw-r--r--ports/unix/pseudo_wrappers.c50
-rw-r--r--ports/unix/wrapfuncs.in70
-rw-r--r--pseudo.1672
-rw-r--r--pseudo.c1224
-rw-r--r--pseudo.h165
-rw-r--r--pseudo_client.c1982
-rw-r--r--pseudo_client.h89
-rw-r--r--pseudo_db.c2563
-rw-r--r--pseudo_db.h94
-rw-r--r--pseudo_ipc.c243
-rw-r--r--pseudo_ipc.h68
-rw-r--r--pseudo_profile.c64
-rw-r--r--pseudo_server.c529
-rw-r--r--pseudo_server.h23
-rw-r--r--pseudo_util.c1495
-rw-r--r--pseudo_wrappers.c292
-rw-r--r--pseudodb.c49
-rw-r--r--pseudolog.1395
-rw-r--r--pseudolog.c901
-rwxr-xr-xrun_tests.sh48
-rw-r--r--table_templates/pseudo_tables.c43
-rw-r--r--table_templates/pseudo_tables.h24
-rw-r--r--templatefile.py111
-rw-r--r--templates/func_deps4
-rw-r--r--templates/guts16
-rw-r--r--templates/port_deps5
-rw-r--r--templates/port_wrappers5
-rw-r--r--templates/pseudo_ports6
-rw-r--r--templates/wrapfuncs.c95
-rw-r--r--templates/wrapfuncs.h12
-rw-r--r--templates/wrapper_table24
-rwxr-xr-xtest/test-chroot.sh27
-rwxr-xr-xtest/test-dir-move.sh14
-rwxr-xr-xtest/test-env_i.sh11
-rwxr-xr-xtest/test-execl.sh21
-rwxr-xr-xtest/test-pseudo_disable-fork-env_i.sh30
-rwxr-xr-xtest/test-pseudo_disable-fork.sh18
-rwxr-xr-xtest/test-pseudo_unload-fork-env_i.sh29
-rwxr-xr-xtest/test-pseudo_unload-fork.sh18
-rwxr-xr-xtest/test-reexec-chroot.sh30
-rwxr-xr-xtest/test-umask.sh32
-rwxr-xr-xtest/test-xattr.sh50
311 files changed, 22420 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e6e11d9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*.o
+Makefile
+libpseudo.so
+pseudo_wrapfuncs.*
+pseudo_wrapper_table.c
+pseudo
+pseudodb
+pseudolog
+pseudo_profile
+pseudo_tables.c
+pseudo_tables.h
+port_wrappers.c
+pseudo_ports.h
+templatefile.pyc
+func_deps.mk
+port_deps.mk
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..602bfc9
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 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 Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ 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 Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+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 other code 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.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ 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, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) 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.
+
+ d) 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.
+
+ e) 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 materials to be 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 with
+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 Lesser 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 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.
+
+ 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., 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/ChangeLog.txt b/ChangeLog.txt
new file mode 100644
index 0000000..5431f1e
--- /dev/null
+++ b/ChangeLog.txt
@@ -0,0 +1,664 @@
+2015-09-22:
+ * (seebs) Fix modes after fopen/fopen64.
+
+2015-09-04:
+ * (seebs) add return value printing to wrapper debug!
+ * (seebs) make mkdirat() restore errno, also don't pass
+ AT_SYMLINK_NOFOLLOW to fchmodat() since it is ignored, and
+ we know that if the thing exists at all, it is a directory
+ and thus AT_SYMLINK_NOFOLLOW would be irrelevant anyway.
+ * (seebs) and 1.7.3, of course.
+
+2015-09-03:
+ * (seebs) Use original mode, not 0600, for the chmods used to
+ ensure that an 0700 umask didn't prevent writing.
+ * (seebs) remove the comment saying that 0700 umasks were expected
+ to break things.
+ * (seebs) and call this 1.7.2.
+
+2015-09-02:
+ * (seebs) call this 1.7.1, since that seems to be all the bugs
+ I can immediately find.
+
+2015-09-01:
+ * (seebs) use PSEUDO_STATBUF and base_lstat in path resolution, because
+ plain lstat can fail on XFS if an inode number is out of the 32-bit
+ range.
+ * (seebs) if no pointer was provided to realpath, provide allocated
+ dup.
+
+2015-08-24:
+ * (seebs) drop unused/unimplemented "picky_fchmodat", since I
+ currently have no systems which implement it, and the implementation
+ for NO_REAL_AT_FUNCTIONS was difficult to define.
+ * (seebs) drop unused "doing_link" in fchownat, which I think predates
+ the current database entirely.
+ * (seebs) drop unused "old_trailing_slash".
+ * (seebs) tag this as 1.7.0.
+
+2015-08-22:
+ * (seebs) Prevent files from getting created with real filesystem
+ mode 0, even with umask.
+ * (seebs) xattrdb logic fixes.
+ * (seebs) Debugging messages.
+
+2015-08-21:
+ * (seebs) don't put incorrect host UIDs in dummy entries in db.
+ * (seebs) merge existing values for chown/chmod with xattrdb.
+
+2015-08-20:
+ * (seebs) don't send open/exec messages unless server is logging.
+
+2015-08-19:
+ * (seebs) Reduce alloc/free cycles in path computations.
+
+2015-08-17:
+ * (seebs) profiling improvements
+
+2015-08-14:
+ * (seebs) profiling cleanup, add profiler to gitignore
+ * Start using 1.7.0 version for internal work.
+
+2015-08-13:
+ * (seebs) client profiling stuff (first pass)
+
+2015-07-17:
+ * (seebs) allow actually making fifos without randomly closing fd 0
+ * 1.6.7
+
+2015-07-16:
+ * (seebs) don't truncate xattr attributes that end with a slash.
+ * (seebs) allow actually making fifos
+ * 1.6.6
+
+2015-05-04:
+ * (seebs) don't give spurious trailing slash diagnostics
+ * 1.6.5
+
+2015-01-22:
+ * (seebs) work when --prefix doesn't exist yet
+ * 1.6.4
+
+2015-01-15:
+ * (seebs) Rework PSEUDO_PASSWD path allocation for clarity,
+ I hope.
+ * 1.6.3
+
+2015-01-14:
+ * (seebs) Merge pabigot's fix for a missing antimagic() in
+ etc_file code.
+ * (seebs) Make configure reject a "prefix" of the current
+ directory. It fails in strange ways and is easy to detect.
+ * (seebs) handle colon-separated lists in PSEUDO_PASSWD.
+ Derived in part from contribution from pabigot.
+ * (seebs) change allocation order in password file locking.
+
+2015-01-05:
+ * (seebs) First try at handling trailing slashes in path names
+ correctly. (Which is to say, a trailing slash on a file name
+ which is not a directory name should yield ENOTDIR from a lot
+ of things.)
+
+2014-10-03:
+ * (seebs) in fact, suppress a lot of sanity checks entirely for
+ did_unlink.
+ * (seebs) merge get_file_path functionality into find_file_dev,
+ since you never call find_file_dev unless you want to do that.
+ * (seebs) If a file is in the database by inode but not path,
+ don't try to create a new link for it.
+ * (seebs) when renaming, the link of the "old" file name should be
+ contingent on whether *it* was in the database, not whether
+ the *new* name was in the database. Whoops.
+ * (seebs) 1.6.2
+
+2014-10-02:
+ * (seebs) use sqlite3_bind_int64 for inodes.
+ * (seebs) suppress path mismatch warnings for did_unlink.
+
+2014-07-18:
+ * (seebs) Make "server already offline" message into a debugging
+ message (PDBGF_INVOKE).
+ * (seebs) 1.6.1
+
+2014-07-17:
+ * (seebs) Restrict symbol version restrictions to x86-64 and i386
+ rather than x86-64 and any old thing. Fixes build problems on ARM.
+
+2014-07-11:
+ * (seebs) merge in symbol version restrictions (slightly tweaked)
+ for Linux.
+
+2014-07-10:
+ * (seebs) Don't pass -L/usr/lib* for sqlite, because that's probably
+ the default.
+ * (seebs) include sys/xattr.h explicitly for Darwin, also fix things
+ up so we aren't relying on seeing a declaration for our dummy
+ wrapper for fgetgrent_r which doesn't exist here.
+ * (seebs) fix typo in xattr message
+ * (seebs) make xattr work on Darwin, too.
+
+2014-06-13:
+ * (seebs) don't follow symlinks for lutimes.
+ * (seebs) Use sizeof instead of strlen to initialize static value.
+
+2014-05-27:
+ * (seebs) start noticing umask, mask it out from open or mkdir
+ calls rather than relying on underlying open/mkdir to do it.
+ * (seebs) add a test for umask and filesystem-vs-db modes.
+
+2014-05-16:
+ * (seebs) fchmodat: don't drop flags, report failures, to improve
+ compatibility/consistency. Cache the knowledge that
+ AT_SYMLINK_NOFOLLOW gets ENOTSUP.
+ * (seebs) mask out group/other write bits in real filesystem to
+ reduce risks when assembling a rootfs including world-writeable
+ directories.
+ * (seebs) send a round-trip message to the server before exiting
+ if any messages have been sent, so the program won't exit before
+ the server processed those messages.
+ * (pabigot) Don't memory-leak on checks for PSEUDO_UNLOAD, also
+ check the correct environment when spawning new things. (Contributed
+ by Peter A. Bigot).
+ * (seebs) mask 022 back in too, when restoring a database mode.
+
+2014-05-15:
+ * (seebs) drop flags when calling fchmodat() to appease GNU tar.
+
+2014-04-24:
+ * (seebs) extended attribute support
+
+2014-01-23:
+ * (seebs) mknod wasn't calling mknodat.
+ * (seebs) mkdirat wasn't recording the logical DB mode for directories
+
+2014-01-22:
+ * (seebs) notes on some Futures planning.
+ * (seebs) Typo/formatting issue in man page.
+ * (seebs) message formatting/clarity cleanup and typo fixes.
+ * (seebs) First draft of passwd_fallback implementation.
+ * (seebs) bump this all to 1.6.0.
+
+2014-01-15:
+ * (seebs) performance test/test case updates.
+
+2013-06-20:
+ * (seebs) refactor debugging code
+ * (seebs) start using 1.5.2 numbering
+
+2013-06-18:
+ * (seebs) Fix bug in oldclone (reported by rich@noir.com).
+
+2013-02-27:
+ * (seebs) Oh, hey, what if I took out my debug messages?
+ * (seebs) update docs a bit to reduce bitrot
+
+2013-02-26:
+ * (seebs) When built -DNDEBUG, completely drop pseudo_debug calls.
+ * (seebs) Add PSEUDO_ALLOW_FSYNC to allow temporary re-enabling of
+ fsync to work around filesystem bugs.
+ * (seebs) call that 1.5.1.
+
+2013-02-17:
+ * (seebs) Fix uninitialized variable in unlink, fix force-fsync for
+ Darwin (off64_t is not a distinct type there).
+
+2013-02-16:
+ * (seebs) Add wrapper setting which automatically returns a fixed
+ value from some wrappers. Add fixed-value wrappers for all the
+ *sync() functions. These are all contingent on --enable-force-async.
+
+2013-02-15:
+ * (seebs) Add support for in-memory DB. This, plus upcoming
+ fsync-related changes, are expected to be big enough to justify
+ calling this 1.5.
+
+2013-02-13:
+ * (seebs) calling link while chrooted could in some cases result
+ in the root path not being prepended at all. One more try!
+ * (seebs) 1.4.5.
+
+2013-02-12:
+ * (seebs) calling link while chrooted could in some cases result
+ in the root path being double-appended.
+ * (seebs) and tag 1.4.4 so that can get out as a clean update.
+
+2013-01-31:
+ * (seebs) tag 1.4.3 (to avoid any problems with the changes since
+ 1.4.2 and old tarballs)
+
+2013-01-30:
+ * (seebs) Subtle tweaks to avoid GLIBC_2.7 dependencies which
+ can cause trouble trying to copy libpseudo.so to older hosts.
+ * (seebs) add "with-sqlite-lib" to simplify cases where sqlite's
+ libdir computation differs from what we otherwise want; for
+ instance, with bitbake, we often end up wanting $lib = lib64,
+ but $sqlite_lib = lib.
+
+2012-12-13:
+ * (seebs) tag 1.4.2.
+
+2012-12-12:
+ * (seebs) Remove extra tab from the path alloc code in
+ makewrappers. (Which has no effect since I changed my mind about
+ the implementation which would have made it show up.)
+ * (seebs) linkat() implementation. as a side effect, clear up
+ the documentation and behavior of link(2) to reflect host
+ OS semantics: Linux will hardlink symlinks, Darwin will only
+ hardlink their targets.
+ * (seebs) make linkat() implementation compile/run on Darwin,
+ fix header bitrot for Mountain Lion.
+
+2012-08-09:
+ * (seebs) base_stat should be real_stat64, not stat64
+ * (seebs) add stat64/lstat64/fstat64 wrappers to Linux (not
+ previously needed because the libc versions call stuff we
+ already wrap).
+
+2012-08-02:
+ * (seebs) fix some Darwin-specific bitrot for clang/llvm.
+ * (seebs) Drop the _plain thing, convert unix/guts/* to use
+ PSEUDO_STATBUF.
+ * (seebs) Tag 1.4.1.
+
+2012-07-27:
+ * (seebs) Convert from .tgz to tar.bz2 since that's handier for
+ Yocto.
+
+2012-07-24:
+ * (seebs) Fix a couple of bitrots from the update.
+
+2012-07-20:
+ * (seebs) Add --cflags, deprecate --arch.
+ * (seebs) tag 1.4 (since this should now work on arbitrary targets)
+
+2012-06-28:
+ * (seebs) Tag 1.3.1.
+
+2012-06-27:
+ * (seebs) Fix chroot coredump with long root path.
+
+2012-04-30:
+ * (seebs) Update README about new upstream.
+
+2012-04-09:
+ * (seebs) Improvements to rpath logic for sqlite, etc.
+ * (seebs) Improvements to the logic for picking options
+ like -m32, -m64 (which is to say, on ARM: Don't.)
+
+2012-03-28:
+ * (seebs) Cleanup unused variables, stray semicolons, add
+ comments to some unused functions which exist because the
+ wrapper generator makes them anyway.
+ * (seebs) Make system() drop environment if PSEUDO_UNLOAD is
+ set.
+
+2012-03-27:
+ * (seebs) Merge in:
+ * (mhatle) Improve configuration compatibility with OE-Core.
+ * (seebs) Provide option to statically link libsqlite.
+ * (seebs) 1.3 branch
+
+2012-03-26:
+ * (seebs) Add popen() call to set up environment.
+
+2012-02-06:
+ * (seebs) Merge O_LARGEFILE into flags, not mode (thanks to Lei
+ Liu at Wind River for the fix).
+
+2012-02-02:
+ * (seebs) stash dir name for DIR * from opendir using dirfd.
+ * (seebs) add closedir.
+ * (seebs) add initial pass at renameat()
+ * (seebs) update makewrappers with smarter *dirfd handling.
+ * (seebs) in base_path, don't try to strlen the result if
+ fd_path() returns NULL.
+
+2011-11-02:
+ * (seebs) Call this 1.2 because the UNLOAD change is moderately
+ significant, and so's the clone change.
+
+2011-11-01:
+ * (mhatle) Stop valgrind from reporting use of uninitialized
+ memory from pseudo_client:client_ping()
+
+2011-10-26:
+ * (mhatle) update clone wrapper to add an intermediate function
+ to avoid setting environment variables in the parent.
+
+2011-10-20:
+ * (mhatle) change from internal PSEUDO_RELOADED to external
+ PSEUDO_UNLOAD environment variable. Enable external programs
+ to have a safe and reliable way to unload pseudo on the next
+ exec*. PSEUDO_UNLOAD also will disable pseudo if we're in a
+ fork/clone situation in the same way PSEUDO_DISABLED=1 would.
+
+2011-07-19:
+ * (seebs) initialize a variable in that "realpath" code.
+
+2011-06-08:
+ * (seebs) Get the modern realpath from glibc instead of the old
+ one inexplicably proferred by RTLD_NEXT. Fixes realpath(path, NULL)
+ when PSEUDO_DISABLED=1.
+
+2011-06-06:
+ * (seebs) revise system() handler substantially. It now
+ pollutes the environment but works.
+ * (seebs) Call it "1.1.1" so the nice folks doing Yocto
+ can have an official branch and not need to use git.
+ * (seebs) add "tarball" make target.
+
+2011-06-02:
+ * (seebs) intercept system() so the pseudo environment is
+ properly set for it.
+ * (seebs) call this "1.1" since the cumulative result of all
+ of these changes is pretty big.
+
+2011-05-31:
+ * (seebs) Don't mask in 0100 to filesystem modes for things which
+ are not actually directories, because this breaks the special
+ logic in euidaccess for X_OK.
+
+2011-05-25:
+ * (seebs) fix for ulckpwdf()
+
+2011-04-21:
+ * (seebs) don't use strerror in wrappers, because it can
+ lead to malloc deadlocks if part of setting up a malloc
+ operation falls into strerror which uses locale... Curse
+ you, Fedora 13. You and your perfectly reasonable and
+ standards-conforming behavior which happened to inconvenience
+ me.
+
+2011-04-16:
+ * (seebs) remove duplicate definition of real_clone() from
+ the oldclone port.
+
+2011-04-13:
+ * (seebs) base_path of an empty string should be an empty string,
+ not $pwd.
+
+2011-04-04:
+ * (seebs) whitespace cleanup for Python code
+
+2011-04-01:
+ * (seebs) update README
+
+2011-03-25:
+ * (seebs) don't try to search path when you don't have one
+ * (seebs) merge in ports branch
+ * (seebs) fix permissions on subports/preports
+ * (seebs) try to force debug fd to 2
+
+2011-03-24:
+ * (seebs) more work on OS X port.
+ * (seebs) include errno in the verbose debug output
+ * (seebs) fix darwin fcntl.
+ * (seebs) fix *xattr for darwin (they take more arguments)
+
+2011-02-18:
+ * (seebs) moving things to Unix port, cleanup for Darwin
+
+2011-02-14:
+ * (seebs) first pass on splitting out ports
+ * (seebs) various cleanup
+
+2011-02-10:
+ * (seebs) pseudo_client_shutdown(), and the pseudo server, have to
+ be smart enough to make the local state directory in case the
+ pseudo binary is invoked directly by a user before being spawned
+ by the client.
+
+2011-02-09:
+ * (seebs) the long-awaited cleanup of the database initialization
+ code. it's not really beautiful but it's quite a bit better.
+
+2011-02-08:
+ * (seebs) Get full paths for exec*()
+
+2011-01-24:
+ * (mhatle) Revert last result cache and related commits.
+ caching proved to be unreliable.
+
+2011-01-14:
+ * (seebs) Automatically create prefix/state directories.
+ * (mhatle) Avoid caching OP_EXEC calls
+
+2011-01-13:
+ * (seebs) Subtle cache fixup.
+
+2010-12-17:
+ * (mhatle) Disabled additional early setup if pseudo is disabled
+
+2010-12-16:
+ * (mhatle) change the journal from PERSIST to OFF
+ * (seebs) update docs now that fakeroot and password support are in,
+ this being long overdue
+ * (seebs) fix parallel build issue introduced with maketables
+
+2010-12-15:
+ * (mhatle) add sqlite call profiling, enable with NPROFILE
+ * (mhatle) as a result of profiling, optimize inode search
+ * (mhatle) rearrange the pseudo_op file data operations to reduce
+ the number of selects.
+ * (mhatle) add the ability to cache the last select result
+ * (mhatle) change the indexing in pseudo_db.c
+
+2010-12-14:
+ * (mhatle) restructure wrapfuncs.c
+
+2010-12-09:
+ * (mhatle) Add doc/program_flow to attempt to explain startup/running
+ * (mhatle) guts/* minor cleanup
+ * (mhatle) Reorganize into a new constructor for libpseudo ONLY
+ pseudo main() now manually calls the util init
+ new / revised init for client, wrappers and utils
+ * (mhatle) Add central "reinit" function
+ * (mhatle) Add manul execv* functions
+ * (mhatle) rename pseudo_populate_wrappers to pseudo_check_wrappers
+
+2010-12-08:
+ * (mhatle) Add guts/clone.c to cleanup the clone support
+ * (mhatle) guts/clone.c only run setupenv and reinit when NOT PSEUDO_RELOADED
+ * (mhatle) guts/execve.c whitespace fixe
+ * (mhatle) guts/fork.c similar to guts/clone.c change
+ * (mhatle) pseudo_client.c add reinit function
+ * (mhatle) pseudo_client.c revise client reset, include code from pseudo_wrappers.c
+ * (mhatle) pseudo_server.c move the pid writing to the parent
+ * (mhatle) pseudo_wrappers.c clone cleanup and populate cleanup
+
+2010-12-07:
+ * (seebs) whitespace fixes
+ * (seebs) improve fork and PSEUDO_DISABLED
+ * (seebs) add support for clone(2)
+ * (mhatle) rework/improve clone(2) support
+ * (mhatle) add test code for PSEUDO_DISABLED
+
+2010-12-02:
+ * (seebs) rework of fork/exec, add PSEUDO_DISABLED
+
+2010-11-30:
+ * (seebs) move *_t types to a separate file.
+ * (seebs) remove unused tables from pseudo_db.c
+ * (seebs) cleanup .gitignore
+
+2010-11-17:
+ * (seebs) add "Futures.txt" notes about future development plans
+ * (seebs) split some of the templating code out of makewrappers
+
+2010-11-16:
+ * (seebs) database move functionality (first pass)
+
+2010-10-25:
+ * (seebs) various makewrappers cleanups (pylint, mostly)
+
+2010-10-12:
+ * (seebs) add missing copyright to Python makewrappers.
+ * (seebs) detab makewrappers
+
+2010-10-11:
+ * (seebs) do the other *xattr() wrappers.
+ * (seebs) Replace makewrappers with Python implementation and some
+ template files.
+
+2010-10-06:
+ * (mhatle) Add the fsetxattr wrapper to return ENOTSUP
+ (note: workaround gnu coreutils 'cp')
+
+2010-09-16:
+ * (seebs) change exec*() to use file, rather than path or filename,
+ also add OP_EXEC where it was missing.
+
+2010-09-15:
+ * (seebs) allow setting default RPATH entry distinct from sqlite3
+ directory.
+
+2010-09-08:
+ * (seebs) handle mkfifo without guaranteeing an EINVAL response.
+
+2010-09-02:
+ * (seebs) fix errno for getcwd() with insufficient size
+ * (seebs) Add an RPATH entry to the pseudo binary to find the sqlite3
+ library.
+
+2010-09-01:
+ * (seebs) add missing casts to even more printf arguments
+
+2010-08-31:
+ * (seebs) add missing casts to printf arguments, after being warned
+ about them a mere twenty or thirty thousand times.
+
+2010-08-27:
+ * (seebs) fix a bug caused by memcmp with wrong length
+ * (seebs) stop hand-coding lengths of memcmp (torek was right...)
+
+2010-08-26:
+ * (seebs) make offsets.c slightly less useless
+ * (seebs) don't overwrite LD_LIBRARY_PATH values that include us
+
+2010-08-25:
+ * (seebs) fix the signal mask restore
+
+2010-08-24:
+ * (seebs) try to restore signal mask before calling exec()
+ * (seebs) move errno restoration after code which could set errno
+
+2010-08-19:
+ * (seebs) handle insane edge case involving regcomp/regexec
+
+2010-08-17:
+ * (seebs) create speculative-deletion logic
+ * (seebs) remove crackpot theories about cross-device renames
+
+2010-08-16:
+ * (rp) Fix ld_preload/ld_library_path mixup.
+ * (seebs) Handle failed allocations.
+ * (seebs) Fix logic for dropping empty LD_PRELOAD.
+
+2010-08-12:
+ * (seebs) Fix install of libpseudo so the plain library is created
+ when using $(SUFFIX), this is needed so pseudo daemons don't
+ need to know $(SUFFIX) so you can use prebuilts.
+ * (seebs) Remove spurious "const" from modifiable table.
+
+2010-08-11:
+ * (seebs) document the new variables.
+
+2010-08-10:
+ * (mhatle) add execl, execle, execlp, execv, and execvp wrappers
+ * (seebs) handle ... for execl, etc.
+ * (mhatle) add a local cache of variables, instead of using environ
+ * (mhatle) rewrite pseudo_setupenv, pseudo_dropenv routines
+ we now support running "/usr/bin/env -i env" in pseudo!
+
+2010-08-06:
+ * (mhatle) Fix an exec program with an empty environment
+
+2010-08-03:
+ * (mhatle) Fix parallel build problem
+ * (mhatle) allow both environment CFLAGS and internals CFLAGS
+ * (mhatle) add PSEUDO_BINDIR, PSEUDO_LIBDIR, PSEUDO_LOCALSTATEDIR
+ to allow specific overrides above and beyond PSEUDO_PREFIX
+
+2010-07-30:
+ * (kscherer) added .gitignore file
+ * (kscherer) added sqlite version check to configure script
+ * (kscherer) added basic test harness
+ * (kscherer) fixed bug that when moving a directory the contents
+ of the dir were removed from the db
+
+2010-06-29:
+ * (seebs) handle the other half of the suffix case -- when
+ libpseudo is in LD_PRELOAD under another name.
+ * (seebs) remove a couple of debugging messages.
+
+2010-06-28: 0.3
+ * (seebs) back out PSEUDO_SUFFIX -- it causes problem when
+ rebuilding the library but not the server, but this is
+ a permissible use case.
+
+2010-06-21:
+ * (seebs) add mkstemp64
+
+2010-06-02:
+ * (seebs) add PSEUDO_NOSYMLINKEXP feature and documentation.
+
+2010-04-30:
+ * (seebs) rework pdb_history
+ * (seebs) small cleanups and bulletproofing.
+ * (seebs) fix up PSEUDO_DEBUG_FILE, use it for server as well.
+
+2010-04-27:
+ * (seebs) fix -P in pseudolog
+ * (seebs) document PSEUDO_DEBUG_FILE
+
+2010-04-26:
+ * (seebs) many bug fixes and updates
+ * (seebs) allow deleting entries in pseudolog
+ * (seebs) correct race conditions and related bugs
+
+2010-04-20:
+ * (seebs) add quick sanity-check option for pseudo
+ * (seebs) report on rows deleted
+ * (seebs) unlink after removing db entry to reduce race conditions
+
+2010-04-19:
+ * (seebs) fix crash if xstat() or similar routine called with null path
+ * (seebs) fix list of client processes still running
+ * (seebs) fix pathname mismatches introduced by chroot() support
+
+2010-04-16:
+ * (seebs) add tracking of program names
+ * (seebs) track message types
+ * (seebs) small bug fixes and improvements galore
+
+2010-04-06:
+ * (seebs) implement various passwd-related utilities, various
+ bugfixes.
+
+2010-03-25:
+ * (seebs) fix return values.
+
+2010-03-24:
+ * (seebs) add chroot syscall
+ * (seebs) add chroot handling to path canonicalization
+ * (seebs) add many calls just to get path fixups
+ * (seebs) handle virtualizing rootness of absolute symlinks
+
+2010-03-24:
+ * (seebs) regenerate wrappers when makewrappers is changed.
+ * (seebs) begin prep for chroot
+ * (seebs) standardize path expansion
+ * (seebs) extend makewrappers to handle function pointer args
+
+2010-03-17:
+ * (seebs) fixup help options
+ * (seebs) use strerror() in a couple more places
+ * (seebs) mention ^ specification in pseudolog -h output.
+
+2010-03-16:
+ * (seebs) Fix missing error checking noted by comp.lang.c reader.
+
+2010-03-16: 0.2
+ * first public release
+
+(There's no changelog before that, sorry.)
diff --git a/Futures.txt b/Futures.txt
new file mode 100644
index 0000000..284005a
--- /dev/null
+++ b/Futures.txt
@@ -0,0 +1,54 @@
+Some notes on features under discussion or consideration, and some
+vague implementation thoughts:
+
+* Add some kind of filter for "the directory we care about"
+ - pseudo operations would only go to the server for items
+ in this directory
+ - optionally, logging could be generated for accesses
+ *outside* this directory
+ - intent is to reduce server load dramatically, and improve
+ and/or streamline the logging path when, e.g., doing
+ host contamination checking
+ - probably implies a new message type, MSG_LOG.
+
+* Database recovery/reconstruction
+ - capability for failing harder in the event of apparent
+ corruption
+
+* Log database performance improvements
+ - some way to do "live" queries against the log database
+ while the server is running
+ - some way to flush unwanted data
+ - possibly a way to separate logging into multiple files
+ or otherwise restrict the amount of stuff produced
+ - logging filters of some sort
+
+* Canonicalization speedups
+ - possibly implement some kind of cache of directory names
+ and known canonicalizations
+
+* Possibly limited handling for extended attributes
+ - specifically, the case where they're used to manpulate or
+ query the plain old modes ala chmod/stat.
+
+* Test cases
+ - boy, do we need test cases!
+
+* Clean up *at() functions
+ - The *at() function implementations are gratuitously complicated,
+ I believe because they predate the auto-canonicalization that came
+ with the rewritten pseudo wrappers.
+
+* Memory allocation/deallocation issues.
+ - there's a number of places where results from pseudo_get_value() are
+ strdup'd, then the originals freed, which makes no sense.
+ - there's at least one unchecked realloc() to do with the fd table.
+
+* Benchmarking/performance work.
+ - It'd be nice to get some kind of measurement of how much time is
+ going to which parts of pseudo (database access, filesystem access,
+ IPC overhead, logic, client processing, and so on).
+ - Maybe some work on finding ways to make profiling work, since I was
+ having issues getting good profiling data.
+ - Some moderately-canonical benchmarks would be nice to have for evaluating
+ the costs of various common use cases.
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..7fa5149
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,213 @@
+#
+# Makefile.in/Makefile, build rules for pseudo
+#
+# Copyright (c) 2008-2015 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the Lesser GNU General Public License version 2.1 as
+# published by the Free Software Foundation.
+#
+# This program 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 Lesser GNU General Public License for more details.
+#
+# You should have received a copy of the Lesser GNU General Public License
+# version 2.1 along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+# configuration flags
+PREFIX=@PREFIX@
+LIBDIR=@LIBDIR@
+SUFFIX=@SUFFIX@
+SQLITE=@SQLITE@
+SQLITE_LIB=@SQLITE_LIB@
+SQLITE_MEMORY=@SQLITE_MEMORY@
+FORCE_ASYNC=@FORCE_ASYNC@
+XATTR=@XATTR@
+XATTRDB=@XATTRDB@
+PROFILING=@PROFILING@
+ifeq (true,$(PROFILING))
+ PSEUDO_PROFILE=$(BIN)/pseudo_profile
+else
+ PSEUDO_PROFILE=
+endif
+define cond
+ $(if $(filter true,$($(1))),-D$(2))
+endef
+OPTDEFS=$(call cond,XATTR,PSEUDO_XATTR_SUPPORT) \
+ $(call cond,XATTRDB,PSEUDO_XATTRDB) \
+ $(call cond,PROFILING,PSEUDO_PROFILING)
+PASSWD_FALLBACK=@PASSWD_FALLBACK@
+BITS=@BITS@
+ARCH_FLAGS=@ARCH_FLAGS@
+MARK64=@MARK64@
+RPATH=@RPATH@
+VERSION=1.7.4
+
+LIB=@LIB@
+BIN=bin
+LOCALSTATE=var/pseudo
+BINDIR=$(PREFIX)/$(BIN)
+LOCALSTATEDIR=$(PREFIX)/$(LOCALSTATE)
+
+CFLAGS_BASE=-pipe -std=gnu99 -Wall -W -Wextra
+CFLAGS_CODE=-fPIC -D_LARGEFILE64_SOURCE -D_ATFILE_SOURCE $(ARCH_FLAGS)
+CFLAGS_DEFS=-DPSEUDO_PREFIX='"$(PREFIX)"' -DPSEUDO_SUFFIX='"$(SUFFIX)"' -DPSEUDO_BINDIR='"$(BIN)"' -DPSEUDO_LIBDIR='"$(LIB)"' -DPSEUDO_LOCALSTATEDIR='"$(LOCALSTATE)"' -DPSEUDO_VERSION='"$(VERSION)"' $(SQLITE_MEMORY) $(FORCE_ASYNC) -DPSEUDO_PASSWD_FALLBACK='$(PASSWD_FALLBACK)' $(OPTDEFS)
+CFLAGS_DEBUG=-O2 -g
+@DEFAULT_SQLITE@CFLAGS_SQL=-L$(SQLITE)/$(SQLITE_LIB) -I$(SQLITE)/include $(RPATH)
+CFLAGS_PSEUDO=$(CFLAGS_BASE) $(CFLAGS_CODE) $(CFLAGS_DEFS) \
+ $(CFLAGS_DEBUG) $(CFLAGS_SQL)
+
+GLOB_PATTERN=guts/*.c
+GUTS=$(filter-out "$(GLOB_PATTERN)",$(wildcard $(GLOB_PATTERN)))
+
+SOURCES=$(wildcard *.c)
+OBJS=$(subst .c,.o,$(SOURCES))
+
+SHOBJS=pseudo_tables.o pseudo_util.o
+DBOBJS=pseudo_db.o
+WRAPOBJS=pseudo_wrappers.o
+
+# needed for anything that links with pseduo_client.o, pretty much
+CLIENT_LDFLAGS=-ldl -lpthread
+DB_LDFLAGS=@SQLITE_LDARG@ -lpthread
+
+PSEUDO=$(BIN)/pseudo
+PSEUDODB=$(BIN)/pseudodb
+PSEUDOLOG=$(BIN)/pseudolog
+LIBPSEUDO=$(LIB)/libpseudo.so
+
+TEMPLATES=templates/guts templates/wrapfuncs.c templates/wrapfuncs.h templates/wrapper_table
+TABLES=table_templates/pseudo_tables.c table_templates/pseudo_tables.h
+
+all: $(LIBPSEUDO) $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE)
+
+test: all $(BIN) $(LIB) $(LOCALSTATE)
+ @./run_tests.sh -v
+
+install-lib: $(LIBPSEUDO)
+ mkdir -p $(DESTDIR)$(LIBDIR)
+ cp $(LIBPSEUDO) $(DESTDIR)$(LIBDIR)
+ $(if $(SUFFIX),cp $(LIBPSEUDO) $(DESTDIR)$(LIBDIR)/libpseudo$(SUFFIX).so,:)
+
+install-bin: $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE)
+ mkdir -p $(DESTDIR)$(BINDIR)
+ cp $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE) $(DESTDIR)$(BINDIR)
+
+install-data:
+ mkdir -p $(DESTDIR)$(LOCALSTATEDIR)
+
+install: all install-lib install-bin install-data
+
+$(BIN) $(LIB) $(LOCALSTATE):
+ mkdir -p $@
+
+pseudo: $(PSEUDO)
+
+$(PSEUDO): $(BIN) pseudo.o $(SHOBJS) $(DBOBJS) pseudo_client.o pseudo_server.o pseudo_ipc.o
+ $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $(PSEUDO) \
+ pseudo.o pseudo_server.o pseudo_client.o pseudo_ipc.o \
+ $(DBOBJS) $(SHOBJS) $(DB_LDFLAGS) $(CLIENT_LDFLAGS)
+
+pseudolog: $(PSEUDOLOG)
+
+$(PSEUDOLOG): $(BIN) pseudolog.o $(SHOBJS) $(DBOBJS) pseudo_client.o pseudo_ipc.o
+ $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $(PSEUDOLOG) pseudolog.o pseudo_client.o pseudo_ipc.o \
+ $(DBOBJS) $(SHOBJS) $(DB_LDFLAGS) $(CLIENT_LDFLAGS)
+
+pseudodb: $(PSEUDODB)
+
+$(PSEUDODB): $(BIN) pseudodb.o $(SHOBJS) $(DBOBJS) pseudo_ipc.o
+ $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $(PSEUDODB) pseudodb.o \
+ $(DBOBJS) $(SHOBJS) pseudo_ipc.o $(DB_LDFLAGS) $(CLIENT_LDFLAGS)
+
+libpseudo: $(LIBPSEUDO)
+
+$(LIBPSEUDO): $(LIB) $(WRAPOBJS) pseudo_client.o pseudo_ipc.o $(SHOBJS)
+ $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -shared -o $(LIBPSEUDO) \
+ pseudo_client.o pseudo_ipc.o \
+ $(WRAPOBJS) $(SHOBJS) $(CLIENT_LDFLAGS)
+
+# *everything* now relies on stuff that's generated in the
+# wrapper process.
+%.o: %.c pseudo_wrapfuncs.h
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $(CFLAGS_PSEUDO) $<
+
+$(OBJS): pseudo_tables.h
+
+pseudo_client.o pseudo_server.o pseudo_ipc.o: pseudo_ipc.h
+
+pseudo_client.o: pseudo_client.h
+
+pseudo_server.o: pseudo_server.h
+
+tables: enums/*.in maketables templatefile.py $(TABLES)
+ ./maketables enums/*.in
+
+wrappers: makewrappers templatefile.py $(TEMPLATES) ports/*/wrapfuncs.in
+ CC="$(CC) $(CFLAGS) $(CFLAGS_PSEUDO)" ./makewrappers "xattr=$(XATTR)"
+
+.SECONDARY: tables wrappers
+
+pseudo_wrapfuncs.c pseudo_wrapfuncs.h: wrappers
+
+pseudo_tables.c pseudo_tables.h: tables
+
+pseudo_tables.o: pseudo_tables.c
+ $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -c -o pseudo_tables.o pseudo_tables.c
+
+# no-strict-aliasing is needed for the function pointer trickery.
+pseudo_wrappers.o: $(GUTS) pseudo_wrappers.c pseudo_wrapfuncs.c pseudo_wrapfuncs.h pseudo_tables.h
+ $(CC) -fno-strict-aliasing $(CFLAGS) $(CFLAGS_PSEUDO) -D_GNU_SOURCE -c -o pseudo_wrappers.o pseudo_wrappers.c
+
+offsets32:
+ $(CC) -m32 -o offsets32 offsets.c
+
+offsets64:
+ $(CC) -m64 -o offsets64 offsets.c
+
+$(PSEUDO_PROFILE): $(BIN) pseudo_profile
+ cp pseudo_profile $(BIN)
+
+pseudo_profile: Makefile pseudo_profile.c tables wrappers
+ $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o pseudo_profile pseudo_profile.c
+
+clean:
+ rm -f *.o *.so $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) \
+ pseudo_wrapfuncs.h pseudo_wrapfuncs.c \
+ pseudo_wrapper_table.c \
+ pseudo_tables.c pseudo_tables.h \
+ pseudo_ports.h port_wrappers.c \
+ offsets32 offsets64 \
+ pseudo_profile \
+ port_deps.mk func_deps.mk
+ touch port_deps.mk func_deps.mk
+
+distclean: clean
+ rm -f Makefile
+ rm -rf ./$(BIN) ./$(LIB) ./$(LOCALSTATE)
+ @echo "WARNING: Makefile has been removed. You must reconfigure to do anything else."
+
+nuke: distclean
+ case "$(PREFIX)" in "`pwd`"/*) rm -rf "$(PREFIX)";; esac
+ @echo "WARNING: Removed $(PREFIX)."
+
+tarball:
+ @test -d .git || ( echo >&2 "Tarball can only be made from git tree."; exit 1)
+ rm -rf pseudo-$(VERSION)
+ mkdir -p pseudo-$(VERSION)
+ ( parent=$(PWD); \
+ cd pseudo-$(VERSION) && \
+ git clone $$parent && \
+ mv pseudo/* . && \
+ rm -rf pseudo/.git* && \
+ rmdir pseudo \
+ )
+ tar cjf pseudo-$(VERSION).tar.bz2 pseudo-$(VERSION)
+
+# Note when we need to rebuild pseudo_wrappers.o
+include port_deps.mk
+include func_deps.mk
+
diff --git a/README b/README
new file mode 100644
index 0000000..afe6b27
--- /dev/null
+++ b/README
@@ -0,0 +1,87 @@
+pseudo -- an analogue to sudo
+
+IMPORTANT NOTE:
+
+As of this writing, the official home for pseudo's git repository has
+changed to:
+ git://git.yoctoproject.org/pseudo
+
+The old site at:
+ https://github.com/wrpseudo/pseudo
+
+is no longer the "real" master, although I'll probably try to keep it in
+sync.
+
+OVERVIEW:
+
+The pseudo utility offers a way to run commands in a virtualized "root"
+environment, allowing ordinary users to run commands which give the illusion
+of creating device nodes, changing file ownership, and otherwise doing
+things necessary for creating distribution packages or filesystems.
+
+To configure, run the provided configure script. Note that this is
+NOT an autoconf script.
+
+Configure options:
+ --prefix=/path/to/install/dir
+ --with-sqlite=/path/to/sqlite/dir
+ --bits={32,64}
+ --suffix=<text>
+ --enable-static-sqlite
+ --with-sqlite-lib
+ --libdir=...
+ --enable-memory-db
+ --enable-force-async
+ --with-sqlite-lib=...
+ --enable-static-sqlite
+ --with-static-sqlite=...
+ --with-rpath=...|--without-rpath
+ --cflags=''
+
+There is no default prefix. The default for sqlite is /usr, and for
+bits is 32. You need a reasonably modern sqlite3 -- it has to have
+sqlite3_clear_bindings(), which at least one default sqlite3 install
+did not. (But that was dated 2006, so I'm not sure this will affect
+most readers.)
+
+The suffix value can be used to append a particular text string to file
+names (such as libpseudo<suffix>.so). This was used in the WR environment
+to create libpseudo-<host_libc_md5sum>.so, to ensure that libpseudo was
+rebuilt if the host libc changed.
+
+This code is not particularly portable, but works on Linux and also
+on 64-bit Intel Darwin systems.
+
+Limited user documentation is provided in the given man page files (these
+are not currently installed, merely provided in the source directory), and
+some documentation on internals is provided in the doc/ directory.
+
+The memory-db option uses an in-memory SQLite database, which is flushed
+to disk on failures. It's disabled by default with SQLite 3.6 and earlier
+due to performance weirdness.
+
+The --enable-force-async option causes wrappers for fsync() and friends to
+all return "success" instantly unless PSEUDO_ALLOW_FSYNC was set in the
+environment when the client initialized (usually after a fork or exec).
+
+
+FUTURE DIRECTIONS:
+
+* The chroot() functionality is incomplete, though now sufficient for
+ the real-world use cases we've tried.
+* I have no intention of converting to autoconf. It is the wrong tool
+ for the job.
+
+Please feel free to send bug feedback, change requests, or general
+commentary.
+
+
+ACKNOWLEDGEMENTS:
+
+My various coworkers, both engineering and management, made this possible.
+While I did most of the actual typing, this code has benefitted greatly
+from detailed code reviews, excellent reproducers for bugs, and the
+consistent support of the whole group for the project. It's been a great
+deal of fun, and I'm pretty happy that we're finally ready to make it
+available for other people to look at.
+
diff --git a/configure b/configure
new file mode 100755
index 0000000..e5ef9ce
--- /dev/null
+++ b/configure
@@ -0,0 +1,341 @@
+#!/bin/sh
+#
+# configure, simulation of autoconf script, much simplified
+#
+# Copyright (c) 2008-2014 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the Lesser GNU General Public License version 2.1 as
+# published by the Free Software Foundation.
+#
+# This program 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 Lesser GNU General Public License for more details.
+#
+# You should have received a copy of the Lesser GNU General Public License
+# version 2.1 along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# not a real configure script...
+opt_prefix=
+opt_libdir=
+opt_suffix=
+opt_arch=x86
+opt_bits=
+opt_sqlite=/usr
+opt_rpath=
+opt_memory=
+opt_async=
+opt_xattr=
+opt_xattrdb=false
+opt_profile=false
+opt_passwd_fallback='""'
+
+compile_x86_32=-m32
+compile_x86_64=-m64
+
+usage()
+{
+ echo >&2 "usage:"
+ echo >&2 " configure --prefix=..."
+ echo >&2 " [--libdir=...]"
+ echo >&2 " [--suffix=...]"
+ echo >&2 " [--enable-memory-db]"
+ echo >&2 " [--enable-xattr]"
+ echo >&2 " [--enable-xattrdb]"
+ echo >&2 " [--enable-profiling]"
+ echo >&2 " [--enable-force-async]"
+ echo >&2 " [--with-sqlite=...]"
+ echo >&2 " [--with-sqlite-lib=...]"
+ echo >&2 " [--enable-static-sqlite]"
+ echo >&2 " [--with-static-sqlite=...]"
+ echo >&2 " [--with-rpath=...|--without-rpath]"
+ echo >&2 " [--with-passwd-fallback=...|--without-passwd-fallback]"
+ echo >&2 " [--cflags='']"
+ echo >&2 " [--bits=32|64]"
+ exit 1
+}
+
+sqlite_ldarg=-lsqlite3
+
+maybe_rpath=
+use_maybe_rpath=true
+
+for arg
+do
+ case $arg in
+ --disable-*)
+ arg="--enable-${arg%--disable-}=no"
+ ;;
+ esac
+ case $arg in
+ --) shift; break ;;
+ --prefix=*)
+ opt_prefix=${arg#--prefix=}
+ if [ -d "$opt_prefix" ]; then
+ maybe_prefix=$(cd "$opt_prefix"; pwd)
+ else
+ maybe_prefix=$opt_prefix
+ fi
+ if [ "$maybe_prefix" = "$(pwd)" ]; then
+ echo >&2 "ERROR: Prefix is current directory. That doesn't work."
+ exit 1
+ fi
+ ;;
+ --libdir=*)
+ opt_libdir=${arg#--libdir=}
+ ;;
+ --with-static-sqlite=*)
+ opt_sqlite_ldarg=${arg#--with-static-sqlite=}
+ sqlite_ldarg=$opt_sqlite_ldarg
+ use_maybe_rpath=false
+ ;;
+ --without-passwd-fallback)
+ opt_passwd_fallback='NULL'
+ ;;
+ --with-passwd-fallback=*)
+ opt_passwd_fallback="${arg#--with-passwd-fallback=}"
+ # Trim a trailing /, remove quotes. So / => "".
+ opt_passwd_fallback="\"${opt_passwd_fallback%/}\""
+ ;;
+ --enable-static-sqlite)
+ sqlite_ldarg='$(SQLITE)/$(SQLITE_LIB)/libsqlite3.a'
+ use_maybe_rpath=false
+ ;;
+ --enable-force-async=no | --disable-force-async)
+ opt_async=false
+ ;;
+ --enable-force-async=yes | --enable-force-async)
+ opt_async=true
+ ;;
+ --enable-profiling=no)
+ opt_profiling=false
+ ;;
+ --enable-profiling=yes | --enable-profiling)
+ opt_profiling=true
+ ;;
+ --enable-xattr=no)
+ opt_xattr=false
+ ;;
+ --enable-xattr=yes | --enable-xattr)
+ opt_xattr=true
+ ;;
+ --enable-xattrdb=no)
+ opt_xattrdb=false
+ ;;
+ --enable-xattrdb=yes | --enable-xattrdb)
+ opt_xattrdb=true
+ ;;
+ --enable-memory-db=no)
+ opt_memory=false
+ ;;
+ --enable-memory-db=yes | --enable-memory-db)
+ opt_memory=true
+ ;;
+ --with-sqlite=*)
+ opt_sqlite=${arg#--with-sqlite=}
+ # assign new value if unset
+ maybe_rpath='-Wl,-R$(SQLITE)/$(SQLITE_LIB)'
+ ;;
+ --with-sqlite-lib=*)
+ opt_sqlite_lib=${arg#--with-sqlite-lib=}
+ # assign new value if unset
+ maybe_rpath='-Wl,-R$(SQLITE)/$(SQLITE_LIB)'
+ ;;
+ --without-rpath)
+ opt_rpath=''
+ use_maybe_rpath=false
+ ;;
+ --with-rpath=*)
+ rpath=${arg#--with-rpath=}
+ opt_rpath=${rpath:+-Wl,-R$rpath}
+ use_maybe_rpath=false
+ ;;
+ --suffix=*)
+ opt_suffix=${arg#--suffix=}
+ ;;
+ --arch=*)
+ echo >&2 "WARNING: The --arch option is now deprecated. Use --cflags."
+ opt_arch=${arg#--arch=}
+ ;;
+ --cflags=*)
+ opt_cflags=${arg#--cflags=}
+ ;;
+ --bits=*)
+ opt_bits=${arg#--bits=}
+ ;;
+ *)
+ echo >&2 "warning: Unrecognized option '$arg'"
+ ;;
+ esac
+done
+
+case $opt_arch in
+'' | x86 | arm )
+ ;;
+*) echo >&2 "Untested arch $opt_arch."
+ ;;
+esac
+
+if [ -z "$opt_bits" ]; then
+ printf >&2 "Bit width unspecified;"
+ case $(file -L /bin/sh 2>/dev/null) in
+ *64-bit*) opt_bits=64;;
+ *32-bit*) opt_bits=32;;
+ esac
+ if [ -n "$opt_bits" ]; then
+ echo >&2 " guessing bit width is $opt_bits, based on /bin/sh."
+ else
+ echo >&2 " can't tell, assuming 32."
+ opt_bits=32
+ fi
+fi
+
+case $opt_bits in
+64) opt_mark64=64;;
+32) opt_mark64=;;
+*) echo >&2 "Unknown bit size $opt_bits (only 32 and 64 known)."
+ ;;
+esac
+
+if [ "${opt_cflags-UNSET}" = "UNSET" ]; then
+ # Some targets want something like -m64.
+ eval arch_flags=\$compile_${opt_arch}_${opt_bits}
+ echo >&2 "WARNING: Guessing architecture CFLAGS '${arch_flags-<unset>}'."
+ echo >&2 "If you need specific flags, use --cflags."
+else
+ arch_flags=$opt_cflags
+fi
+
+if $use_maybe_rpath && [ -n "$maybe_rpath" ]; then
+ echo >&2 "Adding default RPATH for sqlite."
+ opt_rpath="${opt_rpath+${opt_rpath} }${maybe_rpath}"
+fi
+
+if [ -z "$opt_prefix" ]; then
+ usage
+fi
+
+if [ -z "$opt_libdir" ]; then
+ opt_libdir=$opt_prefix/lib$opt_mark64
+fi
+
+# We need to find the libdir relative to the prefix, this is required
+# by the code in pseudo-utils.c that handles relocation.
+opt_lib=${opt_libdir#$opt_prefix/}
+if [ "$opt_lib" = "$opt_libdir" ]; then
+ echo >&2 "libdir must be relative to prefix."
+ exit 1
+fi
+
+if [ -z "$opt_sqlite_lib" ]; then
+ opt_sqlite_lib=$opt_lib
+fi
+
+if [ ! -f "${opt_sqlite}/include/sqlite3.h" ]; then
+ echo >&2 "SQLite3 headers not found in at ${opt_sqlite}/include/sqlite3.h. Please check that SQLite3 and SQLite3 headers are installed."
+ exit 1
+fi
+
+read t1 t2 SQLITE3_VERSION << EOF
+ `grep "#define SQLITE_VERSION_NUMBER " ${opt_sqlite}/include/sqlite3.h`
+EOF
+
+echo "SQLite header for version ${SQLITE3_VERSION} found in ${opt_sqlite}."
+
+if [ "${SQLITE3_VERSION}" -lt "03006000" ]; then
+ echo >&2 "Pseudo requires SQLite version 3, 3.6.x or later."
+ exit 1
+fi
+
+if [ -z "$opt_async" ]; then
+ opt_async=false
+fi
+
+if $opt_async; then
+ FORCE_ASYNC="-DPSEUDO_FORCE_ASYNC"
+else
+ FORCE_ASYNC=""
+fi
+
+if getfattr --help >/dev/null 2>&1; then
+ xattr_runs=true
+else
+ xattr_runs=false
+fi
+
+
+if [ -z "$opt_xattr" ]; then
+ if $opt_xattrdb; then
+ opt_xattr=true
+ echo "xattr DB support implies extended attribute support"
+ else
+ if $xattr_runs; then
+ opt_xattr=true
+ echo "getfattr runs, enabling extended attribute support"
+ else
+ opt_xattr=false
+ echo "getfattr fails, disabling extended attribute support"
+ fi
+ fi
+fi
+
+if $opt_xattr || $opt_xattrdb; then
+ if ! $xattr_runs; then
+ echo >&2 "WARNING: getfattr doesn't work, but xattr-related features requestd."
+ fi
+fi
+
+if [ -z "$opt_memory" ]; then
+ if [ "${SQLITE3_VERSION}" -lt "03007000" ]; then
+ echo "Disabling in-memory database by default (sqlite too old)."
+ opt_memory=false
+ else
+ echo "Enabling in-memory database by default."
+ opt_memory=true
+ fi
+fi
+
+if $opt_memory; then
+ if [ "${SQLITE3_VERSION}" -lt "03007000" ]; then
+ cat >&2 <<EOF
+WARNING: sqlite prior to 3.7 has been known to perform exceedingly poorly
+with the in-memory database option. You asked for it, you get it, but if
+you get horrible performance, try turning it off.
+EOF
+ fi
+ SQLITE_MEMORY="-DUSE_MEMORY_DB"
+else
+ SQLITE_MEMORY=""
+fi
+
+# Suppress the -L if sqlite is in the default path.
+if [ "$opt_sqlite" = "/usr" ]; then
+ default_sqlite="# "
+fi
+
+touch port_deps.mk
+touch func_deps.mk
+
+sed -e '
+ s,@PREFIX@,'"$opt_prefix"',g
+ s,@XATTR@,'"$opt_xattr"',g
+ s,@XATTRDB@,'"$opt_xattrdb"',g
+ s,@PROFILING@,'"$opt_profiling"',g
+ s,@LIBDIR@,'"$opt_libdir"',g
+ s,@LIB@,'"$opt_lib"',g
+ s,@SUFFIX@,'"$opt_suffix"',g
+ s,@SQLITE@,'"$opt_sqlite"',g
+ s,@ARCH_FLAGS@,'"$arch_flags"',g
+ s,@DEFAULT_SQLITE@,'"$default_sqlite"',g
+ s,@SQLITE_LDARG@,'"$sqlite_ldarg"',g
+ s,@SQLITE_LIB@,'"$opt_sqlite_lib"',g
+ s,@SQLITE_MEMORY@,'"$SQLITE_MEMORY"',g
+ s,@FORCE_ASYNC@,'"$FORCE_ASYNC"',g
+ s,@PASSWD_FALLBACK@,'$opt_passwd_fallback',g
+ s!@RPATH@!'"$opt_rpath"'!g
+ s,@MARK64@,'"$opt_mark64"',g
+ s,@ARCH@,'"$opt_arch"',g
+ s,@BITS@,'"$opt_bits"',g
+' < Makefile.in > Makefile
diff --git a/debian/README.Debian b/debian/README.Debian
new file mode 100644
index 0000000..4886897
--- /dev/null
+++ b/debian/README.Debian
@@ -0,0 +1,6 @@
+pseudo for Debian
+
+This package provides a wrapper around pseudo partially replicating the
+behaviour of fakeroot (and, in fact, based on fakeroot's launcher script).
+
+ -- Andrew Shadura <andrewsh@debian.org> Sat, 21 Nov 2015 18:44:57 +0100
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..03856e7
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+pseudo (1.7.4-1) unstable; urgency=low
+
+ * Initial release (Closes: #796973).
+
+ -- Andrew Shadura <andrewsh@debian.org> Sun, 22 Nov 2015 21:25:49 +0100
diff --git a/debian/clean b/debian/clean
new file mode 100644
index 0000000..585d8cd
--- /dev/null
+++ b/debian/clean
@@ -0,0 +1,2 @@
+a b
+*.pyc
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..f06bfdc
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,23 @@
+Source: pseudo
+Section: utils
+Priority: optional
+Maintainer: Andrew Shadura <andrewsh@debian.org>
+Build-Depends: debhelper (>=9), attr, libattr1-dev, python, libsqlite3-dev
+Standards-Version: 3.9.6
+Homepage: https://www.yoctoproject.org/tools-resources/projects/pseudo
+
+Package: pseudo
+Architecture: any
+Multi-Arch: foreign
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Provides: fakeroot
+Description: advanced tool for simulating superuser privileges
+ The pseudo utility offers a way to run commands in a virtualized "root"
+ environment, allowing ordinary users to run commands which give the
+ illusion of creating device nodes, changing file ownership, and otherwise doing
+ things necessary for creating distribution packages or filesystems.
+ .
+ Pseudo has a lot of similarities to fakeroot but is a new implementation
+ that improves on the problems seen using fakeroot. Pseudo is now
+ extensively used by Poky as a replacement to fakeroot but can also be
+ used standalone in many other use cases.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..ae1207b
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,47 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: pseudo
+Source: <http://www.yoctoproject.org/tools-resources/projects/pseudo>
+
+Files: *
+Copyright: 2008-2014 Wind River Systems, Inc.
+License: LGPL-2.1+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the Lesser GNU General Public License version 2.1 as
+ published by the Free Software Foundation.
+ .
+ This program 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 Lesser GNU General Public License for more details.
+ .
+ On Debian systems, the GNU LGPL 2.1 can be found in
+ /usr/share/common-licenses/LGPL-2.1.
+
+Files: debian/fakeroot-pseudo
+Copyright:
+ 1997—2001 Joost Witteveen
+ 2002—2009 Clint Adams <clint@debian.org>
+ 2015 Andrew Shadura <andrewsh@debian.org>
+License: GPL-3+
+
+Files: debian/fakeroot-pseudo.1
+Copyright:
+ J.H.M. Dassen <jdassen@debian.org>
+ Joost Witteveen
+ Clint Adams <clint@debian.org>
+ Andrew Shadura <andrewsh@debian.org>
+License: GPL-3+
+
+License: GPL-3+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ .
+ This program 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 General Public License for more details.
+ .
+ On Debian systems, the GNU GPL version 3 can be found in
+ /usr/share/common-licenses/GPL-3.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..123ff25
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,7 @@
+doc/chroot
+doc/database
+doc/overview
+doc/passwd
+doc/pseudo_ipc
+doc/utils
+README
diff --git a/debian/fakeroot-pseudo b/debian/fakeroot-pseudo
new file mode 100755
index 0000000..b9fdeac
--- /dev/null
+++ b/debian/fakeroot-pseudo
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+usage () {
+cat - >&2 <<EOF
+fakeroot-like wrapper for pseudo, create a fake root environment.
+ usage: fakeroot [-l|--lib fakerootlib] [-f|--faked fakedbin]
+ [-i file] [-s file] [-u|--unknown-is-real]
+ [-b|--fd-base fd] [-h|--help] [-v|--version]
+ [--] [command]
+EOF
+ exit 1
+}
+
+stderr ()
+{
+ local i
+ for i
+ do
+ echo >&2 "fakeroot: $i"
+ done
+}
+
+fatal ()
+{
+ stderr "$@"
+ exit 1
+}
+
+# strip /bin/fakeroot to find install prefix
+FAKEROOT_PREFIX=/usr
+FAKEROOT_BINDIR=/usr/bin
+
+KEEPSTATEDIR=0
+
+GETOPTEST=`getopt --version`
+case $GETOPTEST in
+getopt*) # GNU getopt
+ FAKE_TEMP=`getopt -l lib: -l faked: -l unknown-is-real -l fd-base: -l version -l help -- +l:f:i:s:ub:vh "$@"`
+ ;;
+*) # POSIX getopt ?
+ FAKE_TEMP=`getopt l:f:i:s:ub:vh "$@"`
+ ;;
+esac
+
+if [ "$?" -ne 0 ]
+then
+ usage
+fi
+
+eval set -- "$FAKE_TEMP"
+
+while test "X$1" != "X--"; do
+ case "$1" in
+ -l|--lib)
+ shift
+ ;;
+ -f|--faked)
+ shift
+ ;;
+ -i)
+ shift
+ if test -d "$1"
+ then
+ PSEUDO_LOCALSTATEDIR="$(readlink -m "$1")"
+ else
+ stderr "local state dir \`$1' doesn't exist."
+ fi
+ ;;
+ -s)
+ shift
+ if mkdir -p "$1"
+ then
+ PSEUDO_LOCALSTATEDIR="$(readlink -m "$1")"
+ KEEPSTATEDIR=1
+ else
+ stderr "local state dir \`$1' could not be created."
+ fi
+ ;;
+ -u|--unknown-is-real)
+ ;;
+ -b|--fd-base)
+ shift
+ ;;
+ -v|--version)
+ printf "fakeroot wrapper for "
+ pseudo -V
+ exit 0
+ ;;
+ -h|--help)
+ usage
+ ;;
+ esac
+ shift
+done
+
+shift #get rid of the '--'
+
+if [ -z "$PSEUDO_LOCALSTATEDIR" ]
+ then
+ UID=`id -u`
+ if [ -d /run/user/$UID -a -w /run/user/$UID ]
+ then
+ PSEUDO_LOCALSTATEDIR=/run/user/$UID/pseudo/$$
+ mkdir -p "$PSEUDO_LOCALSTATEDIR"
+ else
+ PSEUDO_LOCALSTATEDIR=/tmp/pseudo.$UID/$$
+ if ! mkdir -p "$PSEUDO_LOCALSTATEDIR"
+ then
+ fatal "Could not find a suitable state dir"
+ fi
+ fi
+fi
+
+export PSEUDO_PREFIX=/usr
+export PSEUDO_LOCALSTATEDIR
+
+pseudo "$@"
+RESULT=$?
+
+if [ "$KEEPSTATEDIR" -ne 1 ]
+then
+ rm -rf "$PSEUDO_LOCALSTATEDIR"
+fi
+
+exit $RESULT
+
+# Local Variables:
+# mode: shell-script
+# End:
diff --git a/debian/fakeroot-pseudo.1 b/debian/fakeroot-pseudo.1
new file mode 100644
index 0000000..6289390
--- /dev/null
+++ b/debian/fakeroot-pseudo.1
@@ -0,0 +1,163 @@
+.\" Process this file with
+.\" groff -man -Tascii foo.1
+.\"
+.\" "verbatim" environment (from strace.1)
+.de CW
+.sp
+.nf
+.ft CW
+..
+.de CE
+.ft
+.fi
+.sp
+..
+.\"
+.TH fakeroot 1 "22 November 2015" "Debian Project" "Debian manual"
+.\" Manpage by J.H.M. Dassen <jdassen@debian.org>,
+.\" Clint Adams <clint@debian.org>
+.\" and Andrew Shadura <andrewsh@debian.org>
+.SH NAME
+fakeroot \- run a command in an environment faking root privileges for file
+manipulation
+.SH SYNOPSIS
+.B fakeroot
+.B [\-i|\-s
+.IB local-state-dir ]
+.B [\-h|\-\-help ]
+.B [\-v|\-\-version ]
+.BI [\-\-]
+.BI [command]
+.SH DESCRIPTION
+.B fakeroot
+runs a command in an environment wherein it appears to have root privileges
+for file manipulation. This is useful for allowing users to create file system
+images, archives and packages (tar, ar, .deb etc.) with files in them
+with root permissions/ownership. Without
+.B fakeroot
+one would need to have root privileges to create the constituent files of
+the archives with the correct permissions and ownership, and then pack them
+up, or one would have to construct the archives directly, without using the
+archiver.
+This version of
+.B fakeroot
+uses pseudo(1) to replace the file manipulation library functions (chmod(2),
+stat(2) etc.) by ones that simulate the effect the real library
+functions would have had, had the user really been root.
+
+.SH OPTIONS
+.TP
+.BI \-l \ arg\fR,\ \fB\-\-lib\ \fIarg
+Does nothing, accepted for compatibility only.
+.TP
+.BI \-\-faked \ arg
+Does nothing, accepted for compatibility only.
+.TP
+.BI [\-\-] \ command
+Any command you want to be ran as fakeroot. Use \(oq\-\-\(cq if in the command
+you have other options that may confuse fakeroot's option parsing.
+.TP
+.BI \-s \ local-state-dir
+Keep the
+.I pseudo
+state directory on exit. This directory holds the
+.I pseudo
+database files and log files. See the
+.I pseudo
+documentation on the details on how this directory can be reused.
+.TP
+.BI \-i \ local-state-dir
+Load a
+.I pseudo
+environment previously saved using \-s from the specified directory.
+Note that this does not implicitly save the direcotry, use \-s instead for
+that behaviour. Using the same file for both \-i and \-s in a single
+.BR fakeroot
+invocation is safe.
+.TP
+.BR \-u ,
+.BR \-\-unknown\-is\-real
+Does nothing, accepted for compatibility only.
+.TP
+.BI \-b \ fd
+Does nothing, accepted for compatibility only.
+.TP
+.BI \-h
+Display help.
+.TP
+.BI \-v
+Display version.
+
+.SH EXAMPLES
+Here is an example session with
+.BR fakeroot .
+Notice that inside the fake root environment file manipulation that
+requires root privileges succeeds, but is not really happening.
+.CW
+$ whoami
+joost
+$ fakeroot /bin/bash
+# whoami
+root
+# mknod hda3 b 3 1
+# ls \-ld hda3
+brw\-r\-\-r\-\- 1 root root 3, 1 Jul 2 22:58 hda3
+# chown joost:root hda3
+# ls \-ld hda3
+brw\-r\-\-r\-\- 1 joost root 3, 1 Jul 2 22:58 hda3
+# ls \-ld /
+drwxr\-xr\-x 20 root root 1024 Jun 17 21:50 /
+# chown joost:users /
+# chmod a+w /
+# ls \-ld /
+drwxrwxrwx 20 joost users 1024 Jun 17 21:50 /
+# exit
+$ ls \-ld /
+drwxr\-xr\-x 20 root root 1024 Jun 17 21:50 //
+$ ls \-ld hda3
+\-rw\-r\-\-r\-\- 1 joost users 0 Jul 2 22:58 hda3
+.CE
+Only the effects that user
+.B joost
+could do anyway happen for real.
+
+.B fakeroot
+was specifically written to enable users to create Debian GNU/Linux
+packages (in the
+.B deb(5)
+format) without giving them root privileges.
+This can be done by commands like
+.B dpkg-buildpackage \-rfakeroot
+or
+.B debuild \-rfakeroot
+(actually, \-rfakeroot is default in debuild nowadays, so you don't
+need that argument).
+.SH SECURITY ASPECTS
+.B fakeroot
+is a regular, non-setuid program. It does not enhance a user's
+privileges, or decrease the system's security.
+.SH BUGS
+See
+.B pseudo(1)
+for the details on the bugs of the underlying fakeroot implementation.
+.SH COPYING
+.B fakeroot
+wrapper for
+.B pseudo
+is distributed under the GNU General Public License version 3.0 or later.
+.B pseudo
+itself is distributed under the GNU Lesser General Public License version
+2.1 or later.
+.SH AUTHORS
+The original
+.B fakeroot
+manual page has mostly been written by J.H.M. Dassen
+.RI <jdassen@debian.org>
+with rather a lot modifications and additions by joost and Clint.
+It was later modified by Andrew Shadura for this fakeroot wrapper for
+.BR pseudo .
+.SH "SEE ALSO"
+.BR pseudo (1)
+.BR fakeroot (1)
+.BR dpkg\-buildpackage (1),
+.BR debuild (1)
diff --git a/debian/install b/debian/install
new file mode 100644
index 0000000..a44783d
--- /dev/null
+++ b/debian/install
@@ -0,0 +1 @@
+debian/fakeroot-pseudo usr/bin
diff --git a/debian/manpages b/debian/manpages
new file mode 100644
index 0000000..5bb175d
--- /dev/null
+++ b/debian/manpages
@@ -0,0 +1,3 @@
+debian/fakeroot-pseudo.1
+pseudo.1
+pseudolog.1
diff --git a/debian/patches/manpage.patch b/debian/patches/manpage.patch
new file mode 100644
index 0000000..f184e65
--- /dev/null
+++ b/debian/patches/manpage.patch
@@ -0,0 +1,28 @@
+From: Andrew Shadura <andrewsh@debian.org>
+Subject: Add NAME section.
+
+Each manual page should start with a "NAME" section, which lists the
+name and a brief description of the page separated by "\-".
+
+--- a/pseudo.1
++++ b/pseudo.1
+@@ -16,6 +16,8 @@
+ .\" version 2.1 along with this program; if not, write to the Free Software
+ .\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ .TH pseudo 1 "pseudo - pretending to be root"
++.SH NAME
++pseudo \- run a command in a virtual root environment
+ .SH SYNOPSIS
+ .B pseudo
+ .RB [ \-dflv ]
+--- a/pseudolog.1
++++ b/pseudolog.1
+@@ -16,6 +16,8 @@
+ .\" version 2.1 along with this program; if not, write to the Free Software
+ .\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ .TH pseudolog 1 "pseudo - pretending to be root"
++.SH NAME
++pseudolog \- pseudo log parser
+ .SH SYNOPSIS
+ .B pseudolog \-l
+ .RB [ \-Pv ]
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..973b94b
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1 @@
+manpage.patch
diff --git a/debian/postinst b/debian/postinst
new file mode 100755
index 0000000..52e1b26
--- /dev/null
+++ b/debian/postinst
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+set -e
+
+case "$1" in
+ configure)
+ # continue below
+ update-alternatives --install /usr/bin/fakeroot fakeroot \
+ /usr/bin/fakeroot-pseudo 60 \
+ --slave /usr/share/man/man1/fakeroot.1.gz \
+ fakeroot.1.gz /usr/share/man/man1/fakeroot-pseudo.1.gz
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ exit 0
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 0
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/prerm b/debian/prerm
new file mode 100755
index 0000000..43c0ec2
--- /dev/null
+++ b/debian/prerm
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -e
+
+case "$1" in
+ remove|deconfigure)
+ update-alternatives --remove fakeroot /usr/bin/fakeroot-pseudo
+ ;;
+ upgrade)
+ ;;
+
+ failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 0
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..61fe338
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,20 @@
+#!/usr/bin/make -f
+
+export CFLAGS := $(shell dpkg-buildflags --get CFLAGS) $(shell dpkg-buildflags --get CPPFLAGS) $(shell dpkg-buildflags --get LDFLAGS)
+
+%:
+ dh $@
+
+override_dh_auto_configure:
+ ./configure --prefix=/usr --bits=$(shell dpkg-architecture -qDEB_HOST_ARCH_BITS) --libdir=/usr/lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH)/pseudo --without-rpath
+
+override_dh_auto_install:
+ dh_auto_install
+ rm -r debian/*/usr/var
+
+override_dh_auto_test:
+ unset LD_PRELOAD; \
+ dh_auto_test
+
+override_dh_installchangelogs:
+ dh_installchangelogs ChangeLog.txt
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 0000000..9e7c0da
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1 @@
+version=3
diff --git a/doc/chroot b/doc/chroot
new file mode 100644
index 0000000..a0d1758
--- /dev/null
+++ b/doc/chroot
@@ -0,0 +1,31 @@
+Pseudo incorporates functionality similar to that of fakechroot.
+
+The implementation is basically as follows: When incoming arguments
+are known to be paths, they are automatically converted to absolute,
+fully canonicalized, paths. When the chroot(2) call is used, pseudo
+intercepts it and stashes the path to the new root directory internally
+in the client. This path is then silently prepended to absolute
+paths. The internal tracking of file paths uses absolute paths
+including the path to the chroot directory. So, all underlying calls
+are invoked with absolute, canonical, paths.
+
+A few calls are then modified. For instance, when readlink() returns
+a link, it strips a leading prefix looking like the chroot directory;
+when symlink() creates a link, if the link path is absolute, the chroot
+directory is prepended to it.
+
+This behavior, which may seem surprising, is an attempt to produce
+path resolution equivalent to what would normally happen when
+canonicalizing a path using real chroot; if you create a link to /tmp
+while in a chroot environment, following that link should yield
+<chroot>/tmp. Arguably, now that we are intercepting nearly every
+incoming path, we should just prepend the chroot path when following
+absolute symlinks -- this may get revisited.
+
+Similarly, when glob() is called, the path to be globbed has the
+chroot directory prepended, and then the results have any leading
+chroot directory stripped from them.
+
+This implementation is far from complete or perfect; the intent is
+that it provide enough functionality to allow RPM and similar tools
+to run file system installs using chroot().
diff --git a/doc/database b/doc/database
new file mode 100644
index 0000000..7003aff
--- /dev/null
+++ b/doc/database
@@ -0,0 +1,120 @@
+There are two databases. The log database contains a record of operations
+and events. (Operation logging is optional.) The file database contains a
+record of known files. In general, the file database is configured with
+sqlite options favoring stability, while the log database is configured for
+speed, as operation logging tends to outnumber file operations by a large
+margin.
+
+FILES:
+ id (unique key)
+ path (varchar, if known)
+ dev (integer)
+ ino (integer)
+ uid (integer)
+ gid (integer)
+ mode (integer)
+ rdev (integer)
+ deleting (integer)
+
+There are two indexes on the file database, one by path and one by device
+and inode. Earlier versions of pseudo ignored symlinks, but this turned
+out to create problems; specifically, if you had a symlink to a directory,
+and accessed a file through that, it could create unexpected results. Names
+are fully canonicalized by the client, except for functions which would
+operate directly on a symlink, in which case the last path component is not
+replaced.
+
+It is not an error to have multiple entries with the same device and inode.
+Updates to uid, gid, mode, or rdev are applied to every file with the same
+device and inode. Operations by name are handled by looking up the name
+to obtain the device and inode, then modifying all matching records.
+
+If a file shows up with no name (this should VERY rarely happen), it is stored
+in the database with the special name 'NAMELESS FILE'. This name can never
+be sent by the client (all names are sent as absolute paths). If a laterThe
+request comes in with a valid name, the 'NAMELESS FILE' is renamed to it so
+it can be unlinked later.
+
+The "deleting" field is used to track files which are in the midst of
+being possibly-deleted. The issue is that if you issue an unlink database
+operation only after removing a file, there is a window during which
+another program can genuinely create a new file with the same name or
+inode, and that request can reach the daemon before the unlink operation
+does. To address this, three operations are addded: may-unlink, did-unlink,
+and cancel-unlink. The may-unlink operation tags a file for possible
+deletion, setting "deleting" to 1. A clash involving a file marked for
+deletion is resolved by deleting the existing database entry without
+complaint. A did-unlink operation deletes a file ONLY if it is marked
+for deletion. A cancel-unlink operation unmarks a file for deletion.
+
+You can still have a race condition here, but it seems a lot less likely.
+The failure would be:
+ Process A marks file X for deletion.
+ Process A unlinks file X.
+ Process B creates file X.
+ Process B requests a link for X.
+ The server unlinks the previously marked X.
+ The server creates a new link for X, not marked for deletion.
+ Process B marks file X for deletion.
+ Process A's delete-marked-file request finally shows up.
+ The server unlinks the newly-marked X.
+ Process B fails to delete the file.
+ Process B attempts to cancel the mark-for-deletion.
+
+This shouldn't be a common occurrence.
+
+Rename operations use a pair of paths, separated by a null byte; the client
+sends the total length of both names (plus the null byte), and the server
+knows to split them around the null byte. The impact of a rename on things
+contained within a directory is handled in SQL:
+ UPDATE files SET path = replace(path, oldpath, newpath) WHERE
+ path = oldpath;
+ UPDATE files SET path = replace(path, oldpath, newpath) WHERE
+ (path > oldpath || '/') && (path < oldpath || '0);
+That is to say, anything which either starts with "oldpath/" or is exactly
+equal to oldpath gets renamed, with oldpath replaced by newpath... The
+unusual constructions are to address two key issues. One is that an "OR"
+would prevent proper use of the index. The other is that a pattern,
+such as "LIKE oldpath || '/%'", would prevent use of the index (at least
+in sqlite). The gimmick is that the only things greater than 'a/' and less
+than 'a0' are strings which begin with 'a/' and have additional characters
+after it.
+
+LOGS
+ id (unique key)
+ stamp (integer, seconds since epoch)
+ operation (id from operations, can be null)
+ client (integer identifier)
+ dev (integer)
+ ino (integer)
+ mode (integer)
+ path (varchar)
+ result (result id)
+ severity (severity id)
+ text (anything else you wanted to say)
+ tag (identifier for operations)
+ access (integer)
+
+The log database contains a primary table (logs). As of this writing it
+is not indexed, because indexing is expensive during writes (common, for
+the log database) and very few queries are usually run.
+
+The log database also contains, when created, tables of operations, result
+types, and severities. These exist so that queries can be run against
+a log database even if these values might have changed in a newer build
+of pseudo. The tables of operations and severities are just id->name pairs.
+No enforcement of the relation is currently provided.
+
+The log database "tag" field, added since the initial release of pseudo,
+is available for tagging operations. When a client connects to the
+pseudo server, it passes the value of the environment variable PSEUDO_TAG;
+this tag is then recorded for all log entries pertaining to that client.
+
+The log database "access" field, added since the initial release of pseudo,
+stores values which are bitwise masks of the values 1 (execute), 2 (write),
+4 (read), and 8 (append), used to report the modes with which a file was
+opened. These values are not completely reliable. A value of 0 is typical
+for non-open operations, and a value outside the 0-15 range (usually -1)
+indicates that something went wrong trying to identify the mode of a given
+open.
+
diff --git a/doc/overview b/doc/overview
new file mode 100644
index 0000000..37d223a
--- /dev/null
+++ b/doc/overview
@@ -0,0 +1,94 @@
+Overview:
+
+The pseudo program and library combine to provide an environment which
+provides the illusion of root permissions with respect to file creation,
+ownership, and related functions.
+
+The underlying mechanism of pseudo is a library inserted using LD_PRELOAD,
+which provides replacement symbols for core C library functions. At this
+time, the implementation is specific to modern glibc. Support for other
+systems is certainly possible, but not currently implemented or immediately
+planned. The symbols wrapped are generally those that are documented in
+section 2 of the manual -- the ones which are essentially system calls.
+
+The library works by replacing each real function with a wrapper function
+which obtains the addresses of "real" functions (those in the next library
+down in the chain, typically glibc) and then calls custom-written wrappers
+which alter the behavior of these functions and return results corresponding
+to the virtual environment.
+
+Underlying this is access to a server process, which is automatically
+spawned by the library if one is not available. The server process maintains
+a UNIX domain socket while it is active, and maintains a database (using
+sqlite) of files known to the system. Files are recorded in the database
+only if they are created within the virtualized environment or have been
+altered by it; files merely read are not added.
+
+There are four layers of logic for performing or wrapping any function,
+although not all functions involve all four layers:
+
+1. The generic wrapper, which handles details such as thread-synchronization.
+This function handles the mutex used to keep multiple threads from trying to
+write to the same socket at once, and also disables wrappers when a value
+called "antimagic" is set. The antimagic value is set internally by the
+pseudo client code, and the check for whether or not to use it is controlled
+by the mutex (actually by the mutex owner variable, which is protected by
+the mutex.) Without that, read operations in another thread during the
+"antimagic" part of an operation would bypass pseudo, yielding erratically
+wrong results! Wrappers are where pathnames get canonicalized.
+2. The wrapper function itself. This function may translate a single
+operation into two or more logical operations. This function has no awareness
+of the database, but can send queries to the general client code.
+3. The general client code. This code maintains additional data, such as
+a mapping of file descriptors to paths. In most cases, this code also
+forwards requests to the server code. (If the server is unavailable, the
+client can restart it.)
+4. The server code. This code is fairly simple; all it does is maintain
+the database of file information. Operations consist either of a request
+for information (e.g., a stat(2) call) or notification of a change. The
+server sends back failure or success notices.
+
+As a fairly typical example, the progress of a stat(2) call is:
+
+* The __xstat() wrapper is called. This wrapper checks the version argument
+ against the _STAT_VER constant in case we some day run into a system where
+ programs call stat with different versions of struct stat. (Hasn't happened
+ yet.)
+* The __xstat() wrapper calls the __fxstatat() wrapper, which in turn calls
+ the __fxstatat64() wrapper (this allows us to have only one copy of the
+ logic shared among all the path-based stat syscalls).
+* The __fxstatat64() wrapper calls the underlying __fxstatat64() function,
+ which has been mapped to the name real___fxstatat64(). (If this fails,
+ the wrapper function returns immediately.)
+* The __fxstatat64() wrapper passes the resulting stat buffer and path to the
+ client code and asks for a response.
+* The client code converts the stat buffer into a pseudo_msg_t message
+ object, and canonicalizes the path (resolving symlinks and eliminating
+ extra slashes, as well as references to . and ..).
+* The client code now sends the pseudo_msg_t object and converted path to
+ the server as a message.
+* The server receives the message. Since this is a stat() operation (using
+ a path, not a dev/inode pair, for identification), the server searches its
+ database for existing entries with the corresponding name.
+* If the server finds an object, it updates the contents of the pseudo_msg_t
+ with the recorded values for uid, gid, mode, and raw device number, and
+ sends the message back with status SUCCEED.
+* The server also performs sanity checks to see whether there may be other
+ suspiciously-similar entries in the database, in which case it emits
+ diagnostics. (Usually to pseudo.log.)
+* If the server finds no object, it sends the message back with status FAIL.
+* The client code returns the message to the wrapper function.
+* If the status was SUCCEED, the wrapper function copies the modified
+ fields back into its stat buffer; otherwise, it does not.
+* The wrapper function returns the original exit status from stat.
+
+Most of the functions wrapped are syscalls. There are a few exceptions, such
+as mkstemp, fopen, and freopen. These are wrapped because, in glibc, they
+call internal functions which make inline assembly syscalls, rather than
+calling the syscall entry points. In each case, the wrapper makes the real
+call without intervention, then snoops the results for a file descriptor to
+path mapping. (This would be done to opendir/fdopendir/closedir as well,
+but the DIR * is opaque and can't be snooped practically. This is why
+some versions of 'rm -r' can, at higher diagnostic levels, generate a slew
+of warnings about file descriptors being reopened when no close was
+observed.)
diff --git a/doc/passwd b/doc/passwd
new file mode 100644
index 0000000..42d41c6
--- /dev/null
+++ b/doc/passwd
@@ -0,0 +1,27 @@
+Pseudo provides limited support for faking up password file access.
+
+Routines such as setpwent(), getpwent(), and so on, are modified to
+possibly pick a password file other than /etc/passwd, and likewise
+for setgrent() and /etc/group. The logic is as follows:
+
+* If a chroot directory is set, <chroot>/etc is tried first.
+* If PSEUDO_PASSWD is set, PSEUDO_PASSWD/etc is tried next.
+* Otherwise, fall back on /etc.
+
+In each case, failure to find a passwd or group file results in
+going on to trying the next case.
+
+The behavior of lckpwdf()/ulckpwdf() is a special case. In this
+case, the same order of directories is tried, but pseudo attempts
+to create the files, rather than attempting to open existing files.
+
+The underlying implementation directs nearly everything to
+fgetpwent_r() and fgetgrent_r(), which are extensions available in
+glibc. This allows pseudo to avoid having to actually implement yet
+another horrible passwd file parser which would inevitably have
+bugs that have already been fixed dozens of times in other
+implementations.
+
+Note that both the chroot directory and PSEUDO_PASSWD are assumed
+to be the parent directory of etc, not the directory containing the
+passwd and group files.
diff --git a/doc/perftest b/doc/perftest
new file mode 100644
index 0000000..b9169ae
--- /dev/null
+++ b/doc/perftest
@@ -0,0 +1,8 @@
+There's a new "perftest" program. I recommend you run it with a ramdisk. So,
+if you've mounted a ramdisk on /tmp/ram:
+ ./perftest /tmp/ram
+
+This is a pretty unscientific test, but is designed to exercise some
+reasonably common use cases and give ballpark figures as to the effect
+of changes to the database indexes, etc.
+
diff --git a/doc/ports b/doc/ports
new file mode 100644
index 0000000..4debb64
--- /dev/null
+++ b/doc/ports
@@ -0,0 +1,23 @@
+The "ports" system provides functionality for porting pseudo to new targets.
+The original motivation was a requirement to support older and newer Linux
+systems on which the signature for clone() differed.
+
+The Darwin port is totally nonfunctional at this point, but it compiles,
+and sufficiently careful hand-tuning of DYLD_INSERT_LIBRARIES and a
+manually-started server can actually pass a simple test. It will get
+worked on in my copious free time.
+
+The basic design of a port is that it provides a wrapfuncs.in list of
+function signatures, guts implementations, and optionally some port-specific
+defines or a block of wrapper code for pseudo_wrappers.c. This is used
+for cases where the default wrapper would not be appropriate, and may
+be combined with the new hand_wrapped=1 flag in wrapfuncs.in.
+
+A port may specify preports or subports. Preports are ports that are
+included and processed *before* the current port -- meaning that functions
+in the current port can override them. Subports are processed *after*
+the current port -- meaning that they can override functions in the
+current port. The preports and subports are specified by scripts,
+which echo a list of port names to standard output. (Be sure any
+diagnostic messages go to standard error.)
+
diff --git a/doc/program_flow b/doc/program_flow
new file mode 100644
index 0000000..3a399a7
--- /dev/null
+++ b/doc/program_flow
@@ -0,0 +1,84 @@
+This is a quick attempt at documenting the basic program flow for both the main
+pseudo executable and the pseudo wrapper library. The key thing to note is
+that there are key init functions. These functions are designed to be
+re-invoked if it becomes necessary to reset the system environment.
+
+libpseudo execution flow:
+ # on startup #
+
+ pseudo_wrappers.c: (constructor) _libpseudo_init()
+ pseudo_util.c: pseudo_init_util()
+ copy environment
+ setup PSEUDO_DEBUG levels
+ pseudo_wrappers.c: pseudo_init_wrappers()
+ setup pseudo_functions
+ setup pseudo_logfile
+ pseudo_client.c: pseudo_init_client()
+ setup PSEUDO_DISABLED
+ setup pseudo_prefix_dir_fd
+ setup pseudo_localstate_dir_fd
+ setup PSEUDO_NOSYMLINKEXP
+ setup PSEUDO_UIDS
+ setup PSEUDO_GIDS
+ setup PSEUDO_CHROOT
+ setup PSEUDO_PASSWD
+
+ # regular program execution #
+ exec*()
+ pseudo_check_wrappers():
+ pseudo_reinit_libpseudo if necessary
+ call wrap_exec*()
+ pseudo_setupenv()
+ if PSEUDO_UNLOAD
+ pseudo_dropenv()
+ real_exec*()
+
+ fork()
+ pseudo_check_wrappers():
+ pseudo_reinit_libpseudo if necessary
+ call wrap_fork()
+ real_fork()
+ if (child)
+ pseudo_setupenv()
+ if !PSEUDO_UNLOAD
+ pseudo_reinit_libpseudo()
+ _libpseudo_init()
+ else
+ pseudo_dropenv()
+
+ clone()
+ pseudo_check_wrappers():
+ pseudo_reinit_libpseudo if necessary
+ call wrap_clone()
+ pseudo_setupenv()
+ if !PSEUDO_UNLOAD
+ pseudo_reinit_libpseudo()
+ _libpseudo_init()
+ else
+ pseudo_dropenv()
+ real_clone()
+
+ ... normal function wrappers ... (templates/wrapfuncs.c):
+ pseudo_check_wrappers() || !real_*
+ return enosys
+ variadic setup (if necessary)
+ if pseudo_disabled return real_*()
+ pseudo_sigblock
+ pseudo_getlock
+ if antimagic rc = real_*()
+ else rc = wrap_*()
+ variadic end (if necessary)
+ pseudo_droplock
+ unmask signals
+ return rc
+
+pseudo execution flow:
+ pseudo.c: main()
+ pseudo_util.c: pseudo_init_util()
+ <see above>
+ check LD_PRELOAD
+ process arguments
+ setup PSEUDO_OPTS
+ ...
+ startup server
+
diff --git a/doc/pseudo_ipc b/doc/pseudo_ipc
new file mode 100644
index 0000000..0892bb7
--- /dev/null
+++ b/doc/pseudo_ipc
@@ -0,0 +1,78 @@
+MESSAGE PASSING
+
+typedef struct {
+ pseudo_msg_type_t type;
+ op_id_t op;
+ res_id_t result;
+ int access;
+ int client;
+ dev_t dev;
+ unsigned long long ino;
+ uid_t uid;
+ gid_t gid;
+ unsigned long long mode;
+ dev_t rdev;
+ unsigned int pathlen;
+ int nlink;
+ char path[];
+} pseudo_msg_t;
+
+This structure is used for every communication between the client and the
+server. The last field is optional (it's a C99ism called a flexible array
+member, allowing a single allocation to hold both the structure and the
+variable-length character data at the end).
+
+All messages contain items up through 'pathlen'. If pathlen is not zero,
+an additional pathlen bytes containing path are provided; path is
+null-terminated.
+
+Every message from client should get a response from server. The server
+never really sends a path, currently, but maybe it will someday. Note that
+all server responses will in general share a single message object,
+and future operations may cause that object to be reallocated; the same
+goes for messages received by the server. Basically, pseudo_msg_receive
+is not thread-safe; this is part of (but not all of) the reason that there's
+mutex stuff in the wrappers. (The other part is the "antimagic" being
+able to blow things up.)
+
+type is one of PING, OP, FASTOP, SHUTDOWN, ACK, or NAK. The client
+only sends PING, OP, or FASTOP. FASTOP takes no response, otherwise the
+server should always send ACK. When run with '-S', the pseudo program
+runs as a client, sending a SHUTDOWN message to a server -- but only if
+it can find one, it does not start a new one. In this case, the server
+could respond with a NAK, in which case it sends a message in which
+"path" is a list of space-separated PIDs of currently-living clients,
+for the program to print out in an error message. The server will
+not shut down while there are living clients. (The request, though,
+causes it to shut down immediately when there are no more clients,
+rather than waiting for the timeout period.)
+
+result is the result of a particular operation. It applies only in replies
+to OP messages.
+
+client should be the client's PID on send, and the server's client number for
+that client on response. (The response isn't checked, and this is just a
+debugging feature.)
+
+dev/ino/uid/gid/mode/rdev/path are information about the file. They should
+all be provided on send if possible, but the server only generally changes
+uid/gid/mode/rdev on response, and never sends a path back. Dev and inode
+are currently changed by stat-by-path operations, but this may turn out to
+be wrong.
+
+access holds information about the open mode of a file (read, write, append,
+etc.), but is not fully implemented.
+
+A field "xerrno" used to exist; it was never actually implemented.
+
+nlink is used to forward the number of links. The server DOES NOT modify
+this. Rather, nlink is used to provide better diagnostics when checking
+paths against inodes.
+
+32/64 bit: This structure should have the same offsets for every element,
+including path, on both 64-bit and 32-bit machines. (Check with 'offsets.c'.)
+It is *not* an error if sizeof(pseudo_msg_t) is different; the padding
+happens after the path element. (Note: This is contrary to C99, TC1, but is
+correct according to the current standard. Anyway, gcc's always done it this
+way.) The data written are always pathlen + offsetof(pseudo_msg_t, path),
+and that's correct.
diff --git a/doc/utils b/doc/utils
new file mode 100644
index 0000000..35520d4
--- /dev/null
+++ b/doc/utils
@@ -0,0 +1,33 @@
+pseudolog
+ Displays or creates log entries. This offers a quick first
+ approximation of the sorts of queries one is likely to need to
+ run.
+
+pseudo
+ The pseudo server. Run on the command line, the default behavior
+ is to set up a pseudo environment (LD_PRELOAD, etc) then run either
+ the specified command or a shell by default. The -d option specifies
+ a background daemon, and -f specifies a foreground daemon (which
+ may display output directly). The launcher function isn't really
+ 32-bit/64-bit aware, but if you have both types of libraries in
+ suitably-named directories, it'll do the right thing anyway.
+
+ Path may be in environment as PSEUDO_PREFIX, specified on command
+ line with -P path, or inferred from the path to $0. (The last
+ generates a diagnostic.)
+
+ To stop the pseudo server, either wait a while (the default timeout
+ is 30 seconds, or whatever you specified with "-t" or PSEUDO_OPTS
+ when starting it) or run "pseudo -S". The server will not exit
+ while clients are active, but requesting a shutdown sets the timeout
+ to one second, so it will exit quickly after the last client
+ disconnects.
+
+libpseudo.so
+ The library providing the wrapper functionality, which spawns
+ the pseudo server automatically if needed. If the environment
+ variable PSEUDO_ENOSYS_ABORT is set, attempts to call missing
+ system calls will abort() rather than merely emitting a diagnostic.
+
+pseudodb
+ allows browsing and modification of db (not implemented)
diff --git a/enums/debug_type.in b/enums/debug_type.in
new file mode 100644
index 0000000..950333a
--- /dev/null
+++ b/enums/debug_type.in
@@ -0,0 +1,30 @@
+debug_type: PDBG; INDEXED unsigned char symbolic = '\0', const char * description = NULL, FLAGS
+# Note: For convenience/consistency with the old numerc debug levels
+# stuff, these are sorted in a very rough approximation of "likelihood
+# that the user will care." So, PSEUDO_DEBUG=1 will pick up the
+# consistency checks. In general, most numeric debug levels will be
+# significantly less chatty than they used to be; there was one
+# level 5 message, I think, and nothing else above a 4. Which was a
+# problem.
+# Note: Descriptions should be under 32 characters to match the formatting
+# in pseudo's help message.
+consistency, 'n', "consistency checks"
+file, 'f', "file creation/deletion"
+op, 'o', "operations"
+pid, 'P', "show process IDs"
+client, 'c', "client side startup/shutdown"
+server, 'v', "server side startup/shutdown"
+db, 'd', "database interactions"
+xattrdb, 'D', "xattr database"
+profile, 'R', "profiling"
+syscall, 'y', "system calls"
+env, 'e', "environment manipulation"
+chroot, 'r', "chroot functionality"
+path, 'p', "path computations"
+sql, 's', "SQL query information"
+wrapper, 'w', "wrapper functionality"
+ipc, 'i', "client/server interactions"
+invoke, 'k', "invocation and launching"
+benchmark, 'b', "performance statistics"
+verbose, 'V', "extra detail"
+xattr, 'x', "extended attributes"
diff --git a/enums/msg_type.in b/enums/msg_type.in
new file mode 100644
index 0000000..578d571
--- /dev/null
+++ b/enums/msg_type.in
@@ -0,0 +1,7 @@
+msg_type: PSEUDO_MSG
+ping
+shutdown
+op
+ack
+nak
+fastop
diff --git a/enums/op.in b/enums/op.in
new file mode 100644
index 0000000..61ee666
--- /dev/null
+++ b/enums/op.in
@@ -0,0 +1,29 @@
+op: OP; int wait = 0
+chdir, 0
+chmod, 0
+chown, 0
+chroot, 0
+close, 0
+creat, 0
+dup, 0
+fchmod, 0
+fchown, 0
+fstat, 1
+link, 0
+mkdir, 0
+mknod, 1
+open, 0
+rename, 0
+stat, 1
+unlink, 0
+symlink, 0
+exec, 0
+may-unlink, 1
+did-unlink, 0
+cancel-unlink, 0
+get-xattr, 1
+list-xattr, 1
+remove-xattr, 1
+set-xattr, 0
+create-xattr, 1
+replace-xattr, 1
diff --git a/enums/query_field.in b/enums/query_field.in
new file mode 100644
index 0000000..5124c5d
--- /dev/null
+++ b/enums/query_field.in
@@ -0,0 +1,28 @@
+query_field: PSQF
+# Note: These are later used as bitwise masks into a value,
+# currently an unsigned long; if the number of these gets up
+# near 32, that may take rethinking. The first thing to
+# go would probably be something special to do for FTYPE and
+# PERM because they aren't "real" database fields -- both
+# of them actually imply MODE.
+access
+client
+dev
+fd
+ftype
+gid
+id
+inode
+mode
+op
+order
+path
+perm
+program
+result
+severity
+stamp
+tag
+text
+type
+uid
diff --git a/enums/query_type.in b/enums/query_type.in
new file mode 100644
index 0000000..974e24f
--- /dev/null
+++ b/enums/query_type.in
@@ -0,0 +1,9 @@
+query_type: PSQT; const char * sql = "LITTLE BOBBY TABLES"
+exact, "="
+less, "<"
+greater, ">"
+bitand, "&"
+notequal, "!="
+like, "LIKE"
+notlike, "NOT LIKE"
+sqlpat, "LIKE"
diff --git a/enums/res.in b/enums/res.in
new file mode 100644
index 0000000..435338f
--- /dev/null
+++ b/enums/res.in
@@ -0,0 +1,4 @@
+res: RESULT
+succeed
+fail
+error
diff --git a/enums/sev.in b/enums/sev.in
new file mode 100644
index 0000000..24c2c6e
--- /dev/null
+++ b/enums/sev.in
@@ -0,0 +1,6 @@
+sev: SEVERITY
+debug
+info
+warn
+error
+critical
diff --git a/guts/COPYRIGHT b/guts/COPYRIGHT
new file mode 100644
index 0000000..06188b2
--- /dev/null
+++ b/guts/COPYRIGHT
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2008-2014 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
diff --git a/guts/README b/guts/README
new file mode 100644
index 0000000..0a1fe5f
--- /dev/null
+++ b/guts/README
@@ -0,0 +1,220 @@
+The files in this directory are partially machine-generated, and are
+all covered by the COPYRIGHT file in this directory.
+
+The set of functions covered here may seem surprising. For instance,
+obviously, fopen(3) simply calls the underlying open(2) syscall. But...
+There is a problem. In a few places in glibc, the syscalls are inlined
+such that there is no actual call to the C function open(2), just a raw
+call. So there are a couple of functions (fopen, freopen) which are
+wrapped with intent only to detect the possible creation of files.
+
+Many of these functions are closely related. Some programs may have
+calls to openat(), while others have calls to __openat_2(). To reduce
+code duplication, a number of functions are implemented purely as calls
+to other functions.
+
+When a *at() function exists, the regular function is implemented
+as *at() with AT_FDCWD as the directory fd (see the dummy #define of
+this in pseudo_client.h, used for systems which lack these.) On systems
+where AT_NOFOLLOW_SYMLINKS is not defined, the underlying *at() functions
+don't exist, so we provide a bare implementation which works only when
+the fd is AT_FDCWD...
+
+The creat64 and open64 families are equivalent to the plain versions with
+O_LARGEFILE in mode bits. (Again, there's a suitable dummy #define
+in pseudo_client.h.) By contrast, the stat64 functions actually do have
+some difference -- the structure they manipulate is not the same.
+
+The following table shows which functions are merely wrappers around
+other functions:
+
+ canonicalize_file_name: realpath
+ chmod: fchmodat
+ chown: fchownat
+ creat64: openat
+ creat: openat
+ __fxstatat: __fxstatat64
+ __fxstat: __fxstat64
+ get_current_dir_name: getcwd
+ getwd: getcwd
+ __lxstat64: __fxstatat64
+ __lxstat: __fxstatat
+ mkdir: mkdirat
+ mkfifoat: __xmknodat
+ mkfifo: mkfifoat
+ open64: openat
+ __openat_2: openat
+ __openat64_2: openat
+ openat64: openat
+ open: openat
+ remove: unlink or rmdir
+ rename: renameat
+ symlink: symlinkat
+ unlink: unlinkat
+ __xmknod: __xmknodat
+ __xstat64: __fxstatat64
+ __xstat: __fxstatat
+
+The following functions are full implementations:
+
+ chdir
+ fchdir
+ fchmod
+ fchmodat
+ fchown
+ fchownat
+ __fxstat64
+ __fxstatat64
+ getcwd
+ lchown
+ mkdirat
+ openat
+ rmdir
+ symlinkat
+ unlinkat
+ __xmknodat
+
+The following functions provide only partial implementations, to trap special
+cases, to track internal data structures (for instance, close() is tracked so
+that the path to a file descriptor can be dropped when the file descriptor
+is closed), or to handle functions which may not use the underlying syscall
+wrappers:
+
+ close
+ dup
+ dup2
+ execl* (no guts implementations; see pseudo_wrappers.c)
+ execv
+ execve
+ execvp
+ fclose
+ fopen
+ fopen64
+ freopen
+ freopen64
+ mkstemp
+ mkstemp64
+ fcntl
+ fork
+ link
+
+The following functions don't have any direct database interactions,
+but are used to simulate the permissions system:
+
+ getegid
+ getuid
+ setgid
+ setreuid
+ geteuid
+ setegid
+ setgroups
+ setuid
+ getgid
+ seteuid
+ setregid
+ getresgid
+ setfsgid
+ setresgid
+ getresuid
+ setfsuid
+ setresuid
+
+The following functions are present only to allow filename mangling
+for chroot(2) implementation. Most of them have no logic beyond
+calling the underlying routine.
+
+ access
+ acct
+ chroot
+ eaccess
+ euidaccess
+ fts_open
+ ftw64
+ ftw
+ glob64
+ glob
+ lutimes
+ mkdtemp
+ mktemp
+ nftw64
+ nftw
+ opendir
+ pathconf
+ readlinkat
+ readlink
+ realpath
+ scandir64
+ scandir
+ truncate64
+ truncate
+ utime
+ utimes
+
+The following functions are unimplemented. renameat could be done now (it
+would have been hard previously due to file name mangling issues), but
+since it's never come up, we haven't done it. The tempnam() functions are
+fairly hard to get right, and perhaps more imporantly, extremely
+dangerous. Since there's no evidence that they're in use anymore, I've
+dummied them out:
+
+ renameat
+ tempnam
+ tmpnam
+
+The following functions are partially emulated in order to provide for
+emulation of various getpw*() and getgr*() functions. No handling is
+provided for putpw*() or putgr*(). Nearly everything is ultimately
+implemented in terms of fgetpwent_r() and fgetgrent_r(), which are
+GNU extensions corresponding to fgetpwent() and fgetgrent(), allowing
+pseudo to read password information from an arbitrary stream; the
+setpwent() and setgrent() functions are modified to pick /etc/* from
+the pseudo_chroot path, if one is set, or from PSEUDO_PASSWD, if that
+is set, or else the system /etc/* files.
+
+ endgrent
+ endpwent
+ getgrent
+ getgrent_r
+ getgrgid
+ getgrgid_r
+ getgrnam
+ getgrnam_r
+ getgrouplist
+ getgroups
+ getpw
+ getpwent
+ getpwent_r
+ getpwnam
+ getpwnam_r
+ getpwuid
+ getpwuid_r
+ lckpwdf
+ setgrent
+ setgroups
+ setpwent
+ ulckpwdf
+
+The implementation of getgroups() is inauthentic in that it always checks
+the group file when called, rather than checking the group file once
+"at login" (whatever that means in our context) and returning that saved
+status. We don't think this matters. setgroups() just fails; this will
+be corrected if we come up with a compelling reason to do so.
+
+The following functions set errnot to ENOTSUP and return -1; this is
+needed because we don't actually track or manage extended attributes, but
+a few programs attempt to use *setxattr() to set regular permissions,
+and only use a regular chmod if the *setxattr() call returns -1 and
+sets errno to ENOTSUP.
+
+ fgetxattr
+ flistxattr
+ fremovexattr
+ fsetxattr
+ getxattr
+ lgetxattr
+ listxattr
+ llistxattr
+ lremovexattr
+ lsetxattr
+ removexattr
+ setxattr
diff --git a/makedata.c b/makedata.c
new file mode 100644
index 0000000..d3f5ed9
--- /dev/null
+++ b/makedata.c
@@ -0,0 +1,32 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+int
+main(void) {
+ int i, j, k, l;
+ /* templateish form used so it's obvious that it's long enough */
+ char name[] = "dir_%d/dir_%d/%d%d.txt";
+ for (i = 0; i < 10; ++i) {
+ snprintf(name, sizeof(name), "dir_%d", i);
+ mkdir(name, 0755);
+ for (j = 0; j < 40; ++j) {
+ snprintf(name, sizeof(name), "dir_%d/dir_%d", i, j);
+ mkdir(name, 0755);
+ for (k = 0; k < 10; ++k) {
+ for (l = 0; l < 10; ++l) {
+ FILE *fp;
+ snprintf(name, sizeof(name),
+ "dir_%d/dir_%d/%d%d.txt",
+ i, j, k, l);
+ fp = fopen(name, "w");
+ if (fp) {
+ fprintf(fp, "dummy file.\n");
+ fclose(fp);
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
diff --git a/maketables b/maketables
new file mode 100755
index 0000000..b32312e
--- /dev/null
+++ b/maketables
@@ -0,0 +1,275 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2010, 2013 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the Lesser GNU General Public License version 2.1 as
+# published by the Free Software Foundation.
+#
+# This program 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 Lesser GNU General Public License for more details.
+#
+# You should have received a copy of the Lesser GNU General Public License
+# version 2.1 along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""convert tables.in files to enums, tables, and support code.
+
+Inputs are a type name, prefix, and a list of columns, followed by a list of
+names with optional "= value" suffixes, plus optional additional columns.
+The names are used to create enums and a table of strings, as well as
+to/from lookups between the ids and names. If additional columns are
+defined, each column (separated by ", ") is used to create an additional
+table of the given name, and a lookup function from ids. Example:
+ foo: FFF; const char *bar = "GOZINTA"
+ hello, "yah"
+ world, "nope"
+produces:
+ typedef enum {
+ FFF_UNKNOWN = -1,
+ FFF_MIN = 0,
+ FFF_NONE = 0,
+ FFF_HELLO,
+ FFF_WORLD,
+ FFF_MAX
+ } foo_id_t;
+ extern const char *foo_name(foo_id_t id);
+ extern foo_id_t foo_id(const char *name);
+ extern const char *foo_bar(foo_id_t id);
+
+such that foo_name(1) => "hello" and foo_bar(1) => "yah". If there
+is an assigned value for a column description, missing column values
+yield that value, otherwise they yield "unknown".
+
+Values out of range yield "unknown", and unrecognized names yield the
+value -1. Note that the "MAX" value is one more than the highest defined
+value. (This is for consistency with C array bounds.)
+"""
+
+import glob
+import sys
+import string
+from templatefile import TemplateFile
+
+class DataType:
+ """a set of related DataItem objects"""
+
+ def __init__(self, path):
+ """read the first line of path, then make tuples of the rest"""
+ source = file(path)
+ definition = source.readline().rstrip()
+ self.name, qualifiers = string.split(definition, ': ', 2)
+ if '; ' in qualifiers:
+ self.prefix, columns = string.split(qualifiers, '; ')
+ else:
+ self.prefix = qualifiers
+ columns = []
+ self.flags = False
+ if len(columns):
+ self.columns = []
+ columns = string.split(columns, ', ')
+ for col in columns:
+ indexed = False
+ if col.startswith("FLAGS"):
+ print "Flags: set for %s" % self.name
+ self.flags = True
+ continue
+ if col.startswith("INDEXED "):
+ col = col[8:]
+ indexed = True
+ if "=" in col:
+ name, default = string.split(col, ' = ')
+ else:
+ name, default = col, ""
+ if " " in name:
+ words = string.split(name, ' ')
+ name = words[-1]
+ del words[-1]
+ type = ' '.join(words)
+ else:
+ type = "char *"
+ self.columns.append({"indexed":indexed, "type":type, "name":name, "value":default})
+ else:
+ self.columns = []
+ self.data = []
+ self.comments = []
+ index = 1
+ for line in source.readlines():
+ item = {}
+ if line.startswith('#'):
+ self.comments.append(line.rstrip().replace('#', ''))
+ continue
+ # first entry on the line is the "real" name/id, following hunks
+ # are additional columns
+ cols = string.split(line.rstrip(), ', ')
+ item["name"] = cols.pop(0)
+ item["upper"] = item["name"].replace('-', '_').upper()
+ column_list = []
+ for col in self.columns:
+ if len(cols) > 0:
+ value = cols.pop(0)
+ if col["indexed"]:
+ if not "max" in col:
+ col["max"] = value
+ if value > col["max"]:
+ col["max"] = value
+ if not "min" in col:
+ col["min"] = value
+ if value < col["min"]:
+ col["min"] = value
+ column_list.append({"name":col["name"], "value":value})
+ else:
+ column_list.append({"name":col["name"], "value":col["value"]})
+ item["cols"] = column_list
+ item["index"] = index
+ index = index + 1
+ self.data.append(item)
+
+ def __getitem__(self, key):
+ """Make this object look like a dict for Templates to use"""
+ attr = getattr(self, key)
+
+ if callable(attr):
+ return attr()
+ else:
+ return attr
+
+ def __repr__(self):
+ column = 0
+ out = ""
+ out += "type: %s_t" % self.name
+ out += " (prefix '%s_ENUM')\n" % self.prefix
+ for col in self.columns:
+ out += " extra column: %s %s (default %s)\n" % (col["type"], col["name"], col["value"])
+ out += " "
+ for item in self.data:
+ column = column + 1
+ if column > 4 and column % 4 == 1:
+ out += "\n "
+ out += "%-19s" % item["name"]
+# for col in item["cols"]:
+# out += "\t%s(%s)\n" % (col["name"], col["value"])
+ return out
+
+ def comment(self):
+ if len(self.comments):
+ return '/*' + '\n *'.join(self.comments) + ' */\n'
+ else:
+ return ''
+
+ def names(self):
+ return ',\n\t'.join('"%s"' % x["name"] for x in self.data)
+
+ def enums(self):
+ return ',\n\t'.join('%s_%s' % (self.prefix, x["upper"]) for x in self.data)
+
+ def flag_enums(self):
+ if not self.flags:
+ return ""
+ enum_lines = []
+ enum_lines.append('typedef enum {')
+ prefix = self.prefix + 'F'
+ for x in self.data:
+ enum_lines.append('\t%s_%s = (1 << %s_%s),' %
+ (prefix, x["upper"], self.prefix, x["upper"]))
+ enum_lines.append('} pseudo_%s_f;' % self.name)
+ return '\n'.join(enum_lines)
+
+ def column_names(self):
+ decl_lines = []
+ column = 0
+ for col in self.columns:
+ decl_lines.append("static %s %s_id_to_%s[] = {" % (col["type"], self.name, col["name"]))
+ decl_lines.append('\t%s,' % col["value"])
+ for item in self.data:
+ decl_lines.append('\t%s,' % item["cols"][column]["value"])
+ decl_lines.append('\t0')
+ decl_lines.append("};")
+ if col["indexed"]:
+ decl_lines.append("static int %s_%s_to_id[] = {" % (self.name, col["name"]))
+ for item in self.data:
+ decl_lines.append('\t[%s] = %d,' % (item["cols"][column]["value"], item["index"]))
+ decl_lines.append("};")
+ column = column + 1
+ return '\n'.join(decl_lines)
+
+ def column_funcs(self):
+ decl_lines = []
+ for col in self.columns:
+ decl_lines.append('extern %s' % col["type"])
+ decl_lines.append('pseudo_%s_%s(pseudo_%s_t id) {' %
+ (self.name, col["name"], self.name))
+ decl_lines.append('\tif (id < 0 || id >= %s_MAX)' % (self.prefix))
+ decl_lines.append('\t\treturn %s;' % col["value"])
+ decl_lines.append('\treturn %s_id_to_%s[id];' %
+ (self.name, col["name"]))
+ decl_lines.append('}')
+ if col["indexed"]:
+ table_name = '%s_%s_to_id' % (self.name, col["name"])
+ decl_lines.append('extern int')
+ decl_lines.append('pseudo_%s_%s_id(%s val) {' %
+ (self.name, col["name"], col["type"]))
+ decl_lines.append('\tif ((val < %s) || (val > %s)) {' % (col["min"], col["max"]))
+ decl_lines.append('\t\treturn -1;')
+ decl_lines.append('\t}')
+ decl_lines.append('\tif (%s[val] != 0) {' % table_name)
+ decl_lines.append('\t\treturn %s[val];' % table_name)
+ decl_lines.append('\t}')
+ decl_lines.append('\treturn -1;')
+ decl_lines.append('}')
+ return '\n'.join(decl_lines)
+
+ def column_protos(self):
+ decl_lines = []
+ for col in self.columns:
+ decl_lines.append('extern %s pseudo_%s_%s(pseudo_%s_t id);' %
+ (col["type"], self.name, col["name"], self.name))
+ if col["indexed"]:
+ decl_lines.append('extern int pseudo_%s_%s_id(%s val);' %
+ (self.name, col["name"], col["type"]))
+ return '\n'.join(decl_lines)
+
+def main():
+ """Read in function defintions, write out files based on templates."""
+ datatypes = []
+ templates = []
+
+ # error checking helpfully provided by the exception handler
+ copyright_file = open('guts/COPYRIGHT')
+ TemplateFile.copyright = copyright_file.read()
+ copyright_file.close()
+
+ for path in glob.glob('table_templates/*'):
+ try:
+ template_file = TemplateFile(path)
+ template_file.emit('copyright')
+ template_file.emit('header')
+ templates.append(template_file)
+ except IOError:
+ print "Invalid or malformed template %s. Aborting." % path
+ exit(1)
+
+ for filename in sys.argv[1:]:
+ # read in the datatype
+ sys.stdout.write("%s: " % filename)
+ datatype = DataType(filename)
+ datatypes.append(datatype)
+ print datatype.__repr__()
+ print ""
+
+ print "Writing datatypes...",
+ for datatype in datatypes:
+ # populate various tables and files with each datatype
+ for template_file in templates:
+ template_file.emit('body', datatype)
+ print "done. Cleaning up."
+
+ for template_file in templates:
+ # clean up files
+ template_file.emit('footer')
+ template_file.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/makewrappers b/makewrappers
new file mode 100755
index 0000000..e9191ed
--- /dev/null
+++ b/makewrappers
@@ -0,0 +1,638 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2008-2011,2013 Wind River Systems, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the Lesser GNU General Public License version 2.1 as
+# published by the Free Software Foundation.
+#
+# This program 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 Lesser GNU General Public License for more details.
+#
+# You should have received a copy of the Lesser GNU General Public License
+# version 2.1 along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""convert wrapfuncs.in to wrapper function stubs and tables"""
+
+import datetime
+import glob
+import sys
+import re
+import os.path
+import string
+import subprocess
+from templatefile import TemplateFile
+
+class ArgumentList:
+ """A (possibly empty) list of arguments"""
+
+ def __init__(self, text):
+ "parse a comma-separated argument list (including function prototypes)"
+ self.args = []
+ self.variadic = False
+ self.variadic_decl = ""
+ self.variadic_start = ""
+ self.variadic_end = ""
+ # (void) is an empty list, not a list of a single argument which is void
+ if text == "void":
+ return
+
+ depth = 0
+ accum = ''
+ comma_sep = text.split(', ')
+ # now, what if there was a comma embedded in an argument?
+ for arg in comma_sep:
+ lcount = arg.count('(')
+ rcount = arg.count(')')
+ depth = depth + lcount - rcount
+ if (depth > 0):
+ accum += arg + ', '
+ else:
+ self.args.append(Argument(accum + arg))
+ accum = ''
+ if depth != 0:
+ raise Exception("mismatched ()s while parsing '%s'" % text)
+ if self.args[-1].vararg:
+ self.variadic = True
+ self.variadic_arg = self.args[-1]
+ self.last_fixed_arg = self.args[-2].name
+ self.variadic_decl = "va_list ap;\n"
+ self.variadic_start = "va_start(ap, %s);\n" % self.last_fixed_arg
+ if self.variadic_arg.vararg_wraps:
+ self.variadic_decl += "\t%s;\n" % \
+ self.variadic_arg.vararg_wraps.decl()
+ self.variadic_start += ("\t%s = va_arg(ap, %s);"
+ "\n\tva_end(ap);\n") % \
+ (self.variadic_arg.name,
+ self.variadic_arg.type)
+ else:
+ # lie blatantly; we don't handle this case
+ self.variadic = False
+
+ # for a wrap function, the outer foo() wrapper will convert to a va_list,
+ # but the inner wrap_foo() just passes the va_list through.
+ def maybe_variadic_start(self):
+ """Use va_arg() to grab an optional argument if needed."""
+ if self.variadic and self.variadic_arg.vararg_wraps:
+ return self.variadic_start
+ else:
+ return ""
+
+ def maybe_variadic_decl(self):
+ """Declare va_list ap and optional argument, if needed."""
+ if self.variadic and self.variadic_arg.vararg_wraps:
+ return self.variadic_decl
+ else:
+ return ""
+
+ def decl(self, comment=False, wrap=False):
+ """Produce the declaration form of this argument list."""
+ if not self.args:
+ return "void"
+
+ return ', '.join(x.decl(comment=comment, wrap=wrap) for x in self.args)
+
+ def call(self):
+ """Produce the calling form of this argument list."""
+ if not self.args:
+ return ""
+ return ', '.join([x.call() for x in self.args])
+
+ def __repr__(self):
+ if not self.args:
+ return "no arguments"
+ else:
+ return '::'.join([x.decl() for x in self.args])
+
+
+class Argument:
+ """A function argument such as 'char *path' or 'char (*foo)(void)'"""
+ def __init__(self, text):
+ """get the type and name of a trivial C declaration"""
+ self.vararg = False
+ self.function_pointer = False
+ self.spacer = ''
+
+ if text == 'void':
+ raise Exception("Tried to declare a nameless object of type void.")
+
+ if text.startswith('...'):
+ self.vararg = True
+ if len(text) > 3:
+ # we're a wrapper for something else, declared as
+ # ...{real_decl}, as in the third argument to open(2)
+ text = text[4:-1]
+ # stash a copy of these values without the vararg flag, so
+ # we can declare them prettily later
+ self.vararg_wraps = Argument(text)
+ else:
+ # nothing to do.
+ self.vararg_wraps = None
+ self.type, self.name = None, None
+ return
+ else:
+ self.vararg = False
+
+ # try for a function pointer
+ match = re.match('(.*)\(\*([a-zA-Z0-9$_]*)\)\((.*)\)', text)
+ if match:
+ self.function_pointer = True
+ self.args = match.group(3)
+ self.type = match.group(1)
+ self.name = match.group(2).rstrip()
+ else:
+ # plain declaration
+ match = re.match('(.*[ *])\(?\*?([a-zA-Z0-9$_]*)\)?', text)
+ # there may not be a match, say in the special case
+ # where an arg is '...'
+ if match:
+ self.type, self.name = match.group(1).rstrip(), match.group(2)
+ else:
+ self.type, self.name = None, None
+
+ # spacing between type and name, needed if type ends with a character
+ # which could be part of an identifier
+ if re.match('[_a-zA-Z0-9]', self.type[-1]):
+ self.spacer = ' '
+
+ def decl(self, comment=False, wrap=False):
+ """Produce the declaration form of this argument."""
+ if self.function_pointer:
+ decl = "%s%s(*%s)(%s)" % \
+ (self.type, self.spacer, self.name, self.args)
+ else:
+ decl = "%s%s%s" % (self.type, self.spacer, self.name)
+
+ if self.vararg:
+ if self.vararg_wraps:
+ if comment:
+ decl = "... { %s }" % decl
+ else:
+ decl = "... /* %s */" % decl
+ else:
+ if wrap:
+ decl = "va_list ap"
+ else:
+ decl = "..."
+ return decl
+
+ def call(self):
+ """Produce the call form of this argument (usually its name)."""
+ if self.type == 'void':
+ return ''
+
+ if self.vararg and not self.vararg_wraps:
+ return "ap"
+
+ return self.name
+
+ def __str__(self):
+ return self.decl()
+
+ def __repr__(self):
+ return self.decl()
+
+typedata = {
+ 'char *': { 'format': '%s', 'value': 'rc ? rc : "<nil>"' },
+ 'const char *': { 'format': '%s', 'value': 'rc ? rc : "<nil>"' },
+ 'DIR *': { 'format': '%p', 'value': '(void *) rc' },
+ 'FILE *': { 'format': '%p', 'value': '(void *) rc' },
+ 'FTS *': { 'format': '%p', 'value': '(void *) rc' },
+ 'gid_t': { 'format': '%ld', 'value': ' (long) rc' },
+ 'int': { 'format': '%d', 'value': 'rc' },
+ 'long': { 'format': '%ld', 'value': 'rc' },
+ 'mode_t': { 'format': '0%lo', 'value': '(long) rc' },
+ 'off_t': { 'format': '%lld', 'value': '(long long) rc' },
+ 'size_t': { 'format': '%lu', 'value': '(unsigned long) rc' },
+ 'ssize_t': { 'format': '%ld', 'value': '(long) rc' },
+ 'struct group *': { 'format': '%p', 'value': '(void *) rc' },
+ 'struct passwd *': { 'format': '%p', 'value': '(void *) rc' },
+ 'uid_t': { 'format': '%ld', 'value': ' (long) rc' },
+ 'void *': { 'format': '%p', 'value': 'rc' },
+ 'void': { 'format': 'void%s', 'value': '""' },
+}
+
+class Function:
+ """A function signature and additional data about how the function works"""
+ def __init__(self, port, line):
+ # table of known default values:
+ default_values = {
+ 'gid_t': '0',
+ 'uid_t': '0',
+ 'int': '-1',
+ 'long': '-1',
+ 'mode_t': '0',
+ 'ssize_t': '-1'
+ }
+
+ self.dirfd = 'AT_FDCWD'
+ self.flags = '0'
+ self.port = port
+ self.directory = ''
+ self.version = 'NULL'
+ # On Darwin, some functions are SECRETLY converted to foo$INODE64
+ # when called. So we have to look those up for real_*
+ self.inode64 = None
+ self.real_func = None
+ self.paths_to_munge = []
+ self.specific_dirfds = {}
+ self.hand_wrapped = None
+ self.async_skip = None
+ # used for the copyright date when creating stub functions
+ self.date = datetime.date.today().year
+
+ function, comments = line.split(';')
+ comment = re.search('/\* *(.*) *\*/', comments)
+ if comment:
+ self.comments = comment.group(1)
+ else:
+ self.comments = None
+
+ bits = re.match('([^(]*)\((.*)\)', function)
+ type_and_name = Argument(bits.group(1))
+ self.type, self.name = type_and_name.type, type_and_name.name
+ # convenient to have this declared here so we can use its .decl later
+ if self.type != 'void':
+ self.return_code = Argument("%s rc" % self.type)
+
+ # Some args get special treatment:
+ # * If the arg has a name ending in 'path', we will canonicalize it.
+ # * If the arg is named 'dirfd' or 'flags', it becomes the default
+ # values for the dirfd and flags arguments when canonicalizing.
+ # * If the name ends in dirfd, we do the same fancy stuff.
+ # * Note that the "comments" field (/* ... */ after the decl) can
+ # override the dirfd/flags values.
+ self.args = ArgumentList(bits.group(2))
+ for arg in self.args.args:
+ # ignore varargs, they never get these special treatments
+ if arg.vararg:
+ pass
+ elif arg.name.endswith('dirfd'):
+ if len(arg.name) > 5:
+ self.specific_dirfds[arg.name[:-5]] = True
+ self.dirfd = 'dirfd'
+ elif arg.name == 'flags':
+ self.flags = 'flags'
+ elif arg.name.endswith('path'):
+ self.paths_to_munge.append(arg.name)
+
+ # pick default values
+ if self.type == 'void':
+ self.default_value = ''
+ elif self.type[-1:] == '*':
+ self.default_value = 'NULL'
+ else:
+ try:
+ self.default_value = default_values[self.type]
+ except KeyError:
+ raise KeyError("Function %s has return type %s,"
+ "for which there is no default value." %
+ (self.name, self.type))
+
+ # handle special comments, such as flags=AT_SYMLINK_NOFOLLOW
+ if self.comments:
+ modifiers = self.comments.split(', ')
+ for mod in modifiers:
+ key, value = mod.split('=')
+ value = value.rstrip()
+ setattr(self, key, value)
+
+ def maybe_inode64(self):
+ if self.inode64 and os.uname()[0] == 'Darwin':
+ return "$INODE64"
+ else:
+ return ""
+
+ def end_maybe_skip(self):
+ if self.hand_wrapped:
+ return """/* Hand-written wrapper for this function. */
+#endif
+"""
+ else:
+ return ""
+
+ def maybe_skip(self):
+ if self.hand_wrapped:
+ return """/* Hand-written wrapper for this function. */
+#if 0
+"""
+ else:
+ return ""
+
+ def maybe_async_skip(self):
+ if self.async_skip:
+ return """/* This function is not called if pseudo is configured --enable-force-async */
+#ifdef PSEUDO_FORCE_ASYNC
+ if (!pseudo_allow_fsync) {
+ PROFILE_DONE;
+ return %s;
+ }
+#endif
+""" % self.async_skip
+ else:
+ return ""
+
+ def comment(self):
+ """declare self (in a comment)"""
+ return self.decl(comment = True)
+
+ def decl(self, comment=False, wrap=True):
+ """declare self"""
+ if self.type[-1:] == '*':
+ spacer = ''
+ else:
+ spacer = ' '
+ return "%s%s%s(%s)" % \
+ (self.type, spacer, self.name, self.args.decl(comment, wrap))
+
+ def decl_args(self):
+ """declare argument list"""
+ return self.args.decl()
+
+ def wrap_args(self):
+ """declare argument list for wrap_foo() variant"""
+ return self.args.decl(wrap = True)
+
+ def call_args(self):
+ """present argument list for a function call"""
+ return self.args.call()
+
+ def fix_paths(self):
+ """create/allocate canonical paths"""
+ fix_paths = []
+ for path in self.paths_to_munge:
+ prefix = path[:-4]
+ if prefix not in self.specific_dirfds:
+ prefix = ''
+ fix_paths.append(
+ "%s = pseudo_root_path(__func__, __LINE__, %s%s, %s, %s);" %
+ (path, prefix, self.dirfd, path, self.flags))
+ return "\n\t\t".join(fix_paths)
+
+ def real_predecl(self):
+ if self.real_func:
+ return self.decl().replace(self.name, self.real_func, 1) + ";"
+ else:
+ return ""
+
+ def real_init(self):
+ if self.real_func:
+ return self.real_func
+ else:
+ return "NULL"
+
+ def rc_return(self):
+ """return rc (or just return)"""
+ if self.type == 'void':
+ return "return;"
+ else:
+ return "return rc;"
+
+ def rc_format(self):
+ """the format string to use for the return value"""
+ return typedata.get(self.type, { 'format': '[%s]', 'value': '"' + self.type + '"' })['format']
+
+ def rc_value(self):
+ """the value to pass for the format string for the return value"""
+ return typedata.get(self.type, { 'format': '[%s]', 'value': '"' + self.type + '"' })['value']
+
+ def rc_decl(self):
+ """declare rc (if needed)"""
+ if self.type == 'void':
+ return ""
+ else:
+ return "%s = %s;" % (self.return_code.decl(), self.default_value)
+
+ def rc_assign(self):
+ """assign something to rc (or discard it)"""
+ if self.type == 'void':
+ return "(void)"
+ else:
+ return "rc ="
+
+ def def_return(self):
+ """return default value (or just return)"""
+ if self.type == 'void':
+ return "return;"
+ else:
+ return "return %s;" % self.default_value
+
+ def __getitem__(self, key):
+ """Make this object look like a dict for Templates to use"""
+ try:
+ attr = getattr(self, key)
+ except AttributeError:
+ # There's a few attributes that are handled inside the args
+ # object, so check there too...
+ attr = getattr(self.args, key)
+
+ if callable(attr):
+ return attr()
+ else:
+ return attr
+
+ def __repr__(self):
+ pretty = "%(name)s returns %(type)s and takes " % self
+ pretty += repr(self.args)
+ if self.comments:
+ pretty += ' (%s)' % self.comments
+ return pretty
+
+ def funcdeps(self):
+ return 'pseudo_wrappers.o: ports/%s/guts/%s.c' % ( self.port, self.name )
+
+class Port:
+ """
+A Port is a set of function declarations and code providing
+details specific to a specific host environment, such as Linux.
+Ports can override each other, and each port can indicate
+additional ports to include.
+"""
+
+ def __init__(self, port, sources):
+ self.name = port
+ self.subports = []
+ self.preports = []
+ print port
+
+ if os.path.exists(self.portfile("pseudo_wrappers.c")):
+ self.wrappers = self.portfile("pseudo_wrappers.c")
+ else:
+ self.wrappers = None
+
+ if os.path.exists(self.portfile("portdefs.h")):
+ self.portdef_file = self.portfile("portdefs.h")
+ else:
+ self.portdef_file = None
+
+ if os.path.exists(self.portfile("wrapfuncs.in")):
+ self.funcs = process_wrapfuncs(port)
+ else:
+ self.funcs = {}
+
+ for source in sources:
+ source.emit('port', self)
+
+ if os.path.exists(self.portfile("preports")):
+ subport_proc = subprocess.Popen([self.portfile("preports"), self.name], stdout=subprocess.PIPE)
+ portlist = subport_proc.communicate()[0]
+ retcode = subport_proc.poll()
+ if retcode:
+ raise Exception("preports script failed for port %s" % self.name)
+
+ for preport in string.split(portlist):
+ next = Port(preport, sources)
+ self.preports.append(next)
+
+ if os.path.exists(self.portfile("subports")):
+ subport_proc = subprocess.Popen([self.portfile("subports"), self.name], stdout=subprocess.PIPE)
+ portlist = subport_proc.communicate()[0]
+ retcode = subport_proc.poll()
+ if retcode:
+ raise Exception("subports script failed for port %s" % self.name)
+
+ for subport in string.split(portlist):
+ next = Port(subport, sources)
+ self.subports.append(next)
+
+ def functions(self):
+ mergedfuncs = {}
+ for pre in self.preports:
+ prefuncs = pre.functions()
+ for name in prefuncs.keys():
+ if name in mergedfuncs:
+ print "Warning: %s from %s overriding %s" % (name, pre.name, mergedfuncs[name].port)
+ mergedfuncs[name] = prefuncs[name]
+ for name in self.funcs.keys():
+ if name in mergedfuncs:
+ print "Warning: %s from %s overriding %s" % (name, self.name, mergedfuncs[name].port)
+ mergedfuncs[name] = self.funcs[name]
+ for sub in self.subports:
+ subfuncs = sub.functions()
+ for name in subfuncs.keys():
+ if name in mergedfuncs:
+ print "Warning: %s from %s overriding %s" % (name, sub.name, mergedfuncs[name].port)
+ mergedfuncs[name] = subfuncs[name]
+ return mergedfuncs
+
+ def define(self):
+ return '#define PSEUDO_PORT_%s 1' % string.upper(self.name).replace('/', '_')
+
+ def portdeps(self):
+ deps = []
+ if self.wrappers:
+ deps.append(self.wrappers)
+ if self.portdef_file:
+ deps.append(self.portdef_file)
+ if deps:
+ return 'pseudo_wrappers.o: %s' % ' '.join(deps)
+ else:
+ return '# no extra dependencies for %s.' % self.name
+
+ def portdefs(self):
+ if self.portdef_file:
+ return '#include "%s"' % self.portdef_file
+ else:
+ return '/* no portdefs for %s */' % self.name
+
+ def include(self):
+ if self.wrappers:
+ return '#include "%s"' % self.wrappers
+ else:
+ return '/* no #include for %s */' % self.name
+
+ def portfile(self, name):
+ return "ports/%s/%s" % (self.name, name)
+
+ def __getitem__(self, key):
+ """Make this object look like a dict for Templates to use"""
+ try:
+ attr = getattr(self, key)
+ except AttributeError:
+ return None
+
+ if callable(attr):
+ return attr()
+ else:
+ return attr
+
+
+def process_wrapfuncs(port):
+ """Process a wrapfuncs.in file, generating a list of prototypes."""
+ filename = "ports/%s/wrapfuncs.in" % port
+ funcs = {}
+ directory = os.path.dirname(filename)
+ sys.stdout.write("%s: " % filename)
+ funclist = open(filename)
+ for line in funclist:
+ line = line.rstrip()
+ if line.startswith('#') or not line:
+ continue
+ try:
+ func = Function(port, line)
+ func.directory = directory
+ funcs[func.name] = func
+ sys.stdout.write(".")
+ except Exception, e:
+ print "Parsing failed:", e
+ exit(1)
+ funclist.close()
+ print ""
+ return funcs
+
+def main(argv):
+ """Read in function definitions, write out files based on templates."""
+ funcs = []
+ sources = []
+
+ for arg in argv:
+ name, value = arg.split('=')
+ os.environ["port_" + name] = value
+
+ # error checking helpfully provided by the exception handler
+ copyright_file = open('guts/COPYRIGHT')
+ TemplateFile.copyright = copyright_file.read()
+ copyright_file.close()
+
+ for path in glob.glob('templates/*'):
+ try:
+ print "Considering template: " + path
+ source = TemplateFile(path)
+ if source.name.endswith('.c') or source.name.endswith('.h'):
+ source.emit('copyright')
+ source.emit('header')
+ sources.append(source)
+ except IOError:
+ print "Invalid or malformed template %s. Aborting." % path
+ exit(1)
+
+ try:
+ port = Port('common', sources)
+
+ except KeyError:
+ print "Unknown uname -s result: '%s'." % uname_s
+ print "Known system types are:"
+ print "%-20s %-10s %s" % ("uname -s", "port name", "description")
+ for key in host_ports:
+ print "%-20s %-10s %s" % (key, host_ports[key],
+ host_descrs[host_ports[key]])
+
+ # the per-function stuff
+ print "Writing functions...",
+ all_funcs = port.functions()
+ for name in sorted(all_funcs.keys()):
+ # populate various tables and files with each function
+ for source in sources:
+ source.emit('body', all_funcs[name])
+ print "done. Cleaning up."
+
+ for source in sources:
+ # clean up files
+ source.emit('footer')
+ source.close()
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/offsets.c b/offsets.c
new file mode 100644
index 0000000..5e9cddd
--- /dev/null
+++ b/offsets.c
@@ -0,0 +1,56 @@
+/*
+ * offsets.c, print offsets in pseudo_ipc structure
+ *
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+
+int
+main(void) {
+ printf("type: %d\n", (int) offsetof(pseudo_msg_t, type));
+ printf("op: %d\n", (int) offsetof(pseudo_msg_t, op));
+ printf("result: %d\n", (int) offsetof(pseudo_msg_t, result));
+ printf("access: %d\n", (int) offsetof(pseudo_msg_t, access));
+ printf("client: %d\n", (int) offsetof(pseudo_msg_t, client));
+ printf("dev: %d\n", (int) offsetof(pseudo_msg_t, dev));
+ printf("ino: %d\n", (int) offsetof(pseudo_msg_t, ino));
+ printf("uid: %d\n", (int) offsetof(pseudo_msg_t, uid));
+ printf("gid: %d\n", (int) offsetof(pseudo_msg_t, gid));
+ printf("mode: %d\n", (int) offsetof(pseudo_msg_t, mode));
+ printf("rdev: %d\n", (int) offsetof(pseudo_msg_t, rdev));
+ printf("pathlen: %d\n", (int) offsetof(pseudo_msg_t, pathlen));
+ printf("nlink: %d\n", (int) offsetof(pseudo_msg_t, nlink));
+ printf("deleting: %d\n", (int) offsetof(pseudo_msg_t, deleting));
+ printf("path: %d\n", (int) offsetof(pseudo_msg_t, path));
+ printf("size: %d\n", (int) sizeof(pseudo_msg_t));
+ return 0;
+}
+
diff --git a/perftest b/perftest
new file mode 100755
index 0000000..97dced1
--- /dev/null
+++ b/perftest
@@ -0,0 +1,57 @@
+#!/bin/sh
+# do a quick performance test of pseudo
+opt_f=false
+flag_f=
+
+while getopts "f" o
+do
+ case $o in
+ f) opt_f=true
+ flag_f=-f
+ ;;
+ \?) die "Usage: perftest [-f] [directory]";;
+ esac;
+done
+shift `expr $OPTIND - 1`
+
+die() {
+ printf "%s\n" "$*" >&2
+ exit 1
+}
+
+doit() (
+ cd $dir
+ printf "%s\n" "Making test data..."
+ time ./makedata
+ printf "%s\n" "Timing tar command."
+ time sh -c 'tar cf - dir_[0-9] | tar -C new -xf -'
+ printf "%s\n" "Timing find command."
+ time find new -perm 0100 -exec true {} +
+ printf "%s\n" "Timing rm."
+ time rm -rf dir_[0-9] new
+)
+
+[ -x bin/pseudo ] || die "You need a bin/pseudo to test."
+case $# in
+0) dir="perftest.d";;
+1) [ -d "$1" ] || die "Specify an existing directory to test in. '%s' is not a directory." "$1"
+ dir="$1/perftest.d"
+ ;;
+*) die "Usage: perftest [directory]"
+ ;;
+esac
+
+if $opt_f || [ `id -u` = x0 ]; then
+ printf "Running test in %s.\n" "$dir"
+ doit
+ printf "Done.\n"
+else
+ [ -d $dir ] && die "Directory '$dir' already exists, delete it if you're done."
+ mkdir $dir
+ mkdir -p $dir/new
+ cc -o $dir/makedata makedata.c
+ printf "%s\n" "Running performance test (total time at end)"
+ time bin/pseudo ./perftest -f ${dir%perftest.d}
+ rm -rf $dir
+fi
+
diff --git a/ports/common/guts/execl.c b/ports/common/guts/execl.c
new file mode 100644
index 0000000..be86b49
--- /dev/null
+++ b/ports/common/guts/execl.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int execl(const char *file, const char *arg, va_list ap)
+ * int rc = -1;
+ */
+
+ /* NOTE THAT THIS IS NEVER USED!
+ * We implement all execl() in terms of execv()
+ * so this call is not used.
+ */
+
+ rc = real_execl(file, arg, ap);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/common/guts/execle.c b/ports/common/guts/execle.c
new file mode 100644
index 0000000..a3bc3ca
--- /dev/null
+++ b/ports/common/guts/execle.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int execle(const char *file, const char *arg, va_list ap)
+ * int rc = -1;
+ */
+
+ /* NOTE THAT THIS IS NEVER USED!
+ * We implement all execl() in terms of execv()
+ * so this call is not used.
+ */
+
+ rc = real_execle(file, arg, ap);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/common/guts/execlp.c b/ports/common/guts/execlp.c
new file mode 100644
index 0000000..3cf2889
--- /dev/null
+++ b/ports/common/guts/execlp.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int execlp(const char *file, const char *arg, va_list ap)
+ * int rc = -1;
+ */
+
+ /* NOTE THAT THIS IS NEVER USED!
+ * We implement all execl() in terms of execv()
+ * so this call is not used.
+ */
+
+ rc = real_execlp(file, arg, ap);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/common/guts/execv.c b/ports/common/guts/execv.c
new file mode 100644
index 0000000..ba1ce65
--- /dev/null
+++ b/ports/common/guts/execv.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_execv(const char *file, char *const *argv) {
+ * int rc = -1;
+ */
+ /* note: we don't canonicalize this, because we are intentionally
+ * NOT redirecting execs into the chroot environment. If you try
+ * to execute /bin/sh, you get the actual /bin/sh, not
+ * <CHROOT>/bin/sh. This allows use of basic utilities. This
+ * design will likely be revisited.
+ */
+ if (antimagic == 0) {
+ const char *path_guess = pseudo_exec_path(file, 0);
+ pseudo_client_op(OP_EXEC, PSA_EXEC, -1, -1, path_guess, 0);
+ }
+
+ pseudo_setupenv();
+ if (pseudo_has_unload(NULL))
+ pseudo_dropenv();
+
+ /* if exec() fails, we may end up taking signals unexpectedly...
+ * not much we can do about that.
+ */
+ sigprocmask(SIG_SETMASK, &pseudo_saved_sigmask, NULL);
+ rc = real_execv(file, argv);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/common/guts/execve.c b/ports/common/guts/execve.c
new file mode 100644
index 0000000..24cc177
--- /dev/null
+++ b/ports/common/guts/execve.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_execve(const char *file, char *const *argv, char *const *envp) {
+ * int rc = -1;
+ */
+ char * const *new_environ;
+ /* note: we don't canonicalize this, because we are intentionally
+ * NOT redirecting execs into the chroot environment. If you try
+ * to execute /bin/sh, you get the actual /bin/sh, not
+ * <CHROOT>/bin/sh. This allows use of basic utilities. This
+ * design will likely be revisited.
+ */
+ if (antimagic == 0) {
+ const char *path_guess = pseudo_exec_path(file, 0);
+ pseudo_client_op(OP_EXEC, PSA_EXEC, -1, -1, path_guess, 0);
+ }
+
+ new_environ = pseudo_setupenvp(envp);
+ if (pseudo_has_unload(new_environ))
+ new_environ = pseudo_dropenvp(new_environ);
+
+ /* if exec() fails, we may end up taking signals unexpectedly...
+ * not much we can do about that.
+ */
+ sigprocmask(SIG_SETMASK, &pseudo_saved_sigmask, NULL);
+ rc = real_execve(file, argv, new_environ);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/common/guts/execvp.c b/ports/common/guts/execvp.c
new file mode 100644
index 0000000..e6bf09f
--- /dev/null
+++ b/ports/common/guts/execvp.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_execvp(const char *file, char *const *argv) {
+ * int rc = -1;
+ */
+
+ /* note: we don't canonicalize this, because we are intentionally
+ * NOT redirecting execs into the chroot environment. If you try
+ * to execute /bin/sh, you get the actual /bin/sh, not
+ * <CHROOT>/bin/sh. This allows use of basic utilities. This
+ * design will likely be revisited.
+ */
+ if (antimagic == 0) {
+ const char *path_guess = pseudo_exec_path(file, 1);
+ pseudo_client_op(OP_EXEC, PSA_EXEC, -1, -1, path_guess, 0);
+ }
+
+ pseudo_setupenv();
+ if (pseudo_has_unload(NULL))
+ pseudo_dropenv();
+
+ /* if exec() fails, we may end up taking signals unexpectedly...
+ * not much we can do about that.
+ */
+ sigprocmask(SIG_SETMASK, &pseudo_saved_sigmask, NULL);
+ rc = real_execvp(file, argv);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/common/guts/fork.c b/ports/common/guts/fork.c
new file mode 100644
index 0000000..bebe3b0
--- /dev/null
+++ b/ports/common/guts/fork.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_fork(void) {
+ * int rc = -1;
+ */
+ rc = real_fork();
+ /* special case: we may want to enable or disable
+ * pseudo in the child process
+ */
+ if (rc == 0) {
+ pseudo_setupenv();
+ if (!pseudo_has_unload(NULL)) {
+ pseudo_reinit_libpseudo();
+ } else {
+ pseudo_dropenv();
+ }
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/common/pseudo_wrappers.c b/ports/common/pseudo_wrappers.c
new file mode 100644
index 0000000..81be635
--- /dev/null
+++ b/ports/common/pseudo_wrappers.c
@@ -0,0 +1,408 @@
+/* these aren't used, but the wrapper table isn't happy unless they
+ * exist
+ */
+static int
+wrap_execl(const char *file, const char *arg, va_list ap) {
+ (void) file;
+ (void) arg;
+ (void) ap;
+ return 0;
+}
+
+static int
+wrap_execle(const char *file, const char *arg, va_list ap) {
+ (void) file;
+ (void) arg;
+ (void) ap;
+ return 0;
+}
+
+static int
+wrap_execlp(const char *file, const char *arg, va_list ap) {
+ (void) file;
+ (void) arg;
+ (void) ap;
+ return 0;
+}
+
+static char **
+execl_to_v(va_list ap, const char *argv0, char *const **envp) {
+ size_t i = 0;
+ size_t alloc_size = 256;
+
+ char **argv = malloc((sizeof *argv) * alloc_size);
+
+ if (!argv) {
+ pseudo_debug(PDBGF_CLIENT, "execl failed: couldn't allocate memory for %lu arguments\n",
+ (unsigned long) alloc_size);
+ return NULL;
+ }
+ argv[i++] = (char *) argv0;
+
+ while (argv[i-1]) {
+ argv[i++] = va_arg(ap, char *const);
+ if (i > alloc_size - 1) {
+ alloc_size = alloc_size + 256;
+ argv = realloc(argv, (sizeof *argv) * alloc_size);
+ if (!argv) {
+ pseudo_debug(PDBGF_CLIENT, "execl failed: couldn't allocate memory for %lu arguments\n",
+ (unsigned long) alloc_size);
+ return NULL;
+ }
+ }
+ }
+ if (envp) {
+ *envp = va_arg(ap, char **);
+ }
+ return argv;
+}
+
+/* The following wrappers require Special Handling */
+
+int
+execl(const char *file, const char *arg, ...) {
+ sigset_t saved;
+ va_list ap;
+ char **argv;
+
+ int rc = -1;
+
+ PROFILE_START;
+
+ if (!pseudo_check_wrappers()) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("execl");
+ PROFILE_DONE;
+ return rc;
+ }
+
+ va_start(ap, arg);
+ argv = execl_to_v(ap, arg, 0);
+ va_end(ap);
+ if (!argv) {
+ errno = ENOMEM;
+ PROFILE_DONE;
+ return -1;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: execl\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ PROFILE_DONE;
+ return -1;
+ }
+
+ int save_errno;
+
+ /* exec*() use this to restore the sig mask */
+ pseudo_saved_sigmask = saved;
+ rc = wrap_execv(file, argv);
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: execl\n");
+ errno = save_errno;
+ free(argv);
+ PROFILE_DONE;
+ return rc;
+}
+
+int
+execlp(const char *file, const char *arg, ...) {
+ sigset_t saved;
+ va_list ap;
+ char **argv;
+
+ int rc = -1;
+ PROFILE_START;
+
+ if (!pseudo_check_wrappers()) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("execlp");
+ PROFILE_DONE;
+ return rc;
+ }
+
+ va_start(ap, arg);
+ argv = execl_to_v(ap, arg, 0);
+ va_end(ap);
+ if (!argv) {
+ errno = ENOMEM;
+ PROFILE_DONE;
+ return -1;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: execlp\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ PROFILE_DONE;
+ return -1;
+ }
+
+ int save_errno;
+
+ /* exec*() use this to restore the sig mask */
+ pseudo_saved_sigmask = saved;
+ rc = wrap_execvp(file, argv);
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: execlp\n");
+ errno = save_errno;
+ free(argv);
+ PROFILE_DONE;
+ return rc;
+}
+
+int
+execle(const char *file, const char *arg, ...) {
+ sigset_t saved;
+ va_list ap;
+ char **argv;
+ char **envp;
+
+ int rc = -1;
+ PROFILE_START;
+
+ if (!pseudo_check_wrappers()) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("execle");
+ PROFILE_DONE;
+ return rc;
+ }
+
+ va_start(ap, arg);
+ argv = execl_to_v(ap, arg, (char *const **)&envp);
+ va_end(ap);
+ if (!argv) {
+ errno = ENOMEM;
+ PROFILE_DONE;
+ return -1;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: execle\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ PROFILE_DONE;
+ return -1;
+ }
+
+ int save_errno;
+
+ /* exec*() use this to restore the sig mask */
+ pseudo_saved_sigmask = saved;
+ rc = wrap_execve(file, argv, envp);
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: execle\n");
+ errno = save_errno;
+ free(argv);
+ PROFILE_DONE;
+ return rc;
+}
+
+int
+execv(const char *file, char *const *argv) {
+ sigset_t saved;
+
+ int rc = -1;
+
+ PROFILE_START;
+
+ if (!pseudo_check_wrappers() || !real_execv) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("execv");
+ PROFILE_DONE;
+ return rc;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: execv\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ PROFILE_DONE;
+ return -1;
+ }
+
+ int save_errno;
+
+ /* exec*() use this to restore the sig mask */
+ pseudo_saved_sigmask = saved;
+ rc = wrap_execv(file, argv);
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: execv\n");
+ errno = save_errno;
+ PROFILE_DONE;
+ return rc;
+}
+
+int
+execve(const char *file, char *const *argv, char *const *envp) {
+ sigset_t saved;
+
+ int rc = -1;
+ PROFILE_START;
+
+ if (!pseudo_check_wrappers() || !real_execve) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("execve");
+ PROFILE_DONE;
+ return rc;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: execve\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ PROFILE_DONE;
+ return -1;
+ }
+
+ int save_errno;
+
+ /* exec*() use this to restore the sig mask */
+ pseudo_saved_sigmask = saved;
+ rc = wrap_execve(file, argv, envp);
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: execve\n");
+ errno = save_errno;
+ PROFILE_DONE;
+ return rc;
+}
+
+int
+execvp(const char *file, char *const *argv) {
+ sigset_t saved;
+
+ int rc = -1;
+ PROFILE_START;
+
+ if (!pseudo_check_wrappers() || !real_execvp) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("execvp");
+ PROFILE_DONE;
+ return rc;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: execvp\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ PROFILE_DONE;
+ return -1;
+ }
+
+ int save_errno;
+
+ /* exec*() use this to restore the sig mask */
+ pseudo_saved_sigmask = saved;
+ rc = wrap_execvp(file, argv);
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: execvp\n");
+ errno = save_errno;
+ PROFILE_DONE;
+ return rc;
+}
+
+/* no profiling in fork because it wouldn't work anyway
+ * half the time
+ */
+int
+fork(void) {
+ sigset_t saved;
+
+ int rc = -1;
+
+ if (!pseudo_check_wrappers() || !real_fork) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("fork");
+ return rc;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: fork\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ return -1;
+ }
+
+ int save_errno;
+
+ rc = wrap_fork();
+
+ save_errno = errno;
+
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: fork\n");
+ errno = save_errno;
+ return rc;
+}
+
+int
+vfork(void) {
+ /* we don't provide support for the distinct semantics
+ * of vfork()
+ */
+ return fork();
+}
+
+static int
+wrap_execv(const char *file, char *const *argv) {
+ int rc = -1;
+
+#include "guts/execv.c"
+
+ return rc;
+}
+
+static int
+wrap_execve(const char *file, char *const *argv, char *const *envp) {
+ int rc = -1;
+
+#include "guts/execve.c"
+
+ return rc;
+}
+
+static int
+wrap_execvp(const char *file, char *const *argv) {
+ int rc = -1;
+
+#include "guts/execvp.c"
+
+ return rc;
+}
+
+static int
+wrap_fork(void) {
+ int rc = -1;
+
+#include "guts/fork.c"
+
+ return rc;
+}
+
diff --git a/ports/common/subports b/ports/common/subports
new file mode 100755
index 0000000..e2aac56
--- /dev/null
+++ b/ports/common/subports
@@ -0,0 +1,8 @@
+#!/bin/sh
+case $(uname -s) in
+Linux) echo "linux";;
+Darwin) echo "darwin";;
+*) echo >&2 "Unknown result from uname -s: %(uname -s). Aborting."
+ exit 1
+ ;;
+esac
diff --git a/ports/common/wrapfuncs.in b/ports/common/wrapfuncs.in
new file mode 100644
index 0000000..17440f9
--- /dev/null
+++ b/ports/common/wrapfuncs.in
@@ -0,0 +1,7 @@
+int execlp(const char *file, const char *arg, ...); /* hand_wrapped=1 */
+int execl(const char *file, const char *arg, ...); /* hand_wrapped=1 */
+int execle(const char *file, const char *arg, ...); /* hand_wrapped=1 */
+int execv(const char *file, char *const *argv); /* hand_wrapped=1 */
+int execve(const char *file, char *const *argv, char *const *envp); /* hand_wrapped=1 */
+int execvp(const char *file, char *const *argv); /* hand_wrapped=1 */
+int fork(void); /* hand_wrapped=1 */
diff --git a/ports/darwin/guts/COPYRIGHT b/ports/darwin/guts/COPYRIGHT
new file mode 100644
index 0000000..c96e1b1
--- /dev/null
+++ b/ports/darwin/guts/COPYRIGHT
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
diff --git a/ports/darwin/guts/fcntl.c b/ports/darwin/guts/fcntl.c
new file mode 100644
index 0000000..c0b142b
--- /dev/null
+++ b/ports/darwin/guts/fcntl.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2011, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fcntl(int fd, int cmd, ... { struct flock *lock })
+ * int rc = -1;
+ */
+ int save_errno;
+ long long flag = 0;
+
+ va_start(ap, cmd);
+ flag = va_arg(ap, long long);
+ va_end(ap);
+ rc = real_fcntl(fd, cmd, flag);
+
+ switch (cmd) {
+ case F_DUPFD:
+#ifdef F_DUPFD_CLOEXEC
+ /* it doesn't exist now, but if I take this out they'll add it
+ * just to mess with me.
+ */
+ case F_DUPFD_CLOEXEC:
+#endif
+ /* actually do something */
+ save_errno = errno;
+ if (rc != -1) {
+ pseudo_debug(PDBGF_OP, "fcntl_dup: %d->%d\n", fd, rc);
+ pseudo_client_op(OP_DUP, 0, fd, rc, 0, 0);
+ }
+ errno = save_errno;
+ break;
+ default:
+ /* nothing to do, we hope */
+ break;
+ }
+
+ save_errno = errno;
+ pseudo_debug(PDBGF_OP, "fcntl(fd %d, cmd %d, %llx) => %d (%s)\n",
+ fd, cmd, flag, rc, strerror(errno));
+ errno = save_errno;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/fgetgrent_r.c b/ports/darwin/guts/fgetgrent_r.c
new file mode 100644
index 0000000..e760cdd
--- /dev/null
+++ b/ports/darwin/guts/fgetgrent_r.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fgetgrent_r(FILE *fp, struct group*gbuf, char *buf, size_t buflen, struct group **gbufp)
+ * int rc = -1;
+ */
+
+ rc = real_fgetgrent_r(fp, gbuf, buf, buflen, gbufp);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/fgetpwent_r.c b/ports/darwin/guts/fgetpwent_r.c
new file mode 100644
index 0000000..cfea5b8
--- /dev/null
+++ b/ports/darwin/guts/fgetpwent_r.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fgetpwent_r(FILE *fp, struct passwd *pbuf, char *buf, size_t buflen, struct passwd **pbufp)
+ * int rc = -1;
+ */
+
+ rc = real_fgetpwent_r(fp, pbuf, buf, buflen, pbufp);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/fgetxattr.c b/ports/darwin/guts/fgetxattr.c
new file mode 100644
index 0000000..dbb3681
--- /dev/null
+++ b/ports/darwin/guts/fgetxattr.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size, u_int32_t position, int options)
+ * ssize_t rc = -1;
+ */
+
+ rc = shared_getxattr(NULL, filedes, name, value, size, position, options);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/flistxattr.c b/ports/darwin/guts/flistxattr.c
new file mode 100644
index 0000000..bfaa4e9
--- /dev/null
+++ b/ports/darwin/guts/flistxattr.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t flistxattr(int filedes, char *list, size_t size, int options)
+ * ssize_t rc = -1;
+ */
+
+ rc = shared_listxattr(NULL, filedes, list, size, options);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/fremovexattr.c b/ports/darwin/guts/fremovexattr.c
new file mode 100644
index 0000000..4edc38c
--- /dev/null
+++ b/ports/darwin/guts/fremovexattr.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fremovexattr(int filedes, const char *name, int options)
+ * int rc = -1;
+ */
+
+ rc = shared_removexattr(NULL, filedes, name, options);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/fsetxattr.c b/ports/darwin/guts/fsetxattr.c
new file mode 100644
index 0000000..d707595
--- /dev/null
+++ b/ports/darwin/guts/fsetxattr.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fsetxattr(int filedes, const char *name, const void *value, size_t size, u_int32_t position, int options)
+ * int rc = -1;
+ */
+
+ rc = shared_setxattr(NULL, filedes, name, value, size, position, options);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/fstat.c b/ports/darwin/guts/fstat.c
new file mode 100644
index 0000000..7695147
--- /dev/null
+++ b/ports/darwin/guts/fstat.c
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fstat(int fd, struct stat *buf)
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+
+ rc = real_fstat(fd, buf);
+
+ if (rc == -1) {
+ return rc;
+ }
+
+ /* query database
+ * note that symlink canonicalizing is now automatic, so we
+ * don't need to check for a symlink on this end
+ */
+ msg = pseudo_client_op(OP_FSTAT, 0, fd, -1, 0, buf);
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(buf, msg);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/getgrent_r.c b/ports/darwin/guts/getgrent_r.c
new file mode 100644
index 0000000..9d5db5a
--- /dev/null
+++ b/ports/darwin/guts/getgrent_r.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp)
+ * int rc = -1;
+ */
+
+ rc = real_getgrent_r(gbuf, buf, buflen, gbufp);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/getgrouplist.c b/ports/darwin/guts/getgrouplist.c
new file mode 100644
index 0000000..85fccc9
--- /dev/null
+++ b/ports/darwin/guts/getgrouplist.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getgrouplist(const char *name, int basegid, int *groups, int *ngroups) {
+ * int rc = -1;
+ */
+
+ int found = 0;
+ int found_group = 0;
+ char buf[PSEUDO_PWD_MAX];
+ struct group grp, *gbuf = &grp;
+
+ setgrent();
+ while ((rc = wrap_getgrent_r(gbuf, buf, PSEUDO_PWD_MAX, &gbuf)) == 0) {
+ int i = 0;
+ for (i = 0; gbuf->gr_mem[i]; ++i) {
+ if (!strcmp(gbuf->gr_mem[i], name)) {
+ if (found < *ngroups)
+ groups[found] = gbuf->gr_gid;
+ ++found;
+ if ((int) gbuf->gr_gid == basegid)
+ found_group = 1;
+ }
+ }
+ }
+ endgrent();
+ if (!found_group) {
+ if (found < *ngroups)
+ groups[found] = basegid;
+ ++found;
+ }
+ if (found >= *ngroups)
+ rc = -1;
+ else
+ rc = found;
+ *ngroups = found;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/getgroups.c b/ports/darwin/guts/getgroups.c
new file mode 100644
index 0000000..3cbeb76
--- /dev/null
+++ b/ports/darwin/guts/getgroups.c
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getgroups(int size, gid_t *list) {
+ * int rc = -1;
+ */
+ struct passwd *p = wrap_getpwuid(wrap_getuid());
+ int oldsize = size;
+
+ if (p) {
+ rc = wrap_getgrouplist(p->pw_name, wrap_getgid(), (int *) list, &size);
+ if (oldsize == 0 || size <= oldsize)
+ rc = size;
+ } else {
+ errno = ENOENT;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/getpwent_r.c b/ports/darwin/guts/getpwent_r.c
new file mode 100644
index 0000000..3de41b9
--- /dev/null
+++ b/ports/darwin/guts/getpwent_r.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp)
+ * int rc = -1;
+ */
+
+ rc = real_getpwent_r(pwbuf, buf, buflen, pwbufp);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/getxattr.c b/ports/darwin/guts/getxattr.c
new file mode 100644
index 0000000..ecef9cf
--- /dev/null
+++ b/ports/darwin/guts/getxattr.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options)
+ * ssize_t rc = -1;
+ */
+
+ rc = shared_getxattr(path, -1, name, value, size, position, options);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/listxattr.c b/ports/darwin/guts/listxattr.c
new file mode 100644
index 0000000..5a8a7a8
--- /dev/null
+++ b/ports/darwin/guts/listxattr.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t listxattr(const char *path, char *list, size_t size, int options)
+ * ssize_t rc = -1;
+ */
+
+ rc = shared_listxattr(path, -1, list, size, options);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/lstat.c b/ports/darwin/guts/lstat.c
new file mode 100644
index 0000000..01e0f30
--- /dev/null
+++ b/ports/darwin/guts/lstat.c
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int lstat(const char *path, struct stat *buf)
+ * int rc = -1;
+ */
+
+ pseudo_msg_t *msg;
+
+ rc = real_lstat(path, buf);
+ if (rc == -1) {
+ return rc;
+ }
+
+ /* query database
+ * note that symlink canonicalizing is now automatic, so we
+ * don't need to check for a symlink on this end
+ */
+ msg = pseudo_client_op(OP_STAT, 0, -1, -1, path, buf);
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(buf, msg);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/open.c b/ports/darwin/guts/open.c
new file mode 100644
index 0000000..f34b0d3
--- /dev/null
+++ b/ports/darwin/guts/open.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011-2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int open(const char *path, int flags, ... { int mode })
+ * int rc = -1;
+ */
+
+ struct stat buf = { };
+ int existed = 1;
+ int save_errno;
+
+ /* mask out mode bits appropriately */
+ mode = mode & ~pseudo_umask;
+#ifdef PSEUDO_FORCE_ASYNCH
+ flags &= ~O_SYNC;
+#endif
+
+ /* if a creation has been requested, check whether file exists */
+ if (flags & O_CREAT) {
+ save_errno = errno;
+ rc = real_stat(path, &buf);
+ existed = (rc != -1);
+ if (!existed)
+ pseudo_debug(PDBGF_FILE, "open_creat: %s -> 0%o\n", path, mode);
+ errno = save_errno;
+ }
+
+ /* because we are not actually root, secretly mask in 0600 to the
+ * underlying mode. The ", 0" is because the only time mode matters
+ * is if a file is going to be created, in which case it's
+ * not a directory.
+ */
+ rc = real_open(path, flags, PSEUDO_FS_MODE(mode, 0));
+ save_errno = errno;
+
+ if (rc != -1) {
+ int stat_rc;
+ stat_rc = real_stat(path, &buf);
+
+ if (stat_rc != -1) {
+ buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode);
+ if (!existed) {
+ real_fchmod(rc, PSEUDO_FS_MODE(mode, 0));
+ pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, -1, path, &buf);
+ } else {
+ pseudo_debug(PDBGF_CONSISTENCY, "open (fd %d, path %s, flags %d) succeeded, but stat failed (%s).\n",
+ rc, path, flags, strerror(errno));
+ pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, -1, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/removexattr.c b/ports/darwin/guts/removexattr.c
new file mode 100644
index 0000000..c125b1a
--- /dev/null
+++ b/ports/darwin/guts/removexattr.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int removexattr(const char *path, const char *name, int options)
+ * int rc = -1;
+ */
+
+ rc = shared_removexattr(path, -1, name, options);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/scandir.c b/ports/darwin/guts/scandir.c
new file mode 100644
index 0000000..6492b1b
--- /dev/null
+++ b/ports/darwin/guts/scandir.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_scandir(const char *path, struct dirent ***namelist, int (*filter)(struct dirent *), int (*compar)(const void *, const void *)) {
+ * int rc = -1;
+ */
+
+ rc = real_scandir(path, namelist, filter, compar);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/setxattr.c b/ports/darwin/guts/setxattr.c
new file mode 100644
index 0000000..10ffba4
--- /dev/null
+++ b/ports/darwin/guts/setxattr.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int setxattr(const char *path, const char *name, const void *value, size_t size, u_int32_t position, int options)
+ * int rc = -1;
+ */
+
+ rc = shared_setxattr(path, -1, name, value, size, position, options);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/stat.c b/ports/darwin/guts/stat.c
new file mode 100644
index 0000000..8a0742c
--- /dev/null
+++ b/ports/darwin/guts/stat.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int stat(const char *path, struct stat *buf)
+ * int rc = -1;
+ */
+
+ pseudo_msg_t *msg;
+ int save_errno;
+
+ rc = real_stat(path, buf);
+ if (rc == -1) {
+ return rc;
+ }
+ save_errno = errno;
+
+ /* query database
+ * note that symlink canonicalizing is now automatic, so we
+ * don't need to check for a symlink on this end
+ */
+ msg = pseudo_client_op(OP_STAT, 0, -1, AT_FDCWD, path, buf);
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(buf, msg);
+ }
+
+ errno = save_errno;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/guts/sync_file_range.c b/ports/darwin/guts/sync_file_range.c
new file mode 100644
index 0000000..e0a31a4
--- /dev/null
+++ b/ports/darwin/guts/sync_file_range.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int sync_file_range(int fd, off_t offset, off_t nbytes, unsigned int flags)
+ * int rc = -1;
+ */
+
+ rc = real_sync_file_range(fd, offset, nbytes, flags);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/darwin/portdefs.h b/ports/darwin/portdefs.h
new file mode 100644
index 0000000..900d98e
--- /dev/null
+++ b/ports/darwin/portdefs.h
@@ -0,0 +1,13 @@
+#define PRELINK_LIBRARIES "DYLD_INSERT_LIBRARIES"
+#define PRELINK_PATH "DYLD_LIBRARY_PATH"
+#define PSEUDO_STATBUF_64 0
+#define PSEUDO_STATBUF struct stat
+#define PSEUDO_LINKPATH_SEPARATOR ":"
+/* hackery to allow sneaky things to be done with getgrent() */
+extern int pseudo_host_etc_passwd_fd;
+extern int pseudo_host_etc_group_fd;
+extern FILE *pseudo_host_etc_passwd_file;
+extern FILE *pseudo_host_etc_group_file;
+/* Darwin ALWAYS follows symlinks for link(2) */
+#undef PSEUDO_LINK_SYMLINK_BEHAVIOR
+#define PSEUDO_LINK_SYMLINK_BEHAVIOR AT_SYMLINK_FOLLOW
diff --git a/ports/darwin/preports b/ports/darwin/preports
new file mode 100755
index 0000000..a996c69
--- /dev/null
+++ b/ports/darwin/preports
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "unix" "uids_generic"
diff --git a/ports/darwin/pseudo_wrappers.c b/ports/darwin/pseudo_wrappers.c
new file mode 100644
index 0000000..e33533e
--- /dev/null
+++ b/ports/darwin/pseudo_wrappers.c
@@ -0,0 +1,606 @@
+/*
+ * pseudo_wrappers.c, darwin pseudo wrappers
+ *
+ * Copyright (c) 2008-2011 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/* we need XATTR_NOFOLLOW in scope */
+#include <sys/xattr.h>
+
+/* shared functionality for the xattr code */
+/* Each of these functions is expecting to get an optional name, and
+ * a populated statbuf to use for sending messages to the server.
+ */
+
+/* to avoid namespace pollution and such, we now duplicate the
+ * basic functionality of a POSIX ACL list, as used by libacl or
+ * the kernel. Documentation was obtained from the headers of libacl
+ * and from a page or two of _The Linux Programming Interface_, by
+ * Michael Kerrisk.
+ */
+
+typedef struct {
+ uint16_t tag;
+ uint16_t perm;
+ uint32_t id;
+} acl_entry;
+
+typedef struct {
+ uint32_t version;
+ acl_entry entries[];
+} acl_header;
+
+enum acl_tags {
+ ACL_UNDEFINED = 0x0,
+ ACL_USER_OBJ = 0x1,
+ ACL_USER = 0x2,
+ ACL_GROUP_OBJ = 0x4,
+ ACL_GROUP = 0x8,
+ ACL_MASK = 0x10,
+ ACL_OTHER = 0x20,
+};
+
+static const int endian_test = 1;
+static const char *endian_tester = (char *) &endian_test;
+
+static inline int
+le16(int x16) {
+ if (*endian_tester) {
+ return x16;
+ } else {
+ return ((x16 & 0xff) << 8) | ((x16 & 0xff00) >> 8);
+ }
+}
+
+static inline int
+le32(int x32) {
+ if (*endian_tester) {
+ return x32;
+ } else {
+ return ((x32 & 0xff) << 24) | ((x32 & 0xff00) << 8) |
+ ((x32 & 0xff0000) >> 8) | ((x32 & 0xff000000) >> 24);
+ }
+}
+
+/* set mode to match the contents of header. Return non-zero on error.
+ * On a zero return, mode is a valid posix mode, and *extra is set to
+ * 1 if any of the entries are not reflected by that mode. On a non-zero
+ * return, no promises are made about *extra or *mode.
+ */
+static int
+posix_permissions(const acl_header *header, int entries, int *extra, int *mode) {
+ int acl_seen = 0;
+ if (le32(header->version) != 2) {
+ pseudo_diag("Fatal: ACL support no available for header version %d.\n",
+ le32(header->version));
+ return 1;
+ }
+ *mode = 0;
+ *extra = 0;
+ for (int i = 0; i < entries; ++i) {
+ const acl_entry *e = &header->entries[i];
+ int tag = le16(e->tag);
+ int perm = le16(e->perm);
+ acl_seen |= tag;
+ switch (tag) {
+ case ACL_USER_OBJ:
+ *mode = *mode | (perm << 6);
+ break;
+ case ACL_GROUP_OBJ:
+ *mode = *mode | (perm << 3);
+ break;
+ case ACL_OTHER:
+ *mode = *mode | perm;
+ break;
+ case ACL_USER:
+ case ACL_GROUP:
+ case ACL_MASK:
+ *extra = *extra + 1;
+ break;
+ default:
+ pseudo_debug(PDBGF_XATTR, "Unknown tag in ACL: 0x%x.\n",
+ tag);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#define RC_AND_BUF \
+ int rc; \
+ PSEUDO_STATBUF buf; \
+ if (path) { \
+ rc = base_lstat(path, &buf); \
+ } else { \
+ rc = base_fstat(fd, &buf); \
+ } \
+ if (rc == -1) { \
+ return rc; \
+ }
+
+/* Note: no profiling implementation yet. */
+static ssize_t shared_getxattr(const char *path, int fd, const char *name, void *value, size_t size, u_int32_t position, int options) {
+ RC_AND_BUF
+
+ if (!strncmp(name, "com.apple.", 10)) {
+ if (fd != -1) {
+ return real_fgetxattr(fd, name, value, size, position, options);
+ } else {
+ return real_getxattr(path, name, value, size, position, options);
+ }
+ }
+
+ pseudo_debug(PDBGF_XATTR, "getxattr(%s [fd %d], %s)\n",
+ path ? path : "<no path>", fd, name);
+ pseudo_msg_t *result = pseudo_client_op(OP_GET_XATTR, 0, fd, -1, path, &buf, name);
+ if (result->result != RESULT_SUCCEED) {
+ errno = ENOATTR;
+ return -1;
+ }
+
+ if (value) {
+ pseudo_debug(PDBGF_XATTR, "returned attributes: '%s' (%d bytes)\n",
+ result->path, result->pathlen);
+ if (size >= result->pathlen) {
+ memcpy(value, result->path, result->pathlen);
+ } else {
+ memcpy(value, result->path, size);
+ errno = ERANGE;
+ }
+ }
+ return result->pathlen;
+}
+
+static int shared_setxattr(const char *path, int fd, const char *name, const void *value, size_t size, u_int32_t position, int options) {
+ RC_AND_BUF
+ pseudo_op_t op;
+
+ pseudo_debug(PDBGF_XATTR, "setxattr(%s [fd %d], %s => '%.*s')\n",
+ path ? path : "<no path>", fd, name, (int) size, (char *) value);
+
+ if (!strncmp(name, "com.apple.", 10)) {
+ if (fd != -1) {
+ return real_fsetxattr(fd, name, value, size, position, options);
+ } else {
+ return real_setxattr(path, name, value, size, position, options);
+ }
+ }
+
+ /* this may be a plain chmod */
+ if (!strcmp(name, "system.posix_acl_access")) {
+ int extra;
+ int mode;
+ int entries = (size - sizeof(acl_header)) / sizeof(acl_entry);
+ if (!posix_permissions(value, entries, &extra, &mode)) {
+ pseudo_debug(PDBGF_XATTR, "posix_acl_access translated to mode %04o. Remaining attribute(s): %d.\n",
+ mode, extra);
+ buf.st_mode = mode;
+ /* we want to actually issue a corresponding chmod,
+ * as well, or else the file ends up 0600 on the
+ * host. Using the slightly-less-efficient wrap_chmod
+ * avoids possible misalignment.
+ */
+ if (path) {
+ wrap_chmod(path, mode);
+ } else {
+ wrap_fchmod(fd, mode);
+ }
+ /* we are sneaky, and do not actually record this using
+ * extended attributes. */
+ if (!extra) {
+ return 0;
+ }
+ }
+ }
+
+ if (options & XATTR_CREATE) {
+ op = OP_CREATE_XATTR;
+ } else if (options & XATTR_REPLACE) {
+ op = OP_REPLACE_XATTR;
+ } else {
+ op = OP_SET_XATTR;
+ }
+
+ pseudo_msg_t *result = pseudo_client_op(op, 0, fd, -1, path, &buf, name, value, size);
+
+ /* we automatically assume success */
+ if (op == OP_SET_XATTR) {
+ return 0;
+ }
+
+ /* CREATE/REPLACE operations can report failure */
+ if (!result || result->result == RESULT_FAIL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static ssize_t shared_listxattr(const char *path, int fd, char *list, size_t size, int options) {
+ RC_AND_BUF
+ char extra_name_buf[4096];
+ ssize_t real_attr_len;
+ ssize_t used = 0;
+ if (fd != -1) {
+ real_attr_len = real_flistxattr(fd, extra_name_buf, sizeof(extra_name_buf), options);
+ } else {
+ real_attr_len = real_listxattr(path, extra_name_buf, sizeof(extra_name_buf), options);
+ }
+ pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of FS xattr names, starting '%.*s'\n",
+ (int) real_attr_len, (int) real_attr_len, extra_name_buf);
+
+ /* we don't care why there aren't any */
+ if (real_attr_len < 1) {
+ real_attr_len = 0;
+ }
+
+ pseudo_msg_t *result = pseudo_client_op(OP_LIST_XATTR, 0, fd, -1, path, &buf);
+
+ if (result->result != RESULT_SUCCEED && real_attr_len < 1) {
+ pseudo_debug(PDBGF_XATTR, "listxattr: no success.\n");
+ errno = ENOATTR;
+ return -1;
+ }
+
+ if (list) {
+ pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of names, starting '%.*s'\n",
+ (int) result->pathlen, (int) result->pathlen, result->path);
+ if (size >= result->pathlen) {
+ memcpy(list, result->path, result->pathlen);
+ used = result->pathlen;
+ } else {
+ memcpy(list, result->path, size);
+ used = size;
+ errno = ERANGE;
+ }
+ if (real_attr_len > 0) {
+ if ((ssize_t) size >= used + real_attr_len) {
+ memcpy(list + used, extra_name_buf, real_attr_len);
+ used += real_attr_len;
+ } else {
+ memcpy(list + used, extra_name_buf, size - used);
+ used = size;
+ errno = ERANGE;
+ }
+
+ }
+ } else {
+ used = real_attr_len + result->pathlen;
+ }
+ return used;
+}
+
+static int shared_removexattr(const char *path, int fd, const char *name, int options) {
+ RC_AND_BUF
+
+ if (!strncmp(name, "com.apple.", 10)) {
+ if (fd != -1) {
+ return real_fremovexattr(fd, name, options);
+ } else {
+ return real_removexattr(path, name, options);
+ }
+ }
+
+ pseudo_msg_t *result = pseudo_client_op(OP_REMOVE_XATTR, 0, fd, -1, path, &buf, name);
+
+ if (result->result != RESULT_SUCCEED) {
+ /* docs say ENOATTR, but I don't have one */
+ errno = ENOENT;
+ return -1;
+ }
+ return 0;
+}
+
+
+/* there's no fgetgrent_r or fgetpwent_r in Darwin */
+
+#define PLENTY_LONG 2048
+/* the original uid/gid code for Linux was written in terms of the
+ * fget*ent_r() functions... which Darwin doesn't have. But wait! They're
+ * actually pretty easy to implement.
+ */
+int
+pseudo_fgetgrent_r(FILE *fp, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) {
+ char linebuf[PLENTY_LONG] = { 0 };
+ char *s, *t, *u;
+ size_t max_members;
+ char **members;
+ size_t member = 0;
+ long started_at = -1;
+ gid_t gid;
+ int error = ENOENT;
+ size_t len;
+
+ /* any early exit should set *gbufp to NULL */
+ if (gbufp)
+ *gbufp = NULL;
+
+ if (!gbuf || !fp || !buf)
+ goto error_out;
+
+ if (fp == pseudo_host_etc_group_file) {
+ struct group *g;
+ pseudo_antimagic();
+ g = getgrent();
+ pseudo_magic();
+ if (g) {
+ char *s = linebuf;
+ s += snprintf(linebuf, PLENTY_LONG,
+ "%s:%s:%ld:",
+ g->gr_name,
+ g->gr_passwd,
+ (long) g->gr_gid);
+ if (g->gr_mem) {
+ int i;
+ for (i = 0; g->gr_mem[i]; ++i) {
+ s += snprintf(s,
+ PLENTY_LONG - (s - linebuf),
+ "%s,",
+ g->gr_mem[i]);
+ }
+ if (s[-1] == ',')
+ --s;
+ }
+ strcpy(s, "\n");
+ } else {
+ goto error_out;
+ }
+ } else {
+ started_at = ftell(fp);
+ if (started_at == -1) {
+ goto error_out;
+ }
+ s = fgets(linebuf, PLENTY_LONG, fp);
+ if (!s) {
+ goto error_out;
+ }
+ }
+ /* fgets will have stored a '\0' if there was no error; if there
+ * was an error, though, linebuf was initialized to all zeroes so
+ * the string is null-terminated anyway...
+ */
+ len = strlen(linebuf);
+ if (len > buflen) {
+ error = ERANGE;
+ goto error_out;
+ }
+ memcpy(buf, linebuf, len);
+ /* round up to 8, hope for the best? */
+ len = len + 8 + (((unsigned long long) (buf + len)) % 8);
+ members = (char **) (buf + len);
+ if (len >= buflen) {
+ error = ERANGE;
+ goto error_out;
+ }
+ /* this is how many pointers we have room for... */
+ max_members = (buflen - len) / sizeof(*members);
+
+ t = buf;
+ /* yes, I can assume that Darwin has strsep() */
+ s = strsep(&t, ":");
+ if (!s) {
+ goto error_out;
+ }
+ gbuf->gr_name = s;
+ s = strsep(&t, ":");
+ if (!s) {
+ goto error_out;
+ }
+ gbuf->gr_passwd = s;
+ s = strsep(&t, ":");
+ if (!s) {
+ goto error_out;
+ }
+ gid = (gid_t) strtol(s, &u, 10);
+ /* should be a null byte, otherwise we didn't get a valid number */
+ if (*u)
+ goto error_out;
+ gbuf->gr_gid = gid;
+
+ /* now, s points to a comma-separated list of members, which we
+ * want to stash pointers to in 'members'.
+ */
+ s = strsep(&t, ":");
+ t = s;
+ while ((s = strsep(&t, ",")) != NULL) {
+ if (*s) {
+ if (member + 1 > max_members) {
+ errno = ERANGE;
+ goto error_out;
+ }
+ members[member++] = s;
+ }
+ }
+ if (member + 1 > max_members) {
+ errno = ERANGE;
+ goto error_out;
+ }
+ members[member++] = NULL;
+ *gbufp = gbuf;
+ return 0;
+
+error_out:
+ if (started_at != -1)
+ fseek(fp, started_at, SEEK_SET);
+ return error;
+ return -1;
+}
+
+int
+pseudo_fgetpwent_r(FILE *fp, struct passwd *pbuf, char *buf, size_t buflen, struct passwd **pbufp) {
+ char linebuf[PLENTY_LONG] = { 0 };
+ char *s, *t, *u;
+ long started_at = -1;
+ __darwin_time_t timestamp;
+ uid_t uid;
+ gid_t gid;
+ int error = ENOENT;
+ size_t len;
+
+ /* any early exit should set *gbufp to NULL */
+ if (pbufp)
+ *pbufp = NULL;
+
+ if (!pbuf || !fp || !buf)
+ goto error_out;
+
+ if (fp == pseudo_host_etc_passwd_file) {
+ struct passwd *p;
+
+ pseudo_antimagic();
+ p = getpwent();
+ pseudo_magic();
+ if (p) {
+ snprintf(linebuf, PLENTY_LONG,
+ "%s:%s:%ld:%ld:%s:%ld:%ld:%s:%s:%s\n",
+ p->pw_name,
+ p->pw_passwd,
+ (long) p->pw_uid,
+ (long) p->pw_gid,
+ p->pw_class,
+ (long) p->pw_change,
+ (long) p->pw_expire,
+ p->pw_gecos,
+ p->pw_dir,
+ p->pw_shell);
+ } else {
+ goto error_out;
+ }
+ } else {
+ started_at = ftell(fp);
+ if (started_at == -1) {
+ goto error_out;
+ }
+ s = fgets(linebuf, PLENTY_LONG, fp);
+ if (!s) {
+ goto error_out;
+ }
+ }
+ /* fgets will have stored a '\0' if there was no error; if there
+ * was an error, though, linebuf was initialized to all zeroes so
+ * the string is null-terminated anyway...
+ */
+ len = strlen(linebuf);
+ if (len > buflen) {
+ error = ERANGE;
+ goto error_out;
+ }
+ if (linebuf[len - 1] == '\n') {
+ linebuf[len - 1] = '\0';
+ --len;
+ }
+ memcpy(buf, linebuf, len);
+
+ t = buf;
+ /* yes, I can assume that Darwin has strsep() */
+ s = strsep(&t, ":");
+ if (!s) {
+ goto error_out;
+ }
+ pbuf->pw_name = s;
+
+ s = strsep(&t, ":");
+ if (!s)
+ goto error_out;
+ pbuf->pw_passwd = s;
+
+ s = strsep(&t, ":");
+ if (!s)
+ goto error_out;
+ uid = (uid_t) strtol(s, &u, 10);
+ /* should be a null byte, otherwise we didn't get a valid number */
+ if (*u)
+ goto error_out;
+ pbuf->pw_uid = uid;
+
+ s = strsep(&t, ":");
+ if (!s)
+ goto error_out;
+ gid = (gid_t) strtol(s, &u, 10);
+ /* should be a null byte, otherwise we didn't get a valid number */
+ if (*u)
+ goto error_out;
+ pbuf->pw_gid = gid;
+
+ s = strsep(&t, ":");
+ if (!s)
+ goto error_out;
+ pbuf->pw_class = s;
+
+ s = strsep(&t, ":");
+ if (!s)
+ goto error_out;
+ timestamp = (__darwin_time_t) strtol(s, &u, 10);
+ /* should be a null byte, otherwise we didn't get a valid number */
+ if (*u)
+ goto error_out;
+ pbuf->pw_change = timestamp;
+
+ timestamp = (__darwin_time_t) strtol(s, &u, 10);
+ /* should be a null byte, otherwise we didn't get a valid number */
+ if (*u)
+ goto error_out;
+ pbuf->pw_expire = timestamp;
+
+ s = strsep(&t, ":");
+ if (!s)
+ goto error_out;
+ pbuf->pw_gecos = s;
+
+ s = strsep(&t, ":");
+ if (!s)
+ goto error_out;
+ pbuf->pw_dir = s;
+
+ s = strsep(&t, ":");
+ if (!s)
+ goto error_out;
+ pbuf->pw_shell = s;
+
+ *pbufp = pbuf;
+ return 0;
+
+error_out:
+ if (started_at != -1)
+ fseek(fp, started_at, SEEK_SET);
+ return error;
+ return -1;
+}
+
+int
+pseudo_getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) {
+ /* note that we don't wrap fgetpwent_r, since there's no path
+ * references in it.
+ */
+ if (!pseudo_pwd) {
+ errno = ENOENT;
+ return -1;
+ }
+ return pseudo_fgetpwent_r(pseudo_pwd, pwbuf, buf, buflen, pwbufp);
+}
+
+int
+pseudo_getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) {
+ /* note that we don't wrap fgetgrent_r, since there's no path
+ * references in it.
+ */
+ if (!pseudo_grp) {
+ errno = ENOENT;
+ return -1;
+ }
+ return pseudo_fgetgrent_r(pseudo_grp, gbuf, buf, buflen, gbufp);
+}
+
diff --git a/ports/darwin/subports b/ports/darwin/subports
new file mode 100755
index 0000000..57bdd6d
--- /dev/null
+++ b/ports/darwin/subports
@@ -0,0 +1,3 @@
+#!/bin/sh
+# no subports at this time
+exit 0
diff --git a/ports/darwin/wrapfuncs.in b/ports/darwin/wrapfuncs.in
new file mode 100644
index 0000000..306ad66
--- /dev/null
+++ b/ports/darwin/wrapfuncs.in
@@ -0,0 +1,27 @@
+# On Darwin, mode_t promotes to int, so you have to use int for va_arg
+int open(const char *path, int flags, ...{int mode}); /* flags=0 */
+int stat(const char *path, struct stat *buf); /* inode64=1 */
+int lstat(const char *path, struct stat *buf); /* flags=AT_SYMLINK_NOFOLLOW, inode64=1 */
+int fstat(int fd, struct stat *buf); /* inode64=1 */
+int fcntl(int fd, int cmd, ...{struct flock *lock});
+# just so we know the inums of symlinks
+# for emulation of passwd utilities
+int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups);
+# local color UIDs
+int getgrouplist(const char *name, int basegid, int *groups, int *ngroups);
+int scandir(const char *path, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)());
+int getgroups(int size, gid_t *list);
+int fgetgrent_r(FILE *fp, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp); /* real_func=pseudo_fgetgrent_r */
+int fgetpwent_r(FILE *fp, struct passwd *pbuf, char *buf, size_t buflen, struct passwd **pbufp); /* real_func=pseudo_fgetpwent_r */
+int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp); /* real_func=pseudo_getpwent_r */
+int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp); /* real_func=pseudo_getgrent_r */
+int sync_file_range(int fd, off_t offset, off_t nbytes, unsigned int flags); /* async_skip=0 */
+
+ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options); /* flags=(options&XATTR_NOFOLLOW?AT_SYMLINK_NOFOLLOW:0) */
+ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size, u_int32_t position, int options);
+int setxattr(const char *path, const char *name, const void *value, size_t size, u_int32_t position, int options); /* flags=0 */
+int fsetxattr(int filedes, const char *name, const void *value, size_t size, u_int32_t position, int options);
+ssize_t listxattr(const char *path, char *list, size_t size, int options); /* flags=0 */
+ssize_t flistxattr(int filedes, char *list, size_t size, int options);
+int removexattr(const char *path, const char *name, int options); /* flags=0 */
+int fremovexattr(int filedes, const char *name, int options);
diff --git a/ports/linux/guts/COPYRIGHT b/ports/linux/guts/COPYRIGHT
new file mode 100644
index 0000000..c96e1b1
--- /dev/null
+++ b/ports/linux/guts/COPYRIGHT
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
diff --git a/ports/linux/guts/__fxstat.c b/ports/linux/guts/__fxstat.c
new file mode 100644
index 0000000..db9716b
--- /dev/null
+++ b/ports/linux/guts/__fxstat.c
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int
+ * wrap___fxstat(int ver, int fd, struct stat *buf) {
+ * int rc = -1;
+ */
+
+ struct stat64 buf64;
+ /* populate buffer with complete data */
+ real___fxstat(ver, fd, buf);
+ /* obtain fake data */
+ rc = wrap___fxstat64(ver, fd, &buf64);
+ /* overwrite */
+ pseudo_stat32_from64(buf, &buf64);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__fxstat64.c b/ports/linux/guts/__fxstat64.c
new file mode 100644
index 0000000..8601904
--- /dev/null
+++ b/ports/linux/guts/__fxstat64.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int
+ * wrap___fxstat64(int ver, int fd, struct stat64 *buf) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ int save_errno;
+
+ rc = real___fxstat64(ver, fd, buf);
+ save_errno = errno;
+ if (rc == -1) {
+ return rc;
+ }
+ if (ver != _STAT_VER) {
+ pseudo_debug(PDBGF_CLIENT, "version mismatch: got stat version %d, only supporting %d\n", ver, _STAT_VER);
+ errno = save_errno;
+ return rc;
+ }
+ msg = pseudo_client_op(OP_FSTAT, 0, fd, -1, 0, buf);
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(buf, msg);
+ }
+
+ errno = save_errno;
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__fxstatat.c b/ports/linux/guts/__fxstatat.c
new file mode 100644
index 0000000..94c5ff6
--- /dev/null
+++ b/ports/linux/guts/__fxstatat.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags) {
+ * int rc = -1;
+ */
+
+ struct stat64 buf64;
+ /* populate buffer with complete data */
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (flags & AT_SYMLINK_NOFOLLOW) {
+ rc = real___lxstat(ver, path, buf);
+ } else {
+ rc = real___xstat(ver, path, buf);
+ }
+#else
+ real___fxstatat(ver, dirfd, path, buf, flags);
+#endif
+ /* obtain fake data */
+ rc = wrap___fxstatat64(ver, dirfd, path, &buf64, flags);
+ /* overwrite */
+ pseudo_stat32_from64(buf, &buf64);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__fxstatat64.c b/ports/linux/guts/__fxstatat64.c
new file mode 100644
index 0000000..62fc3f1
--- /dev/null
+++ b/ports/linux/guts/__fxstatat64.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ int save_errno;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+#endif
+ if (flags & AT_SYMLINK_NOFOLLOW) {
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real___lxstat64(ver, path, buf);
+#else
+ rc = real___fxstatat64(ver, dirfd, path, buf, flags);
+#endif
+ if (rc == -1) {
+ return rc;
+ }
+ } else {
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real___xstat64(ver, path, buf);
+#else
+ rc = real___fxstatat64(ver, dirfd, path, buf, flags);
+#endif
+ if (rc == -1) {
+ return rc;
+ }
+ }
+ save_errno = errno;
+
+ if (ver != _STAT_VER) {
+ pseudo_debug(PDBGF_CLIENT, "version mismatch: got stat version %d, only supporting %d\n", ver, _STAT_VER);
+ errno = save_errno;
+ return rc;
+ }
+
+ /* query database
+ * note that symlink canonicalizing is now automatic, so we
+ * don't need to check for a symlink on this end
+ */
+ msg = pseudo_client_op(OP_STAT, 0, -1, dirfd, path, buf);
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(buf, msg);
+ }
+
+ errno = save_errno;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__lxstat.c b/ports/linux/guts/__lxstat.c
new file mode 100644
index 0000000..32b0301
--- /dev/null
+++ b/ports/linux/guts/__lxstat.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___lxstat(int ver, const char *path, struct stat *buf) {
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstatat(ver, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);
+
+/*
+ * }
+ */
diff --git a/ports/linux/guts/__lxstat64.c b/ports/linux/guts/__lxstat64.c
new file mode 100644
index 0000000..ac1f782
--- /dev/null
+++ b/ports/linux/guts/__lxstat64.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___lxstat64(int ver, const char *path, struct stat64 *buf) {
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstatat64(ver, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);
+
+/*
+ * }
+ */
diff --git a/ports/linux/guts/__openat64_2.c b/ports/linux/guts/__openat64_2.c
new file mode 100644
index 0000000..e970df7
--- /dev/null
+++ b/ports/linux/guts/__openat64_2.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010,2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___openat64_2(int dirfd, const char *path, int flags) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(dirfd, path, flags | O_LARGEFILE, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__openat_2.c b/ports/linux/guts/__openat_2.c
new file mode 100644
index 0000000..33ed620
--- /dev/null
+++ b/ports/linux/guts/__openat_2.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___openat_2(int dirfd, const char *path, int flags) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(dirfd, path, flags, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__xmknod.c b/ports/linux/guts/__xmknod.c
new file mode 100644
index 0000000..fa31b66
--- /dev/null
+++ b/ports/linux/guts/__xmknod.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___xmknod(int ver, const char *path, mode_t mode, dev_t *dev) {
+ * int rc = -1;
+ */
+
+ rc = wrap___xmknodat(ver, AT_FDCWD, path, mode, dev);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__xmknodat.c b/ports/linux/guts/__xmknodat.c
new file mode 100644
index 0000000..296918a
--- /dev/null
+++ b/ports/linux/guts/__xmknodat.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___xmknodat(int ver, int dirfd, const char *path, mode_t mode, dev_t *dev) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ struct stat64 buf;
+
+ /* mask out mode bits appropriately */
+ mode = mode & ~pseudo_umask;
+
+ /* we don't use underlying call, so _ver is irrelevant to us */
+ (void) ver;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ rc = real___xstat64(_STAT_VER, path, &buf);
+#else
+ rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, AT_SYMLINK_NOFOLLOW);
+#endif
+ if (rc != -1) {
+ /* if we can stat the file, you can't mknod it */
+ errno = EEXIST;
+ return -1;
+ }
+ if (!dev) {
+ errno = EINVAL;
+ return -1;
+ }
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real_open(path, O_CREAT | O_WRONLY | O_EXCL,
+ PSEUDO_FS_MODE(mode, 0));
+#else
+ rc = real_openat(dirfd, path, O_CREAT | O_WRONLY | O_EXCL,
+ PSEUDO_FS_MODE(mode, 0));
+#endif
+ if (rc == -1) {
+ return -1;
+ }
+ real_fchmod(rc, PSEUDO_FS_MODE(mode, 0));
+ real___fxstat64(_STAT_VER, rc, &buf);
+ /* mknod does not really open the file. We don't have
+ * to use wrap_close because we've never exposed this file
+ * descriptor to the client code.
+ */
+ real_close(rc);
+
+ /* mask in the mode type bits again */
+ buf.st_mode = (PSEUDO_DB_MODE(buf.st_mode, mode) & 07777) |
+ (mode & ~07777);
+ buf.st_rdev = *dev;
+ msg = pseudo_client_op(OP_MKNOD, 0, -1, dirfd, path, &buf);
+ if (msg && msg->result != RESULT_SUCCEED) {
+ errno = EPERM;
+ rc = -1;
+ } else {
+ /* just pretend we worked */
+ rc = 0;
+ }
+ if (rc == -1) {
+ int save_errno = errno;
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ real_unlink(path);
+#else
+ real_unlinkat(dirfd, path, AT_SYMLINK_NOFOLLOW);
+#endif
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__xstat.c b/ports/linux/guts/__xstat.c
new file mode 100644
index 0000000..ec10abb
--- /dev/null
+++ b/ports/linux/guts/__xstat.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___xstat(int ver, const char *path, struct stat *buf) {
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstatat(ver, AT_FDCWD, path, buf, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/__xstat64.c b/ports/linux/guts/__xstat64.c
new file mode 100644
index 0000000..ed62e7e
--- /dev/null
+++ b/ports/linux/guts/__xstat64.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap___xstat64(int ver, const char *path, struct stat64 *buf) {
+ * int rc = -1;
+ */
+ rc = wrap___fxstatat64(ver, AT_FDCWD, path, buf, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/canonicalize_file_name.c b/ports/linux/guts/canonicalize_file_name.c
new file mode 100644
index 0000000..9a04f33
--- /dev/null
+++ b/ports/linux/guts/canonicalize_file_name.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_canonicalize_file_name(const char *filename) {
+ * char * rc = NULL;
+ */
+
+ rc = wrap_realpath(filename, NULL);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/creat64.c b/ports/linux/guts/creat64.c
new file mode 100644
index 0000000..2d2fc27
--- /dev/null
+++ b/ports/linux/guts/creat64.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_creat64(const char *path, ...mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(AT_FDCWD, path, O_CREAT|O_WRONLY|O_TRUNC|O_LARGEFILE, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/eaccess.c b/ports/linux/guts/eaccess.c
new file mode 100644
index 0000000..e2119cc
--- /dev/null
+++ b/ports/linux/guts/eaccess.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_eaccess(const char *path, int mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_access(path, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/euidaccess.c b/ports/linux/guts/euidaccess.c
new file mode 100644
index 0000000..85433a8
--- /dev/null
+++ b/ports/linux/guts/euidaccess.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_euidaccess(const char *path, int mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_access(path, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/fcntl.c b/ports/linux/guts/fcntl.c
new file mode 100644
index 0000000..639fd24
--- /dev/null
+++ b/ports/linux/guts/fcntl.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_fcntl(int fd, int cmd, ...struct flock *lock) {
+ * int rc = -1;
+ */
+ long arg;
+ int save_errno;
+
+ /* we don't know whether we need lock or arg; grab both, which
+ * should be safe enough on Linuxy systems. */
+ va_start(ap, cmd);
+ arg = va_arg(ap, long);
+ va_end(ap);
+
+ switch (cmd) {
+ case F_DUPFD:
+#ifdef F_DUPFD_CLOEXEC
+ case F_DUPFD_CLOEXEC:
+#endif
+ /* actually do something */
+ rc = real_fcntl(fd, cmd, arg);
+ save_errno = errno;
+ if (rc != -1) {
+ pseudo_debug(PDBGF_OP, "fcntl_dup: %d->%d\n", fd, rc);
+ pseudo_client_op(OP_DUP, 0, fd, rc, 0, 0);
+ }
+ errno = save_errno;
+ break;
+ /* no argument: */
+ case F_GETFD:
+ case F_GETFL:
+ case F_GETOWN:
+ case F_GETSIG:
+ case F_GETLEASE:
+ rc = real_fcntl(fd, cmd);
+ break;
+ /* long argument */
+ case F_SETFD:
+ case F_SETFL:
+ case F_SETOWN:
+ case F_SETSIG:
+ case F_SETLEASE:
+ case F_NOTIFY:
+ rc = real_fcntl(fd, cmd, arg);
+ break;
+ /* struct flock * argument */
+ case F_GETLK:
+ case F_SETLK:
+ case F_SETLKW:
+ rc = real_fcntl(fd, cmd, lock);
+ break;
+#if defined(F_GETLK64) && (F_GETLK64 != F_GETLK)
+ /* the cast is safe, all struct pointers must smell the same */
+ case F_GETLK64:
+ case F_SETLK64:
+ case F_SETLKW64:
+ rc = real_fcntl(fd, cmd, (struct flock64 *) lock);
+ break;
+#endif
+ default:
+ pseudo_diag("unknown fcntl argument %d, assuming long argument.\n",
+ cmd);
+ rc = real_fcntl(fd, cmd, arg);
+ break;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/fopen64.c b/ports/linux/guts/fopen64.c
new file mode 100644
index 0000000..b243345
--- /dev/null
+++ b/ports/linux/guts/fopen64.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static FILE *
+ * wrap_fopen64(const char *path, const char *mode) {
+ * FILE * rc = 0;
+ */
+ struct stat64 buf;
+ int save_errno;
+
+ int existed = (real___xstat64(_STAT_VER, path, &buf) != -1);
+
+ rc = real_fopen64(path, mode);
+ save_errno = errno;
+
+ if (rc) {
+ int fd = fileno(rc);
+
+ pseudo_debug(PDBGF_FILE, "fopen64 '%s': fd %d <FILE %p>\n", path, fd, (void *) rc);
+ if (real___fxstat64(_STAT_VER, fd, &buf) != -1) {
+ if (!existed) {
+ real_fchmod(fd, PSEUDO_FS_MODE(0666 & ~pseudo_umask, 0));
+ pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, &buf);
+ } else {
+ pseudo_debug(PDBGF_CONSISTENCY, "fopen64 (fd %d) succeeded, but fstat failed (%s).\n",
+ fd, strerror(errno));
+ pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/freopen64.c b/ports/linux/guts/freopen64.c
new file mode 100644
index 0000000..4bad533
--- /dev/null
+++ b/ports/linux/guts/freopen64.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static FILE *
+ * wrap_freopen64(const char *path, const char *mode, FILE *stream) {
+ * FILE * rc = NULL;
+ */
+ struct stat64 buf;
+ int save_errno;
+ int existed = (real___xstat64(_STAT_VER, path, &buf) != -1);
+
+ rc = real_freopen64(path, mode, stream);
+ save_errno = errno;
+
+ if (rc) {
+ int fd = fileno(rc);
+
+ pseudo_debug(PDBGF_FILE, "freopen64 '%s': fd %d\n", path, fd);
+ if (real___fxstat64(_STAT_VER, fd, &buf) != -1) {
+ if (!existed) {
+ real_fchmod(fd, PSEUDO_FS_MODE(0666 & ~pseudo_umask, 0));
+ pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, &buf);
+ } else {
+ pseudo_debug(PDBGF_FILE, "fopen (fd %d) succeeded, but stat failed (%s).\n",
+ fd, strerror(errno));
+ pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/fstat.c b/ports/linux/guts/fstat.c
new file mode 100644
index 0000000..2cf2787
--- /dev/null
+++ b/ports/linux/guts/fstat.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fstat(int fd, struct stat *buf)
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstat(_STAT_VER, fd, buf);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/fstat64.c b/ports/linux/guts/fstat64.c
new file mode 100644
index 0000000..4a759f7
--- /dev/null
+++ b/ports/linux/guts/fstat64.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fstat64(int fd, struct stat *buf)
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstat64(_STAT_VER, fd, buf);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/ftw64.c b/ports/linux/guts/ftw64.c
new file mode 100644
index 0000000..a375fbf
--- /dev/null
+++ b/ports/linux/guts/ftw64.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd) {
+ * int rc = -1;
+ */
+
+ rc = real_ftw64(path, fn, nopenfd);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/get_current_dir_name.c b/ports/linux/guts/get_current_dir_name.c
new file mode 100644
index 0000000..79f82f9
--- /dev/null
+++ b/ports/linux/guts/get_current_dir_name.c
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_get_current_dir_name(void) {
+ * char * rc = NULL;
+ */
+
+ /* this relies on a Linux extension, but we dutifully
+ * emulated that extension.
+ */
+ rc = wrap_getcwd(NULL, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/getgrent_r.c b/ports/linux/guts/getgrent_r.c
new file mode 100644
index 0000000..b04373d
--- /dev/null
+++ b/ports/linux/guts/getgrent_r.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010-2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) {
+ * int rc = -1;
+ */
+
+ /* note that we don't wrap fgetgrent_r, since there's no path
+ * references in it.
+ */
+ if (!pseudo_grp) {
+ errno = ENOENT;
+ return -1;
+ }
+ rc = fgetgrent_r(pseudo_grp, gbuf, buf, buflen, gbufp);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/getgrouplist.c b/ports/linux/guts/getgrouplist.c
new file mode 100644
index 0000000..3489ec9
--- /dev/null
+++ b/ports/linux/guts/getgrouplist.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups) {
+ * int rc = -1;
+ */
+
+ int found = 0;
+ int found_group = 0;
+ char buf[PSEUDO_PWD_MAX];
+ struct group grp, *gbuf = &grp;
+
+ setgrent();
+ while ((rc = wrap_getgrent_r(gbuf, buf, PSEUDO_PWD_MAX, &gbuf)) == 0) {
+ int i = 0;
+ for (i = 0; gbuf->gr_mem[i]; ++i) {
+ if (!strcmp(gbuf->gr_mem[i], user)) {
+ if (found < *ngroups)
+ groups[found] = gbuf->gr_gid;
+ ++found;
+ if (gbuf->gr_gid == group)
+ found_group = 1;
+ }
+ }
+ }
+ endgrent();
+ if (!found_group) {
+ if (found < *ngroups)
+ groups[found] = group;
+ ++found;
+ }
+ if (found >= *ngroups)
+ rc = -1;
+ else
+ rc = found;
+ *ngroups = found;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/getgroups.c b/ports/linux/guts/getgroups.c
new file mode 100644
index 0000000..afb9662
--- /dev/null
+++ b/ports/linux/guts/getgroups.c
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getgroups(int size, gid_t *list) {
+ * int rc = -1;
+ */
+ struct passwd *p = wrap_getpwuid(wrap_getuid());
+ int oldsize = size;
+
+ if (p) {
+ rc = wrap_getgrouplist(p->pw_name, wrap_getgid(), list, &size);
+ if (oldsize == 0 || size <= oldsize)
+ rc = size;
+ } else {
+ errno = ENOENT;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/getpw.c b/ports/linux/guts/getpw.c
new file mode 100644
index 0000000..62b44da
--- /dev/null
+++ b/ports/linux/guts/getpw.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getpw(uid_t uid, char *buf) {
+ * int rc = -1;
+ */
+ static struct passwd pwd;
+ static char pwbuf[PSEUDO_PWD_MAX];
+ struct passwd *pwp;
+
+ pseudo_diag("warning: unsafe getpw() called. hoping buf has at least %d chars.\n",
+ PSEUDO_PWD_MAX);
+ rc = wrap_getpwuid_r(uid, &pwd, pwbuf, PSEUDO_PWD_MAX, &pwp);
+ /* different error return conventions */
+ if (rc != 0) {
+ errno = rc;
+ rc = -1;
+ } else {
+ snprintf(buf, PSEUDO_PWD_MAX, "%s:%s:%d:%d:%s:%s:%s",
+ pwd.pw_name,
+ pwd.pw_passwd,
+ pwd.pw_uid,
+ pwd.pw_gid,
+ pwd.pw_gecos,
+ pwd.pw_dir,
+ pwd.pw_shell);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/getpwent_r.c b/ports/linux/guts/getpwent_r.c
new file mode 100644
index 0000000..4fd9cc0
--- /dev/null
+++ b/ports/linux/guts/getpwent_r.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010-2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) {
+ * int rc = -1;
+ */
+
+ /* note that we don't wrap fgetpwent_r, since there's no path
+ * references in it.
+ */
+ if (!pseudo_pwd) {
+ errno = ENOENT;
+ return -1;
+ }
+ rc = fgetpwent_r(pseudo_pwd, pwbuf, buf, buflen, pwbufp);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/getresgid.c b/ports/linux/guts/getresgid.c
new file mode 100644
index 0000000..13551a4
--- /dev/null
+++ b/ports/linux/guts/getresgid.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) {
+ * int rc = -1;
+ */
+ if (rgid)
+ *rgid = pseudo_rgid;
+ if (egid)
+ *egid = pseudo_egid;
+ if (sgid)
+ *sgid = pseudo_sgid;
+ if (rgid && egid && sgid) {
+ rc = 0;
+ } else {
+ rc = -1;
+ errno = EFAULT;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/getresuid.c b/ports/linux/guts/getresuid.c
new file mode 100644
index 0000000..2e47520
--- /dev/null
+++ b/ports/linux/guts/getresuid.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) {
+ * int rc = -1;
+ */
+ if (ruid)
+ *ruid = pseudo_ruid;
+ if (euid)
+ *euid = pseudo_euid;
+ if (suid)
+ *suid = pseudo_suid;
+ if (ruid && euid && suid) {
+ rc = 0;
+ } else {
+ rc = -1;
+ errno = EFAULT;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/glob64.c b/ports/linux/guts/glob64.c
new file mode 100644
index 0000000..ccac6e4
--- /dev/null
+++ b/ports/linux/guts/glob64.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_glob64(const char *pattern, int flags, int (*errfunc)(const char *, int), glob64_t *pglob) {
+ * int rc = -1;
+ */
+ char *rpattern = NULL;
+ int alloced = 0;
+
+ /* note: no canonicalization */
+ if (pattern && (*pattern == '/') && pseudo_chroot_len) {
+ size_t len = strlen(pattern) + pseudo_chroot_len + 2;
+ rpattern = malloc(len);
+ if (!rpattern) {
+ errno = ENOMEM;
+ return GLOB_NOSPACE;
+ }
+ snprintf(rpattern, len, "%s/%s", pseudo_chroot, pattern);
+ alloced = 1;
+ }
+
+ rc = real_glob64(alloced ? rpattern : pattern, flags, errfunc, pglob);
+
+ free(rpattern);
+
+ if (rc == 0) {
+ unsigned int i;
+ for (i = 0; i < pglob->gl_pathc; ++i) {
+ pseudo_dechroot(pglob->gl_pathv[i], (size_t) -1);
+ }
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/lchown.c b/ports/linux/guts/lchown.c
new file mode 100644
index 0000000..4eb1202
--- /dev/null
+++ b/ports/linux/guts/lchown.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_lchown(const char *path, uid_t owner, gid_t group) {
+ */
+
+ rc = wrap_fchownat(AT_FDCWD, path, owner, group, AT_SYMLINK_NOFOLLOW);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/lckpwdf.c b/ports/linux/guts/lckpwdf.c
new file mode 100644
index 0000000..b452ec0
--- /dev/null
+++ b/ports/linux/guts/lckpwdf.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_lckpwdf(void) {
+ * int rc = -1;
+ */
+ rc = pseudo_pwd_lck_open();
+ if (rc != -1) {
+ struct flock lck = {
+ .l_type = F_RDLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 1
+ };
+ /* I don't really care whether this works. */
+ fcntl(rc, F_SETFD, FD_CLOEXEC);
+ /* Now lock it. */
+ alarm(15); /* magic number from man page */
+ rc = fcntl(rc, F_SETLKW, &lck);
+ alarm(0);
+ if (rc == -1) {
+ int save_errno = errno;
+ pseudo_pwd_lck_close();
+ errno = save_errno;
+ }
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/lstat.c b/ports/linux/guts/lstat.c
new file mode 100644
index 0000000..19c202f
--- /dev/null
+++ b/ports/linux/guts/lstat.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int lstat(const char *path, struct stat *buf)
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstatat(_STAT_VER, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/lstat64.c b/ports/linux/guts/lstat64.c
new file mode 100644
index 0000000..94eb60f
--- /dev/null
+++ b/ports/linux/guts/lstat64.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int lstat64(const char *path, struct stat *buf)
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstatat64(_STAT_VER, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/mkstemp64.c b/ports/linux/guts/mkstemp64.c
new file mode 100644
index 0000000..48be612
--- /dev/null
+++ b/ports/linux/guts/mkstemp64.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_mkstemp64(char *template) {
+ * int rc = -1;
+ */
+ struct stat64 buf;
+ int save_errno;
+ size_t len;
+ char *tmp_template;
+
+ if (!template) {
+ errno = EFAULT;
+ return 0;
+ }
+
+ len = strlen(template);
+ tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW);
+
+ if (!tmp_template) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ rc = real_mkstemp64(tmp_template);
+
+ if (rc != -1) {
+ save_errno = errno;
+
+ if (real___fxstat64(_STAT_VER, rc, &buf) != -1) {
+ real_fchmod(rc, PSEUDO_FS_MODE(0600, 0));
+ pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf);
+ pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, &buf);
+ } else {
+ pseudo_debug(PDBGF_CONSISTENCY, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n",
+ rc, strerror(errno));
+ pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, 0);
+ }
+ errno = save_errno;
+ }
+ /* mkstemp only changes the XXXXXX at the end. */
+ memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6);
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/nftw64.c b/ports/linux/guts/nftw64.c
new file mode 100644
index 0000000..82571cd
--- /dev/null
+++ b/ports/linux/guts/nftw64.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag) {
+ * int rc = -1;
+ */
+
+ rc = real_nftw64(path, fn, nopenfd, flag);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/open.c b/ports/linux/guts/open.c
new file mode 100644
index 0000000..0a0596c
--- /dev/null
+++ b/ports/linux/guts/open.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_open(const char *path, int flags, ...mode_t mode) {
+ * int rc = -1;
+ */
+
+ return wrap_openat(AT_FDCWD, path, flags, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/open64.c b/ports/linux/guts/open64.c
new file mode 100644
index 0000000..8028ede
--- /dev/null
+++ b/ports/linux/guts/open64.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010,2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_open64(const char *path, int flags, ...mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(AT_FDCWD, path, flags | O_LARGEFILE, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/openat.c b/ports/linux/guts/openat.c
new file mode 100644
index 0000000..eb7c0b5
--- /dev/null
+++ b/ports/linux/guts/openat.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2008-2010, 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_openat(int dirfd, const char *path, int flags, ...mode_t mode) {
+ * int rc = -1;
+ */
+ struct stat64 buf;
+ int existed = 1;
+ int save_errno;
+
+ /* mask out mode bits appropriately */
+ mode = mode & ~pseudo_umask;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+#endif
+
+#ifdef PSEUDO_FORCE_ASYNCH
+ /* Yes, I'm aware that every Linux system I've seen has
+ * DSYNC and RSYNC being the same value as SYNC.
+ */
+
+ flags &= ~(O_SYNC
+#ifdef O_DIRECT
+ | O_DIRECT
+#endif
+#ifdef O_DSYNC
+ | O_DSYNC
+#endif
+#ifdef O_RSYNC
+ | O_RSYNC
+#endif
+ );
+#endif
+
+ /* if a creation has been requested, check whether file exists */
+ if (flags & O_CREAT) {
+ save_errno = errno;
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real___xstat64(_STAT_VER, path, &buf);
+#else
+ rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, 0);
+#endif
+ existed = (rc != -1);
+ if (!existed)
+ pseudo_debug(PDBGF_FILE, "openat_creat: %s -> 0%o\n", path, mode);
+ errno = save_errno;
+ }
+
+ /* because we are not actually root, secretly mask in 0600 to the
+ * underlying mode. The ", 0" is because the only time mode matters
+ * is if a file is going to be created, in which case it's
+ * not a directory.
+ */
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real_open(path, flags, PSEUDO_FS_MODE(mode, 0));
+#else
+ rc = real_openat(dirfd, path, flags, PSEUDO_FS_MODE(mode, 0));
+#endif
+ save_errno = errno;
+
+ if (rc != -1) {
+ int stat_rc;
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ stat_rc = real___xstat64(_STAT_VER, path, &buf);
+#else
+ stat_rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, 0);
+#endif
+
+ if (stat_rc != -1) {
+ buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode);
+ if (!existed) {
+ real_fchmod(rc, PSEUDO_FS_MODE(mode, 0));
+ pseudo_client_op(OP_CREAT, 0, -1, dirfd, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, &buf);
+ } else {
+ pseudo_debug(PDBGF_FILE, "openat (fd %d, path %d/%s, flags %d) succeeded, but stat failed (%s).\n",
+ rc, dirfd, path, flags, strerror(errno));
+ pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/openat64.c b/ports/linux/guts/openat64.c
new file mode 100644
index 0000000..8dedcbf
--- /dev/null
+++ b/ports/linux/guts/openat64.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010,2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_openat64(int dirfd, const char *path, int flags, ...mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(dirfd, path, flags | O_LARGEFILE, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/scandir.c b/ports/linux/guts/scandir.c
new file mode 100644
index 0000000..afcebaf
--- /dev/null
+++ b/ports/linux/guts/scandir.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_scandir(const char *path, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const void *, const void *)) {
+ * int rc = -1;
+ */
+
+ rc = real_scandir(path, namelist, filter, compar);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/scandir64.c b/ports/linux/guts/scandir64.c
new file mode 100644
index 0000000..1317b73
--- /dev/null
+++ b/ports/linux/guts/scandir64.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_scandir64(const char *path, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)(const void *, const void *)) {
+ * int rc = -1;
+ */
+
+ rc = real_scandir64(path, namelist, filter, compar);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/setfsgid.c b/ports/linux/guts/setfsgid.c
new file mode 100644
index 0000000..0e5a10b
--- /dev/null
+++ b/ports/linux/guts/setfsgid.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setfsgid(gid_t fsgid) {
+ * int rc = -1;
+ */
+ if (pseudo_euid == 0 ||
+ pseudo_egid == fsgid || pseudo_rgid == fsgid || pseudo_sgid == fsgid) {
+ pseudo_fgid = fsgid;
+ rc = 0;
+ } else {
+ rc = -1;
+ errno = EPERM;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/setfsuid.c b/ports/linux/guts/setfsuid.c
new file mode 100644
index 0000000..e52b65e
--- /dev/null
+++ b/ports/linux/guts/setfsuid.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setfsuid(uid_t fsuid) {
+ * int rc = -1;
+ */
+ if (pseudo_euid == 0 ||
+ pseudo_euid == fsuid || pseudo_ruid == fsuid || pseudo_suid == fsuid) {
+ pseudo_fuid = fsuid;
+ rc = 0;
+ } else {
+ rc = -1;
+ errno = EPERM;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/setgroups.c b/ports/linux/guts/setgroups.c
new file mode 100644
index 0000000..31b2b57
--- /dev/null
+++ b/ports/linux/guts/setgroups.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setgroups(size_t size, const gid_t *list) {
+ * int rc = -1;
+ */
+
+ /* let gcc know we're ignoring these */
+ (void) size;
+ (void) list;
+ /* you always have all group privileges. we're like magic! */
+ rc = 0;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/setresgid.c b/ports/linux/guts/setresgid.c
new file mode 100644
index 0000000..2a26405
--- /dev/null
+++ b/ports/linux/guts/setresgid.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setresgid(gid_t rgid, gid_t egid, gid_t sgid) {
+ * int rc = -1;
+ */
+ rc = 0;
+ if (pseudo_euid != 0 && rgid != (gid_t) -1 &&
+ rgid != pseudo_egid && rgid != pseudo_rgid && rgid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && egid != (gid_t) -1 &&
+ egid != pseudo_egid && egid != pseudo_rgid && egid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && sgid != (gid_t) -1 &&
+ sgid != pseudo_egid && sgid != pseudo_rgid && sgid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (rc != -1) {
+ if (rgid != (gid_t) -1)
+ pseudo_rgid = rgid;
+ if (egid != (gid_t) -1)
+ pseudo_egid = egid;
+ if (sgid != (gid_t) -1)
+ pseudo_sgid = sgid;
+ pseudo_fgid = pseudo_egid;
+ pseudo_client_touchuid();
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/setresuid.c b/ports/linux/guts/setresuid.c
new file mode 100644
index 0000000..a0a367f
--- /dev/null
+++ b/ports/linux/guts/setresuid.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setresuid(uid_t ruid, uid_t euid, uid_t suid) {
+ * int rc = -1;
+ */
+ rc = 0;
+ if (pseudo_euid != 0 && ruid != (uid_t) -1 &&
+ ruid != pseudo_euid && ruid != pseudo_ruid && ruid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && euid != (uid_t) -1 &&
+ euid != pseudo_euid && euid != pseudo_ruid && euid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && suid != (uid_t) -1 &&
+ suid != pseudo_euid && suid != pseudo_ruid && suid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (rc != -1) {
+ if (ruid != (uid_t) -1)
+ pseudo_ruid = ruid;
+ if (euid != (uid_t) -1)
+ pseudo_euid = euid;
+ if (suid != (uid_t) -1)
+ pseudo_suid = suid;
+ pseudo_fuid = pseudo_euid;
+ pseudo_client_touchuid();
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/stat.c b/ports/linux/guts/stat.c
new file mode 100644
index 0000000..1fe800e
--- /dev/null
+++ b/ports/linux/guts/stat.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int stat(const char *path, struct stat *buf)
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstatat(_STAT_VER, AT_FDCWD, path, buf, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/stat64.c b/ports/linux/guts/stat64.c
new file mode 100644
index 0000000..53dd156
--- /dev/null
+++ b/ports/linux/guts/stat64.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int stat64(const char *path, struct stat *buf)
+ * int rc = -1;
+ */
+
+ rc = wrap___fxstatat64(_STAT_VER, AT_FDCWD, path, buf, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/truncate64.c b/ports/linux/guts/truncate64.c
new file mode 100644
index 0000000..a798984
--- /dev/null
+++ b/ports/linux/guts/truncate64.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_truncate64(const char *path, off64_t length) {
+ * int rc = -1;
+ */
+
+ rc = real_truncate64(path, length);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/guts/ulckpwdf.c b/ports/linux/guts/ulckpwdf.c
new file mode 100644
index 0000000..ed6a671
--- /dev/null
+++ b/ports/linux/guts/ulckpwdf.c
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_ulckpwdf(void) {
+ * int rc = -1;
+ */
+
+ /* lock is cleared automatically on close */
+ rc = pseudo_pwd_lck_close();
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/newclone/guts/clone.c b/ports/linux/newclone/guts/clone.c
new file mode 100644
index 0000000..ee6fc09
--- /dev/null
+++ b/ports/linux/newclone/guts/clone.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2008-2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * clone(...) {
+ * ....
+ */
+ /* because clone() doesn't actually continue in this function, we
+ * can't check the return and fix up environment variables in the
+ * child. Instead, we have to create an intermediate function,
+ * wrap_clone_child, which does this fix up.
+ */
+
+ struct clone_args * myargs = malloc(sizeof(struct clone_args));
+
+ myargs->fn = fn;
+ myargs->flags = flags;
+ myargs->arg = arg;
+
+ /* call the real syscall */
+ rc = (*real_clone)(wrap_clone_child, child_stack, flags, myargs, pid, tls, ctid);
+
+ /* If we're not sharing memory, we need to free myargs in the parent */
+ if (!(flags & CLONE_VM))
+ free(myargs);
+
+/* ...
+ * return rc;
+ * }
+ */
diff --git a/ports/linux/newclone/pseudo_wrappers.c b/ports/linux/newclone/pseudo_wrappers.c
new file mode 100644
index 0000000..1fc6c59
--- /dev/null
+++ b/ports/linux/newclone/pseudo_wrappers.c
@@ -0,0 +1,92 @@
+static int
+wrap_clone(int (*fn)(void *), void *child_stack, int flags, void *arg, va_list
+ap) {
+ /* unused */
+ (void) fn;
+ (void) child_stack;
+ (void) flags;
+ (void) arg;
+ (void) ap;
+ return 0;
+}
+
+struct clone_args {
+ int (*fn)(void *);
+ int flags;
+ void *arg;
+};
+
+int wrap_clone_child(void *args) {
+ struct clone_args *clargs = args;
+
+ int (*fn)(void *) = clargs->fn;
+ int flags = clargs->flags;
+ void *arg = clargs->arg;
+
+ /* We always free in the client */
+ free(clargs);
+
+ if (!(flags & CLONE_VM)) {
+ pseudo_setupenv();
+ if (!pseudo_has_unload(NULL)) {
+ pseudo_reinit_libpseudo();
+ } else {
+ pseudo_dropenv();
+ }
+ }
+
+ return fn(arg);
+}
+
+int
+clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) {
+ sigset_t saved;
+ va_list ap;
+ pid_t *pid;
+ struct user_desc *tls;
+ pid_t *ctid;
+
+ int rc = -1;
+
+ if (!pseudo_check_wrappers() || !real_clone) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("clone");
+ return rc;
+ }
+
+ va_start(ap, arg);
+ pid = va_arg(ap, pid_t *);
+ tls = va_arg(ap, struct user_desc *);
+ ctid = va_arg(ap, pid_t *);
+ va_end(ap);
+
+ pseudo_debug(PDBGF_WRAPPER, "called: clone\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ return -1;
+ }
+
+ int save_errno;
+ int save_disabled = pseudo_disabled;
+
+#include "guts/clone.c"
+
+ if (save_disabled != pseudo_disabled) {
+ if (pseudo_disabled) {
+ pseudo_disabled = 0;
+ pseudo_magic();
+ } else {
+ pseudo_disabled = 1;
+ pseudo_antimagic();
+ }
+ }
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: clone\n");
+ errno = save_errno;
+ return rc;
+}
diff --git a/ports/linux/newclone/wrapfuncs.in b/ports/linux/newclone/wrapfuncs.in
new file mode 100644
index 0000000..8849310
--- /dev/null
+++ b/ports/linux/newclone/wrapfuncs.in
@@ -0,0 +1 @@
+int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...); /* hand_wrapped=1 */
diff --git a/ports/linux/noxattr/guts/fgetxattr.c b/ports/linux/noxattr/guts/fgetxattr.c
new file mode 100644
index 0000000..9d33643
--- /dev/null
+++ b/ports/linux/noxattr/guts/fgetxattr.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size)
+ * ssize_t rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) filedes;
+ (void) name;
+ (void) value;
+ (void) size;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/flistxattr.c b/ports/linux/noxattr/guts/flistxattr.c
new file mode 100644
index 0000000..77db021
--- /dev/null
+++ b/ports/linux/noxattr/guts/flistxattr.c
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t flistxattr(int filedes, char *list, size_t size)
+ * ssize_t rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) filedes;
+ (void) list;
+ (void) size;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/fremovexattr.c b/ports/linux/noxattr/guts/fremovexattr.c
new file mode 100644
index 0000000..529a9de
--- /dev/null
+++ b/ports/linux/noxattr/guts/fremovexattr.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fremovexattr(int filedes, const char *name)
+ * int rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) filedes;
+ (void) name;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/fsetxattr.c b/ports/linux/noxattr/guts/fsetxattr.c
new file mode 100644
index 0000000..3c56ddd
--- /dev/null
+++ b/ports/linux/noxattr/guts/fsetxattr.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fsetxattr(int filedes, const char *name, const void *value, size_t size, int flags)
+ * int rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) filedes;
+ (void) name;
+ (void) value;
+ (void) size;
+ (void) flags;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/getxattr.c b/ports/linux/noxattr/guts/getxattr.c
new file mode 100644
index 0000000..fe8912d
--- /dev/null
+++ b/ports/linux/noxattr/guts/getxattr.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t getxattr(const char *pathname, const char *name, void *value, size_t size)
+ * ssize_t rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) pathname;
+ (void) name;
+ (void) value;
+ (void) size;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/lgetxattr.c b/ports/linux/noxattr/guts/lgetxattr.c
new file mode 100644
index 0000000..404211f
--- /dev/null
+++ b/ports/linux/noxattr/guts/lgetxattr.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t lgetxattr(const char *pathname, const char *name, void *value, size_t size)
+ * ssize_t rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) pathname;
+ (void) name;
+ (void) value;
+ (void) size;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/listxattr.c b/ports/linux/noxattr/guts/listxattr.c
new file mode 100644
index 0000000..1b0b5e7
--- /dev/null
+++ b/ports/linux/noxattr/guts/listxattr.c
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t listxattr(const char *pathname, char *list, size_t size)
+ * ssize_t rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) pathname;
+ (void) list;
+ (void) size;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/llistxattr.c b/ports/linux/noxattr/guts/llistxattr.c
new file mode 100644
index 0000000..a33f970
--- /dev/null
+++ b/ports/linux/noxattr/guts/llistxattr.c
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t llistxattr(const char *pathname, char *list, size_t size)
+ * ssize_t rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) pathname;
+ (void) list;
+ (void) size;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/lremovexattr.c b/ports/linux/noxattr/guts/lremovexattr.c
new file mode 100644
index 0000000..38429da
--- /dev/null
+++ b/ports/linux/noxattr/guts/lremovexattr.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int lremovexattr(const char *pathname, const char *name)
+ * int rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) pathname;
+ (void) name;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/lsetxattr.c b/ports/linux/noxattr/guts/lsetxattr.c
new file mode 100644
index 0000000..140ae8d
--- /dev/null
+++ b/ports/linux/noxattr/guts/lsetxattr.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int lsetxattr(const char *pathname, const char *name, const void *value, size_t size, int flags)
+ * int rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) pathname;
+ (void) name;
+ (void) value;
+ (void) size;
+ (void) flags;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/removexattr.c b/ports/linux/noxattr/guts/removexattr.c
new file mode 100644
index 0000000..cd7f486
--- /dev/null
+++ b/ports/linux/noxattr/guts/removexattr.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int removexattr(const char *pathname, const char *name)
+ * int rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) pathname;
+ (void) name;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/guts/setxattr.c b/ports/linux/noxattr/guts/setxattr.c
new file mode 100644
index 0000000..de2de98
--- /dev/null
+++ b/ports/linux/noxattr/guts/setxattr.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int setxattr(const char *pathname, const char *name, const void *value, size_t size, int flags)
+ * int rc = -1;
+ */
+
+ /* suppress warnings */
+ (void) pathname;
+ (void) name;
+ (void) value;
+ (void) size;
+ (void) flags;
+ errno = ENOTSUP;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/noxattr/wrapfuncs.in b/ports/linux/noxattr/wrapfuncs.in
new file mode 100644
index 0000000..de22ae1
--- /dev/null
+++ b/ports/linux/noxattr/wrapfuncs.in
@@ -0,0 +1,14 @@
+# we use "pathname" to avoid canonicalizing paths, because these functions are
+# unimplemented
+ssize_t getxattr(const char *pathname, const char *name, void *value, size_t size);
+ssize_t lgetxattr(const char *pathname, const char *name, void *value, size_t size);
+ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size);
+ssize_t listxattr(const char *pathname, char *list, size_t size);
+ssize_t llistxattr(const char *pathname, char *list, size_t size);
+ssize_t flistxattr(int filedes, char *list, size_t size);
+int setxattr(const char *pathname, const char *name, const void *value, size_t size, int flags);
+int lsetxattr(const char *pathname, const char *name, const void *value, size_t size, int flags);
+int fsetxattr(int filedes, const char *name, const void *value, size_t size, int flags);
+int removexattr(const char *pathname, const char *name);
+int lremovexattr(const char *pathname, const char *name);
+int fremovexattr(int filedes, const char *name);
diff --git a/ports/linux/oldclone/guts/clone.c b/ports/linux/oldclone/guts/clone.c
new file mode 100644
index 0000000..c6771e5
--- /dev/null
+++ b/ports/linux/oldclone/guts/clone.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2008-2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * clone(...) {
+ * ....
+ */
+ /* because clone() doesn't actually continue in this function, we
+ * can't check the return and fix up environment variables in the
+ * child. Instead, we have to create an intermediate function,
+ * wrap_clone_child, which does this fix up.
+ */
+
+ struct clone_args * myargs = malloc(sizeof(struct clone_args));
+
+ myargs->fn = fn;
+ myargs->flags = flags;
+ myargs->arg = arg;
+
+ /* call the real syscall */
+ rc = (*real_clone)(wrap_clone_child, child_stack, flags, myargs);
+
+ /* If we're not sharing memory, we need to free myargs in the parent */
+ if (!(flags & CLONE_VM))
+ free(myargs);
+
+/* ...
+ * return rc;
+ * }
+ */
diff --git a/ports/linux/oldclone/pseudo_wrappers.c b/ports/linux/oldclone/pseudo_wrappers.c
new file mode 100644
index 0000000..1720dfb
--- /dev/null
+++ b/ports/linux/oldclone/pseudo_wrappers.c
@@ -0,0 +1,76 @@
+static int
+wrap_clone(int (*fn)(void *), void *child_stack, int flags, void *arg) {
+ /* unused */
+ return 0;
+}
+
+struct clone_args {
+ int (*fn)(void *);
+ int flags;
+ void *arg;
+};
+
+int wrap_clone_child(void *args) {
+ struct clone_args *clargs = args;
+
+ int (*fn)(void *) = clargs->fn;
+ int flags = clargs->flags;
+ void *arg = clargs->arg;
+
+ /* We always free in the client */
+ free(clargs);
+
+ if (!(flags & CLONE_VM)) {
+ pseudo_setupenv();
+ if (!pseudo_has_unload(NULL)) {
+ pseudo_reinit_libpseudo();
+ } else {
+ pseudo_dropenv();
+ }
+ }
+
+ return fn(arg);
+}
+
+int
+clone(int (*fn)(void *), void *child_stack, int flags, void *arg) {
+ sigset_t saved;
+
+ int rc = -1;
+
+ if (!pseudo_check_wrappers() || !real_clone) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("clone");
+ return rc;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: clone\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ return -1;
+ }
+
+ int save_errno;
+ int save_disabled = pseudo_disabled;
+
+#include "guts/clone.c"
+
+ if (save_disabled != pseudo_disabled) {
+ if (pseudo_disabled) {
+ pseudo_disabled = 0;
+ pseudo_magic();
+ } else {
+ pseudo_disabled = 1;
+ pseudo_antimagic();
+ }
+ }
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "completed: clone\n");
+ errno = save_errno;
+ return rc;
+}
diff --git a/ports/linux/oldclone/wrapfuncs.in b/ports/linux/oldclone/wrapfuncs.in
new file mode 100644
index 0000000..c915376
--- /dev/null
+++ b/ports/linux/oldclone/wrapfuncs.in
@@ -0,0 +1 @@
+int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); /* hand_wrapped=1 */
diff --git a/ports/linux/portdefs.h b/ports/linux/portdefs.h
new file mode 100644
index 0000000..f0a0e40
--- /dev/null
+++ b/ports/linux/portdefs.h
@@ -0,0 +1,27 @@
+#define PRELINK_LIBRARIES "LD_PRELOAD"
+#define PRELINK_PATH "LD_LIBRARY_PATH"
+#define PSEUDO_STATBUF_64 1
+#define PSEUDO_STATBUF struct stat64
+#define PSEUDO_LINKPATH_SEPARATOR " "
+/* Linux NEVER follows symlinks for link(2)... except on old kernels
+ * I don't care about.
+ */
+#undef PSEUDO_LINK_SYMLINK_BEHAVIOR
+/* Note: 0, here, really means AT_SYMLINK_NOFOLLOW, but specifying that
+ * causes errors; you have to leave it empty or specify AT_SYMLINK_FOLLOW.
+ */
+#define PSEUDO_LINK_SYMLINK_BEHAVIOR 0
+
+/* There were symbol changes that can cause the linker to request
+ * newer versions of glibc, which causes problems occasionally on
+ * older hosts if pseudo is built against a newer glibc and then
+ * run with an older one. Sometimes we can just avoid the symbols,
+ * but memcpy's pretty hard to get away from.
+ */
+#define GLIBC_COMPAT_SYMBOL(sym, ver) __asm(".symver " #sym "," #sym "@GLIBC_" #ver)
+
+#ifdef __amd64__
+GLIBC_COMPAT_SYMBOL(memcpy,2.2.5);
+#elif defined(__i386__)
+GLIBC_COMPAT_SYMBOL(memcpy,2.0);
+#endif
diff --git a/ports/linux/preports b/ports/linux/preports
new file mode 100755
index 0000000..a996c69
--- /dev/null
+++ b/ports/linux/preports
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "unix" "uids_generic"
diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c
new file mode 100644
index 0000000..26b29b0
--- /dev/null
+++ b/ports/linux/pseudo_wrappers.c
@@ -0,0 +1,33 @@
+/* the unix port wants to know that real_stat() and
+ * friends exist. So they do. And because the Linux
+ * port really uses stat64 for those...
+ */
+int
+pseudo_stat(const char *path, struct stat *buf) {
+ return real___xstat(_STAT_VER, path, buf);
+}
+
+int
+pseudo_lstat(const char *path, struct stat *buf) {
+ return real___lxstat(_STAT_VER, path, buf);
+}
+
+int
+pseudo_fstat(int fd, struct stat *buf) {
+ return real___fxstat(_STAT_VER, fd, buf);
+}
+
+int
+pseudo_stat64(const char *path, struct stat64 *buf) {
+ return real___xstat64(_STAT_VER, path, buf);
+}
+
+int
+pseudo_lstat64(const char *path, struct stat64 *buf) {
+ return real___lxstat64(_STAT_VER, path, buf);
+}
+
+int
+pseudo_fstat64(int fd, struct stat64 *buf) {
+ return real___fxstat64(_STAT_VER, fd, buf);
+}
diff --git a/ports/linux/subports b/ports/linux/subports
new file mode 100755
index 0000000..507794e
--- /dev/null
+++ b/ports/linux/subports
@@ -0,0 +1,43 @@
+#!/bin/sh
+found=false
+printf >&2 "Checking for old/new clone mechanics... "
+cat > dummy.c <<EOF
+#include <sched.h>
+int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) { return 0; }
+EOF
+if ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then
+ rm -f dummy.c dummy.o
+ echo >&2 "New clone."
+ echo "linux/newclone"
+ found=true
+fi
+cat > dummy.c <<EOF
+#include <sched.h>
+int clone(int (*fn)(void *), void *child_stack, int flags, void *arg) { return 0; }
+EOF
+if ! $found && ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then
+ rm -f dummy.c dummy.o
+ echo >&2 "Old clone."
+ echo "linux/oldclone"
+ found=true
+fi
+rm -f dummy.c dummy.o
+if ! $found; then
+ echo >&2 "Can't tell, omitting clone(2) support."
+fi
+
+if $port_xattr; then
+ cat > dummy.c <<EOF
+#include <sys/types.h>
+#include <attr/xattr.h>
+int i;
+EOF
+ if ! ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then
+ echo >&2 "Warning: Can't compile trivial program using <attr/xattr.h>".
+ echo >&2 " xattr support will require that header."
+ fi
+ echo "linux/xattr"
+else
+ echo "linux/noxattr"
+fi
+rm -f dummy.c dummy.o
diff --git a/ports/linux/wrapfuncs.in b/ports/linux/wrapfuncs.in
new file mode 100644
index 0000000..3b8955a
--- /dev/null
+++ b/ports/linux/wrapfuncs.in
@@ -0,0 +1,53 @@
+int open(const char *path, int flags, ...{mode_t mode}); /* flags=0 */
+char *get_current_dir_name(void);
+int __xstat(int ver, const char *path, struct stat *buf);
+int __lxstat(int ver, const char *path, struct stat *buf); /* flags=AT_SYMLINK_NOFOLLOW */
+int __fxstat(int ver, int fd, struct stat *buf);
+int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW */
+int __fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags);
+int openat(int dirfd, const char *path, int flags, ...{mode_t mode});
+int __openat_2(int dirfd, const char *path, int flags);
+int __xmknod(int ver, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW */
+int __xmknodat(int ver, int dirfd, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW */
+int fcntl(int fd, int cmd, ...{struct flock *lock});
+# just so we know the inums of symlinks
+char *canonicalize_file_name(const char *filename);
+int eaccess(const char *path, int mode);
+int open64(const char *path, int flags, ...{mode_t mode}); /* flags=0 */
+int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=0 */
+int __openat64_2(int dirfd, const char *path, int flags); /* flags=0 */
+int creat64(const char *path, mode_t mode);
+int stat(const char *path, struct stat *buf); /* real_func=pseudo_stat */
+int lstat(const char *path, struct stat *buf); /* real_func=pseudo_lstat, flags=AT_SYMLINK_NOFOLLOW */
+int fstat(int fd, struct stat *buf); /* real_func=pseudo_fstat */
+int stat64(const char *path, struct stat64 *buf); /* real_func=pseudo_stat64 */
+int lstat64(const char *path, struct stat64 *buf); /* real_func=pseudo_lstat64, flags=AT_SYMLINK_NOFOLLOW */
+int fstat64(int fd, struct stat64 *buf); /* real_func=pseudo_fstat64 */
+int __xstat64(int ver, const char *path, struct stat64 *buf);
+int __lxstat64(int ver, const char *path, struct stat64 *buf); /* flags=AT_SYMLINK_NOFOLLOW */
+int __fxstat64(int ver, int fd, struct stat64 *buf);
+int __fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags);
+FILE *fopen64(const char *path, const char *mode);
+int nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag);
+FILE *freopen64(const char *path, const char *mode, FILE *stream);
+int ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd);
+int glob64(const char *pattern, int flags, int (*errfunc)(const char *, int), glob64_t *pglob);
+int scandir64(const char *path, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)());
+int truncate64(const char *path, off64_t length);
+int mkstemp64(char *template); /* flags=AT_SYMLINK_NOFOLLOW */
+int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups);
+int setgroups(size_t size, const gid_t *list);
+int setfsgid(gid_t fsgid);
+int setfsuid(uid_t fsuid);
+int setresgid(gid_t rgid, gid_t egid, gid_t sgid);
+int setresuid(uid_t ruid, uid_t euid, uid_t suid);
+int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);
+int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
+int scandir(const char *path, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)());
+int getgroups(int size, gid_t *list);
+int lckpwdf(void);
+int ulckpwdf(void);
+int euidaccess(const char *path, int mode);
+int getpw(uid_t uid, char *buf);
+int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp);
+int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp);
diff --git a/ports/linux/xattr/guts/fgetxattr.c b/ports/linux/xattr/guts/fgetxattr.c
new file mode 100644
index 0000000..ae8c3a3
--- /dev/null
+++ b/ports/linux/xattr/guts/fgetxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size)
+ * ssize_t rc = -1;
+ */
+ rc = shared_getxattr(NULL, filedes, name, value, size);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/flistxattr.c b/ports/linux/xattr/guts/flistxattr.c
new file mode 100644
index 0000000..cdd9454
--- /dev/null
+++ b/ports/linux/xattr/guts/flistxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t flistxattr(int filedes, char *list, size_t size)
+ * ssize_t rc = -1;
+ */
+ rc = shared_listxattr(NULL, filedes, list, size);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/fremovexattr.c b/ports/linux/xattr/guts/fremovexattr.c
new file mode 100644
index 0000000..a029d2c
--- /dev/null
+++ b/ports/linux/xattr/guts/fremovexattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fremovexattr(int filedes, const char *name)
+ * int rc = -1;
+ */
+ rc = shared_removexattr(NULL, filedes, name);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/fsetxattr.c b/ports/linux/xattr/guts/fsetxattr.c
new file mode 100644
index 0000000..376cf08
--- /dev/null
+++ b/ports/linux/xattr/guts/fsetxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fsetxattr(int filedes, const char *name, const void *value, size_t size, int xflags)
+ * int rc = -1;
+ */
+ rc = shared_setxattr(NULL, filedes, name, value, size, xflags);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/getxattr.c b/ports/linux/xattr/guts/getxattr.c
new file mode 100644
index 0000000..7bd2bf5
--- /dev/null
+++ b/ports/linux/xattr/guts/getxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t getxattr(const char *path, const char *name, void *value, size_t size)
+ * ssize_t rc = -1;
+ */
+ rc = shared_getxattr(path, -1, name, value, size);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/lgetxattr.c b/ports/linux/xattr/guts/lgetxattr.c
new file mode 100644
index 0000000..675d3da
--- /dev/null
+++ b/ports/linux/xattr/guts/lgetxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size)
+ * ssize_t rc = -1;
+ */
+ rc = shared_getxattr(path, -1, name, value, size);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/listxattr.c b/ports/linux/xattr/guts/listxattr.c
new file mode 100644
index 0000000..0decf71
--- /dev/null
+++ b/ports/linux/xattr/guts/listxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t listxattr(const char *path, char *list, size_t size)
+ * ssize_t rc = -1;
+ */
+ rc = shared_listxattr(path, -1, list, size);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/llistxattr.c b/ports/linux/xattr/guts/llistxattr.c
new file mode 100644
index 0000000..9934256
--- /dev/null
+++ b/ports/linux/xattr/guts/llistxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ssize_t llistxattr(const char *path, char *list, size_t size)
+ * ssize_t rc = -1;
+ */
+ rc = shared_listxattr(path, -1, list, size);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/lremovexattr.c b/ports/linux/xattr/guts/lremovexattr.c
new file mode 100644
index 0000000..1f39788
--- /dev/null
+++ b/ports/linux/xattr/guts/lremovexattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int lremovexattr(const char *path, const char *name)
+ * int rc = -1;
+ */
+ rc = shared_removexattr(path, -1, name);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/lsetxattr.c b/ports/linux/xattr/guts/lsetxattr.c
new file mode 100644
index 0000000..9fe35bc
--- /dev/null
+++ b/ports/linux/xattr/guts/lsetxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int lsetxattr(const char *path, const char *name, const void *value, size_t size, int xflags)
+ * int rc = -1;
+ */
+ rc = shared_setxattr(path, -1, name, value, size, xflags);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/removexattr.c b/ports/linux/xattr/guts/removexattr.c
new file mode 100644
index 0000000..0d4d8e3
--- /dev/null
+++ b/ports/linux/xattr/guts/removexattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int removexattr(const char *path, const char *name)
+ * int rc = -1;
+ */
+ rc = shared_removexattr(path, -1, name);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/guts/setxattr.c b/ports/linux/xattr/guts/setxattr.c
new file mode 100644
index 0000000..bace0d8
--- /dev/null
+++ b/ports/linux/xattr/guts/setxattr.c
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int setxattr(const char *path, const char *name, const void *value, size_t size, int xflags)
+ * int rc = -1;
+ */
+ rc = shared_setxattr(path, -1, name, value, size, xflags);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/linux/xattr/portdefs.h b/ports/linux/xattr/portdefs.h
new file mode 100644
index 0000000..367ca60
--- /dev/null
+++ b/ports/linux/xattr/portdefs.h
@@ -0,0 +1,2 @@
+#include <attr/xattr.h>
+#include <stdint.h>
diff --git a/ports/linux/xattr/pseudo_wrappers.c b/ports/linux/xattr/pseudo_wrappers.c
new file mode 100644
index 0000000..46bc053
--- /dev/null
+++ b/ports/linux/xattr/pseudo_wrappers.c
@@ -0,0 +1,233 @@
+/* shared functionality for the xattr code */
+/* Each of these functions is expecting to get an optional name, and
+ * a populated statbuf to use for sending messages to the server.
+ */
+
+/* to avoid namespace pollution and such, we now duplicate the
+ * basic functionality of a POSIX ACL list, as used by libacl or
+ * the kernel. Documentation was obtained from the headers of libacl
+ * and from a page or two of _The Linux Programming Interface_, by
+ * Michael Kerrisk.
+ */
+
+typedef struct {
+ uint16_t tag;
+ uint16_t perm;
+ uint32_t id;
+} acl_entry;
+
+typedef struct {
+ uint32_t version;
+ acl_entry entries[];
+} acl_header;
+
+enum acl_tags {
+ ACL_UNDEFINED = 0x0,
+ ACL_USER_OBJ = 0x1,
+ ACL_USER = 0x2,
+ ACL_GROUP_OBJ = 0x4,
+ ACL_GROUP = 0x8,
+ ACL_MASK = 0x10,
+ ACL_OTHER = 0x20,
+};
+
+static const int endian_test = 1;
+static const char *endian_tester = (char *) &endian_test;
+
+static inline int
+le16(int x16) {
+ if (*endian_tester) {
+ return x16;
+ } else {
+ return ((x16 & 0xff) << 8) | ((x16 & 0xff00) >> 8);
+ }
+}
+
+static inline int
+le32(int x32) {
+ if (*endian_tester) {
+ return x32;
+ } else {
+ return ((x32 & 0xff) << 24) | ((x32 & 0xff00) << 8) |
+ ((x32 & 0xff0000) >> 8) | ((x32 & 0xff000000) >> 24);
+ }
+}
+
+/* set mode to match the contents of header. Return non-zero on error.
+ * On a zero return, mode is a valid posix mode, and *extra is set to
+ * 1 if any of the entries are not reflected by that mode. On a non-zero
+ * return, no promises are made about *extra or *mode.
+ */
+static int
+posix_permissions(const acl_header *header, int entries, int *extra, int *mode) {
+ int acl_seen = 0;
+ if (le32(header->version) != 2) {
+ pseudo_diag("Fatal: ACL support no available for header version %d.\n",
+ le32(header->version));
+ return 1;
+ }
+ *mode = 0;
+ *extra = 0;
+ for (int i = 0; i < entries; ++i) {
+ const acl_entry *e = &header->entries[i];
+ int tag = le16(e->tag);
+ int perm = le16(e->perm);
+ acl_seen |= tag;
+ switch (tag) {
+ case ACL_USER_OBJ:
+ *mode = *mode | (perm << 6);
+ break;
+ case ACL_GROUP_OBJ:
+ *mode = *mode | (perm << 3);
+ break;
+ case ACL_OTHER:
+ *mode = *mode | perm;
+ break;
+ case ACL_USER:
+ case ACL_GROUP:
+ case ACL_MASK:
+ *extra = *extra + 1;
+ break;
+ default:
+ pseudo_debug(PDBGF_XATTR, "Unknown tag in ACL: 0x%x.\n",
+ tag);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#define RC_AND_BUF \
+ int rc; \
+ PSEUDO_STATBUF buf; \
+ if (path) { \
+ rc = base_lstat(path, &buf); \
+ } else { \
+ rc = base_fstat(fd, &buf); \
+ } \
+ if (rc == -1) { \
+ return rc; \
+ }
+
+static ssize_t shared_getxattr(const char *path, int fd, const char *name, void *value, size_t size) {
+ RC_AND_BUF
+
+ pseudo_debug(PDBGF_XATTR, "getxattr(%s [fd %d], %s)\n",
+ path ? path : "<no path>", fd, name);
+ pseudo_msg_t *result = pseudo_client_op(OP_GET_XATTR, 0, fd, -1, path, &buf, name);
+ if (result->result != RESULT_SUCCEED) {
+ errno = ENOATTR;
+ return -1;
+ }
+
+ if (value) {
+ pseudo_debug(PDBGF_XATTR, "returned attributes: '%s' (%d bytes)\n",
+ result->path, result->pathlen);
+ if (size >= result->pathlen) {
+ memcpy(value, result->path, result->pathlen);
+ } else {
+ memcpy(value, result->path, size);
+ errno = ERANGE;
+ }
+ }
+ return result->pathlen;
+}
+
+static int shared_setxattr(const char *path, int fd, const char *name, const void *value, size_t size, int flags) {
+ RC_AND_BUF
+ pseudo_op_t op;
+
+ pseudo_debug(PDBGF_XATTR, "setxattr(%s [fd %d], %s => '%.*s')\n",
+ path ? path : "<no path>", fd, name, (int) size, (char *) value);
+
+ /* this may be a plain chmod */
+ if (!strcmp(name, "system.posix_acl_access")) {
+ int extra;
+ int mode;
+ int entries = (size - sizeof(acl_header)) / sizeof(acl_entry);
+ if (!posix_permissions(value, entries, &extra, &mode)) {
+ pseudo_debug(PDBGF_XATTR, "posix_acl_access translated to mode %04o. Remaining attribute(s): %d.\n",
+ mode, extra);
+ buf.st_mode = mode;
+ /* we want to actually issue a corresponding chmod,
+ * as well, or else the file ends up 0600 on the
+ * host. Using the slightly-less-efficient wrap_chmod
+ * avoids possible misalignment.
+ */
+ if (path) {
+ wrap_chmod(path, mode);
+ } else {
+ wrap_fchmod(fd, mode);
+ }
+ /* we are sneaky, and do not actually record this using
+ * extended attributes. */
+ if (!extra) {
+ return 0;
+ }
+ }
+ }
+ if (!strcmp(name, "user.pseudo_data")) {
+ pseudo_debug(PDBGF_XATTR | PDBGF_XATTRDB, "user.pseudo_data xattribute does not get to go in database.\n");
+ return -1;
+ }
+
+ switch (flags) {
+ case XATTR_CREATE:
+ op = OP_CREATE_XATTR;
+ break;
+ case XATTR_REPLACE:
+ op = OP_REPLACE_XATTR;
+ break;
+ default:
+ op = OP_SET_XATTR;
+ break;
+ }
+
+ pseudo_msg_t *result = pseudo_client_op(op, 0, fd, -1, path, &buf, name, value, size);
+
+ /* we automatically assume success */
+ if (op == OP_SET_XATTR) {
+ return 0;
+ }
+
+ /* CREATE/REPLACE operations can report failure */
+ if (!result || result->result == RESULT_FAIL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static ssize_t shared_listxattr(const char *path, int fd, char *list, size_t size) {
+ RC_AND_BUF
+ pseudo_msg_t *result = pseudo_client_op(OP_LIST_XATTR, 0, fd, -1, path, &buf);
+ if (result->result != RESULT_SUCCEED) {
+ pseudo_debug(PDBGF_XATTR, "listxattr: no success.\n");
+ errno = ENOATTR;
+ return -1;
+ }
+ if (list) {
+ pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of names, starting '%.*s'\n",
+ (int) result->pathlen, (int) result->pathlen, result->path);
+ if (size >= result->pathlen) {
+ memcpy(list, result->path, result->pathlen);
+ } else {
+ memcpy(list, result->path, size);
+ errno = ERANGE;
+ }
+ }
+ return result->pathlen;
+}
+
+static int shared_removexattr(const char *path, int fd, const char *name) {
+ RC_AND_BUF
+ pseudo_msg_t *result = pseudo_client_op(OP_REMOVE_XATTR, 0, fd, -1, path, &buf, name);
+
+ if (result->result != RESULT_SUCCEED) {
+ /* docs say ENOATTR, but I don't have one */
+ errno = ENOENT;
+ return -1;
+ }
+ return 0;
+}
+
diff --git a/ports/linux/xattr/wrapfuncs.in b/ports/linux/xattr/wrapfuncs.in
new file mode 100644
index 0000000..c37f78a
--- /dev/null
+++ b/ports/linux/xattr/wrapfuncs.in
@@ -0,0 +1,12 @@
+ssize_t getxattr(const char *path, const char *name, void *value, size_t size); /* flags=0 */
+ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size); /* flags=AT_SYMLINK_NOFOLLOW */
+ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size);
+int setxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=0 */
+int lsetxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=AT_SYMLINK_NOFOLLOW */
+int fsetxattr(int filedes, const char *name, const void *value, size_t size, int xflags);
+ssize_t listxattr(const char *path, char *list, size_t size); /* flags=0 */
+ssize_t llistxattr(const char *path, char *list, size_t size); /* flags=AT_SYMLINK_NOFOLLOW */
+ssize_t flistxattr(int filedes, char *list, size_t size);
+int removexattr(const char *path, const char *name); /* flags=0 */
+int lremovexattr(const char *path, const char *name); /* flags=AT_SYMLINK_NOFOLLOW */
+int fremovexattr(int filedes, const char *name);
diff --git a/ports/uids_generic/guts/COPYRIGHT b/ports/uids_generic/guts/COPYRIGHT
new file mode 100644
index 0000000..c96e1b1
--- /dev/null
+++ b/ports/uids_generic/guts/COPYRIGHT
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
diff --git a/ports/uids_generic/guts/endgrent.c b/ports/uids_generic/guts/endgrent.c
new file mode 100644
index 0000000..843cad0
--- /dev/null
+++ b/ports/uids_generic/guts/endgrent.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static void
+ * wrap_endgrent(void) {
+ *
+ */
+
+ pseudo_grp_close();
+
+/* return;
+ * }
+ */
diff --git a/ports/uids_generic/guts/endpwent.c b/ports/uids_generic/guts/endpwent.c
new file mode 100644
index 0000000..f76cf10
--- /dev/null
+++ b/ports/uids_generic/guts/endpwent.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static void
+ * wrap_endpwent(void) {
+ *
+ */
+
+ pseudo_pwd_close();
+
+/* return;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getegid.c b/ports/uids_generic/guts/getegid.c
new file mode 100644
index 0000000..7c14f48
--- /dev/null
+++ b/ports/uids_generic/guts/getegid.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static gid_t
+ * wrap_getegid(void) {
+ * gid_t rc = 0;
+ */
+
+ rc = pseudo_egid;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/geteuid.c b/ports/uids_generic/guts/geteuid.c
new file mode 100644
index 0000000..1745e13
--- /dev/null
+++ b/ports/uids_generic/guts/geteuid.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static uid_t
+ * wrap_geteuid(void) {
+ * uid_t rc = 0;
+ */
+
+ rc = pseudo_euid;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getgid.c b/ports/uids_generic/guts/getgid.c
new file mode 100644
index 0000000..ca8bad3
--- /dev/null
+++ b/ports/uids_generic/guts/getgid.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static gid_t
+ * wrap_getgid(void) {
+ * gid_t rc = 0;
+ */
+
+ rc = pseudo_rgid;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getgrent.c b/ports/uids_generic/guts/getgrent.c
new file mode 100644
index 0000000..e8e07f5
--- /dev/null
+++ b/ports/uids_generic/guts/getgrent.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static struct group *
+ * wrap_getgrent(void) {
+ * struct group * rc = NULL;
+ */
+ static struct group grp;
+ static char grbuf[PSEUDO_PWD_MAX];
+ int r_rc;
+
+ r_rc = wrap_getgrent_r(&grp, grbuf, PSEUDO_PWD_MAX, &rc);
+ /* different error return conventions */
+ if (r_rc != 0) {
+ errno = r_rc;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getgrgid.c b/ports/uids_generic/guts/getgrgid.c
new file mode 100644
index 0000000..c1824e7
--- /dev/null
+++ b/ports/uids_generic/guts/getgrgid.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static struct group *
+ * wrap_getgrgid(gid_t gid) {
+ * struct group * rc = NULL;
+ */
+ static struct group grp;
+ static char grbuf[PSEUDO_PWD_MAX];
+ int r_rc;
+
+ r_rc = wrap_getgrgid_r(gid, &grp, grbuf, PSEUDO_PWD_MAX, &rc);
+ /* different error return conventions */
+ if (r_rc != 0) {
+ errno = r_rc;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getgrgid_r.c b/ports/uids_generic/guts/getgrgid_r.c
new file mode 100644
index 0000000..b043995
--- /dev/null
+++ b/ports/uids_generic/guts/getgrgid_r.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getgrgid_r(gid_t gid, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) {
+ * int rc = -1;
+ */
+
+ setgrent();
+ while ((rc = wrap_getgrent_r(gbuf, buf, buflen, gbufp)) == 0) {
+ /* 0 means no error occurred, and *gbufp == gbuf */
+ if (gbuf->gr_gid == gid) {
+ pseudo_debug(PDBGF_CLIENT, "found group gid %d, name %s\n",
+ gbuf->gr_gid, gbuf->gr_name);
+ endgrent();
+ return rc;
+ }
+ }
+ endgrent();
+ /* we never found a match; rc is 0 if there was no error, or
+ * non-zero if an error occurred. Either way, set the
+ * pwbufp pointer to NULL to indicate that we didn't find
+ * something, and leave rc alone.
+ */
+ *gbufp = NULL;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getgrnam.c b/ports/uids_generic/guts/getgrnam.c
new file mode 100644
index 0000000..0e26444
--- /dev/null
+++ b/ports/uids_generic/guts/getgrnam.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static struct group *
+ * wrap_getgrnam(const char *name) {
+ * struct group * rc = NULL;
+ */
+
+ static struct group grp;
+ static char grbuf[PSEUDO_PWD_MAX];
+ int r_rc;
+
+ r_rc = wrap_getgrnam_r(name, &grp, grbuf, PSEUDO_PWD_MAX, &rc);
+ /* different error return conventions */
+ if (r_rc != 0) {
+ errno = r_rc;
+ }
+
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getgrnam_r.c b/ports/uids_generic/guts/getgrnam_r.c
new file mode 100644
index 0000000..39de641
--- /dev/null
+++ b/ports/uids_generic/guts/getgrnam_r.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getgrnam_r(const char *name, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) {
+ * int rc = -1;
+ */
+
+ setgrent();
+ while ((rc = wrap_getgrent_r(gbuf, buf, buflen, gbufp)) == 0) {
+ /* 0 means no error occurred, and *gbufp == gbuf */
+ if (gbuf->gr_name && !strcmp(gbuf->gr_name, name)) {
+ endgrent();
+ return rc;
+ }
+ }
+ endgrent();
+ /* we never found a match; rc is 0 if there was no error, or
+ * non-zero if an error occurred. Either way, set the
+ * pwbufp pointer to NULL to indicate that we didn't find
+ * something, and leave rc alone.
+ */
+ *gbufp = NULL;
+
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getpwent.c b/ports/uids_generic/guts/getpwent.c
new file mode 100644
index 0000000..3b1f837
--- /dev/null
+++ b/ports/uids_generic/guts/getpwent.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static struct passwd *
+ * wrap_getpwent(void) {
+ * struct passwd * rc = NULL;
+ */
+ static struct passwd pwd;
+ static char pwbuf[PSEUDO_PWD_MAX];
+ int r_rc;
+
+ r_rc = wrap_getpwent_r(&pwd, pwbuf, PSEUDO_PWD_MAX, &rc);
+ /* different error return conventions */
+ if (r_rc != 0) {
+ errno = r_rc;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getpwnam.c b/ports/uids_generic/guts/getpwnam.c
new file mode 100644
index 0000000..024b3d8
--- /dev/null
+++ b/ports/uids_generic/guts/getpwnam.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static struct passwd *
+ * wrap_getpwnam(const char *name) {
+ * struct passwd * rc = NULL;
+ */
+ static struct passwd pwd;
+ static char pwbuf[PSEUDO_PWD_MAX];
+ int r_rc;
+
+ r_rc = wrap_getpwnam_r(name, &pwd, pwbuf, PSEUDO_PWD_MAX, &rc);
+ /* different error return conventions */
+ if (r_rc != 0) {
+ errno = r_rc;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getpwnam_r.c b/ports/uids_generic/guts/getpwnam_r.c
new file mode 100644
index 0000000..5d7a4ea
--- /dev/null
+++ b/ports/uids_generic/guts/getpwnam_r.c
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) {
+ * int rc = -1;
+ */
+
+ setpwent();
+ while ((rc = wrap_getpwent_r(pwbuf, buf, buflen, pwbufp)) == 0) {
+ /* 0 means no error occurred, and *pwbufp == pwbuf */
+ if (pwbuf->pw_name && !strcmp(pwbuf->pw_name, name)) {
+ endpwent();
+ return rc;
+ }
+ }
+ endpwent();
+ /* we never found a match; rc is 0 if there was no error, or
+ * non-zero if an error occurred. Either way, set the
+ * pwbufp pointer to NULL to indicate that we didn't find
+ * something, and leave rc alone.
+ */
+ *pwbufp = NULL;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getpwuid.c b/ports/uids_generic/guts/getpwuid.c
new file mode 100644
index 0000000..11142de
--- /dev/null
+++ b/ports/uids_generic/guts/getpwuid.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static struct passwd *
+ * wrap_getpwuid(uid_t uid) {
+ * struct passwd * rc = NULL;
+ */
+ static struct passwd pwd;
+ static char pwbuf[PSEUDO_PWD_MAX];
+ int r_rc;
+
+ r_rc = wrap_getpwuid_r(uid, &pwd, pwbuf, PSEUDO_PWD_MAX, &rc);
+ /* different error return conventions */
+ if (r_rc != 0) {
+ errno = r_rc;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getpwuid_r.c b/ports/uids_generic/guts/getpwuid_r.c
new file mode 100644
index 0000000..06b920e
--- /dev/null
+++ b/ports/uids_generic/guts/getpwuid_r.c
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_getpwuid_r(uid_t uid, struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) {
+ * int rc = -1;
+ */
+
+ setpwent();
+ while ((rc = wrap_getpwent_r(pwbuf, buf, buflen, pwbufp)) == 0) {
+ /* 0 means no error occurred, and *pwbufp == pwbuf */
+ if (pwbuf->pw_uid == uid) {
+ endpwent();
+ return rc;
+ }
+ }
+ endpwent();
+ /* we never found a match; rc is 0 if there was no error, or
+ * non-zero if an error occurred. Either way, set the
+ * pwbufp pointer to NULL to indicate that we didn't find
+ * something, and leave rc alone.
+ */
+ *pwbufp = NULL;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/getuid.c b/ports/uids_generic/guts/getuid.c
new file mode 100644
index 0000000..e783cc8
--- /dev/null
+++ b/ports/uids_generic/guts/getuid.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static uid_t
+ * wrap_getuid(void) {
+ * uid_t rc = 0;
+ */
+
+ rc = pseudo_ruid;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/setegid.c b/ports/uids_generic/guts/setegid.c
new file mode 100644
index 0000000..ff777a0
--- /dev/null
+++ b/ports/uids_generic/guts/setegid.c
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setegid(gid_t egid) {
+ * int rc = -1;
+ */
+ if (pseudo_euid == 0 || egid == pseudo_egid || egid == pseudo_rgid || egid == pseudo_sgid) {
+ pseudo_egid = egid;
+ pseudo_fgid = egid;
+ pseudo_client_touchgid();
+ rc = 0;
+ } else {
+ rc = -1;
+ errno = EPERM;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/seteuid.c b/ports/uids_generic/guts/seteuid.c
new file mode 100644
index 0000000..430768f
--- /dev/null
+++ b/ports/uids_generic/guts/seteuid.c
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_seteuid(uid_t euid) {
+ * int rc = -1;
+ */
+ if (pseudo_euid == 0 || euid == pseudo_euid || euid == pseudo_ruid || euid == pseudo_suid) {
+ pseudo_euid = euid;
+ pseudo_fuid = euid;
+ pseudo_client_touchuid();
+ rc = 0;
+ } else {
+ rc = -1;
+ errno = EPERM;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/setgid.c b/ports/uids_generic/guts/setgid.c
new file mode 100644
index 0000000..b94db1a
--- /dev/null
+++ b/ports/uids_generic/guts/setgid.c
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setgid(gid_t gid) {
+ * int rc = -1;
+ */
+ if (pseudo_euid == 0) {
+ pseudo_rgid = gid;
+ pseudo_egid = gid;
+ pseudo_sgid = gid;
+ pseudo_fgid = gid;
+ pseudo_client_touchgid();
+ rc = 0;
+ } else if (pseudo_egid == gid || pseudo_sgid == gid || pseudo_rgid == gid) {
+ pseudo_egid = gid;
+ pseudo_fgid = gid;
+ pseudo_client_touchgid();
+ rc = 0;
+ } else {
+ rc = -1;
+ errno = EPERM;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/setgrent.c b/ports/uids_generic/guts/setgrent.c
new file mode 100644
index 0000000..75aab61
--- /dev/null
+++ b/ports/uids_generic/guts/setgrent.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static void
+ * wrap_setgrent(void) {
+ *
+ */
+
+ pseudo_grp_open();
+
+/* return;
+ * }
+ */
diff --git a/ports/uids_generic/guts/setpwent.c b/ports/uids_generic/guts/setpwent.c
new file mode 100644
index 0000000..fb35e07
--- /dev/null
+++ b/ports/uids_generic/guts/setpwent.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static void
+ * wrap_setpwent(void) {
+ *
+ */
+
+ pseudo_pwd_open();
+
+/* return;
+ * }
+ */
diff --git a/ports/uids_generic/guts/setregid.c b/ports/uids_generic/guts/setregid.c
new file mode 100644
index 0000000..78b2037
--- /dev/null
+++ b/ports/uids_generic/guts/setregid.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setregid(gid_t rgid, gid_t egid) {
+ * int rc = -1;
+ */
+ rc = 0;
+ if (pseudo_euid != 0 && rgid != (gid_t) -1 &&
+ rgid != pseudo_egid && rgid != pseudo_rgid && rgid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && egid != (gid_t) -1 &&
+ egid != pseudo_egid && egid != pseudo_rgid && egid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (rc != -1) {
+ if (rgid != (gid_t) -1)
+ pseudo_rgid = rgid;
+ if (egid != (gid_t) -1)
+ pseudo_egid = egid;
+ pseudo_fgid = pseudo_egid;
+ pseudo_client_touchuid();
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/setreuid.c b/ports/uids_generic/guts/setreuid.c
new file mode 100644
index 0000000..3ff82ab
--- /dev/null
+++ b/ports/uids_generic/guts/setreuid.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setreuid(uid_t ruid, uid_t euid) {
+ * int rc = -1;
+ */
+ rc = 0;
+ if (pseudo_euid != 0 && ruid != (uid_t) -1 &&
+ ruid != pseudo_euid && ruid != pseudo_ruid && ruid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && euid != (uid_t) -1 &&
+ euid != pseudo_euid && euid != pseudo_ruid && euid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (rc != -1) {
+ if (ruid != (uid_t) -1)
+ pseudo_ruid = ruid;
+ if (euid != (uid_t) -1)
+ pseudo_euid = euid;
+ pseudo_fuid = pseudo_euid;
+ pseudo_client_touchuid();
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/guts/setuid.c b/ports/uids_generic/guts/setuid.c
new file mode 100644
index 0000000..6bfdf6c
--- /dev/null
+++ b/ports/uids_generic/guts/setuid.c
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_setuid(uid_t uid) {
+ * int rc = -1;
+ */
+ if (pseudo_euid == 0) {
+ pseudo_ruid = uid;
+ pseudo_euid = uid;
+ pseudo_suid = uid;
+ pseudo_fuid = uid;
+ pseudo_client_touchuid();
+ rc = 0;
+ } else if (pseudo_euid == uid || pseudo_suid == uid || pseudo_ruid == uid) {
+ pseudo_euid = uid;
+ pseudo_fuid = uid;
+ pseudo_client_touchuid();
+ rc = 0;
+ } else {
+ rc = -1;
+ errno = EPERM;
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/uids_generic/wrapfuncs.in b/ports/uids_generic/wrapfuncs.in
new file mode 100644
index 0000000..38aa558
--- /dev/null
+++ b/ports/uids_generic/wrapfuncs.in
@@ -0,0 +1,25 @@
+# I bet you never knew there were this many of these!
+gid_t getegid(void);
+gid_t getgid(void);
+int getgrgid_r(gid_t gid, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp);
+int getgrnam_r(const char *name, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp);
+int getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp);
+int getpwuid_r(uid_t uid, struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp);
+int setegid(gid_t egid);
+int seteuid(uid_t euid);
+int setgid(gid_t gid);
+int setregid(gid_t rgid, gid_t egid);
+int setreuid(uid_t ruid, uid_t euid);
+int setuid(uid_t uid);
+struct group *getgrent(void);
+struct group *getgrgid(gid_t gid);
+struct group *getgrnam(const char *name);
+struct passwd *getpwent(void);
+struct passwd *getpwnam(const char *name);
+struct passwd *getpwuid(uid_t uid);
+uid_t geteuid(void);
+uid_t getuid(void);
+void endgrent(void);
+void endpwent(void);
+void setgrent(void);
+void setpwent(void);
diff --git a/ports/unix/guts/COPYRIGHT b/ports/unix/guts/COPYRIGHT
new file mode 100644
index 0000000..c96e1b1
--- /dev/null
+++ b/ports/unix/guts/COPYRIGHT
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
diff --git a/ports/unix/guts/access.c b/ports/unix/guts/access.c
new file mode 100644
index 0000000..0093a3b
--- /dev/null
+++ b/ports/unix/guts/access.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_access(const char *path, int mode) {
+ * int rc = -1;
+ */
+ PSEUDO_STATBUF buf;
+
+ /* note: no attempt to handle the case where user isn't
+ * root.
+ */
+ rc = base_stat(path, &buf);
+ if (rc == -1)
+ return rc;
+
+ if (mode & X_OK) {
+ if (buf.st_mode & 0111) {
+ return 0;
+ } else {
+ errno = EPERM;
+ return -1;
+ }
+ } else {
+ return 0;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/acct.c b/ports/unix/guts/acct.c
new file mode 100644
index 0000000..b8dca5d
--- /dev/null
+++ b/ports/unix/guts/acct.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_acct(const char *path) {
+ * int rc = -1;
+ */
+
+ rc = real_acct(path);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/chdir.c b/ports/unix/guts/chdir.c
new file mode 100644
index 0000000..9e30348
--- /dev/null
+++ b/ports/unix/guts/chdir.c
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_chdir(const char *path) {
+ * int rc = -1;
+ */
+ pseudo_debug(PDBGF_CLIENT, "chdir: '%s'\n",
+ path ? path : "<nil>");
+
+ if (!path) {
+ errno = EFAULT;
+ return -1;
+ }
+ rc = real_chdir(path);
+
+ if (rc != -1) {
+ pseudo_client_op(OP_CHDIR, 0, -1, -1, path, 0);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/chmod.c b/ports/unix/guts/chmod.c
new file mode 100644
index 0000000..a157335
--- /dev/null
+++ b/ports/unix/guts/chmod.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_chmod(const char *path, mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_fchmodat(AT_FDCWD, path, mode, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/chown.c b/ports/unix/guts/chown.c
new file mode 100644
index 0000000..4fcbdda
--- /dev/null
+++ b/ports/unix/guts/chown.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_chown(const char *path, uid_t owner, gid_t group) {
+ * int rc = -1;
+ */
+
+ rc = wrap_fchownat(AT_FDCWD, path, owner, group, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/chroot.c b/ports/unix/guts/chroot.c
new file mode 100644
index 0000000..ac24955
--- /dev/null
+++ b/ports/unix/guts/chroot.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_chroot(const char *path) {
+ * int rc = -1;
+ */
+ pseudo_debug(PDBGF_CLIENT | PDBGF_CHROOT, "chroot: %s\n", path);
+ if (!pseudo_client_op(OP_CHROOT, 0, -1, -1, path, 0)) {
+ pseudo_debug(PDBGF_OP | PDBGF_CHROOT, "chroot failed: %s\n", strerror(errno));
+ rc = -1;
+ } else {
+ rc = 0;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/close.c b/ports/unix/guts/close.c
new file mode 100644
index 0000000..09c73e6
--- /dev/null
+++ b/ports/unix/guts/close.c
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_close(int fd) {
+ * int rc = -1;
+ */
+ /* this cleans up an internal table, and shouldn't even
+ * make it to the server.
+ */
+ pseudo_client_op(OP_CLOSE, 0, fd, -1, 0, 0);
+ rc = real_close(fd);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/closedir.c b/ports/unix/guts/closedir.c
new file mode 100644
index 0000000..1085361
--- /dev/null
+++ b/ports/unix/guts/closedir.c
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_closedir(DIR *dirp) {
+ * int rc = -1;
+ */
+ if (!dirp) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ int fd = dirfd(dirp);
+ pseudo_client_op(OP_CLOSE, 0, fd, -1, 0, 0);
+ rc = real_closedir(dirp);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/creat.c b/ports/unix/guts/creat.c
new file mode 100644
index 0000000..8593cd4
--- /dev/null
+++ b/ports/unix/guts/creat.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_creat(const char *path, mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_open(path, O_CREAT|O_WRONLY|O_TRUNC, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/dup.c b/ports/unix/guts/dup.c
new file mode 100644
index 0000000..f03c2ad
--- /dev/null
+++ b/ports/unix/guts/dup.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_dup(int fd) {
+ * int rc = -1;
+ */
+ int save_errno;
+
+ rc = real_dup(fd);
+ save_errno = errno;
+ pseudo_debug(PDBGF_CLIENT, "dup: %d->%d\n", fd, rc);
+ pseudo_client_op(OP_DUP, 0, fd, rc, 0, 0);
+
+ errno = save_errno;
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/dup2.c b/ports/unix/guts/dup2.c
new file mode 100644
index 0000000..cd335ac
--- /dev/null
+++ b/ports/unix/guts/dup2.c
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_dup2(int oldfd, int newfd) {
+ * int rc = -1;
+ */
+ int save_errno;
+
+ /* close existing one first - this also causes the socket to the
+ * server to get moved around if someone tries to overwrite it. */
+ pseudo_debug(PDBGF_CLIENT, "dup2: %d->%d\n", oldfd, newfd);
+ pseudo_client_op(OP_CLOSE, 0, newfd, -1, 0, 0);
+ rc = real_dup2(oldfd, newfd);
+ save_errno = errno;
+ pseudo_client_op(OP_DUP, 0, oldfd, newfd, 0, 0);
+ errno = save_errno;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fchdir.c b/ports/unix/guts/fchdir.c
new file mode 100644
index 0000000..ba77ebf
--- /dev/null
+++ b/ports/unix/guts/fchdir.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_fchdir(int dirfd) {
+ * int rc = -1;
+ */
+
+ rc = real_fchdir(dirfd);
+
+ if (rc != -1) {
+ pseudo_client_op(OP_CHDIR, 0, -1, dirfd, 0, 0);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fchmod.c b/ports/unix/guts/fchmod.c
new file mode 100644
index 0000000..e2301e3
--- /dev/null
+++ b/ports/unix/guts/fchmod.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_fchmod(int fd, mode_t mode) {
+ * int rc = -1;
+ */
+ PSEUDO_STATBUF buf;
+ int save_errno = errno;
+
+ if (base_fstat(fd, &buf) == -1) {
+ /* can't stat it, can't chmod it */
+ return -1;
+ }
+ buf.st_mode = (buf.st_mode & ~07777) | (mode & 07777);
+ pseudo_client_op(OP_FCHMOD, 0, fd, -1, 0, &buf);
+ real_fchmod(fd, PSEUDO_FS_MODE(mode, S_ISDIR(buf.st_mode)));
+ /* just pretend we worked */
+ errno = save_errno;
+ rc = 0;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fchmodat.c b/ports/unix/guts/fchmodat.c
new file mode 100644
index 0000000..0506794
--- /dev/null
+++ b/ports/unix/guts/fchmodat.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_fchmodat(int dirfd, const char *path, mode_t mode, int flags) {
+ * int rc = -1;
+ */
+ PSEUDO_STATBUF buf;
+ int save_errno = errno;
+
+ if (flags & AT_SYMLINK_NOFOLLOW) {
+ errno = ENOTSUP;
+ return -1;
+ }
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ rc = base_stat(path, &buf);
+#else
+ rc = base_fstatat(dirfd, path, &buf, flags);
+#endif
+ if (rc == -1) {
+ return rc;
+ }
+ if (S_ISLNK(buf.st_mode)) {
+ /* we don't really support chmod of a symlink */
+ errno = ENOSYS;
+ return -1;
+ }
+ save_errno = errno;
+
+#if 0
+ pseudo_msg_t *msg;
+ /* purely for debugging purposes: check whether file
+ * is already in database. We don't need the resulting
+ * information for anything. This is currently ifdefed
+ * out because it's only useful when trying to track where
+ * files are coming from.
+ */
+ msg = pseudo_client_op(OP_STAT, 0, -1, -1, path, &buf);
+ if (!msg || msg->result != RESULT_SUCCEED) {
+ pseudo_debug(PDBGF_FILE, "chmodat to 0%o on %d/%s, ino %llu, new file.\n",
+ mode, dirfd, path, (unsigned long long) buf.st_ino);
+
+ }
+#endif
+
+ /* user bits added so "root" can always access files. */
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ /* note: if path was a symlink, and AT_SYMLINK_NOFOLLOW was
+ * specified, we already bailed previously. */
+ real_chmod(path, PSEUDO_FS_MODE(mode, S_ISDIR(buf.st_mode)));
+#else
+ rc = real_fchmodat(dirfd, path, PSEUDO_FS_MODE(mode, S_ISDIR(buf.st_mode)), flags);
+#endif
+ /* we otherwise ignore failures from underlying fchmod, because pseudo
+ * may believe you are permitted to change modes that the filesystem
+ * doesn't. Note that we also don't need to know whether the
+ * file might be a (pseudo) block device or some such; pseudo
+ * will only modify permission bits based on an OP_CHMOD, and does
+ * not care about device/file type mismatches, only directory/file
+ * or symlink/file.
+ */
+
+ buf.st_mode = (buf.st_mode & ~07777) | (mode & 07777);
+ pseudo_client_op(OP_CHMOD, 0, -1, dirfd, path, &buf);
+ /* don't change errno from what it was originally */
+ errno = save_errno;
+ rc = 0;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fchown.c b/ports/unix/guts/fchown.c
new file mode 100644
index 0000000..89cabe2
--- /dev/null
+++ b/ports/unix/guts/fchown.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_fchown(int fd, uid_t owner, gid_t group) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ PSEUDO_STATBUF buf;
+ int save_errno = errno;
+
+ if (base_fstat(fd, &buf) == -1) {
+ save_errno = errno;
+ pseudo_debug(PDBGF_CONSISTENCY, "fchown failing because fstat failed: %s\n",
+ strerror(errno));
+ errno = save_errno;
+ return -1;
+ }
+ if (owner == (uid_t) -1 || group == (gid_t) -1) {
+ msg = pseudo_client_op(OP_STAT, 0, fd, -1, NULL, &buf);
+ /* copy in any existing values... */
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(&buf, msg);
+ } else {
+ pseudo_debug(PDBGF_FILE, "fchown fd %d, ino %llu, unknown file.\n",
+ fd, (unsigned long long) buf.st_ino);
+ }
+ }
+ /* now override with arguments */
+ if (owner != (uid_t) -1) {
+ buf.st_uid = owner;
+ }
+ if (group != (gid_t) -1) {
+ buf.st_gid = group;
+ }
+ pseudo_debug(PDBGF_OP, "fchown, fd %d: %d:%d -> %d:%d\n",
+ fd, owner, group, buf.st_uid, buf.st_gid);
+ pseudo_client_op(OP_FCHOWN, 0, fd, -1, 0, &buf);
+ /* pretend we worked, errno should be unchanged */
+ errno = save_errno;
+ rc = 0;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fchownat.c b/ports/unix/guts/fchownat.c
new file mode 100644
index 0000000..2888087
--- /dev/null
+++ b/ports/unix/guts/fchownat.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_fchownat(int dirfd, const char *path, uid_t owner, gid_t group, int flags) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ PSEUDO_STATBUF buf;
+ int save_errno = errno;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (flags & AT_SYMLINK_NOFOLLOW) {
+ rc = base_lstat(path, &buf);
+ } else {
+ rc = base_stat(path, &buf);
+ }
+#else
+ rc = base_fstatat(dirfd, path, &buf, flags);
+#endif
+ if (rc == -1) {
+ return rc;
+ }
+ save_errno = errno;
+
+ if (owner == (uid_t) -1 || group == (gid_t) -1) {
+ msg = pseudo_client_op(OP_STAT, 0, -1, -1, path, &buf);
+ /* copy in any existing values... */
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(&buf, msg);
+ } else {
+ pseudo_debug(PDBGF_FILE, "chownat to %d:%d on %d/%s, ino %llu, new file.\n",
+ owner, group, dirfd, path,
+ (unsigned long long) buf.st_ino);
+ }
+ }
+ /* now override with arguments */
+ if (owner != (uid_t) -1) {
+ buf.st_uid = owner;
+ }
+ if (group != (gid_t) -1) {
+ buf.st_gid = group;
+ }
+ pseudo_client_op(OP_CHOWN, 0, -1, dirfd, path, &buf);
+ /* just pretend we worked */
+ errno = save_errno;
+ rc = 0;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fclose.c b/ports/unix/guts/fclose.c
new file mode 100644
index 0000000..4469f5b
--- /dev/null
+++ b/ports/unix/guts/fclose.c
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_fclose(FILE *fp) {
+ * int rc = -1;
+ */
+
+ if (!fp) {
+ errno = EFAULT;
+ return -1;
+ }
+ int fd = fileno(fp);
+ pseudo_client_op(OP_CLOSE, 0, fd, -1, 0, 0);
+ rc = real_fclose(fp);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fdatasync.c b/ports/unix/guts/fdatasync.c
new file mode 100644
index 0000000..4aa77a8
--- /dev/null
+++ b/ports/unix/guts/fdatasync.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fdatasync(int fd)
+ * int rc = -1;
+ */
+
+ /* note: wrapper will never call this if PSEUDO_FORCE_ASYNC
+ * is defined.
+ */
+ rc = real_fdatasync(fd);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fopen.c b/ports/unix/guts/fopen.c
new file mode 100644
index 0000000..87c7d78
--- /dev/null
+++ b/ports/unix/guts/fopen.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008-2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static FILE *
+ * wrap_fopen(const char *path, const char *mode) {
+ * FILE * rc = 0;
+ */
+ PSEUDO_STATBUF buf;
+ int save_errno;
+ int existed = (base_stat(path, &buf) != -1);
+
+ rc = real_fopen(path, mode);
+ save_errno = errno;
+
+ if (rc) {
+ int fd = fileno(rc);
+
+ pseudo_debug(PDBGF_OP, "fopen '%s': fd %d <FILE %p>\n", path, fd, (void *) rc);
+ if (base_fstat(fd, &buf) != -1) {
+ if (!existed) {
+ real_fchmod(fd, PSEUDO_FS_MODE(0666 & ~pseudo_umask, 0));
+ pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, &buf);
+ } else {
+ pseudo_debug(PDBGF_CONSISTENCY, "fopen (fd %d) succeeded, but fstat failed (%s).\n",
+ fd, strerror(errno));
+ pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/freopen.c b/ports/unix/guts/freopen.c
new file mode 100644
index 0000000..e706d9f
--- /dev/null
+++ b/ports/unix/guts/freopen.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static FILE *
+ * wrap_freopen(const char *path, const char *mode, FILE *stream) {
+ * FILE * rc = NULL;
+ */
+ PSEUDO_STATBUF buf;
+ int save_errno;
+ int existed = (base_stat(path, &buf) != -1);
+
+ rc = real_freopen(path, mode, stream);
+ save_errno = errno;
+
+ if (rc) {
+ int fd = fileno(rc);
+
+ pseudo_debug(PDBGF_OP, "freopen '%s': fd %d\n", path, fd);
+ if (base_fstat(fd, &buf) != -1) {
+ if (!existed) {
+ real_fchmod(fd, PSEUDO_FS_MODE(0666 & ~pseudo_umask, 0));
+ pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, &buf);
+ } else {
+ pseudo_debug(PDBGF_OP, "fopen (fd %d) succeeded, but stat failed (%s).\n",
+ fd, strerror(errno));
+ pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fsync.c b/ports/unix/guts/fsync.c
new file mode 100644
index 0000000..6c87a56
--- /dev/null
+++ b/ports/unix/guts/fsync.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int fsync(int fd)
+ * int rc = -1;
+ */
+
+ /* note: wrapper will never call this if PSEUDO_FORCE_ASYNC
+ * is defined.
+ */
+ rc = real_fsync(fd);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/fts_open.c b/ports/unix/guts/fts_open.c
new file mode 100644
index 0000000..964314e
--- /dev/null
+++ b/ports/unix/guts/fts_open.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static FTS *
+ * wrap_fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)) {
+ * FTS * rc = NULL;
+ */
+ char **rpath_argv;
+ int args = 0;
+ int errored = 0;
+ int i;
+
+ if (!path_argv) {
+ errno = EFAULT;
+ return NULL;
+ }
+ /* count args */
+ for (i = 0; path_argv[i]; ++i) {
+ ++args;
+ }
+ rpath_argv = malloc((args + 1) * sizeof(*rpath_argv));
+ if (!rpath_argv) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ for (i = 0; i < args; ++i) {
+ rpath_argv[i] = PSEUDO_ROOT_PATH(AT_FDCWD, path_argv[i], AT_SYMLINK_NOFOLLOW);
+ if (!rpath_argv[i])
+ errored = 1;
+ else
+ rpath_argv[i] = strdup(rpath_argv[i]);
+ }
+
+ if (errored) {
+ errno = ENOMEM;
+ rc = NULL;
+ } else {
+ rc = real_fts_open(path_argv, options, compar);
+ }
+ for (i = 0; i < args; ++i)
+ free(rpath_argv[i]);
+ free(rpath_argv);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/ftw.c b/ports/unix/guts/ftw.c
new file mode 100644
index 0000000..0861194
--- /dev/null
+++ b/ports/unix/guts/ftw.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_ftw(const char *path, int (*fn)(const char *, const struct stat *, int), int nopenfd) {
+ * int rc = -1;
+ */
+
+ rc = real_ftw(path, fn, nopenfd);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/getcwd.c b/ports/unix/guts/getcwd.c
new file mode 100644
index 0000000..2915a18
--- /dev/null
+++ b/ports/unix/guts/getcwd.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_getcwd(char *buf, size_t size) {
+ * char * rc = NULL;
+ */
+ pseudo_debug(PDBGF_CLIENT, "wrap_getcwd: %p, %lu\n",
+ (void *) buf, (unsigned long) size);
+ if (!pseudo_cwd) {
+ pseudo_diag("Asked for CWD, but don't have it!\n");
+ errno = EACCES;
+ return NULL;
+ }
+ /* emulate Linux semantics in case of non-Linux systems. */
+ if (!buf) {
+ /* if we don't have a cwd, something's very wrong... */
+ if (!size) {
+ size = pseudo_cwd_len + 1;
+ if (pseudo_chroot_len && size >= pseudo_chroot_len &&
+ !memcmp(pseudo_cwd, pseudo_chroot, pseudo_chroot_len)) {
+ size -= pseudo_chroot_len;
+ /* if cwd is precisely the same as chroot, we
+ * actually want a /, not an empty string
+ */
+ if (size < 2)
+ size = 2;
+ }
+ }
+ if (size) {
+ buf = malloc(size);
+ } else {
+ pseudo_diag("can't figure out CWD: length %ld + 1 - %ld => %ld\n",
+ (unsigned long) pseudo_cwd_len,
+ (unsigned long) pseudo_chroot_len,
+ (unsigned long) size);
+ }
+ if (!buf) {
+ pseudo_diag("couldn't allocate requested CWD buffer - need %ld byes\n",
+ (unsigned long) size);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+ if (pseudo_cwd_len - (pseudo_cwd_rel - pseudo_cwd) >= size) {
+ pseudo_debug(PDBGF_CLIENT, "only %ld bytes available, need %ld (%ld + 1 - %ld)\n",
+ (unsigned long) size,
+ (unsigned long) pseudo_cwd_len + 1 - pseudo_chroot_len,
+ (unsigned long) pseudo_cwd_len,
+ (unsigned long) pseudo_chroot_len);
+ errno = ERANGE;
+ return NULL;
+ }
+ rc = buf;
+ pseudo_debug(PDBGF_CLIENT, "getcwd: copying %d (%d + 1 - %d) characters from <%s>.\n",
+ (int) ((pseudo_cwd_len + 1) - pseudo_chroot_len),
+ (int) pseudo_cwd_len, (int) pseudo_chroot_len,
+ pseudo_cwd_rel);
+ memcpy(buf, pseudo_cwd_rel, (pseudo_cwd_len + 1) - (pseudo_cwd_rel - pseudo_cwd));
+ if (!*buf) {
+ strcpy(buf, "/");
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/getwd.c b/ports/unix/guts/getwd.c
new file mode 100644
index 0000000..b1bcf90
--- /dev/null
+++ b/ports/unix/guts/getwd.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_getwd(char *buf) {
+ * char * rc = NULL;
+ */
+
+ pseudo_debug(PDBGF_CLIENT, "getwd (getcwd)\n");
+ rc = wrap_getcwd(buf, pseudo_path_max());
+ /* because it would violate everything we have ever known about
+ * UNIX for these functions to have the same errno semantics,
+ * that's why.
+ */
+ if (rc == NULL && errno == ERANGE )
+ errno = ENAMETOOLONG;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/glob.c b/ports/unix/guts/glob.c
new file mode 100644
index 0000000..0012179
--- /dev/null
+++ b/ports/unix/guts/glob.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) {
+ * int rc = -1;
+ */
+ char *rpattern = NULL;
+ int alloced = 0;
+
+ /* note: no canonicalization */
+ if (pattern && (*pattern == '/') && pseudo_chroot_len) {
+ size_t len = strlen(pattern) + pseudo_chroot_len + 2;
+ rpattern = malloc(len);
+ if (!rpattern) {
+ errno = ENOMEM;
+ return GLOB_NOSPACE;
+ }
+ snprintf(rpattern, len, "%s/%s", pseudo_chroot, pattern);
+ alloced = 1;
+ }
+
+ rc = real_glob(alloced ? rpattern : pattern, flags, errfunc, pglob);
+
+ free(rpattern);
+
+ if (rc == 0) {
+ unsigned int i;
+ for (i = 0; i < pglob->gl_pathc; ++i) {
+ pseudo_dechroot(pglob->gl_pathv[i], (size_t) -1);
+ }
+ }
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/lchown.c b/ports/unix/guts/lchown.c
new file mode 100644
index 0000000..60727d0
--- /dev/null
+++ b/ports/unix/guts/lchown.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2008,2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int lchown(const char *path, uid_t owner, gid_t group)
+ * int rc = -1;
+ */
+
+ rc = wrap_chown(path, owner, group);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/link.c b/ports/unix/guts/link.c
new file mode 100644
index 0000000..3b340ee
--- /dev/null
+++ b/ports/unix/guts/link.c
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_link(const char *oldname, const char *newname) {
+ * int rc = -1;
+ */
+ /* since 2.6.18 or so, linkat supports AT_SYMLINK_FOLLOW, which
+ * provides the behavior link() has on most non-Linux systems,
+ * but the default is not to follow symlinks. Better yet, it
+ * does NOT support AT_SYMLINK_NOFOLLOW! So define this in
+ * your port's portdefs.h or hope the default works for you.
+ */
+ rc = wrap_linkat(AT_FDCWD, oldname, AT_FDCWD, newname,
+ PSEUDO_LINK_SYMLINK_BEHAVIOR);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/linkat.c b/ports/unix/guts/linkat.c
new file mode 100644
index 0000000..ec27e47
--- /dev/null
+++ b/ports/unix/guts/linkat.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2012, 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int linkat(int olddirfd, const char *oldname, int newdirfd, const char *newname, int flags)
+ * int rc = -1;
+ */
+
+ int rc2, rflags, save_errno;
+ pseudo_msg_t *msg;
+ char *oldpath = NULL, *newpath = NULL;
+ PSEUDO_STATBUF buf;
+
+ /* This is gratuitously complicated. On Linux 2.6.18 and later,
+ * flags may contain AT_SYMLINK_FOLLOW, which implies following
+ * symlinks; otherwise, linkat() will *not* follow symlinks. FreeBSD
+ * appears to use the same semantics.
+ *
+ * So on Darwin, always pass AT_SYMLINK_FOLLOW, because the
+ * alternative doesn't work. And never pass AT_SYMLINK_NOFOLLOW
+ * because that's not a valid flag to linkat().
+ *
+ * So we need a flag for path resolution which is AT_SYMLINK_NOFOLLOW
+ * unless AT_SYMLINK_FOLLOW was specified, in which case it's 0.
+ */
+
+ rflags = (flags & AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+#endif
+ oldpath = pseudo_root_path(__func__, __LINE__, olddirfd, oldname, rflags);
+ newpath = pseudo_root_path(__func__, __LINE__, newdirfd, newname, AT_SYMLINK_NOFOLLOW);
+ rc = real_link(oldpath, newpath);
+ save_errno = errno;
+ if (rc == -1) {
+ errno = save_errno;
+ return rc;
+ }
+
+ /* if we got this far, the link succeeded, and oldpath and newpath
+ * are the newly-allocated canonical paths. If OS, filesystem, or
+ * the flags value prevent hard linking to symlinks, the resolved
+ * path should be the target's path anyway, so lstat is safe here.
+ */
+ /* find the target: */
+ rc2 = base_lstat(oldpath, &buf);
+ if (rc2 == -1) {
+ pseudo_diag("Fatal: Tried to stat '%s' after linking it, but failed: %s.\n",
+ oldpath, strerror(errno));
+ errno = ENOENT;
+ return rc;
+ }
+ msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, &buf);
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(&buf, msg);
+ }
+ /* Long story short: I am pretty sure we still want OP_LINK even
+ * if the thing linked is a symlink.
+ */
+ pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &buf);
+
+ errno = save_errno;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/lutimes.c b/ports/unix/guts/lutimes.c
new file mode 100644
index 0000000..cdadbbd
--- /dev/null
+++ b/ports/unix/guts/lutimes.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_lutimes(const char *path, const struct timeval *tv) {
+ * int rc = -1;
+ */
+
+ rc = real_lutimes(path, tv);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mkdir.c b/ports/unix/guts/mkdir.c
new file mode 100644
index 0000000..9f116e2
--- /dev/null
+++ b/ports/unix/guts/mkdir.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_mkdir(const char *path, mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_mkdirat(AT_FDCWD, path, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mkdirat.c b/ports/unix/guts/mkdirat.c
new file mode 100644
index 0000000..ef2e3a1
--- /dev/null
+++ b/ports/unix/guts/mkdirat.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2008-2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_mkdirat(int dirfd, const char *path, mode_t mode) {
+ * int rc = -1;
+ */
+ /* mask out mode bits appropriately */
+ mode = mode & ~pseudo_umask;
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ rc = real_mkdir(path, PSEUDO_FS_MODE(mode, 1));
+#else
+ rc = real_mkdirat(dirfd, path, PSEUDO_FS_MODE(mode, 1));
+#endif
+ if (rc != -1) {
+ PSEUDO_STATBUF buf;
+ int stat_rc;
+ int save_errno = errno;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ stat_rc = base_lstat(path, &buf);
+#else
+ stat_rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW);
+#endif
+ if (stat_rc != -1) {
+ buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode);
+ pseudo_client_op(OP_MKDIR, 0, -1, dirfd, path, &buf);
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ real_fchmod(path, PSEUDO_FS_MODE(mode, 1));
+#else
+ real_fchmodat(dirfd, path, PSEUDO_FS_MODE(mode, 1), 0);
+#endif
+ } else {
+ pseudo_debug(PDBGF_OP, "mkdir of %s succeeded, but stat failed: %s\n",
+ path, strerror(errno));
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mkdtemp.c b/ports/unix/guts/mkdtemp.c
new file mode 100644
index 0000000..5337661
--- /dev/null
+++ b/ports/unix/guts/mkdtemp.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_mkdtemp(char *template) {
+ * char * rc = NULL;
+ */
+ PSEUDO_STATBUF buf;
+ int save_errno;
+ size_t len;
+ char *tmp_template;
+
+ if (!template) {
+ errno = EFAULT;
+ return NULL;
+ }
+
+ len = strlen(template);
+ tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW);
+
+ if (!tmp_template) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ rc = real_mkdtemp(tmp_template);
+
+ if (rc != NULL) {
+ save_errno = errno;
+
+ if (base_stat(rc, &buf) != -1) {
+ pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf);
+ } else {
+ pseudo_debug(PDBGF_CONSISTENCY, "mkdtemp (path %s) succeeded, but fstat failed (%s).\n",
+ rc, strerror(errno));
+ }
+ errno = save_errno;
+ }
+ /* mkdtemp only changes the XXXXXX at the end. */
+ memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6);
+ rc = template;
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mkfifo.c b/ports/unix/guts/mkfifo.c
new file mode 100644
index 0000000..32f79fb
--- /dev/null
+++ b/ports/unix/guts/mkfifo.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_mkfifo(const char *path, mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_mkfifoat(AT_FDCWD, path, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mkfifoat.c b/ports/unix/guts/mkfifoat.c
new file mode 100644
index 0000000..6947e70
--- /dev/null
+++ b/ports/unix/guts/mkfifoat.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_mkfifoat(int dirfd, const char *path, mode_t mode) {
+ * int rc = -1;
+ */
+
+ pseudo_msg_t *msg;
+ PSEUDO_STATBUF buf;
+ int save_errno = errno;
+
+ /* mask out mode bits appropriately */
+ mode = mode & ~pseudo_umask;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ rc = base_stat(path, &buf);
+#else
+ rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW);
+#endif
+ if (rc != -1) {
+ /* if we can stat the file, you can't mkfifo it */
+ errno = EEXIST;
+ return -1;
+ }
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real_mkfifo(path, PSEUDO_FS_MODE(mode, 0));
+ if (rc == -1) {
+ return -1;
+ }
+ save_errno = errno;
+ rc = base_stat(path, &buf);
+ real_chmod(path, PSEUDO_FS_MODE(mode, 0));
+#else
+ rc = real_mkfifoat(dirfd, path, PSEUDO_FS_MODE(mode, 0));
+ if (rc == -1) {
+ return -1;
+ }
+ save_errno = errno;
+ rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW);
+ real_fchmodat(dirfd, path, PSEUDO_FS_MODE(mode, 0), 0);
+#endif
+ /* if the stat failed, we are going to give up and nuke
+ * any file we may have created, and hope for the best.
+ */
+ if (rc == 0) {
+ buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode);
+ /* mkfifo/mknod are the same op, in that they create a file
+ * with a non-file type.
+ */
+ msg = pseudo_client_op(OP_MKNOD, 0, -1, dirfd, path, &buf);
+ if (msg && msg->result != RESULT_SUCCEED) {
+ errno = EPERM;
+ rc = -1;
+ } else {
+ /* just pretend we worked */
+ errno = save_errno;
+ rc = 0;
+ }
+ }
+ if (rc == -1) {
+ save_errno = errno;
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ real_unlink(path);
+#else
+ real_unlinkat(dirfd, path, AT_SYMLINK_NOFOLLOW);
+#endif
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mknod.c b/ports/unix/guts/mknod.c
new file mode 100644
index 0000000..eeca65d
--- /dev/null
+++ b/ports/unix/guts/mknod.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2011,2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int mknod(const char *path, mode_t mode, dev_t dev)
+ * int rc = -1;
+ */
+
+ rc = wrap_mknodat(AT_FDCWD, path, mode, dev);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mknodat.c b/ports/unix/guts/mknodat.c
new file mode 100644
index 0000000..76e4dd9
--- /dev/null
+++ b/ports/unix/guts/mknodat.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2011 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev)
+ * int rc = -1;
+ */
+
+ pseudo_msg_t *msg;
+ PSEUDO_STATBUF buf;
+ int save_errno = errno;
+
+ /* mask out mode bits appropriately */
+ mode = mode & ~pseudo_umask;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ rc = base_stat(path, &buf);
+#else
+ rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW);
+#endif
+ if (rc != -1) {
+ /* if we can stat the file, you can't mknod it */
+ errno = EEXIST;
+ return -1;
+ }
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real_open(path, O_CREAT | O_WRONLY | O_EXCL,
+ PSEUDO_FS_MODE(mode, 0));
+#else
+ rc = real_openat(dirfd, path, O_CREAT | O_WRONLY | O_EXCL,
+ PSEUDO_FS_MODE(mode, 0));
+#endif
+ if (rc == -1) {
+ return -1;
+ }
+ real_fchmod(rc, PSEUDO_FS_MODE(mode, 0));
+ base_fstat(rc, &buf);
+ /* mknod does not really open the file. We don't have
+ * to use wrap_close because we've never exposed this file
+ * descriptor to the client code.
+ */
+ real_close(rc);
+
+ /* mask in the mode type bits again */
+ buf.st_mode = (PSEUDO_DB_MODE(buf.st_mode, mode) & 07777) |
+ (mode & ~07777);
+ buf.st_rdev = dev;
+ msg = pseudo_client_op(OP_MKNOD, 0, -1, dirfd, path, &buf);
+ if (msg && msg->result != RESULT_SUCCEED) {
+ errno = EPERM;
+ rc = -1;
+ } else {
+ /* just pretend we worked */
+ errno = save_errno;
+ rc = 0;
+ }
+ if (rc == -1) {
+ save_errno = errno;
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ real_unlink(path);
+#else
+ real_unlinkat(dirfd, path, AT_SYMLINK_NOFOLLOW);
+#endif
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mkstemp.c b/ports/unix/guts/mkstemp.c
new file mode 100644
index 0000000..1e2b026
--- /dev/null
+++ b/ports/unix/guts/mkstemp.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2008-2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_mkstemp(char *template) {
+ * int rc = -1;
+ */
+ PSEUDO_STATBUF buf;
+ int save_errno;
+ size_t len;
+ char *tmp_template;
+
+ if (!template) {
+ errno = EFAULT;
+ return 0;
+ }
+
+ len = strlen(template);
+ tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW);
+
+ if (!tmp_template) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ rc = real_mkstemp(tmp_template);
+
+ if (rc != -1) {
+ save_errno = errno;
+
+ if (base_fstat(rc, &buf) != -1) {
+ real_fchmod(rc, PSEUDO_FS_MODE(0600, 0));
+ pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf);
+ pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, &buf);
+ } else {
+ pseudo_debug(PDBGF_CONSISTENCY, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n",
+ rc, strerror(errno));
+ pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, 0);
+ }
+ errno = save_errno;
+ }
+ /* mkstemp only changes the XXXXXX at the end. */
+ memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6);
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/mktemp.c b/ports/unix/guts/mktemp.c
new file mode 100644
index 0000000..a39d1b7
--- /dev/null
+++ b/ports/unix/guts/mktemp.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_mktemp(char *template) {
+ * char * rc = NULL;
+ */
+ size_t len;
+ char *tmp_template;
+
+ if (!template) {
+ errno = EFAULT;
+ return NULL;
+ }
+
+ len = strlen(template);
+ tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW);
+
+ if (!tmp_template) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ rc = real_mktemp(tmp_template);
+
+ /* mktemp only changes the XXXXXX at the end, and never created
+ * a file -- note the race condition implied here.
+ */
+ memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6);
+ rc = template;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/msync.c b/ports/unix/guts/msync.c
new file mode 100644
index 0000000..fbc5e26
--- /dev/null
+++ b/ports/unix/guts/msync.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int msync(void *addr, size_t length, int flags)
+ * int rc = -1;
+ */
+
+ /* note: wrapper will never call this if PSEUDO_FORCE_ASYNC
+ * is defined.
+ */
+ rc = real_msync(addr, length, flags);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/nftw.c b/ports/unix/guts/nftw.c
new file mode 100644
index 0000000..73daec8
--- /dev/null
+++ b/ports/unix/guts/nftw.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *), int nopenfd, int flag) {
+ * int rc = -1;
+ */
+
+ rc = real_nftw(path, fn, nopenfd, flag);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/opendir.c b/ports/unix/guts/opendir.c
new file mode 100644
index 0000000..c8a78f8
--- /dev/null
+++ b/ports/unix/guts/opendir.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static DIR *
+ * wrap_opendir(const char *path) {
+ * DIR * rc = NULL;
+ */
+ PSEUDO_STATBUF buf;
+ int save_errno;
+
+ rc = real_opendir(path);
+ if (rc) {
+ int fd;
+ save_errno = errno;
+ fd = dirfd(rc);
+ if (base_fstat(fd, &buf) == -1) {
+ pseudo_debug(PDBGF_CONSISTENCY, "diropen (fd %d) succeeded, but fstat failed (%s).\n",
+ fd, strerror(errno));
+ pseudo_client_op(OP_OPEN, PSA_READ, fd, -1, path, 0);
+ } else {
+ pseudo_client_op(OP_OPEN, PSA_READ, fd, -1, path, &buf);
+ }
+
+
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/pathconf.c b/ports/unix/guts/pathconf.c
new file mode 100644
index 0000000..c6caa34
--- /dev/null
+++ b/ports/unix/guts/pathconf.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static long
+ * wrap_pathconf(const char *path, int name) {
+ * long rc = -1;
+ */
+
+ rc = real_pathconf(path, name);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/popen.c b/ports/unix/guts/popen.c
new file mode 100644
index 0000000..5d44c0e
--- /dev/null
+++ b/ports/unix/guts/popen.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * FILE *popen(const char *command, const char *mode)
+ * FILE *rc = NULL;
+ */
+ /* on at least some systems, popen() calls fork and exec
+ * in ways that avoid our usual enforcement of the environment.
+ */
+ pseudo_setupenv();
+ if (pseudo_has_unload(NULL))
+ pseudo_dropenv();
+
+ rc = real_popen(command, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/readlink.c b/ports/unix/guts/readlink.c
new file mode 100644
index 0000000..18d9dc7
--- /dev/null
+++ b/ports/unix/guts/readlink.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static ssize_t
+ * wrap_readlink(const char *path, char *buf, size_t bufsiz) {
+ * ssize_t rc = -1;
+ */
+
+ rc = wrap_readlinkat(AT_FDCWD, path, buf, bufsiz);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/readlinkat.c b/ports/unix/guts/readlinkat.c
new file mode 100644
index 0000000..5282e2b
--- /dev/null
+++ b/ports/unix/guts/readlinkat.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static ssize_t
+ * wrap_readlinkat(int dirfd, const char *path, char *buf, size_t bufsiz) {
+ * ssize_t rc = -1;
+ */
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ rc = real_readlink(path, buf, bufsiz);
+#else
+ rc = real_readlinkat(dirfd, path, buf, bufsiz);
+#endif
+
+ if (rc > 0) {
+ rc = pseudo_dechroot(buf, rc);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c
new file mode 100644
index 0000000..a59808d
--- /dev/null
+++ b/ports/unix/guts/realpath.c
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_realpath(const char *name, char *resolved_name) {
+ * char * rc = NULL;
+ */
+ char *rname = PSEUDO_ROOT_PATH(AT_FDCWD, name, 0);
+ ssize_t len;
+ if (!rname) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+ if ((len = strlen(rname)) >= pseudo_sys_path_max()) {
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+ if (resolved_name) {
+ memcpy(resolved_name, rname, len + 1);
+ rc = resolved_name;
+ } else {
+ rc = strdup(rname);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/remove.c b/ports/unix/guts/remove.c
new file mode 100644
index 0000000..4e2cecb
--- /dev/null
+++ b/ports/unix/guts/remove.c
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2008-2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_remove(const char *path) {
+ * int rc = -1;
+ */
+ PSEUDO_STATBUF buf;
+ if (base_lstat(path, &buf) == -1) {
+ errno = ENOENT;
+ return -1;
+ }
+ if (S_ISDIR(buf.st_mode)) {
+ rc = wrap_rmdir(path);
+ } else {
+ rc = wrap_unlink(path);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/rename.c b/ports/unix/guts/rename.c
new file mode 100644
index 0000000..b8ee8b0
--- /dev/null
+++ b/ports/unix/guts/rename.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2008-2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_rename(const char *oldpath, const char *newpath) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ PSEUDO_STATBUF oldbuf, newbuf;
+ int oldrc, newrc;
+ int save_errno;
+ int old_db_entry = 0;
+ int may_unlinked = 0;
+
+ pseudo_debug(PDBGF_OP, "rename: %s->%s\n",
+ oldpath ? oldpath : "<nil>",
+ newpath ? newpath : "<nil>");
+
+ if (!oldpath || !newpath) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ save_errno = errno;
+
+ newrc = base_lstat(newpath, &newbuf);
+ oldrc = base_lstat(oldpath, &oldbuf);
+
+ errno = save_errno;
+
+ /* newpath must be removed. */
+ /* as with unlink, we have to mark that the file may get deleted */
+ msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, -1, newpath, newrc ? NULL : &newbuf);
+ if (msg && msg->result == RESULT_SUCCEED)
+ may_unlinked = 1;
+ msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, oldrc ? NULL : &oldbuf);
+ if (msg && msg->result == RESULT_SUCCEED)
+ old_db_entry = 1;
+ rc = real_rename(oldpath, newpath);
+ save_errno = errno;
+ if (may_unlinked) {
+ if (rc == -1) {
+ /* since we failed, that wasn't really unlinked -- put
+ * it back.
+ */
+ pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, newpath, &newbuf);
+ } else {
+ /* confirm that the file was removed */
+ pseudo_client_op(OP_DID_UNLINK, 0, -1, -1, newpath, &newbuf);
+ }
+ }
+ if (rc == -1) {
+ /* and we're done. */
+ errno = save_errno;
+ return rc;
+ }
+ save_errno = errno;
+ /* nothing to do for a "rename" of a link to itself */
+ if (newrc != -1 && oldrc != -1 &&
+ newbuf.st_dev == oldbuf.st_dev &&
+ newbuf.st_ino == oldbuf.st_ino) {
+ return rc;
+ }
+
+ /* rename(3) is not mv(1). rename(file, dir) fails; you must provide
+ * the corrected path yourself. You can rename over a directory only
+ * if the source is a directory. Symlinks are simply removed.
+ *
+ * If we got here, the real rename call succeeded. That means newpath
+ * has been unlinked and oldpath has been linked to it.
+ *
+ * There are a ton of special cases to error check. I don't check
+ * for any of them, because in every such case, the underlying rename
+ * failed, and there is nothing to do.
+ * The only tricky part is that, because we used to ignore symlinks,
+ * we may have to rename or remove directory trees even though in
+ * theory rename can never destroy a directory tree.
+ */
+ if (!old_db_entry) {
+ /* create an entry under the old name, which will then be
+ * renamed; this way, children would get renamed too, if there
+ * were any.
+ */
+ if (newrc == 0) {
+ if (newbuf.st_dev != oldbuf.st_dev) {
+ oldbuf.st_dev = newbuf.st_dev;
+ oldbuf.st_ino = newbuf.st_ino;
+ }
+ }
+ pseudo_debug(PDBGF_FILE, "creating new '%s' [%llu] to rename\n",
+ oldpath, (unsigned long long) oldbuf.st_ino);
+ pseudo_client_op(OP_LINK, 0, -1, -1, oldpath, &oldbuf);
+ }
+ pseudo_client_op(OP_RENAME, 0, -1, -1, newpath, &oldbuf, oldpath);
+
+ errno = save_errno;
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/renameat.c b/ports/unix/guts/renameat.c
new file mode 100644
index 0000000..ade0509
--- /dev/null
+++ b/ports/unix/guts/renameat.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2008-2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ PSEUDO_STATBUF oldbuf, newbuf;
+ int oldrc, newrc;
+ int save_errno;
+ int old_db_entry = 0;
+
+ pseudo_debug(PDBGF_FILE, "renameat: %d,%s->%d,%s\n",
+ olddirfd, oldpath ? oldpath : "<nil>",
+ newdirfd, newpath ? newpath : "<nil>");
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+#endif
+
+ if (!oldpath || !newpath) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ save_errno = errno;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ newrc = base_lstat(newpath, &newbuf);
+ oldrc = base_lstat(oldpath, &oldbuf);
+#else
+ oldrc = base_fstatat(olddirfd, oldpath, &oldbuf, AT_SYMLINK_NOFOLLOW);
+ newrc = base_fstatat(newdirfd, newpath, &newbuf, AT_SYMLINK_NOFOLLOW);
+#endif
+
+ errno = save_errno;
+
+ /* newpath must be removed. */
+ /* as with unlink, we have to mark that the file may get deleted */
+ msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, newdirfd, newpath, newrc ? NULL : &newbuf);
+ if (msg && msg->result == RESULT_SUCCEED)
+ old_db_entry = 1;
+ rc = real_renameat(olddirfd, oldpath, newdirfd, newpath);
+ save_errno = errno;
+ if (old_db_entry) {
+ if (rc == -1) {
+ /* since we failed, that wasn't really unlinked -- put
+ * it back.
+ */
+ pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, newdirfd, newpath, &newbuf);
+ } else {
+ /* confirm that the file was removed */
+ pseudo_client_op(OP_DID_UNLINK, 0, -1, newdirfd, newpath, &newbuf);
+ }
+ }
+ if (rc == -1) {
+ /* and we're done. */
+ errno = save_errno;
+ return rc;
+ }
+ save_errno = errno;
+ /* nothing to do for a "rename" of a link to itself */
+ if (newrc != -1 && oldrc != -1 &&
+ newbuf.st_dev == oldbuf.st_dev &&
+ newbuf.st_ino == oldbuf.st_ino) {
+ return rc;
+ }
+
+ /* rename(3) is not mv(1). rename(file, dir) fails; you must provide
+ * the corrected path yourself. You can rename over a directory only
+ * if the source is a directory. Symlinks are simply removed.
+ *
+ * If we got here, the real rename call succeeded. That means newpath
+ * has been unlinked and oldpath has been linked to it.
+ *
+ * There are a ton of special cases to error check. I don't check
+ * for any of them, because in every such case, the underlying rename
+ * failed, and there is nothing to do.
+ * The only tricky part is that, because we used to ignore symlinks,
+ * we may have to rename or remove directory trees even though in
+ * theory rename can never destroy a directory tree.
+ */
+ if (!old_db_entry) {
+ /* create an entry under the old name, which will then be
+ * renamed; this way, children would get renamed too, if there
+ * were any.
+ */
+ if (newrc == 0) {
+ if (newbuf.st_dev != oldbuf.st_dev) {
+ oldbuf.st_dev = newbuf.st_dev;
+ oldbuf.st_ino = newbuf.st_ino;
+ }
+ }
+ pseudo_debug(PDBGF_OP, "creating new '%s' [%llu] to rename\n",
+ oldpath, (unsigned long long) oldbuf.st_ino);
+ pseudo_client_op(OP_LINK, 0, -1, olddirfd, oldpath, &oldbuf);
+ }
+ /* special case: use 'fd' for olddirfd, because
+ * we know it has no other meaning for RENAME
+ */
+ pseudo_client_op(OP_RENAME, 0, olddirfd, newdirfd, newpath, &oldbuf, oldpath);
+
+ errno = save_errno;
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/rmdir.c b/ports/unix/guts/rmdir.c
new file mode 100644
index 0000000..ebc522a
--- /dev/null
+++ b/ports/unix/guts/rmdir.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008-2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_rmdir(const char *path) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ PSEUDO_STATBUF buf;
+ int save_errno;
+ int old_db_entry = 0;
+
+ rc = base_lstat(path, &buf);
+ if (rc == -1) {
+ return rc;
+ }
+ msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, -1, path, &buf);
+ if (msg && msg->result == RESULT_SUCCEED)
+ old_db_entry = 1;
+ rc = real_rmdir(path);
+ if (old_db_entry) {
+ if (rc == -1) {
+ save_errno = errno;
+ pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, path, &buf);
+ errno = save_errno;
+ } else {
+ pseudo_client_op(OP_DID_UNLINK, 0, -1, -1, path, &buf);
+ }
+ } else {
+ pseudo_debug(PDBGF_FILE, "rmdir on <%s>, not in database, no effect.\n", path);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/symlink.c b/ports/unix/guts/symlink.c
new file mode 100644
index 0000000..487c135
--- /dev/null
+++ b/ports/unix/guts/symlink.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_symlink(const char *oldname, const char *newpath) {
+ * int rc = -1;
+ */
+
+ rc = wrap_symlinkat(oldname, AT_FDCWD, newpath);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/symlinkat.c b/ports/unix/guts/symlinkat.c
new file mode 100644
index 0000000..1346db1
--- /dev/null
+++ b/ports/unix/guts/symlinkat.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2008-2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_symlinkat(const char *oldname, int dirfd, const char *newpath) {
+ * int rc = -1;
+ */
+ PSEUDO_STATBUF buf;
+ char *roldname = 0;
+
+ if (oldname[0] == '/' && pseudo_chroot_len && !pseudo_nosymlinkexp) {
+ size_t len = pseudo_chroot_len + strlen(oldname) + 1;
+ roldname = malloc(len);
+ snprintf(roldname, len, "%s%s", pseudo_chroot, oldname);
+ }
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ rc = real_symlink(roldname ? roldname : oldname, newpath);
+#else
+ rc = real_symlinkat(roldname ? roldname : oldname, dirfd, newpath);
+#endif
+
+ if (rc == -1) {
+ free(roldname);
+ return rc;
+ }
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = base_lstat(newpath, &buf);
+#else
+ rc = base_fstatat(dirfd, newpath, &buf, AT_SYMLINK_NOFOLLOW);
+#endif
+ if (rc == -1) {
+ int save_errno = errno;
+ pseudo_diag("symlinkat: couldn't stat '%s' even though symlink creation succeeded (%s).\n",
+ newpath, strerror(errno));
+ errno = save_errno;
+ free(roldname);
+ return rc;
+ }
+ /* just record the entry */
+ pseudo_client_op(OP_SYMLINK, 0, -1, dirfd, newpath, &buf);
+
+ free(roldname);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/sync.c b/ports/unix/guts/sync.c
new file mode 100644
index 0000000..c5d9554
--- /dev/null
+++ b/ports/unix/guts/sync.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * void sync(void)
+ *
+ */
+
+ /* note: wrapper will never call this if PSEUDO_FORCE_ASYNC
+ * is defined.
+ */
+ (void) real_sync();
+
+/* return;
+ * }
+ */
diff --git a/ports/unix/guts/sync_file_range.c b/ports/unix/guts/sync_file_range.c
new file mode 100644
index 0000000..03cfc6c
--- /dev/null
+++ b/ports/unix/guts/sync_file_range.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags)
+ * int rc = -1;
+ */
+
+ rc = real_sync_file_range(fd, offset, nbytes, flags);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/syncfs.c b/ports/unix/guts/syncfs.c
new file mode 100644
index 0000000..2c9a685
--- /dev/null
+++ b/ports/unix/guts/syncfs.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2013 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int syncfs(int fd)
+ * int rc = -1;
+ */
+
+ rc = real_syncfs(fd);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/system.c b/ports/unix/guts/system.c
new file mode 100644
index 0000000..6351592
--- /dev/null
+++ b/ports/unix/guts/system.c
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * int system(const char *command)
+ * int rc = -1;
+ */
+ if (!command)
+ return 1;
+
+ pseudo_setupenv();
+ if (pseudo_has_unload(NULL))
+ pseudo_dropenv();
+
+ rc = real_system(command);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/tempnam.c b/ports/unix/guts/tempnam.c
new file mode 100644
index 0000000..9b0257f
--- /dev/null
+++ b/ports/unix/guts/tempnam.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_tempnam(const char *template, const char *pfx) {
+ * char * rc = NULL;
+ */
+ /* let gcc know we ignored these on purpose */
+ (void) template;
+ (void) pfx;
+ pseudo_diag("tempnam() is so ludicrously insecure as to defy implementation.");
+ errno = ENOMEM;
+ rc = NULL;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/tmpnam.c b/ports/unix/guts/tmpnam.c
new file mode 100644
index 0000000..3fece57
--- /dev/null
+++ b/ports/unix/guts/tmpnam.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static char *
+ * wrap_tmpnam(char *s) {
+ * char * rc = NULL;
+ */
+
+ /* let gcc know we're ignoring this */
+ (void) s;
+ pseudo_diag("tmpnam() is so ludicrously insecure as to defy implementation.");
+ errno = ENOMEM;
+ rc = NULL;
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/truncate.c b/ports/unix/guts/truncate.c
new file mode 100644
index 0000000..6a19a50
--- /dev/null
+++ b/ports/unix/guts/truncate.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_truncate(const char *path, off_t length) {
+ * int rc = -1;
+ */
+
+ rc = real_truncate(path, length);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/umask.c b/ports/unix/guts/umask.c
new file mode 100644
index 0000000..6b060d3
--- /dev/null
+++ b/ports/unix/guts/umask.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2014 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * mode_t umask(mode_t mask)
+ * mode_t rc = 0;
+ */
+
+ pseudo_umask = mask;
+ rc = real_umask(mask);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/unlink.c b/ports/unix/guts/unlink.c
new file mode 100644
index 0000000..d8a5d01
--- /dev/null
+++ b/ports/unix/guts/unlink.c
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2008-2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_unlink(const char *path) {
+ * int rc = -1;
+ */
+
+ rc = wrap_unlinkat(AT_FDCWD, path, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/unlinkat.c b/ports/unix/guts/unlinkat.c
new file mode 100644
index 0000000..e723a01
--- /dev/null
+++ b/ports/unix/guts/unlinkat.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2008-2010, 2012 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_unlinkat(int dirfd, const char *path, int rflags) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ int save_errno;
+ PSEUDO_STATBUF buf;
+ int old_db_entry = 0;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (rflags) {
+ /* the only supported flag is AT_REMOVEDIR. We'd never call
+ * with that flag unless the real AT functions exist, so
+ * something must have gone horribly wrong....
+ */
+ pseudo_diag("wrap_unlinkat called with flags (0x%x), path '%s'\n",
+ rflags, path ? path : "<nil>");
+ errno = ENOSYS;
+ return -1;
+ }
+#endif
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = base_lstat(path, &buf);
+#else
+ rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW);
+#endif
+ if (rc == -1) {
+ return rc;
+ }
+ msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, dirfd, path, &buf);
+ if (msg && msg->result == RESULT_SUCCEED)
+ old_db_entry = 1;
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real_unlink(path);
+#else
+ rc = real_unlinkat(dirfd, path, rflags);
+#endif
+ if (old_db_entry) {
+ if (rc == -1) {
+ save_errno = errno;
+ pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, path, &buf);
+ errno = save_errno;
+ } else {
+ pseudo_client_op(OP_DID_UNLINK, 0, -1, -1, path, &buf);
+ }
+ } else {
+ pseudo_debug(PDBGF_FILE, "unlink on <%s>, not in database, no effect.\n", path);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/utime.c b/ports/unix/guts/utime.c
new file mode 100644
index 0000000..ff65237
--- /dev/null
+++ b/ports/unix/guts/utime.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_utime(const char *path, const struct utimbuf *buf) {
+ * int rc = -1;
+ */
+ rc = real_utime(path, buf);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/guts/utimes.c b/ports/unix/guts/utimes.c
new file mode 100644
index 0000000..69ad949
--- /dev/null
+++ b/ports/unix/guts/utimes.c
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2010 Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * static int
+ * wrap_utimes(const char *path, const struct timeval *times) {
+ * int rc = -1;
+ */
+ rc = real_utimes(path, times);
+
+/* return rc;
+ * }
+ */
diff --git a/ports/unix/pseudo_wrappers.c b/ports/unix/pseudo_wrappers.c
new file mode 100644
index 0000000..b825d56
--- /dev/null
+++ b/ports/unix/pseudo_wrappers.c
@@ -0,0 +1,50 @@
+FILE *
+popen(const char *command, const char *mode) {
+ sigset_t saved;
+
+ FILE *rc = NULL;
+
+ if (!pseudo_check_wrappers() || !real_popen) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("popen");
+ return rc;
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "called: popen\n");
+ pseudo_sigblock(&saved);
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ return NULL;
+ }
+
+ int save_errno;
+ /* exec*() use this to restore the sig mask */
+ pseudo_saved_sigmask = saved;
+ rc = wrap_popen(command, mode);
+
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+#if 0
+/* This can cause hangs on some recentish systems which use locale
+ * stuff for strerror...
+ */
+ pseudo_debug(PDBGF_WRAPPER, "completed: popen (maybe: %s)\n", strerror(save_errno));
+#endif
+ pseudo_debug(PDBGF_WRAPPER, "completed: popen (errno: %d)\n", save_errno);
+ errno = save_errno;
+ return rc;
+}
+
+static FILE *
+wrap_popen(const char *command, const char *mode) {
+ FILE *rc = NULL;
+
+
+
+#include "guts/popen.c"
+
+ return rc;
+}
+
diff --git a/ports/unix/wrapfuncs.in b/ports/unix/wrapfuncs.in
new file mode 100644
index 0000000..5f30ae6
--- /dev/null
+++ b/ports/unix/wrapfuncs.in
@@ -0,0 +1,70 @@
+int creat(const char *path, mode_t mode);
+char *getcwd(char *buf, size_t size);
+char *getwd(char *buf);
+int close(int fd);
+int fchmod(int fd, mode_t mode);
+int fchown(int fd, uid_t owner, gid_t group);
+int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW */
+int dup2(int oldfd, int newfd);
+int dup(int fd);
+int chdir(const char *path);
+int fchdir(int dirfd);
+int access(const char *path, int mode);
+FTS *fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)); /* inode64=1 */
+int ftw(const char *path, int (*fn)(const char *, const struct stat *, int), int nopenfd);
+int nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *), int nopenfd, int flag);
+int glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob);
+int lutimes(const char *path, const struct timeval *tv); /* flags=AT_SYMLINK_NOFOLLOW */
+char *mkdtemp(char *template);
+char *mktemp(char *template);
+long pathconf(const char *path, int name);
+char *realpath(const char *name, char *resolved_name); /* version="GLIBC_2.3" */
+int remove(const char *path); /* flags=AT_SYMLINK_NOFOLLOW */
+DIR *opendir(const char *path);
+int closedir(DIR *dirp);
+char *tempnam(const char *template, const char *pfx);
+char *tmpnam(char *s);
+int truncate(const char *path, off_t length);
+int utime(const char *path, const struct utimbuf *buf);
+int utimes(const char *path, const struct timeval *times);
+# needed because libc stdio does horrible things with inline asm syscalls
+FILE *fopen(const char *path, const char *mode);
+int fclose(FILE *fp);
+FILE *freopen(const char *path, const char *mode, FILE *stream);
+int chroot(const char *path);
+int acct(const char *path);
+int chmod(const char *path, mode_t mode);
+int chown(const char *path, uid_t owner, gid_t group);
+int fchmodat(int dirfd, const char *path, mode_t mode, int flags);
+int fchownat(int dirfd, const char *path, uid_t owner, gid_t group, int flags);
+int link(const char *oldname, const char *newname); /* flags=AT_SYMLINK_NOFOLLOW */
+int linkat(int olddirfd, const char *oldname, int newdirfd, const char *newname, int flags);
+int mkdir(const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */
+int mkdirat(int dirfd, const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */
+int mkfifo(const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */
+int mkfifoat(int dirfd, const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */
+int mknod(const char *path, mode_t mode, dev_t dev); /* flags=AT_SYMLINK_NOFOLLOW */
+int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev); /* flags=AT_SYMLINK_NOFOLLOW */
+int mkstemp(char *template); /* flags=AT_SYMLINK_NOFOLLOW */
+int rename(const char *oldpath, const char *newpath); /* flags=AT_SYMLINK_NOFOLLOW */
+int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); /* flags=AT_SYMLINK_NOFOLLOW */
+int rmdir(const char *path); /* flags=AT_SYMLINK_NOFOLLOW */
+int symlink(const char *oldname, const char *newpath); /* flags=AT_SYMLINK_NOFOLLOW */
+int symlinkat(const char *oldname, int dirfd, const char *newpath); /* flags=AT_SYMLINK_NOFOLLOW */
+int unlink(const char *path); /* flags=AT_SYMLINK_NOFOLLOW */
+int unlinkat(int dirfd, const char *path, int rflags); /* flags=AT_SYMLINK_NOFOLLOW */
+# primarily for use with chroot()
+ssize_t readlink(const char *path, char *buf, size_t bufsiz); /* flags=AT_SYMLINK_NOFOLLOW */
+ssize_t readlinkat(int dirfd, const char *path, char *buf, size_t bufsiz); /* flags=AT_SYMLINK_NOFOLLOW */
+int system(const char *command);
+FILE *popen(const char *command, const char *mode); /* hand_wrapped=1 */
+# Based on experiments by Richard Purdie: Allow pseudo to eliminate
+# sync-type operations globally, mostly relevant for performance reasons
+# during filesystem assembly.
+int fsync(int fd); /* async_skip=0 */
+int fdatasync(int fd); /* async_skip=0 */
+void sync(void); /* async_skip= */
+int syncfs(int fd); /* async_skip=0 */
+int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags); /* async_skip=0 */
+int msync(void *addr, size_t length, int flags); /* async_skip=0 */
+mode_t umask(mode_t mask);
diff --git a/pseudo.1 b/pseudo.1
new file mode 100644
index 0000000..63a6782
--- /dev/null
+++ b/pseudo.1
@@ -0,0 +1,672 @@
+.\"
+.\" pseudo(1) man page
+.\"
+.\" Copyright (c) 2010 Wind River Systems, Inc.
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the Lesser GNU General Public License version 2.1 as
+.\" published by the Free Software Foundation.
+.\"
+.\" This program 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 Lesser GNU General Public License for more details.
+.\"
+.\" You should have received a copy of the Lesser GNU General Public License
+.\" version 2.1 along with this program; if not, write to the Free Software
+.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+.TH pseudo 1 "pseudo - pretending to be root"
+.SH NAME
+pseudo \- run a command in a virtual root environment
+.SH SYNOPSIS
+.B pseudo
+.RB [ \-dflv ]
+[
+.B \-x
+.I flags
+]
+[
+.B \-P
+.I prefix
+]
+[
+.B \-rR
+.I root
+]
+[
+.B \-t
+.I timeout
+]
+.RI [ command ]
+.PP
+.B pseudo \-h
+.PP
+.B pseudo
+.RB [ \-dflv ]
+[
+.B \-x
+.I flags
+]
+[
+.B \-P
+.I prefix
+]
+.RB [ \-BC ]
+.BR \-i\ path
+.PP
+.B pseudo
+.RB [ \-dflv ]
+[
+.B \-x
+.I flags
+]
+[
+.B \-P
+.I prefix
+]
+.RB [ \-BC ]
+.BR \-m\ from\ \-M\ to
+.PP
+.B pseudo
+.RB [ \-dflv ]
+[
+.B \-x
+.I flags
+]
+[
+.B \-P
+.I prefix
+]
+.B \-S
+.PP
+.B pseudo
+.RB [ \-dflv ]
+[
+.B \-x
+.I flags
+]
+[
+.B \-P
+.I prefix
+]
+.B \-V
+.SH DESCRIPTION
+The
+.I pseudo
+utility provides a virtual root environment, hereafter referred to as the
+.IR pseudo\ environment ,
+allowing the creation of file system images and packages by users
+without root privileges. The pseudo environment is implemented by pushing
+a special library
+.RI ( libpseudo.so )
+into the
+.B LD_PRELOAD
+environment variable. This library intercepts a large number of common
+filesystem operations and some user-id related operations, and returns
+values that look as though the operations had been performed by a root
+user. This is in turn managed by a daemon program which keeps a list
+of virtualized file ownership and permissions; this daemon program itself
+is
+.IR pseudo .
+
+The
+.I pseudo
+program itself can also be used as a program launcher. The launcher
+is used to automatically configure a working environment, then execute
+processes within that environment. Alternatively, you can bypass this
+by setting up certain environment variables (see the
+.B ENVIRONMENT
+section below). The
+.I pseudo
+client library
+.RI ( libpseudo.so )
+can then start the server automatically.
+
+The
+.I pseudo
+command can be invoked in one of several possible modes:
+
+.TP 8
+.B \-B
+The
+.B \-B
+option causes
+.I pseudo
+to scan its database, as with the
+.B \-C
+option, but instead of reporting mismatches,
+.I pseudo
+attempts to repair them. Specifically, device and inode number mismatches
+are corrected, and symlink or directory mismatches result in deletion of
+database entries.
+.TP 8
+.B \-C
+The
+.B \-C
+option causes
+.I pseudo
+to scan its database, comparing against the filesystem, and reporting likely
+errors. This may be unreliable when the server is actively running.
+.TP 8
+.B \-h
+The
+.B \-h
+option causes
+.I pseudo
+to print a usage message and exit.
+.TP 8
+.B \-i
+The
+.B \-i
+option causes
+.I pseudo
+to attempt to correct device number mismatches by
+checking inodes; if
+.I path
+has the same inode number as recorded in the database, but a different
+device number, all instances of the device number recorded in the database
+are updated to the device number in the live filesystem for
+.IR path .
+This is intended to handle the mismatches that can occur when remounting
+an NFS filesystem. The
+.B \-i
+option implies the
+.B \-C
+option. You can also specify the
+.B \-B
+option to request that the database be rebuilt.
+.TP 8
+.B \-m
+The
+.B \-m
+and
+.B \-M
+options cause
+.I pseudo
+to rename files, replacing the string
+.I from
+with the string
+.I to.
+The
+.B \-m
+option pair implies the
+.B \-C
+option. You can also specify the
+.B \-B
+option to request that the database be rebuilt.
+.TP 8
+.B \-V
+The
+.B \-V
+option causes
+.I pseudo
+to print configuration information and exit immediately.
+.TP 8
+.B \-S
+The
+.B \-S
+option causes
+.I pseudo
+to try to find an existing server, and if it finds one, instructs that
+server to shut down as soon as all clients are detached from it. Note
+that the server will not shut down while clients are connected to it;
+in this case,
+.I pseudo
+will print a list of the remaining client PIDs.
+.TP 8
+.B \-d
+The
+.B \-d
+option causes pseudo to immediately detach and run in the background
+as a daemon. This is rarely useful except for debugging.
+.PP
+Finally, invoked without any of these options,
+.I pseudo
+sets up an emulated root environment, then invokes
+.I command
+if it was provided, otherwise a shell (using the
+.B SHELL
+environment variable if it is set, or
+.I /bin/sh
+otherwise).
+
+The following options modify the behavior of
+.IR pseudo :
+
+.TP 8
+.BI \-d\ (daemonize)
+Run as a daemon;
+.I pseudo
+detaches from the calling environment and runs as a daemon. The command
+returns successfully if this appears to have succeeded, otherwise it
+produces an error message and returns a failure status.
+
+.TP 8
+.BI \-f\ (foreground)
+Run in the foreground;
+.I pseudo
+runs as a server, and does not try to start other commands. This mode
+is useful for debugging.
+
+.TP 8
+.BI \-l\ (log)
+Enable logging. The
+.I pseudo
+daemon will log every filesystem transaction in the log database.
+
+.TP 8
+.BI \-r\ root
+.TP 8
+.BI \-R\ root
+Set the
+.B PSEUDO_CHROOT
+environment variable, running as though the program had called
+.I chroot(2)
+on the specified path. With
+.BR \-r ,
+this implies changing the working directory to the specified directory;
+with
+.BR \-R ,
+it does not.
+
+.TP 8
+.B \-t timeout
+Set the timeout of the
+.I pseudo
+daemon, in seconds. The default is currently 30 seconds. After this
+long with no attached clients, the
+.I pseudo
+daemon shuts down automatically. The server never shuts down while it
+has attached clients. Note that this does not prevent continued use;
+new clients can restart the daemon if they need it.
+
+.TP 8
+.BI \-v\ (verbose)
+Increase the verbosity of the
+.I pseudo
+daemon, and the client library for any programs started by this
+invocation of
+.IR pseudo .
+This is equivalent to the numeric form of the
+.B PSEUDO_DEBUG
+environment variable; multiple
+.B \-v
+options increase the debugging level.
+
+.TP 8
+.BI \-x\ (debug)
+Set specific deugging flags (the
+.I pseudo
+utility's help message lists them). This is equivalent to the string
+form of the
+.B PSEUDO_DEBUG
+environment variable.
+
+.SH EXAMPLES
+The two most common usages of
+.I pseudo
+are using it to run specific commands, and setting up an environment manually
+for running various other commands.
+
+For the first case, the usage is reasonably simple:
+
+.sp
+$
+.I /path/to/pseudo
+.br
+#
+.I commands which require root privileges
+
+You may have to use the
+.BI \-P prefix
+option to tell
+.I pseudo
+where to look for its database and server. If you specify a full path,
+.I pseudo
+assumes that
+.B PSEUDO_PREFIX
+should be the path to the directory containing the
+.I pseudo
+program, or to the
+.I /bin
+directory containing the
+.I pseudo
+program.
+
+The other way to use
+.I pseudo
+is by setting up an environment. This is suitable for use in
+.I Makefiles
+or similar environments, where you want to run a series of commands in
+the
+.I pseudo
+environment, but not to keep invoking the
+.I pseudo
+command. To do this, set up the
+.BR PSEUDO_PREFIX ,\ LD_PRELOAD ,\ and\ LD_LIBRARY_PATH
+environment variables, then run programs normally. You do not need to
+separately invoke the
+.I pseudo
+daemon; the client library starts it as needed.
+
+If you have moved a directory which
+.I pseudo
+was tracking, you may be able to get the database reattached using the
+.B \-m
+option. A typical usage might be:
+
+.sp
+$
+.I /path/to/pseudo
+.B \-B \-m
+.I oldpath
+.B \-M
+.I newpath
+.br
+
+This requests that
+.I pseudo
+replace the string
+.I oldpath
+with the string
+.I newpath
+at the beginnings of filenames, then regenerate the database, correcting any
+device/inode numbers.
+
+.SH DIAGNOSTICS
+Depending on invocation, diagnostic messages usually go either to standard
+error or to the file
+.B PSEUDO_PREFIX
+.IR /var/pseudo/pseudo.log .
+By default,
+.I pseudo
+daemon messages go into the log file, but messages generated by the client
+code go to standard error. These can be changed using the
+.B PSEUDO_DEBUG_FILE
+environment variable, documented in
+.BR ENVIRONMENT .
+At the default logging level, only critical
+messages are displayed. If you have raised the logging level (using the
+.I \-v
+option or the
+.B PSEUDO_DEBUG
+environment variable), additional messages are displayed. Levels higher
+than 2 are very unlikely to be useful outside of
+.I pseudo
+development.
+
+Diagnostic messages seen by default are those which are believed to indicate
+either a serious internal flaw in
+.I pseudo
+or a completely unexpected failure from the underlying operating system. In
+normal use, you should see no diagnostic messages.
+
+.SH ENVIRONMENT
+The most significant environment variables for
+.I pseudo
+are
+.B LD_PRELOAD
+and
+.BR LD_LIBRARY_PATH .
+However, these variables have no special meaning to
+.IR pseudo ;
+rather, they are used in the standard way to manipulate the dynamic linker
+into loading the
+.I libpseudo
+library so that it can intercept calls into the underlying C library.
+
+The following environment variables are used directly by
+.IR pseudo :
+
+.TP 8
+.B PSEUDO_BINDIR
+This directory holds the path to the
+.I pseudo
+binary; by default, it is the
+.I bin
+directory under
+.B PSEUDO_PREFIX.
+.TP 8
+.B PSEUDO_CHROOT
+This variable holds the current emulated
+.I chroot(2)
+path. Paths that are relative to this are treated as though they were
+instead relative to the filesystem root.
+.TP 8
+.B PSEUDO_DEBUG
+This variable holds either a numeric "debug level" for
+.I pseudo
+to run at, or a set of specific debugging flags, generally letters.
+Use
+.B pseudo -h
+to see the available flags. In general, this is useful only for debugging
+.I pseudo
+itself.
+.TP 8
+.B PSEUDO_DEBUG_FILE
+The name of a file to use for debugging messages from the pseudo client;
+the default is to log to standard error. If the string contains a single
+.BR %s ,
+that string is replaced with the short program name, and if it contains
+a single
+.BR %d ,
+that string is replaced with the process ID. Other format specifiers
+(other than '%%') are not allowed. By default, the
+.I pseudo
+server logs to the file
+.I pseudo.log
+in the
+.I var/pseudo
+directory, while clients log to standard error.
+.TP 8
+.B PSEUDO_DISABLED
+If this variable is set to a value that doesn't look like f, F, n, N, s, S, or
+a numeric zero, the
+.I pseudo
+client library does not modify the behavior of called functions, though it
+continues to intercept them and block signals while processing them. This
+variable is reevaluated on every call to
+.IR fork(2) ,\ clone(2)
+or related functions. If the value starts with a lowercase or uppercase
+.I s
+, the pseudo client disables all server spawning and communications, but still
+operates locally. This means that no filesystem mode or permissions changes
+are actually recorded or reported, but functions like
+.I chown()
+will still report success, even though nothing happens. This function is
+intended for debugging of issues which are complicated by the server's
+involvement.
+.TP 8
+.B PSEUDO_ALLOW_FSYNC
+If this variable is set, pseudo will allow
+.I fsync()
+and related system calls, even it was configured with the
+.I --enable-force-async
+option. Otherwise, that option results in all such calls being
+discarded silently, even when
+.B PSEUDO_DISABLED
+is set. The value specified doesn't matter.
+.TP 8
+.B PSEUDO_ENOSYS_ABORT
+If this variable is set, the
+.I pseudo
+client library calls
+.I abort()
+rather than setting
+.I errno
+to
+.B ENOSYS
+in the event of a call to a missing underlying function. This variable has
+no function outside of debugging
+.I pseudo
+itself.
+.TP 8
+.B PSEUDO_LIBDIR
+This directory holds the path to the
+.I pseudo
+shared libraries; by default, it is the
+.I lib
+directory under
+.BR PSEUDO_PREFIX .
+(On 64-bit hosts,
+.I lib64
+is also used.)
+.TP 8
+.B PSEUDO_LOCALSTATEDIR
+This directory holds the
+.I pseudo
+database files and log files; by default, it is the
+.I var/pseudo
+directory under
+.BR PSEUDO_PREFIX .
+.TP 8
+.B PSEUDO_NOSYMLINKEXP
+By default, when chrooted,
+.I pseudo
+prepends the chroot directory to
+the paths used for absolute symlinks; this behavior ensures that
+opening symlinks produces expected results in most cases. In some
+cases you may want to suppress this. If this variable is unset, or
+set to any value other than 0,
+.I pseudo
+expands symlink paths like this. If this variable is set to 0,
+the behavior is disabled.
+.TP 8
+.BR PSEUDO_OPTS
+This variable holds options to be passed to any new
+.I pseudo
+servers started. Typically, when
+.I pseudo
+is used as a launcher, this will be set automatically; however, you
+can also use it to pass options when using
+.B LD_PRELOAD
+to manually run things in the
+.I pseudo
+environment.
+.TP 8
+.B PSEUDO_PASSWD
+This variable holds the path to a directory containing password and
+group files to use for emulation of various password and group routines.
+It should be the path to a directory containing the
+.I etc
+directory containing files named
+.I passwd
+and
+.IR group .
+When
+.I pseudo
+is emulating a
+.I chroot
+environment, the chroot directory is used by preference. The
+parallelism between these cases is why this variable points at
+the parent directory of
+.I etc
+rather than the directory containing the files. If there is no
+.I chroot
+environment, and this variable is also unset,
+.I pseudo
+falls back to a directory specified at configure time, with the
+default being the root directory. This is controlled by the
+.B PSEUDO_PASSWD_FALLBACK
+definition.
+.TP 8
+.B PSEUDO_PREFIX
+If set, the variable
+.B PSEUDO_PREFIX
+is used to determine the path to use to find the
+.I pseudo
+server, in
+.BR PSEUDO_PREFIX /bin,
+and the
+.I pseudo
+data files, in
+.BR PSEUDO_PREFIX /var/pseudo.
+This variable is automatically set by the
+.I pseudo
+program when it is used as a launcher.
+.TP 8
+.B PSEUDO_PROFILE_PATH
+If
+.I pseudo
+was configured with profiling enabled, specifies a path in which to
+write client profiling information for use with the
+.I pseudo_profile
+utility (not built by default).
+.TP 8
+.B PSEUDO_TAG
+If this variable is set in a client's environment, its value is
+communicated to the server at the beginning of each client session,
+and recorded in the log database if any logging occurs related to a
+specific client. Note that different clients may have different tags
+associated with them; the tag value is per-client, not per-server.
+.TP 8
+.BR PSEUDO_UIDS ,\ PSEUDO_GIDS
+These variables are used internally to pass information about the current
+emulated user and group identity from one process to another.
+.TP 8
+.B PSEUDO_UNLOAD
+This variable is reevaluated on every call to
+.IR fork(2) ,\ exec(3)
+or related functions. If the variable exists
+.RI libpseudo.so
+will be removed from
+.B LD_PRELOAD
+and
+.B PSEUDO_DISABLED
+behavior will also be triggered. For processes
+that simply
+.IR fork(2),
+the behavior will be the same as if
+.B PSEUDO_DISABLED
+was set. For new processes, after a call to
+.IR exec(3)\ or\ system(3)
+pseudo will not be loaded in the new process.
+.TP 8
+.B SHELL
+If set, this will be used when
+.I pseudo
+is invoked without either a command or one of the options which directs
+it to do something other than run a command. Otherwise,
+.I pseudo
+defaults to
+.I /bin/sh .
+.B
+.SH BUGS
+The
+.I pseudo
+database is not particularly robust in the face of whole directory trees
+being moved, or changes in the underlying device and inode numbers. It
+has a reasonable chance of recovering if only the path or the device numbers
+have changed, but it is not particularly designed to address this. A future
+release is expected to have improved resilience in these cases.
+
+The filesystem on which
+.I pseudo
+keeps its database and files must at a minimum support UNIX domain sockets
+and reasonable file locking semantics. Note that
+.I pseudo
+relies on
+.I flock(2)
+locking semantics; a lock has to persist into a child process. This should
+probably eventually be fixed.
+
+The
+.I pseudo
+client library is probably thread-safe, but has not been adequately tested
+or debugged in that context.
+
+Filesystem performance is noticably worse under
+.I pseudo
+than it is otherwise. This is probably because nearly every operation
+(other than reads and writes) involves at least one round-trip network
+communication with the server, and probably some kind of database
+activity.
+
+.SH SEE ALSO
+fakeroot(1), ld.so(8), pseudolog(1), sqlite3(1)
+.SH FURTHER READING
+Documentation of the internals of
+.I pseudo
+may be found in the
+.I doc
+subdirectory of the pseudo source tree.
diff --git a/pseudo.c b/pseudo.c
new file mode 100644
index 0000000..5715af2
--- /dev/null
+++ b/pseudo.c
@@ -0,0 +1,1224 @@
+/*
+ * pseudo.c, main pseudo utility program
+ *
+ * Copyright (c) 2008-2013 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_client.h"
+#include "pseudo_server.h"
+#include "pseudo_db.h"
+
+int opt_B = 0;
+int opt_C = 0;
+int opt_d = 0;
+int opt_f = 0;
+char *opt_i = NULL;
+int opt_l = 0;
+char *opt_m = NULL;
+char *opt_M = NULL;
+long opt_p = 0;
+char *opt_r = NULL;
+int opt_S = 0;
+
+static int pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len);
+static int pseudo_db_check(int fix);
+
+void
+usage(int status) {
+ FILE *f = status ? stderr : stdout;
+ fputs("Usage: pseudo [-dflv] [-x flags] [-P prefix] [-rR root] [-t timeout] [command]\n", f);
+ fputs(" pseudo -h\n", f);
+ fputs(" pseudo [-dflv] [-x flags] [-P prefix] [-BC] -i path\n", f);
+ fputs(" pseudo [-dflv] [-x flags] [-P prefix] [-BC] -m from -M to\n", f);
+ fputs(" pseudo [-dflv] [-x flags] [-P prefix] -C\n", f);
+ fputs(" pseudo [-dflv] [-x flags] [-P prefix] -S\n", f);
+ fputs(" pseudo [-dflv] [-x flags] [-P prefix] -V\n", f);
+ fputs("Debugging flags:\n", f);
+ for (int i = 1; i < PDBG_MAX; i += 2) {
+ unsigned char symbolics[2];
+ const char *descriptions[2];
+ symbolics[0] = pseudo_debug_type_symbolic(i);
+ symbolics[1] = pseudo_debug_type_symbolic(i + 1);
+ descriptions[0] = pseudo_debug_type_description(i);
+ descriptions[1] = pseudo_debug_type_description(i + 1);
+ if (symbolics[1]) {
+ fprintf(f, " %c %-32s %c %-32s\n",
+ symbolics[0], descriptions[0],
+ symbolics[1], descriptions[1]);
+ } else {
+ fprintf(f, " %c %-32s\n",
+ symbolics[0], descriptions[0]);
+ }
+ }
+ exit(status);
+}
+
+/* helper function to make a directory, just like mkdir -p.
+ * Can't use system() because the child shell would end up trying
+ * to do the same thing...
+ */
+static void
+mkdir_p(char *path) {
+ size_t len = strlen(path);
+ size_t i;
+
+ for (i = 1; i < len; ++i) {
+ /* try to create all the directories in path, ignoring
+ * failures
+ */
+ if (path[i] == '/') {
+ path[i] = '\0';
+ (void) mkdir(path, 0755);
+ path[i] = '/';
+ }
+ }
+ (void) mkdir(path, 0755);
+}
+
+/* main server process */
+int
+main(int argc, char *argv[]) {
+ int o;
+ char *s;
+ int lockfd, newfd;
+ char *ld_env = getenv(PRELINK_LIBRARIES);
+ int rc = 0;
+ char opts[pseudo_path_max()], *optptr = opts;
+ char *lockname;
+ char *lockpath;
+
+ opts[0] = '\0';
+
+ pseudo_init_util();
+
+ if (ld_env && strstr(ld_env, "libpseudo")) {
+ pseudo_debug(PDBGF_SERVER, "can't run daemon with libpseudo in %s\n", PRELINK_LIBRARIES);
+ s = pseudo_get_value("PSEUDO_UNLOAD");
+ if (s) {
+ pseudo_diag("pseudo: I can't seem to make %s go away. Sorry.\n", PRELINK_LIBRARIES);
+ pseudo_diag("pseudo: %s: %s\n", PRELINK_LIBRARIES, ld_env);
+ exit(EXIT_FAILURE);
+ }
+ free(s);
+ pseudo_set_value("PSEUDO_UNLOAD", "YES");
+ pseudo_setupenv();
+ pseudo_dropenv(); /* Drop PRELINK_LIBRARIES */
+
+ execv(argv[0], argv);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Be sure to clean PSEUDO_UNLOAD so if we're asked to run any
+ * programs pseudo will be active in the process...
+ * (note: pseudo_set_value doesn't muck w/ the environment, thus
+ * the need for the unsetenv, which is safe because "pseudo"
+ * is the executable in this case!)
+ */
+ pseudo_set_value("PSEUDO_UNLOAD", NULL);
+ unsetenv("PSEUDO_UNLOAD");
+
+ /* we need cwd to canonicalize paths */
+ pseudo_client_getcwd();
+
+ /* warning: GNU getopt permutes arguments, which is just plain
+ * wrong. The + suppresses this annoying behavior, but may not
+ * be compatible with sane option libraries.
+ */
+ while ((o = getopt(argc, argv, "+BCdfhi:lm:M:p:P:r:R:St:vVx:")) != -1) {
+ switch (o) {
+ case 'B': /* rebuild database */
+ opt_B = 1;
+ opt_C = 1;
+ break;
+ case 'C': /* check database */
+ opt_C = 1;
+ break;
+ case 'd': /* run as daemon */
+ opt_d = 1;
+ break;
+ case 'f': /* run foregrounded */
+ opt_f = 1;
+ break;
+ case 'h': /* help */
+ usage(0);
+ break;
+ case 'i': /* renumber devices, assuming stable inodes */
+ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0);
+ if (!s) {
+ pseudo_diag("Can't resolve path '%s'\n", optarg);
+ usage(EXIT_FAILURE);
+ }
+ opt_i = strdup(s);
+ break;
+ case 'l': /* log */
+ optptr += snprintf(optptr, pseudo_path_max() - (optptr - opts),
+ "%s-l", optptr > opts ? " " : "");
+ opt_l = 1;
+ break;
+ case 'm': /* move from... (see also 'M') */
+ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0);
+ if (!s) {
+ pseudo_diag("Can't resolve move-from path '%s'\n", optarg);
+ usage(EXIT_FAILURE);
+ }
+ opt_m = strdup(s);
+ break;
+ case 'M': /* move to... (see also 'm') */
+ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0);
+ if (!s) {
+ pseudo_diag("Can't resolve move-to path '%s'\n", optarg);
+ usage(EXIT_FAILURE);
+ }
+ opt_M = strdup(s);
+ break;
+ case 'p': /* passwd file path */
+ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW);
+ if (!s) {
+ pseudo_diag("Can't resolve passwd path '%s'\n", optarg);
+ usage(EXIT_FAILURE);
+ }
+ pseudo_set_value("PSEUDO_PASSWD", s);
+ break;
+ case 'P': /* prefix */
+ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW);
+ if (!s) {
+ pseudo_diag("Can't resolve prefix path '%s'\n", optarg);
+ usage(EXIT_FAILURE);
+ }
+ pseudo_set_value("PSEUDO_PREFIX", s);
+ break;
+ case 'r': /* chroot to... (fallthrough) */
+ case 'R': /* pseudo root path */
+ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW);
+ if (!s) {
+ pseudo_diag("Can't resolve root path '%s'\n", optarg);
+ usage(EXIT_FAILURE);
+ }
+ pseudo_set_value("PSEUDO_CHROOT", s);
+ if (o == 'r')
+ opt_r = strdup(s);
+ break;
+ case 'S': /* stop */
+ opt_S = 1;
+ break;
+ case 't': /* timeout */
+ pseudo_server_timeout = strtol(optarg, &s, 10);
+ if (*s && !isspace(*s)) {
+ pseudo_diag("Timeout must be an integer value.\n");
+ usage(EXIT_FAILURE);
+ }
+ optptr += snprintf(optptr, pseudo_path_max() - (optptr - opts),
+ "%s-t %d", optptr > opts ? " " : "",
+ pseudo_server_timeout);
+ break;
+ case 'v': /* verbosity */
+ pseudo_debug_verbose();
+ break;
+ case 'V': /* version info */
+ printf("pseudo version %s\n", pseudo_version ? pseudo_version : "<undefined>");
+ printf("pseudo configuration:\n prefix: %s\n",
+ PSEUDO_PREFIX);
+ printf("Set PSEUDO_PREFIX to run with a different prefix.\n");
+ exit(0);
+ break;
+ case 'x': /* debug flags */
+ pseudo_debug_set(optarg);
+ break;
+ case '?':
+ default:
+ pseudo_diag("unknown/invalid argument (option '%c').\n", optopt);
+ usage(EXIT_FAILURE);
+ break;
+ }
+ }
+ pseudo_debug_flags_finalize();
+ /* Options are processed, preserve them... */
+ pseudo_set_value("PSEUDO_OPTS", opts);
+
+ if (!pseudo_get_prefix(argv[0])) {
+ pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* move database */
+ if (opt_m || opt_M) {
+ struct stat buf;
+ pseudo_msg_t *msg;
+ int rc;
+ if (!(opt_m && opt_M)) {
+ pseudo_diag("You cannot move the database without specifying from and to.\n");
+ exit(EXIT_FAILURE);
+ }
+ if (stat(opt_M, &buf) < 0) {
+ pseudo_diag("stat of '%s' failed: %s\n",
+ opt_M, strerror(errno));
+ pseudo_diag("The directory the database is being moved to must exist.\n");
+ exit(EXIT_FAILURE);
+ }
+ msg = pseudo_msg_new(0, opt_M);
+ if (!msg) {
+ pseudo_diag("Can't allocate message structure.\n");
+ exit(EXIT_FAILURE);
+ }
+ rc = pdb_rename_file(opt_m, msg);
+ free(msg);
+ if (rc < 0) {
+ pseudo_diag("Warning: Database move may have failed.\n");
+ pseudo_diag("To try to restore, you can reverse the move.\n");
+ pseudo_diag("To commit to this anyway, run pseudo -C to check the database.\n");
+ exit(EXIT_FAILURE);
+ }
+ pseudo_diag("Rename looked okay, running database sanity check.\n");
+ opt_C = 1;
+ }
+
+ if (opt_i) {
+ int rc;
+ struct stat buf;
+ pseudo_msg_t *msg;
+ if (stat(opt_i, &buf) < 0) {
+ pseudo_diag("stat of '%s' failed: %s\n",
+ opt_i, strerror(errno));
+ pseudo_diag("The file used to renumber the database must exist.\n");
+ exit(EXIT_FAILURE);
+ }
+ msg = pseudo_msg_new(0, opt_i);
+ if (!msg) {
+ pseudo_diag("Couldn't allocate data structure for path.\n");
+ exit(EXIT_FAILURE);
+ }
+ if (pdb_find_file_path(msg, NULL)) {
+ pseudo_diag("Couldn't find a database entry for '%s'.\n", opt_i);
+ exit(EXIT_FAILURE);
+ }
+ if (buf.st_ino != msg->ino) {
+ pseudo_diag("The database inode entry for '%s' doesn't match; you must use -b.\n",
+ opt_i);
+ exit(EXIT_FAILURE);
+ }
+ rc = pdb_renumber_all(msg->dev, buf.st_dev);
+ free(msg);
+ if (rc < 0) {
+ pseudo_diag("Warning: Database renumber failed.\n");
+ exit(EXIT_FAILURE);
+ }
+ pseudo_diag("Renumber looked okay, running database sanity check.\n");
+ opt_C = 1;
+ }
+
+ if (opt_C) {
+ /* if opt_B is set, try to fix database */
+ return pseudo_db_check(opt_B);
+ }
+
+ if (opt_S) {
+ return pseudo_client_shutdown();
+ }
+
+ if (opt_d && opt_f) {
+ pseudo_diag("You cannot run a foregrounded daemon.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (opt_f || opt_d) {
+ if (argc > optind) {
+ pseudo_diag("pseudo: running program implies spawning background daemon.\n");
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ char fullpath[pseudo_path_max()];
+ char *path;
+
+ if (opt_r) {
+ if (chdir(opt_r) == -1) {
+ pseudo_diag("failed to chdir to '%s': %s\n",
+ opt_r, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (argc > optind) {
+ pseudo_debug(PDBGF_INVOKE, "running command: %s\n",
+ argv[optind]);
+ argc -= optind;
+ argv += optind;
+ } else {
+ static char *newargv[2];
+ argv = newargv;
+ pseudo_debug(PDBGF_INVOKE, "running shell.\n");
+ argv[0] = getenv("SHELL");
+ if (!argv[0])
+ argv[0] = "/bin/sh";
+ argv[1] = NULL;
+ }
+
+ if (strchr(argv[0], '/')) {
+ snprintf(fullpath, pseudo_path_max(), "%s", argv[0]);
+ } else {
+ int found = 0;
+ if ((path = getenv("PATH")) == NULL)
+ path = "/bin:/usr/bin";
+ while (*path) {
+ struct stat buf;
+ int len = strcspn(path, ":");
+ snprintf(fullpath, pseudo_path_max(), "%.*s/%s",
+ len, path, argv[0]);
+ path += len;
+ if (*path == ':')
+ ++path;
+ if (!stat(fullpath, &buf)) {
+ if (buf.st_mode & 0111) {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ pseudo_diag("Can't find '%s' in $PATH.\n",
+ argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+ pseudo_setupenv();
+
+ rc = fork();
+ if (rc) {
+ waitpid(rc, &rc, 0);
+ /* try to hint that we don't think we still need
+ * the server.
+ */
+ pseudo_client_shutdown();
+ return WEXITSTATUS(rc);
+ } else {
+ rc = execv(fullpath, argv);
+ if (rc == -1) {
+ pseudo_diag("pseudo: can't run %s: %s\n",
+ argv[0], strerror(errno));
+ }
+ exit(EXIT_FAILURE);
+ }
+ }
+ /* if we got here, we are not running a command, and we are not in
+ * a pseudo environment.
+ */
+ pseudo_new_pid();
+
+ pseudo_debug(PDBGF_SERVER, "opening lock.\n");
+ lockpath = pseudo_localstatedir_path(NULL);
+ if (!lockpath) {
+ pseudo_diag("Couldn't allocate a file path.\n");
+ exit(EXIT_FAILURE);
+ }
+ mkdir_p(lockpath);
+ lockname = pseudo_localstatedir_path(PSEUDO_LOCKFILE);
+ if (!lockname) {
+ pseudo_diag("Couldn't allocate a file path.\n");
+ exit(EXIT_FAILURE);
+ }
+ lockfd = open(lockname, O_RDWR | O_CREAT, 0644);
+ if (lockfd < 0) {
+ pseudo_diag("Can't open or create lockfile %s: %s\n",
+ lockname, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ free(lockname);
+
+ if (lockfd <= 2) {
+ newfd = fcntl(lockfd, F_DUPFD, 3);
+ if (newfd < 0) {
+ pseudo_diag("Can't move lockfile to safe descriptor: %s\n",
+ strerror(errno));
+ } else {
+ close(lockfd);
+ lockfd = newfd;
+ }
+ }
+
+ pseudo_debug(PDBGF_SERVER, "acquiring lock.\n");
+ if (flock(lockfd, LOCK_EX | LOCK_NB) < 0) {
+ if (errno == EACCES || errno == EAGAIN) {
+ pseudo_debug(PDBGF_SERVER, "Existing server has lock. Exiting.\n");
+ } else {
+ pseudo_diag("pseudo: Error obtaining lock: %s\n", strerror(errno));
+ }
+ exit(0);
+ } else {
+ pseudo_debug(PDBGF_SERVER, "Acquired lock.\n");
+ }
+ pseudo_debug(PDBGF_SERVER, "serving (%s)\n", opt_d ? "daemon" : "foreground");
+ return pseudo_server_start(opt_d);
+}
+
+/*
+ * actually process operations.
+ * This first evaluates the message, figures out what's in the DB, does some
+ * sanity checks, then implements the fairly small DB changes required.
+ */
+int
+pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len) {
+ pseudo_msg_t msg_header;
+ pseudo_msg_t by_path = { .op = 0 }, by_ino = { .op = 0 };
+ long long row = -1;
+ pseudo_msg_t db_header;
+ char *path_by_ino = 0;
+ char *oldpath = 0;
+ size_t oldpathlen = 0;
+ int found_path = 0, found_ino = 0;
+ int prefer_ino = 0;
+ int xattr_flags = 0;
+ int trailing_slash = 0;
+
+ if (!msg)
+ return 1;
+
+ msg->result = RESULT_SUCCEED;
+
+ /* debugging message. Primary key first. */
+ switch (msg->op) {
+ case OP_FCHOWN: /* FALLTHROUGH */
+ case OP_FCHMOD: /* FALLTHROUGH */
+ case OP_FSTAT:
+ prefer_ino = 1;
+ pseudo_debug(PDBGF_OP, "%s %llu [%s]: ", pseudo_op_name(msg->op),
+ (unsigned long long) msg->ino,
+ msg->pathlen ? msg->path : "no path");
+ break;
+ default:
+ pseudo_debug(PDBGF_OP, "%s %s [%llu]: ", pseudo_op_name(msg->op),
+ msg->pathlen ? msg->path : "no path",
+ (unsigned long long) msg->ino);
+ break;
+ }
+
+ /* Process rename path separation, there are two paths old / new
+ * stuff into a rename, break them apart (null seperated)
+ */
+
+ if (msg->pathlen) {
+ size_t initial_len;
+ switch (msg->op) {
+ case OP_RENAME:
+ case OP_CREATE_XATTR:
+ case OP_GET_XATTR:
+ case OP_LIST_XATTR:
+ case OP_REPLACE_XATTR:
+ case OP_SET_XATTR:
+ /* In a rename there are two paths, null separated in msg->path */
+ initial_len = strlen(msg->path);
+ oldpath = msg->path + initial_len + 1;
+ /* for rename, the path name would be null-terminated,
+ * but for *xattr, we don't want the null. */
+ oldpathlen = msg->pathlen - (oldpath - msg->path) - 1;
+ pseudo_debug(PDBGF_OP | PDBGF_FILE | PDBGF_XATTR, "%s: path '%s', oldpath '%s' [%d/%d]\n",
+ pseudo_op_name(msg->op), msg->path, oldpath, (int) oldpathlen, (int) msg->pathlen);
+ /* For a rename op, we want to strip any trailing
+ * slashes. For xattr, "oldpath" is the raw data
+ * to be stored. */
+ if (oldpathlen > 0 && msg->op == OP_RENAME) {
+ if (oldpath[oldpathlen - 1] == '/') {
+ oldpath[--oldpathlen] = '\0';
+ }
+ }
+ /* if we got an oldpath, but a 0-length initial
+ * path, we don't want to act as though we had
+ * a non-empty initial path.
+ */
+
+ msg->pathlen = initial_len;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* stash original header, in case we need it later */
+ msg_header = *msg;
+ by_ino = msg_header;
+
+ /* trailing slashes are kept in paths because they affect
+ * path resolution, but we don't want them in the database
+ * because they're optional. For now, any error-checking on
+ * this server-side is purely advisory, but the client should
+ * bail with ENOTDIR a lot earlier in many cases, before the
+ * server even sees anything.
+ */
+ if (msg->pathlen) {
+ if (msg->path[msg->pathlen - 1] == '/') {
+ msg->path[--msg->pathlen] = '\0';
+ trailing_slash = 1;
+ }
+ }
+
+ /* There should usually be a path. Even for f* ops, the client
+ * tries to provide a path from its table of known fd paths.
+ */
+ /* Lookup the full path, with inode and dev if available */
+ if (msg->pathlen && msg->dev && msg->ino) {
+ if (!pdb_find_file_exact(msg, &row)) {
+ /* restore header contents */
+ by_path = *msg;
+ by_ino = *msg;
+ *msg = msg_header;
+ found_path = 1;
+ found_ino = 1;
+ /* note: we have to avoid freeing this later */
+ path_by_ino = msg->path;
+ if (msg->op == OP_LINK) {
+ pseudo_debug(PDBGF_FILE, "[matches existing link]");
+ }
+ }
+ }
+
+ if (!found_path && !found_ino) {
+ if (msg->pathlen) {
+ /* for now, don't canonicalize paths anymore */
+ /* used to do it here, but now doing it in client */
+ if (!pdb_find_file_path(msg, &row)) {
+ by_path = *msg;
+ found_path = 1;
+ } else {
+ if (msg->op != OP_RENAME && msg->op != OP_LINK) {
+ pseudo_debug(PDBGF_FILE, "(new?) ");
+ }
+ }
+ }
+ /* search on original inode -- in case of mismatch */
+ if (msg->dev && msg->ino) {
+ if (!pdb_find_file_dev(&by_ino, &row, &path_by_ino)) {
+ found_ino = 1;
+ }
+ }
+ }
+
+ pseudo_debug(PDBGF_OP, "incoming: '%s'%s [%llu]%s\n",
+ msg->pathlen ? msg->path : "no path",
+ found_path ? "+" : "-",
+ (unsigned long long) msg_header.ino,
+ found_ino ? "+" : "-");
+
+ /* the sanity checks are inappropriate for DID_UNLINK, since it's
+ * completely legitimate to have a new database entry for the
+ * same inode.
+ */
+ if (found_path && msg->op != OP_DID_UNLINK) {
+ /* This is a bad sign. We should never have a different entry
+ * for the inode... But an inode of 0 from an EXEC is normal,
+ * we don't track those.
+ */
+ if (by_path.ino != msg_header.ino && msg_header.ino != 0) {
+ switch (msg->op) {
+ case OP_EXEC:
+ break;
+ default:
+ /* if the path is in the database with a
+ * different inode, but we were expecting
+ * it to get deleted, mark the old one
+ * as deleted.
+ */
+ if (by_path.deleting != 0) {
+ pseudo_debug(PDBGF_FILE, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n",
+ msg->path);
+ pdb_did_unlink_file(msg->path, by_path.deleting);
+ } else {
+ pseudo_diag("inode mismatch: '%s' ino %llu in db, %llu in request.\n",
+ msg->path,
+ (unsigned long long) by_path.ino,
+ (unsigned long long) msg_header.ino);
+ }
+ }
+ }
+ /* If the database entry disagrees on S_ISDIR, it's just
+ * plain wrong. We remove the database entry, because it
+ * is absolutely certain to be wrong. This means found_path
+ * is now 0, because there is no entry in db...
+ *
+ * This used to unlink everything with the inode from
+ * the message -- but what if the entry in the database
+ * had a different inode? We should nuke THAT inode,
+ * and everything agreeing with it, which will also catch
+ * the bogus entry that we noticed.
+ */
+ if (S_ISDIR(by_path.mode) != S_ISDIR(msg_header.mode)) {
+ pseudo_diag("dir mismatch: '%s' [%llu] db mode 0%o, header mode 0%o (unlinking db)\n",
+ msg->path, (unsigned long long) by_path.ino,
+ (int) by_path.mode, (int) msg_header.mode);
+ /* unlink everything with this inode */
+ pdb_unlink_file_dev(&by_path);
+ found_path = 0;
+ } else if (S_ISLNK(by_path.mode) != S_ISLNK(msg_header.mode)) {
+ pseudo_diag("symlink mismatch: '%s' [%llu] db mode 0%o, header mode 0%o (unlinking db)\n",
+ msg->path, (unsigned long long) by_path.ino,
+ (int) by_path.mode, (int) msg_header.mode);
+ /* unlink everything with this inode */
+ pdb_unlink_file_dev(&by_path);
+ found_path = 0;
+ }
+ if (trailing_slash && !S_ISDIR(by_path.mode)) {
+ pseudo_diag("dir quasi-mismatch: '%s' [%llu] db mode 0%o, incoming path had trailing slash. Not unlinking.\n",
+ msg->path, (unsigned long long) by_path.ino,
+ (int) by_path.mode);
+ }
+ }
+
+ /* for OP_DID_UNLINK, the reason this op exists is that the same
+ * inode might have been reclaimed. Don't sanity-check it, and
+ * especially don't delete the database contents!
+ */
+ if (found_ino && msg->op != OP_DID_UNLINK) {
+ /* Not always an absolute failure case.
+ * If a file descriptor shows up unexpectedly and gets
+ * fchown()d, you could have an entry giving the inode and
+ * data, but not path. So, we add the path to the entry.
+ * Any other changes from the incoming message will be applied
+ * at leisure.
+ */
+ if (msg->pathlen && !path_by_ino) {
+ pseudo_debug(PDBGF_FILE, "db path missing: ino %llu, request '%s'.\n",
+ (unsigned long long) msg_header.ino, msg->path);
+ pdb_update_file_path(msg);
+ } else if (!msg->pathlen && path_by_ino) {
+ /* harmless */
+ pseudo_debug(PDBGF_FILE, "req path missing: ino %llu, db '%s'.\n",
+ (unsigned long long) msg_header.ino, path_by_ino);
+ } else if (msg->pathlen && path_by_ino) {
+ /* this suggests a database error, except in LINK
+ * cases. In those cases, it is normal for a
+ * mismatch to occur. :) (SYMLINK shouldn't,
+ * because the symlink gets its own inode number.)
+ *
+ * RENAME can get false positives on this, when
+ * link count is greater than one. So we skip this
+ * test for OP_LINK (always) and OP_RENAME (for link
+ * count greater than one). For RENAME, the test
+ * should be against the old name, though!
+ */
+ int mismatch = 0;
+ switch (msg->op) {
+ case OP_LINK:
+ case OP_EXEC:
+ break;
+ case OP_RENAME:
+ if (msg->nlink == 1 && strcmp(oldpath, path_by_ino)) {
+ mismatch = 1;
+ }
+ break;
+ default:
+ if (strcmp(msg->path, path_by_ino)) {
+ mismatch = 1;
+ }
+ break;
+ }
+ if (mismatch) {
+ /* a mismatch, but we were planning to delete
+ * the file, so it must have gotten deleted
+ * already.
+ */
+ if (by_ino.deleting != 0) {
+ pseudo_debug(PDBGF_FILE, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n",
+ msg->path);
+ pdb_did_unlink_file(path_by_ino, by_ino.deleting);
+ } else {
+ pseudo_diag("path mismatch [%d link%s]: ino %llu db '%s' req '%s'.\n",
+ msg->nlink,
+ msg->nlink == 1 ? "" : "s",
+ (unsigned long long) msg_header.ino,
+ path_by_ino ? path_by_ino : "no path",
+ msg->path);
+ }
+ }
+ } else {
+ /* I don't think I've ever seen this one. */
+ pseudo_debug(PDBGF_FILE, "warning: ino %llu in db (mode 0%o, owner %d), no path known.\n",
+ (unsigned long long) msg_header.ino,
+ (int) by_ino.mode, (int) by_ino.uid);
+ }
+ /* Again, in the case of a directory mismatch, nuke the DB
+ * entry. There is no way it can be right.
+ */
+ if (S_ISDIR(by_ino.mode) != S_ISDIR(msg_header.mode)) {
+ pseudo_diag("dir err : %llu ['%s'] (db '%s') db mode 0%o, header mode 0%o (unlinking db)\n",
+ (unsigned long long) msg_header.ino,
+ msg->pathlen ? msg->path : "no path",
+ path_by_ino ? path_by_ino : "no path",
+ (int) by_ino.mode, (int) msg_header.mode);
+ pdb_unlink_file_dev(msg);
+ found_ino = 0;
+ } else if (S_ISLNK(by_ino.mode) != S_ISLNK(msg_header.mode)) {
+ /* In the current implementation, only msg_header.mode
+ * can ever be a symlink; the test is generic as
+ * insurance against forgetting to fix it in a future
+ * update. */
+ pseudo_diag("symlink err : %llu ['%s'] (db '%s') db mode 0%o, header mode 0%o (unlinking db)\n",
+ (unsigned long long) msg_header.ino,
+ msg->pathlen ? msg->path : "no path",
+ path_by_ino ? path_by_ino : "no path",
+ (int) by_ino.mode, (int) msg_header.mode);
+ pdb_unlink_file_dev(msg);
+ found_ino = 0;
+ }
+ }
+
+ /* In the case of a stat() call, if a mismatch existed, we prefer
+ * by-inode for fstat, by-path for stat. Nothing else actually uses
+ * this...
+ */
+ if (found_ino && (prefer_ino || !found_path)) {
+ db_header = by_ino;
+ } else if (found_path) {
+ db_header = by_path;
+ }
+
+ switch (msg->op) {
+ case OP_CHDIR: /* FALLTHROUGH */
+ case OP_CLOSE:
+ /* these messages are handled entirely on the client side,
+ * as of this writing, but might be logged by accident: */
+ pseudo_diag("error: op %s sent to server.\n", pseudo_op_name(msg->op));
+ break;
+ case OP_EXEC: /* FALLTHROUGH */
+ case OP_OPEN:
+ /* nothing to do -- just sent in case we're logging */
+ break;
+ case OP_CREAT:
+ /* implies a new file -- not a link, which would be OP_LINK */
+ if (found_ino) {
+ /* CREAT should never be sent if the file existed.
+ * So, any existing entry is an error. Nuke it.
+ */
+ pseudo_diag("creat for '%s' replaces existing %llu ['%s'].\n",
+ msg->pathlen ? msg->path : "no path",
+ (unsigned long long) by_ino.ino,
+ path_by_ino ? path_by_ino : "no path");
+ pdb_unlink_file_dev(&by_ino);
+ }
+ if (!found_path) {
+ pseudo_debug(PDBGF_DB, "linking %s for OP_CREAT\n",
+ msg->pathlen ? msg->path : "no path");
+ pdb_link_file(msg, NULL);
+ } else {
+ /* again, an error, but leaving it alone for now. */
+ pseudo_diag("creat ignored for existing file '%s'.\n",
+ msg->pathlen ? msg->path : "no path");
+ }
+ break;
+ case OP_CHMOD: /* FALLTHROUGH */
+ case OP_FCHMOD:
+ pseudo_debug(PDBGF_OP, "mode 0%o ", (int) msg->mode);
+ /* if the inode is known, update it */
+ if (found_ino) {
+ /* obtain the existing data, merge with mode */
+ *msg = by_ino;
+ msg->mode = (msg_header.mode & 07777) |
+ (msg->mode & ~07777);
+ pdb_update_file(msg);
+ } else if (found_path) {
+ /* obtain the existing data, merge with mode */
+ *msg = by_path;
+ msg->mode = (msg_header.mode & 07777) |
+ (by_path.mode & ~07777);
+ pdb_update_file(msg);
+ } else {
+ /* just in case find_file_path screwed up the msg */
+ msg->mode = msg_header.mode;
+ }
+ /* if we've never seen the file at all before, link it.
+ * If we have it in the db by inode, but not by name,
+ * it got fixed during the sanity checks.
+ */
+ if (!found_path && !found_ino) {
+ pseudo_debug(PDBGF_FILE, "(new) ");
+ pseudo_debug(PDBGF_DB, "linking %s for OP_[F]CHMOD\n",
+ msg->pathlen ? msg->path : "no path");
+ pdb_link_file(msg, NULL);
+ }
+ break;
+ case OP_CHOWN: /* FALLTHROUGH */
+ case OP_FCHOWN:
+ pseudo_debug(PDBGF_OP, "owner %d:%d ", (int) msg_header.uid, (int) msg_header.gid);
+ /* if the inode is known, update it */
+ if (found_ino) {
+ /* obtain the existing data, merge with mode */
+ *msg = by_ino;
+ msg->uid = msg_header.uid;
+ msg->gid = msg_header.gid;
+ pdb_update_file(msg);
+ } else if (found_path) {
+ /* obtain the existing data, merge with mode */
+ *msg = by_path;
+ msg->uid = msg_header.uid;
+ msg->gid = msg_header.gid;
+ pdb_update_file(msg);
+ } else {
+ /* just in case find_file_path screwed up the msg */
+ msg->uid = msg_header.uid;
+ msg->gid = msg_header.gid;
+ }
+ /* if we've never seen the file at all before, link it.
+ * If we have it in the db by inode, but not by name,
+ * it got fixed during the sanity checks.
+ */
+ if (!found_path && !found_ino) {
+ pseudo_debug(PDBGF_FILE, "(new) ");
+ pseudo_debug(PDBGF_DB, "linking %s for OP_[F]CHOWN\n",
+ msg->pathlen ? msg->path : "no path");
+ pdb_link_file(msg, NULL);
+ }
+ break;
+ case OP_STAT: /* FALLTHROUGH */
+ case OP_FSTAT:
+ /* db_header will be whichever one looked best, in the rare
+ * case where there might be a clash.
+ */
+ if (found_ino || found_path) {
+ #ifdef PSEUDO_XATTRDB
+ if (db_header.uid == (uid_t) -1 &&
+ db_header.gid == (gid_t) -1) {
+ /* special case: this row was created
+ * to allow xattr lookups, and it's not
+ * actually valid data.
+ */
+ msg->result = RESULT_FAIL;
+ } else
+ #endif
+ {
+ *msg = db_header;
+ }
+ } else {
+ msg->result = RESULT_FAIL;
+ }
+ pseudo_debug(PDBGF_OP | PDBGF_VERBOSE, "%s, ino %llu (old mode 0%o): mode 0%o\n",
+ pseudo_op_name(msg->op), (unsigned long long) msg->ino,
+ (int) msg_header.mode, (int) msg->mode);
+ break;
+ case OP_LINK: /* FALLTHROUGH */
+ case OP_SYMLINK:
+ /* a successful link (client only notifies us for those)
+ * implies that the new path did not previously exist, and
+ * the old path did. We get the stat buffer and the new path.
+ * So, we unlink it, then link it. Neither unlink nor link
+ * touches the message, which was initialized from the
+ * underlying file data in the client.
+ */
+ if (found_path) {
+ pseudo_debug(PDBGF_OP | PDBGF_FILE, "replace %slink: path %s, old ino %llu, mode 0%o, new ino %llu, mode 0%o\n",
+ msg->op == OP_SYMLINK ? "sym" : "",
+ msg->path, (unsigned long long) msg->ino,
+ (int) msg->mode,
+ (unsigned long long) msg_header.ino,
+ (int) msg_header.mode);
+ pdb_unlink_file(msg);
+ } else {
+ pseudo_debug(PDBGF_OP | PDBGF_FILE, "new %slink: path %s, ino %llu, mode 0%o\n",
+ msg->op == OP_SYMLINK ? "sym" : "",
+ msg->path,
+ (unsigned long long) msg_header.ino,
+ (int) msg_header.mode);
+ }
+ if (found_ino) {
+ if (msg->op == OP_SYMLINK) {
+ pseudo_debug(PDBGF_OP | PDBGF_FILE, "symlink: ignoring existing file %llu ['%s']\n",
+ (unsigned long long) by_ino.ino,
+ path_by_ino ? path_by_ino : "no path");
+ } else {
+ *msg = by_ino;
+ pseudo_debug(PDBGF_OP | PDBGF_FILE, "link: copying data from existing file %llu ['%s']\n",
+ (unsigned long long) by_ino.ino,
+ path_by_ino ? path_by_ino : "no path");
+ }
+ } else {
+ *msg = msg_header;
+ }
+ pseudo_debug(PDBGF_DB, "linking %s for %s\n",
+ msg->pathlen ? msg->path : "no path",
+ pseudo_op_name(msg->op));
+ pdb_link_file(msg, NULL);
+ break;
+ case OP_RENAME:
+ /* a rename implies renaming an existing entry... and every
+ * database entry rooted in it, if it's a directory.
+ */
+ pdb_rename_file(oldpath, msg);
+ pdb_update_inode(msg);
+ break;
+ case OP_MAY_UNLINK:
+ if (pdb_may_unlink_file(msg, msg->client)) {
+ /* harmless, but client wants to know so it knows
+ * whether to follow up... */
+ msg->result = RESULT_FAIL;
+ }
+ break;
+ case OP_DID_UNLINK:
+ pdb_did_unlink_file(msg->path, msg->client);
+ break;
+ case OP_CANCEL_UNLINK:
+ pdb_cancel_unlink_file(msg);
+ break;
+ case OP_UNLINK:
+ /* this removes any entries with the given path from the
+ * database. No response is needed.
+ * DO NOT try to fail if the entry is already gone -- if the
+ * server's response didn't make it, the client would resend.
+ */
+ pdb_unlink_file(msg);
+ pdb_unlink_contents(msg);
+ /* If we are seeing an unlink for something with only one
+ * link, we should delete all records for that inode, even
+ * ones through different paths. This handles the case
+ * where something is removed through the wrong path, but
+ * only if it didn't have multiple hard links.
+ *
+ * This should cease to be needed once symlinks are tracked.
+ */
+ if (msg_header.nlink == 1 && found_ino) {
+ pseudo_debug(PDBGF_FILE | PDBGF_OP, "link count 1, unlinking anything with ino %llu.\n",
+ (unsigned long long) msg->ino);
+ pdb_unlink_file_dev(msg);
+ }
+ msg->result = RESULT_NONE;
+ break;
+ case OP_MKDIR: /* FALLTHROUGH */
+ case OP_MKNOD:
+ pseudo_debug(PDBGF_OP, "mode 0%o", (int) msg->mode);
+ /* for us to get called, the client has to have succeeded in
+ * a creation (of a regular file, for mknod) -- meaning this
+ * file DID NOT exist before the call. Fix database:
+ */
+ if (found_path) {
+ pseudo_diag("mkdir/mknod: '%s' [%llu] already existed (mode 0%o), unlinking\n",
+ msg->path, (unsigned long long) by_path.ino,
+ (int) by_path.mode);
+ pdb_unlink_file(msg);
+ }
+ if (found_ino) {
+ pdb_unlink_file_dev(&by_ino);
+ }
+ *msg = msg_header;
+ pseudo_debug(PDBGF_DB, "linking %s for %s\n",
+ msg->pathlen ? msg->path : "no path",
+ pseudo_op_name(msg->op));
+ pdb_link_file(msg, NULL);
+ break;
+ case OP_GET_XATTR:
+ if (pdb_get_xattr(row, &oldpath, &oldpathlen)) {
+ msg->result = RESULT_FAIL;
+ } else {
+ *response_path = oldpath;
+ *response_len = oldpathlen;
+ pseudo_debug(PDBGF_XATTR, "get results: '%.*s' (%d bytes)\n",
+ (int) *response_len, *response_path, (int) *response_len);
+ }
+ break;
+ case OP_LIST_XATTR:
+ if (pdb_list_xattr(row, &oldpath, &oldpathlen)) {
+ msg->result = RESULT_FAIL;
+ } else {
+ pseudo_debug(PDBGF_XATTR, "got %d bytes of xattrs to list: %.*s\n", (int) oldpathlen, (int) oldpathlen, oldpath);
+ *response_path = oldpath;
+ *response_len = oldpathlen;
+ }
+ break;
+ case OP_CREATE_XATTR:
+ case OP_REPLACE_XATTR: /* fallthrough */
+ if (msg->op == OP_CREATE_XATTR) {
+ xattr_flags = XATTR_CREATE;
+ }
+ if (msg->op == OP_REPLACE_XATTR) {
+ xattr_flags = XATTR_REPLACE;
+ }
+ case OP_SET_XATTR:
+ /* we need a row entry to store xattr info */
+ if (row == -1) {
+#ifdef PSEUDO_XATTRDB
+ /* mark the entry as Not Reliable for purposes
+ * of stat-type calls, since changes wouldn't
+ * get reported to us.
+ */
+ msg->uid = (uid_t) -1;
+ msg->gid = (gid_t) -1;
+#endif
+ pseudo_debug(PDBGF_DB, "linking %s (uid -1 if xattr) for %s\n",
+ msg->pathlen ? msg->path : "no path",
+ pseudo_op_name(msg->op));
+ pdb_link_file(msg, &row);
+ }
+ if (pdb_set_xattr(row, oldpath, oldpathlen, xattr_flags)) {
+ msg->result = RESULT_FAIL;
+ }
+ break;
+ case OP_REMOVE_XATTR:
+ pdb_remove_xattr(row, oldpath, oldpathlen);
+ break;
+ default:
+ pseudo_diag("unknown op from client %d, op %d [%s]\n",
+ msg->client, msg->op,
+ msg->pathlen ? msg->path : "no path");
+ break;
+ }
+
+ /* in the case of an exact match, we just used the pointer
+ * rather than allocating space.
+ */
+ if (path_by_ino != msg->path) {
+ free(path_by_ino);
+ }
+ pseudo_debug(PDBGF_OP, "completed %s.\n", pseudo_op_name(msg->op));
+ if (opt_l)
+ pdb_log_msg(SEVERITY_INFO, msg, program, tag, NULL);
+ return 0;
+}
+
+/* SHUTDOWN does not get this far, it's handled in pseudo_server.c */
+int
+pseudo_server_response(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len) {
+ switch (msg->type) {
+ case PSEUDO_MSG_PING:
+ /* mad hackery: if we aren't logging, the client gets told
+ * not to send open/exec notifications, which have no other
+ * purpose.
+ */
+ msg->result = opt_l ? RESULT_SUCCEED : RESULT_FAIL;
+ if (opt_l)
+ pdb_log_msg(SEVERITY_INFO, msg, program, tag, NULL);
+ return 0;
+ break;
+ case PSEUDO_MSG_OP:
+ case PSEUDO_MSG_FASTOP:
+ return pseudo_op(msg, program, tag, response_path, response_len);
+ break;
+ case PSEUDO_MSG_ACK: /* FALLTHROUGH */
+ case PSEUDO_MSG_NAK: /* FALLTHROUGH */
+ default:
+ pdb_log_msg(SEVERITY_WARN, msg, program, tag, "invalid message");
+ return 1;
+ }
+}
+
+int
+pseudo_db_check(int fix) {
+ struct stat buf;
+ pseudo_msg_t *m;
+ pdb_file_list l;
+ int errors = 0;
+ int delete_some = 0;
+ /* magic cookie used to show who's deleting the files */
+ int magic_cookie = (int) getpid();
+ int rc = 0;
+
+ l = pdb_files();
+ if (!l) {
+ pseudo_diag("Couldn't start file list, can't scan.\n");
+ return EXIT_FAILURE;
+ }
+ while ((m = pdb_file(l)) != NULL) {
+ pseudo_debug(PDBGF_DB, "m: %p (%d: %s)\n",
+ (void *) m,
+ m ? (int) m->pathlen : -1,
+ m ? m->path : "<n/a>");
+ if (m->pathlen > 0) {
+ int fixup_needed = 0;
+ pseudo_debug(PDBGF_DB, "Checking <%s>\n", m->path);
+ if (lstat(m->path, &buf)) {
+ errors = EXIT_FAILURE;
+ pseudo_diag("can't stat <%s>\n", m->path);
+ continue;
+ }
+ /* can't check for device type mismatches, uid/gid, or
+ * permissions, because those are the very things we
+ * can't really set.
+ */
+ if (buf.st_ino != m->ino) {
+ pseudo_debug(PDBGF_DB, "ino mismatch <%s>: ino %llu, db %llu\n",
+ m->path,
+ (unsigned long long) buf.st_ino,
+ (unsigned long long) m->ino);
+ m->ino = buf.st_ino;
+ fixup_needed = 1;
+ }
+ if (buf.st_dev != m->dev) {
+ pseudo_debug(PDBGF_DB, "dev mismatch <%s>: dev %llu, db %llu\n",
+ m->path,
+ (unsigned long long) buf.st_dev,
+ (unsigned long long) m->dev);
+ m->dev = buf.st_dev;
+ fixup_needed = 1;
+ }
+ if (S_ISLNK(buf.st_mode) != S_ISLNK(m->mode)) {
+ pseudo_debug(PDBGF_DB, "symlink mismatch <%s>: file %d, db %d\n",
+ m->path,
+ S_ISLNK(buf.st_mode),
+ S_ISLNK(m->mode));
+ fixup_needed = 2;
+ }
+ if (S_ISDIR(buf.st_mode) != S_ISDIR(m->mode)) {
+ pseudo_debug(PDBGF_DB, "symlink mismatch <%s>: file %d, db %d\n",
+ m->path,
+ S_ISDIR(buf.st_mode),
+ S_ISDIR(m->mode));
+ fixup_needed = 2;
+ }
+ if (fixup_needed) {
+ /* in fixup mode, either delete (mismatches) or
+ * correct (dev/ino).
+ */
+ if (fix) {
+ if (fixup_needed == 1) {
+ rc = pdb_update_inode(m);
+ } else if (fixup_needed == 2) {
+ /* mark for deletion */
+ delete_some = 1;
+ rc = pdb_may_unlink_file(m, magic_cookie);
+ }
+ if (rc) {
+ pseudo_diag("error updating file %s\n",
+ m->path);
+ errors = EXIT_FAILURE;
+ }
+ } else {
+ errors = EXIT_FAILURE;
+ }
+ }
+ }
+ }
+ pdb_files_done(l);
+ /* and now delete files marked for deletion */
+ if (delete_some) {
+ rc = pdb_did_unlink_files(magic_cookie);
+ if (rc) {
+ pseudo_diag("error nuking mismatched files.\n");
+ pseudo_diag("database may not be fixed.\n");
+ errors = EXIT_FAILURE;
+ }
+ }
+ return errors;
+}
diff --git a/pseudo.h b/pseudo.h
new file mode 100644
index 0000000..81db201
--- /dev/null
+++ b/pseudo.h
@@ -0,0 +1,165 @@
+/*
+ * pseudo.h, shared definitions and structures for pseudo
+ *
+ * Copyright (c) 2008-2010, 2013 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <stdlib.h>
+#include <fcntl.h>
+
+/* List of magic initialization functions... */
+extern void pseudo_init_wrappers(void);
+extern void pseudo_init_util(void);
+extern void pseudo_init_client(void);
+
+void pseudo_dump_env(char **envp);
+int pseudo_set_value(const char *key, const char *value);
+char *pseudo_get_value(const char *key);
+int pseudo_has_unload(char * const *envp);
+
+#include "pseudo_tables.h"
+
+extern void pseudo_debug_verbose(void);
+extern void pseudo_debug_terse(void);
+extern void pseudo_debug_set(char *);
+extern void pseudo_debug_clear(char *);
+extern void pseudo_debug_flags_finalize(void);
+extern unsigned long pseudo_util_debug_flags;
+extern int pseudo_util_debug_fd;
+extern int pseudo_disabled;
+extern int pseudo_allow_fsync;
+extern int pseudo_diag(char *, ...) __attribute__ ((format (printf, 1, 2)));
+#ifndef NDEBUG
+#define pseudo_debug(x, ...) do { \
+ if ((x) & PDBGF_VERBOSE) { \
+ if ((pseudo_util_debug_flags & PDBGF_VERBOSE) && (pseudo_util_debug_flags & ((x) & ~PDBGF_VERBOSE))) { pseudo_diag(__VA_ARGS__); } \
+ } else { \
+ if (pseudo_util_debug_flags & (x)) { pseudo_diag(__VA_ARGS__); } \
+ } \
+} while (0)
+#define pseudo_debug_call(x, fn, ...) do { \
+ if ((x) & PDBGF_VERBOSE) { \
+ if ((pseudo_util_debug_flags & PDBGF_VERBOSE) && (pseudo_util_debug_flags & ((x) & ~PDBGF_VERBOSE))) { fn(__VA_ARGS__); } \
+ } else { \
+ if (pseudo_util_debug_flags & (x)) { fn(__VA_ARGS__); } \
+ } \
+} while (0)
+#else
+/* this used to be a static inline function, but that meant that arguments
+ * were still evaluated for side effects. We don't want that. The ...
+ * is a C99ism, also supported by GNU C.
+ */
+#define pseudo_debug(...) 0
+#define pseudo_debug_call(...) 0
+#endif
+extern void pseudo_dump_data(char *, const void *, size_t);
+void pseudo_new_pid(void);
+/* pseudo_fix_path resolves symlinks up to this depth */
+#define PSEUDO_MAX_LINK_RECURSION 16
+extern char *pseudo_fix_path(const char *, const char *, size_t, size_t, size_t *, int);
+extern void pseudo_dropenv(void);
+extern char **pseudo_dropenvp(char * const *);
+extern void pseudo_setupenv(void);
+extern char **pseudo_setupenvp(char * const *);
+extern char *pseudo_prefix_path(char *);
+extern char *pseudo_bindir_path(char *);
+extern char *pseudo_libdir_path(char *);
+extern char *pseudo_localstatedir_path(char *);
+extern char *pseudo_get_prefix(char *);
+extern char *pseudo_get_bindir(void);
+extern char *pseudo_get_libdir(void);
+extern char *pseudo_get_localstatedir(void);
+extern int pseudo_logfile(char *defname);
+extern ssize_t pseudo_sys_path_max(void);
+extern ssize_t pseudo_path_max(void);
+#define PSEUDO_PWD_MAX 4096
+extern int pseudo_etc_file(const char *filename, char *realname, int flags, const char **search_dirs, int dircount);
+extern void pseudo_stat32_from64(struct stat *, const struct stat64 *);
+extern void pseudo_stat64_from32(struct stat64 *, const struct stat *);
+
+extern char *pseudo_version;
+
+#ifndef PSEUDO_BINDIR
+ #define PSEUDO_BINDIR "bin"
+#endif
+
+#ifndef PSEUDO_LIBDIR
+ #define PSEUDO_LIBDIR "lib"
+#endif
+
+#define STARTSWITH(x, y) (!memcmp((x), (y), sizeof(y) - 1))
+
+#ifndef PSEUDO_LOCALSTATEDIR
+ #define PSEUDO_LOCALSTATEDIR "var/pseudo"
+#endif
+
+#define PSEUDO_LOCKFILE "pseudo.lock"
+#define PSEUDO_LOGFILE "pseudo.log"
+#define PSEUDO_PIDFILE "pseudo.pid"
+#define PSEUDO_SOCKET "pseudo.socket"
+
+/* some systems might not have *at(). We like to define operations in
+ * terms of each other, and for instance, open(...) is the same as
+ * openat(AT_FDCWD, ...). If no AT_FDCWD is provided, any value that can't
+ * be a valid file descriptor will do. Using -2 because -1 could be
+ * mistaken for a failed syscall return. AT_SYMLINK_NOFOLLOW has to be
+ * non-zero; AT_SYMLINK_FOLLOW has to be non-zero and different. Finally,
+ * if this happened, we set our own flag we can use to indicate that dummy
+ * implementations of the _at functions are needed.
+ */
+#ifndef AT_FDCWD
+#define AT_FDCWD -2
+#define AT_SYMLINK_NOFOLLOW 1
+#define AT_SYMLINK_FOLLOW 2
+#define PSEUDO_NO_REAL_AT_FUNCTIONS
+#endif
+
+/* Likewise, someone might not have O_LARGEFILE (the flag equivalent to
+ * using open64()). Since open64() is the same as O_LARGEFILE in flags,
+ * we implement it that way... If the system has no O_LARGEFILE, we'll
+ * just call open() with nothing special.
+ */
+#ifndef O_LARGEFILE
+#define O_LARGEFILE 0
+#endif
+
+/* Does link(2) let you create hard links to symlinks? Of course not. Who
+ * would ever do that? Well, Linux did, and possibly as a result, linkat()
+ * does by default too; if you are on a host with the historical Unix
+ * behavior of following symlinks to find the link target, you will want
+ * to set this to AT_SYMLINK_FOLLOW. Darwin does.
+ */
+#define PSEUDO_LINK_SYMLINK_BEHAVIOR 0
+
+/* given n, pick a multiple of block enough bigger than n
+ * to give us some breathing room.
+ */
+static inline size_t
+round_up(size_t n, size_t block) {
+ return block * (((n + block / 4) / block) + 1);
+}
+
+#ifdef PSEUDO_PROFILING
+typedef struct {
+ int processes;
+ long long total_ops;
+ long long messages;
+ struct timeval op_time;
+ struct timeval ipc_time;
+ struct timeval wrapper_time;
+} pseudo_profile_t;
+#endif
+#include "pseudo_ports.h"
diff --git a/pseudo_client.c b/pseudo_client.c
new file mode 100644
index 0000000..4eb91df
--- /dev/null
+++ b/pseudo_client.c
@@ -0,0 +1,1982 @@
+/*
+ * pseudo_client.c, pseudo client library code
+ *
+ * Copyright (c) 2008-2013 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+
+#ifdef PSEUDO_XATTRDB
+#include <sys/xattr.h>
+#endif
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_client.h"
+
+/* GNU extension */
+#if PSEUDO_PORT_LINUX
+extern char *program_invocation_name;
+#else
+static char *program_invocation_name = "unknown";
+#endif
+
+static char *base_path(int dirfd, const char *path, int leave_last);
+
+static int connect_fd = -1;
+static int server_pid = 0;
+int pseudo_prefix_dir_fd = -1;
+int pseudo_localstate_dir_fd = -1;
+int pseudo_pwd_fd = -1;
+int pseudo_pwd_lck_fd = -1;
+char *pseudo_pwd_lck_name = NULL;
+FILE *pseudo_pwd = NULL;
+int pseudo_grp_fd = -1;
+FILE *pseudo_grp = NULL;
+char *pseudo_cwd = NULL;
+size_t pseudo_cwd_len;
+char *pseudo_chroot = NULL;
+char *pseudo_passwd = NULL;
+size_t pseudo_chroot_len = 0;
+char *pseudo_cwd_rel = NULL;
+/* used for PSEUDO_DISABLED */
+int pseudo_disabled = 0;
+int pseudo_allow_fsync = 0;
+static int pseudo_local_only = 0;
+static int pseudo_client_logging = 1;
+
+int pseudo_umask = 022;
+
+static char **fd_paths = NULL;
+static int nfds = 0;
+static const char **passwd_paths = NULL;
+static int npasswd_paths = 0;
+#ifdef PSEUDO_PROFILING
+int pseudo_profile_fd = -1;
+static int profile_interval = 1;
+static pseudo_profile_t profile_data;
+struct timeval *pseudo_wrapper_time = &profile_data.wrapper_time;
+#endif
+static int pseudo_inited = 0;
+
+static int sent_messages = 0;
+
+int pseudo_nosymlinkexp = 0;
+
+/* note: these are int, not uid_t/gid_t, so I can use 'em with scanf */
+uid_t pseudo_ruid;
+uid_t pseudo_euid;
+uid_t pseudo_suid;
+uid_t pseudo_fuid;
+gid_t pseudo_rgid;
+gid_t pseudo_egid;
+gid_t pseudo_sgid;
+gid_t pseudo_fgid;
+
+#define PSEUDO_ETC_FILE(filename, realname, flags) pseudo_etc_file(filename, realname, flags, passwd_paths, npasswd_paths)
+
+/* helper function to make a directory, just like mkdir -p.
+ * Can't use system() because the child shell would end up trying
+ * to do the same thing...
+ */
+static void
+mkdir_p(char *path) {
+ size_t len = strlen(path);
+ size_t i;
+
+ for (i = 1; i < len; ++i) {
+ /* try to create all the directories in path, ignoring
+ * failures
+ */
+ if (path[i] == '/') {
+ path[i] = '\0';
+ (void) mkdir(path, 0755);
+ path[i] = '/';
+ }
+ }
+ (void) mkdir(path, 0755);
+}
+
+/* Populating an array of unknown size is one of my least favorite
+ * things. The idea here is to ensure that the logic flow is the same
+ * both when counting expected items, and when populating them.
+ */
+static void
+build_passwd_paths(void)
+{
+ int np = 0;
+ int pass = 0;
+
+ /* should never happen... */
+ if (passwd_paths) {
+ free(passwd_paths);
+ passwd_paths = 0;
+ npasswd_paths = 0;
+ }
+
+#define SHOW_PATH pseudo_debug(PDBGF_CHROOT | PDBGF_VERBOSE, "passwd_paths[%d]: '%s'\n", np, (passwd_paths[np]))
+#define ADD_PATH(p) do { if (passwd_paths) { passwd_paths[np] = (p); SHOW_PATH; } ++np; } while(0)
+#define NUL_BYTE(p) do { if (passwd_paths) { *(p)++ = '\0'; } else { ++(p); } } while(0)
+
+ do {
+ if (pseudo_chroot) {
+ ADD_PATH(pseudo_chroot);
+ }
+ if (pseudo_passwd) {
+ char *s = pseudo_passwd;
+ while (s) {
+ char *t = strchr(s, ':');
+ if (t) {
+ NUL_BYTE(t);
+ }
+ ADD_PATH(s);
+ s = t;
+ }
+ }
+ if (PSEUDO_PASSWD_FALLBACK) {
+ ADD_PATH(PSEUDO_PASSWD_FALLBACK);
+ }
+
+ /* allocation and/or return */
+ if (passwd_paths) {
+ if (np != npasswd_paths) {
+ pseudo_diag("internal error: path allocation was inconsistent.\n");
+ } else {
+ /* yes, we allocated one extra for a trailing
+ * null pointer.
+ */
+ passwd_paths[np] = NULL;
+ }
+ return;
+ } else {
+ passwd_paths = malloc((np + 1) * sizeof(*passwd_paths));
+ npasswd_paths = np;
+ if (!passwd_paths) {
+ pseudo_diag("couldn't allocate storage for password paths.\n");
+ exit(1);
+ }
+ np = 0;
+ }
+ } while (++pass < 2);
+ /* in theory the second pass already returned, but. */
+ pseudo_diag("should totally not have gotten here.\n");
+
+ return;
+}
+
+#ifdef PSEUDO_XATTRDB
+/* We really want to avoid calling the wrappers for these inside the
+ * implementation. pseudo_wrappers will reinitialize these after it's
+ * gotten the real_* found.
+ */
+ssize_t (*pseudo_real_lgetxattr)(const char *, const char *, void *, size_t) = lgetxattr;
+ssize_t (*pseudo_real_fgetxattr)(int, const char *, void *, size_t) = fgetxattr;
+int (*pseudo_real_lsetxattr)(const char *, const char *, const void *, size_t, int) = lsetxattr;
+int (*pseudo_real_fsetxattr)(int, const char *, const void *, size_t, int) = fsetxattr;
+/* Executive summary: We use an extended attribute,
+ * user.pseudo_data, to store exactly the data we would otherwise
+ * have stored in the database. Which is to say, uid, gid, mode, rdev.
+ *
+ * If we don't find a value, save an empty one with a lower version
+ * number to indicate that we don't have data to reduce round trips.
+ */
+typedef struct {
+ int version;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ dev_t rdev;
+} pseudo_db_data_t;
+
+static pseudo_msg_t xattrdb_data;
+
+pseudo_msg_t *
+pseudo_xattrdb_save(int fd, const char *path, const struct stat64 *buf) {
+ int rc = -1;
+ if (!path && fd < 0)
+ return NULL;
+ if (!buf)
+ return NULL;
+ pseudo_db_data_t pseudo_db_data = {
+ .version = 1,
+ .uid = buf->st_uid,
+ .gid = buf->st_gid,
+ .mode = buf->st_mode,
+ .rdev = buf->st_rdev
+ };
+ if (path) {
+ rc = pseudo_real_lsetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0);
+ } else if (fd >= 0) {
+ rc = pseudo_real_fsetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0);
+ }
+ pseudo_debug(PDBGF_XATTRDB, "tried to save data for %s/%d: uid %d, mode %o, rc %d.\n",
+ path ? path : "<nil>", fd, (int) pseudo_db_data.uid, (int) pseudo_db_data.mode, rc);
+ /* none of the other fields are checked on save, and the value
+ * is currently only really used by mknod.
+ */
+ if (rc == 0) {
+ xattrdb_data.result = RESULT_SUCCEED;
+ return &xattrdb_data;
+ }
+ return NULL;
+}
+
+pseudo_msg_t *
+pseudo_xattrdb_load(int fd, const char *path, const struct stat64 *buf) {
+ int rc = -1, retryrc = -1;
+ if (!path && fd < 0)
+ return NULL;
+ /* don't try to getxattr on a thing unless we think it is
+ * likely to work.
+ */
+ if (buf) {
+ if (!S_ISDIR(buf->st_mode) && !S_ISREG(buf->st_mode)) {
+ return NULL;
+ }
+ }
+ pseudo_db_data_t pseudo_db_data;
+ if (path) {
+ rc = pseudo_real_lgetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data));
+ if (rc == -1) {
+ pseudo_db_data = (pseudo_db_data_t) { .version = 0 };
+ retryrc = pseudo_real_lsetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0);
+ }
+ } else if (fd >= 0) {
+ rc = pseudo_real_fgetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data));
+ if (rc == -1) {
+ pseudo_db_data = (pseudo_db_data_t) { .version = 0 };
+ retryrc = pseudo_real_fsetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0);
+ }
+ }
+ pseudo_debug(PDBGF_XATTRDB, "tried to load data for %s[%d]: rc %d, version %d.\n",
+ path ? path : "<nil>", fd, rc, pseudo_db_data.version);
+ if (rc == -1 && retryrc == 0) {
+ /* there's no data, but there could have been; treat
+ * this as an empty database result.
+ */
+ pseudo_debug(PDBGF_XATTRDB, "wrote version 0 for %s[%d]\n",
+ path ? path : "<nil>", fd);
+ xattrdb_data.result = RESULT_FAIL;
+ return &xattrdb_data;
+ } else if (rc == -1) {
+ /* we can't create an extended attribute, so we may have
+ * used the database.
+ */
+ return NULL;
+ }
+ /* Version 0 = just recording that we looked and found
+ * nothing.
+ * Version 1 = actually implemented.
+ */
+ switch (pseudo_db_data.version) {
+ case 0:
+ default:
+ xattrdb_data.result = RESULT_FAIL;
+ break;
+ case 1:
+ xattrdb_data.uid = pseudo_db_data.uid;
+ xattrdb_data.gid = pseudo_db_data.gid;
+ xattrdb_data.mode = pseudo_db_data.mode;
+ xattrdb_data.rdev = pseudo_db_data.rdev;
+ xattrdb_data.result = RESULT_SUCCEED;
+ break;
+ }
+ return &xattrdb_data;
+}
+#endif
+
+#ifdef PSEUDO_PROFILING
+static int pseudo_profile_pid = -2;
+
+static void
+pseudo_profile_start(void) {
+ /* We use -1 as a starting value, and -2 as a value
+ * indicating not to try to open it.
+ */
+ int existing_data = 0;
+ int pid = getpid();
+ if (pseudo_profile_pid > 0 && pseudo_profile_pid != pid) {
+ /* looks like there's been a fork. We intentionally
+ * abandon any existing stats, since the parent might
+ * want to write them, and we want to combine with
+ * any previous stats for this pid.
+ */
+ close(pseudo_profile_fd);
+ pseudo_profile_fd = -1;
+ }
+ if (pseudo_profile_fd == -1) {
+ int fd = -2;
+ char *profile_path = pseudo_get_value("PSEUDO_PROFILE_PATH");
+ pseudo_profile_pid = pid;
+ if (profile_path) {
+ fd = open(profile_path, O_RDWR | O_CREAT, 0600);
+ if (fd >= 0) {
+ fd = pseudo_fd(fd, MOVE_FD);
+ }
+ if (fd < 0) {
+ fd = -2;
+ } else {
+ if (pid > 0) {
+ pseudo_profile_t data;
+ off_t rc;
+
+ rc = lseek(fd, pid * sizeof(data), SEEK_SET);
+ if (rc == (off_t) (pid * sizeof(data))) {
+ rc = read(fd, &profile_data, sizeof(profile_data));
+ /* cumulative with other values in same file */
+ if (rc == sizeof(profile_data)) {
+ pseudo_debug(PDBGF_PROFILE, "pid %d found existing profiling data.\n", pid);
+ existing_data = 1;
+ ++profile_data.processes;
+ } else {
+ pseudo_debug(PDBGF_PROFILE, "read failed for pid %d: %d, %d\n", pid, (int) rc, errno);
+ }
+ profile_interval = 1;
+ }
+ }
+ }
+ }
+ pseudo_profile_fd = fd;
+ } else {
+ pseudo_debug(PDBGF_PROFILE, "_start called with existing fd? (pid %d)", (int) pseudo_profile_pid);
+ existing_data = 1;
+ ++profile_data.processes;
+ profile_data.total_ops = 0;
+ profile_data.messages = 0;
+ profile_data.wrapper_time = (struct timeval) { .tv_sec = 0 };
+ profile_data.op_time = (struct timeval) { .tv_sec = 0 };
+ profile_data.ipc_time = (struct timeval) { .tv_sec = 0 };
+ }
+ if (!existing_data) {
+ pseudo_debug(PDBGF_PROFILE, "pid %d found no existing profiling data.\n", pid);
+ profile_data = (pseudo_profile_t) {
+ .processes = 1,
+ .total_ops = 0,
+ .messages = 0,
+ .wrapper_time = (struct timeval) { .tv_sec = 0 },
+ .op_time = (struct timeval) { .tv_sec = 0 },
+ .ipc_time = (struct timeval) { .tv_sec = 0 },
+ };
+ }
+}
+
+static inline void
+fix_tv(struct timeval *tv) {
+ if (tv->tv_usec > 1000000) {
+ tv->tv_sec += tv->tv_usec / 1000000;
+ tv->tv_usec %= 1000000;
+ } else if (tv->tv_usec < 0) {
+ /* C99 and later guarantee truncate-towards-zero.
+ * We want -1 through -1000000 usec to produce
+ * -1 seconds, etcetera. Note that sec is
+ * negative, so yes, we want to add to tv_sec and
+ * subtract from tv_usec.
+ */
+ int sec = (tv->tv_usec - 999999) / 1000000;
+ tv->tv_sec += sec;
+ tv->tv_usec -= 1000000 * sec;
+ }
+}
+
+static int profile_reported = 0;
+static void
+pseudo_profile_report(void) {
+ if (pseudo_profile_fd < 0) {
+ return;
+ }
+ fix_tv(&profile_data.wrapper_time);
+ fix_tv(&profile_data.op_time);
+ fix_tv(&profile_data.ipc_time);
+ if (pseudo_profile_pid >= 0) {
+ int rc1, rc2;
+ rc1 = lseek(pseudo_profile_fd, pseudo_profile_pid * sizeof(profile_data), SEEK_SET);
+ rc2 = write(pseudo_profile_fd, &profile_data, sizeof(profile_data));
+ if (!profile_reported) {
+ pseudo_debug(PDBGF_PROFILE, "pid %d (%s) saving profiling info: %d, %d.\n",
+ pseudo_profile_pid,
+ program_invocation_name ? program_invocation_name : "unknown",
+ rc1, rc2);
+ profile_reported = 1;
+ }
+ }
+}
+#endif
+
+void
+pseudo_init_client(void) {
+ char *env;
+
+ pseudo_antimagic();
+ pseudo_new_pid();
+ if (connect_fd != -1) {
+ close(connect_fd);
+ connect_fd = -1;
+ }
+#ifdef PSEUDO_PROFILING
+ if (pseudo_profile_fd > -1) {
+ close(pseudo_profile_fd);
+ }
+ pseudo_profile_fd = -1;
+#endif
+
+ /* in child processes, PSEUDO_DISABLED may have become set to
+ * some truthy value, in which case we'd disable pseudo,
+ * or it may have gone away, in which case we'd enable
+ * pseudo (and cause it to reinit the defaults).
+ */
+ env = getenv("PSEUDO_DISABLED");
+ if (!env) {
+ env = pseudo_get_value("PSEUDO_DISABLED");
+ }
+ if (env) {
+ int actually_disabled = 1;
+ switch (*env) {
+ case '0':
+ case 'f':
+ case 'F':
+ case 'n':
+ case 'N':
+ actually_disabled = 0;
+ break;
+ case 's':
+ case 'S':
+ actually_disabled = 0;
+ pseudo_local_only = 1;
+ break;
+ }
+ if (actually_disabled) {
+ if (!pseudo_disabled) {
+ pseudo_antimagic();
+ pseudo_disabled = 1;
+ }
+ pseudo_set_value("PSEUDO_DISABLED", "1");
+ } else {
+ if (pseudo_disabled) {
+ pseudo_magic();
+ pseudo_disabled = 0;
+ pseudo_inited = 0; /* Re-read the initial values! */
+ }
+ pseudo_set_value("PSEUDO_DISABLED", "0");
+ }
+ } else {
+ pseudo_set_value("PSEUDO_DISABLED", "0");
+ }
+
+ /* ALLOW_FSYNC is here because some crazy hosts will otherwise
+ * report incorrect values for st_size/st_blocks. I can sort of
+ * understand st_blocks, but bogus values for st_size? Not cool,
+ * dudes, not cool.
+ */
+ env = getenv("PSEUDO_ALLOW_FSYNC");
+ if (!env) {
+ env = pseudo_get_value("PSEUDO_ALLOW_FSYNC");
+ } else {
+ pseudo_set_value("PSEUDO_ALLOW_FSYNC", env);
+ }
+ if (env) {
+ pseudo_allow_fsync = 1;
+ } else {
+ pseudo_allow_fsync = 0;
+ }
+
+ /* in child processes, PSEUDO_UNLOAD may become set to
+ * some truthy value, in which case we're being asked to
+ * remove pseudo from the LD_PRELOAD. We need to make sure
+ * this value gets loaded into the internal variables.
+ *
+ * If we've been told to unload, but are still available
+ * we need to act as if unconditionally disabled.
+ */
+ env = getenv("PSEUDO_UNLOAD");
+ if (env) {
+ pseudo_set_value("PSEUDO_UNLOAD", env);
+ pseudo_disabled = 1;
+ }
+
+ /* Setup global items needed for pseudo to function... */
+ if (!pseudo_inited) {
+ /* Ensure that all of the values are reset */
+ server_pid = 0;
+ pseudo_prefix_dir_fd = -1;
+ pseudo_localstate_dir_fd = -1;
+ pseudo_pwd_fd = -1;
+ pseudo_pwd_lck_fd = -1;
+ pseudo_pwd_lck_name = NULL;
+ pseudo_pwd = NULL;
+ pseudo_grp_fd = -1;
+ pseudo_grp = NULL;
+ pseudo_cwd = NULL;
+ pseudo_cwd_len = 0;
+ pseudo_chroot = NULL;
+ pseudo_passwd = NULL;
+ pseudo_chroot_len = 0;
+ pseudo_cwd_rel = NULL;
+ pseudo_nosymlinkexp = 0;
+ }
+
+ if (!pseudo_disabled && !pseudo_inited) {
+ char *pseudo_path = 0;
+
+ pseudo_umask = umask(022);
+ umask(pseudo_umask);
+
+ pseudo_path = pseudo_prefix_path(NULL);
+ if (pseudo_prefix_dir_fd == -1) {
+ if (pseudo_path) {
+ pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY);
+ /* directory is missing? */
+ if (pseudo_prefix_dir_fd == -1 && errno == ENOENT) {
+ pseudo_debug(PDBGF_CLIENT, "prefix directory '%s' doesn't exist, trying to create\n", pseudo_path);
+ mkdir_p(pseudo_path);
+ pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY);
+ }
+ pseudo_prefix_dir_fd = pseudo_fd(pseudo_prefix_dir_fd, MOVE_FD);
+ } else {
+ pseudo_diag("No prefix available to to find server.\n");
+ exit(1);
+ }
+ if (pseudo_prefix_dir_fd == -1) {
+ pseudo_diag("Can't open prefix path '%s' for server: %s\n",
+ pseudo_path,
+ strerror(errno));
+ exit(1);
+ }
+ }
+ free(pseudo_path);
+ pseudo_path = pseudo_localstatedir_path(NULL);
+ if (pseudo_localstate_dir_fd == -1) {
+ if (pseudo_path) {
+ pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY);
+ /* directory is missing? */
+ if (pseudo_localstate_dir_fd == -1 && errno == ENOENT) {
+ pseudo_debug(PDBGF_CLIENT, "local state directory '%s' doesn't exist, trying to create\n", pseudo_path);
+ mkdir_p(pseudo_path);
+ pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY);
+ }
+ pseudo_localstate_dir_fd = pseudo_fd(pseudo_localstate_dir_fd, MOVE_FD);
+ } else {
+ pseudo_diag("No local state directory available for server/file interactions.\n");
+ exit(1);
+ }
+ if (pseudo_localstate_dir_fd == -1) {
+ pseudo_diag("Can't open local state path '%s': %s\n",
+ pseudo_path,
+ strerror(errno));
+ exit(1);
+ }
+ }
+ free(pseudo_path);
+
+ env = pseudo_get_value("PSEUDO_NOSYMLINKEXP");
+ if (env) {
+ char *endptr;
+ /* if the environment variable is not an empty string,
+ * parse it; "0" means turn NOSYMLINKEXP off, "1" means
+ * turn it on (disabling the feature). An empty string
+ * or something we can't parse means to set the flag; this
+ * is a safe default because if you didn't want the flag
+ * set, you normally wouldn't set the environment variable
+ * at all.
+ */
+ if (*env) {
+ pseudo_nosymlinkexp = strtol(env, &endptr, 10);
+ if (*endptr)
+ pseudo_nosymlinkexp = 1;
+ } else {
+ pseudo_nosymlinkexp = 1;
+ }
+ } else {
+ pseudo_nosymlinkexp = 0;
+ }
+ free(env);
+ env = pseudo_get_value("PSEUDO_UIDS");
+ if (env)
+ sscanf(env, "%d,%d,%d,%d",
+ &pseudo_ruid, &pseudo_euid,
+ &pseudo_suid, &pseudo_fuid);
+ free(env);
+
+ env = pseudo_get_value("PSEUDO_GIDS");
+ if (env)
+ sscanf(env, "%d,%d,%d,%d",
+ &pseudo_rgid, &pseudo_egid,
+ &pseudo_sgid, &pseudo_fuid);
+ free(env);
+
+ env = pseudo_get_value("PSEUDO_CHROOT");
+ if (env) {
+ pseudo_chroot = strdup(env);
+ if (pseudo_chroot) {
+ pseudo_chroot_len = strlen(pseudo_chroot);
+ } else {
+ pseudo_diag("Can't store chroot path '%s'\n", env);
+ }
+ }
+ free(env);
+
+ env = pseudo_get_value("PSEUDO_PASSWD");
+ if (env) {
+ /* note: this means that pseudo_passwd is a
+ * string we're allowed to modify... */
+ pseudo_passwd = strdup(env);
+ }
+ free(env);
+ build_passwd_paths();
+
+ pseudo_inited = 1;
+ }
+ if (!pseudo_disabled) {
+ pseudo_client_getcwd();
+#ifdef PSEUDO_PROFILING
+ pseudo_profile_start();
+#endif
+ }
+
+ pseudo_magic();
+}
+
+static void
+pseudo_file_close(int *fd, FILE **fp) {
+ if (!fp || !fd) {
+ pseudo_diag("pseudo_file_close: needs valid pointers.\n");
+ return;
+ }
+ pseudo_antimagic();
+ if (*fp) {
+#if PSEUDO_PORT_DARWIN
+ if (*fp == pseudo_host_etc_passwd_file) {
+ endpwent();
+ } else if (*fp != pseudo_host_etc_group_file) {
+ endgrent();
+ } else {
+ fclose(*fp);
+ }
+#else
+ fclose(*fp);
+#endif
+ *fd = -1;
+ *fp = 0;
+ }
+#if PSEUDO_PORT_DARWIN
+ if (*fd == pseudo_host_etc_passwd_fd ||
+ *fd == pseudo_host_etc_group_fd) {
+ *fd = -1;
+ }
+#endif
+ /* this should be impossible */
+ if (*fd >= 0) {
+ close(*fd);
+ *fd = -1;
+ }
+ pseudo_magic();
+}
+
+static FILE *
+pseudo_file_open(char *name, int *fd, FILE **fp) {
+ if (!fp || !fd || !name) {
+ pseudo_diag("pseudo_file_open: needs valid pointers.\n");
+ return NULL;
+ }
+ pseudo_file_close(fd, fp);
+ pseudo_antimagic();
+ *fd = PSEUDO_ETC_FILE(name, NULL, O_RDONLY);
+#if PSEUDO_PORT_DARWIN
+ if (*fd == pseudo_host_etc_passwd_fd) {
+ *fp = pseudo_host_etc_passwd_file;
+ setpwent();
+ } else if (*fd == pseudo_host_etc_group_fd) {
+ *fp = pseudo_host_etc_group_file;
+ setgrent();
+ }
+#endif
+ if (*fd >= 0) {
+ *fd = pseudo_fd(*fd, MOVE_FD);
+ *fp = fdopen(*fd, "r");
+ if (!*fp) {
+ close(*fd);
+ *fd = -1;
+ }
+ }
+ pseudo_magic();
+ return *fp;
+}
+
+/* there is no spec I know of requiring us to defend this fd
+ * against being closed by the user.
+ */
+int
+pseudo_pwd_lck_open(void) {
+ pseudo_pwd_lck_close();
+ if (!pseudo_pwd_lck_name) {
+ pseudo_pwd_lck_name = malloc(pseudo_path_max());
+ if (!pseudo_pwd_lck_name) {
+ pseudo_diag("couldn't allocate space for passwd lockfile path.\n");
+ return -1;
+ }
+ }
+ pseudo_antimagic();
+ pseudo_pwd_lck_fd = PSEUDO_ETC_FILE(".pwd.lock",
+ pseudo_pwd_lck_name, O_RDWR | O_CREAT);
+ pseudo_magic();
+ return pseudo_pwd_lck_fd;
+}
+
+int
+pseudo_pwd_lck_close(void) {
+ if (pseudo_pwd_lck_fd != -1) {
+ pseudo_antimagic();
+ close(pseudo_pwd_lck_fd);
+ if (pseudo_pwd_lck_name) {
+ unlink(pseudo_pwd_lck_name);
+ free(pseudo_pwd_lck_name);
+ pseudo_pwd_lck_name = 0;
+ }
+ pseudo_magic();
+ pseudo_pwd_lck_fd = -1;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+FILE *
+pseudo_pwd_open(void) {
+ return pseudo_file_open("passwd", &pseudo_pwd_fd, &pseudo_pwd);
+}
+
+void
+pseudo_pwd_close(void) {
+ pseudo_file_close(&pseudo_pwd_fd, &pseudo_pwd);
+}
+
+FILE *
+pseudo_grp_open(void) {
+ return pseudo_file_open("group", &pseudo_grp_fd, &pseudo_grp);
+}
+
+void
+pseudo_grp_close(void) {
+ pseudo_file_close(&pseudo_grp_fd, &pseudo_grp);
+}
+
+void
+pseudo_client_touchuid(void) {
+ static char uidbuf[256];
+ snprintf(uidbuf, 256, "%d,%d,%d,%d",
+ pseudo_ruid, pseudo_euid, pseudo_suid, pseudo_fuid);
+ pseudo_set_value("PSEUDO_UIDS", uidbuf);
+}
+
+void
+pseudo_client_touchgid(void) {
+ static char gidbuf[256];
+ snprintf(gidbuf, 256, "%d,%d,%d,%d",
+ pseudo_rgid, pseudo_egid, pseudo_sgid, pseudo_fgid);
+ pseudo_set_value("PSEUDO_GIDS", gidbuf);
+}
+
+int
+pseudo_client_chroot(const char *path) {
+ /* free old value */
+ free(pseudo_chroot);
+
+ pseudo_debug(PDBGF_CLIENT | PDBGF_CHROOT, "client chroot: %s\n", path);
+ if (!strcmp(path, "/")) {
+ pseudo_chroot_len = 0;
+ pseudo_chroot = 0;
+ pseudo_set_value("PSEUDO_CHROOT", NULL);
+ return 0;
+ }
+ /* allocate new value */
+ pseudo_chroot_len = strlen(path);
+ pseudo_chroot = malloc(pseudo_chroot_len + 1);
+ if (!pseudo_chroot) {
+ pseudo_diag("Couldn't allocate chroot directory buffer.\n");
+ pseudo_chroot_len = 0;
+ errno = ENOMEM;
+ return -1;
+ }
+ memcpy(pseudo_chroot, path, pseudo_chroot_len + 1);
+ pseudo_set_value("PSEUDO_CHROOT", pseudo_chroot);
+ return 0;
+}
+
+char *
+pseudo_root_path(const char *func, int line, int dirfd, const char *path, int leave_last) {
+ char *rc;
+ pseudo_antimagic();
+ rc = base_path(dirfd, path, leave_last);
+ pseudo_magic();
+ if (!rc) {
+ pseudo_diag("couldn't allocate absolute path for '%s'.\n",
+ path);
+ }
+ pseudo_debug(PDBGF_CHROOT, "root_path [%s, %d]: '%s' from '%s'\n",
+ func, line,
+ rc ? rc : "<nil>",
+ path ? path : "<nil>");
+ return rc;
+}
+
+int
+pseudo_client_getcwd(void) {
+ char *cwd;
+ cwd = malloc(pseudo_path_max());
+ if (!cwd) {
+ pseudo_diag("Can't allocate CWD buffer!\n");
+ return -1;
+ }
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "getcwd: trying to find cwd.\n");
+ if (getcwd(cwd, pseudo_path_max())) {
+ /* cwd now holds a canonical path to current directory */
+ free(pseudo_cwd);
+ pseudo_cwd = cwd;
+ pseudo_cwd_len = strlen(pseudo_cwd);
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "getcwd okay: [%s] %d bytes\n", pseudo_cwd, (int) pseudo_cwd_len);
+ if (pseudo_chroot_len &&
+ pseudo_cwd_len >= pseudo_chroot_len &&
+ !memcmp(pseudo_cwd, pseudo_chroot, pseudo_chroot_len) &&
+ (pseudo_cwd[pseudo_chroot_len] == '\0' ||
+ pseudo_cwd[pseudo_chroot_len] == '/')) {
+ pseudo_cwd_rel = pseudo_cwd + pseudo_chroot_len;
+ } else {
+ pseudo_cwd_rel = pseudo_cwd;
+ }
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "abscwd: <%s>\n", pseudo_cwd);
+ if (pseudo_cwd_rel != pseudo_cwd) {
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "relcwd: <%s>\n", pseudo_cwd_rel);
+ }
+ return 0;
+ } else {
+ pseudo_diag("Can't get CWD: %s\n", strerror(errno));
+ return -1;
+ }
+}
+
+static char *
+fd_path(int fd) {
+ if (fd >= 0 && fd < nfds) {
+ return fd_paths[fd];
+ }
+ if (fd == AT_FDCWD) {
+ return pseudo_cwd;
+ }
+ return 0;
+}
+
+static void
+pseudo_client_path(int fd, const char *path) {
+ if (fd < 0)
+ return;
+
+ if (fd >= nfds) {
+ int i;
+ pseudo_debug(PDBGF_CLIENT, "expanding fds from %d to %d\n",
+ nfds, fd + 1);
+ fd_paths = realloc(fd_paths, (fd + 1) * sizeof(char *));
+ for (i = nfds; i < fd + 1; ++i)
+ fd_paths[i] = 0;
+ nfds = fd + 1;
+ } else {
+ if (fd_paths[fd]) {
+ pseudo_debug(PDBGF_CLIENT, "reopening fd %d [%s] -- didn't see close\n",
+ fd, fd_paths[fd]);
+ }
+ /* yes, it is safe to free null pointers. yay for C89 */
+ free(fd_paths[fd]);
+ fd_paths[fd] = 0;
+ }
+ if (path) {
+ fd_paths[fd] = strdup(path);
+ }
+}
+
+static void
+pseudo_client_close(int fd) {
+ if (fd < 0 || fd >= nfds)
+ return;
+
+ free(fd_paths[fd]);
+ fd_paths[fd] = 0;
+}
+
+/* spawn server */
+static int
+client_spawn_server(void) {
+ int status;
+ FILE *fp;
+ char * pseudo_pidfile;
+
+ if ((server_pid = fork()) != 0) {
+ if (server_pid == -1) {
+ pseudo_diag("couldn't fork server: %s\n", strerror(errno));
+ return 1;
+ }
+ pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "spawned server, pid %d\n", server_pid);
+ /* wait for the child process to terminate, indicating server
+ * is ready
+ */
+ waitpid(server_pid, &status, 0);
+ server_pid = -2;
+ pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE);
+ fp = fopen(pseudo_pidfile, "r");
+ if (fp) {
+ if (fscanf(fp, "%d", &server_pid) != 1) {
+ pseudo_debug(PDBGF_CLIENT, "Opened server PID file, but didn't get a pid.\n");
+ }
+ fclose(fp);
+ } else {
+ pseudo_debug(PDBGF_CLIENT, "no pid file (%s): %s\n",
+ pseudo_pidfile, strerror(errno));
+ }
+ pseudo_debug(PDBGF_CLIENT, "read new pid file: %d\n", server_pid);
+ free(pseudo_pidfile);
+ /* at this point, we should have a new server_pid */
+ return 0;
+ } else {
+ char *base_args[] = { NULL, NULL, NULL };
+ char **argv;
+ char *option_string = pseudo_get_value("PSEUDO_OPTS");
+ int args;
+ int fd;
+
+ pseudo_new_pid();
+ base_args[0] = pseudo_bindir_path("pseudo");
+ base_args[1] = "-d";
+ if (option_string) {
+ char *s;
+ int arg;
+
+ /* count arguments in PSEUDO_OPTS, starting at 2
+ * for pseudo/-d/NULL, plus one for the option string.
+ * The number of additional arguments may be less
+ * than the number of spaces, but can't be more.
+ */
+ args = 4;
+ for (s = option_string; *s; ++s)
+ if (*s == ' ')
+ ++args;
+
+ argv = malloc(args * sizeof(char *));
+ argv[0] = base_args[0];
+ argv[1] = base_args[1];
+ arg = 2;
+ while ((s = strsep(&option_string, " ")) != NULL) {
+ if (*s) {
+ argv[arg++] = strdup(s);
+ }
+ }
+ argv[arg] = 0;
+ } else {
+ argv = base_args;
+ }
+
+ /* close any higher-numbered fds which might be open,
+ * such as sockets. We don't have to worry about 0 and 1;
+ * the server closes them already, and more importantly,
+ * they can't have been opened or closed without us already
+ * having spawned a server... The issue is just socket()
+ * calls which could result in fds being left open, and those
+ * can't overwrite fds 0-2 unless we closed them...
+ *
+ * No, really. It works.
+ */
+ for (fd = 3; fd < 1024; ++fd) {
+ if (fd != pseudo_util_debug_fd)
+ close(fd);
+ }
+ /* and now, execute the server */
+
+ pseudo_set_value("PSEUDO_UNLOAD", "YES");
+ pseudo_setupenv();
+ pseudo_dropenv(); /* drop PRELINK_LIBRARIES */
+
+ pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER | PDBGF_INVOKE, "calling execv on %s\n", argv[0]);
+
+ execv(argv[0], argv);
+ pseudo_diag("critical failure: exec of pseudo daemon failed: %s\n", strerror(errno));
+ exit(1);
+ }
+}
+
+static int
+client_ping(void) {
+ pseudo_msg_t ping;
+ pseudo_msg_t *ack;
+ char tagbuf[pseudo_path_max()];
+ char *tag = pseudo_get_value("PSEUDO_TAG");
+
+ memset(&ping, 0, sizeof(ping));
+
+ ping.type = PSEUDO_MSG_PING;
+ ping.op = OP_NONE;
+
+ ping.pathlen = snprintf(tagbuf, sizeof(tagbuf), "%s%c%s",
+ program_invocation_name ? program_invocation_name : "<unknown>",
+ 0,
+ tag ? tag : "");
+ free(tag);
+ ping.client = getpid();
+ ping.result = 0;
+ errno = 0;
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending ping\n");
+ if (pseudo_msg_send(connect_fd, &ping, ping.pathlen, tagbuf)) {
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "error pinging server: %s\n", strerror(errno));
+ return 1;
+ }
+ ack = pseudo_msg_receive(connect_fd);
+ if (!ack) {
+ pseudo_debug(PDBGF_CLIENT, "no ping response from server: %s\n", strerror(errno));
+ /* and that's not good, so... */
+ server_pid = 0;
+ return 1;
+ }
+ if (ack->type != PSEUDO_MSG_ACK) {
+ pseudo_debug(PDBGF_CLIENT, "invalid ping response from server: expected ack, got %d\n", ack->type);
+ /* and that's not good, so... */
+ server_pid = 0;
+ return 1;
+ } else {
+ /* The server tells us whether or not to log things. */
+ if (ack->result == RESULT_SUCCEED) {
+ pseudo_client_logging = 1;
+ } else {
+ pseudo_client_logging = 0;
+ }
+ }
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "ping ok\n");
+ return 0;
+}
+
+static void
+void_client_ping(void) {
+ client_ping();
+}
+
+int
+pseudo_fd(int fd, int how) {
+ int newfd;
+
+ if (fd < 0)
+ return(-1);
+
+ /* If already above PSEUDO_MIN_FD, no need to move */
+ if ((how == MOVE_FD) && (fd >= PSEUDO_MIN_FD)) {
+ newfd = fd;
+ } else {
+ newfd = fcntl(fd, F_DUPFD, PSEUDO_MIN_FD);
+
+ if (how == MOVE_FD)
+ close(fd);
+ }
+
+ /* Set close on exec, even if we didn't move it. */
+ if ((newfd >= 0) && (fcntl(newfd, F_SETFD, FD_CLOEXEC) < 0))
+ pseudo_diag("Can't set close on exec flag: %s\n",
+ strerror(errno));
+
+ return(newfd);
+}
+
+static int
+client_connect(void) {
+ /* we have a server pid, is it responsive? */
+ struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET };
+ int cwd_fd;
+
+#if PSEUDO_PORT_DARWIN
+ sun.sun_len = strlen(PSEUDO_SOCKET) + 1;
+#endif
+
+ connect_fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ connect_fd = pseudo_fd(connect_fd, MOVE_FD);
+ if (connect_fd == -1) {
+ pseudo_diag("Can't create socket: %s (%s)\n", sun.sun_path, strerror(errno));
+ return 1;
+ }
+
+ pseudo_debug(PDBGF_CLIENT, "connecting socket...\n");
+ cwd_fd = open(".", O_RDONLY);
+ if (cwd_fd == -1) {
+ pseudo_diag("Couldn't stash directory before opening socket: %s",
+ strerror(errno));
+ close(connect_fd);
+ connect_fd = -1;
+ return 1;
+ }
+ if (fchdir(pseudo_localstate_dir_fd) == -1) {
+ pseudo_diag("Couldn't chdir to server directory [%d]: %s\n",
+ pseudo_localstate_dir_fd, strerror(errno));
+ close(connect_fd);
+ close(cwd_fd);
+ connect_fd = -1;
+ return 1;
+ }
+ if (connect(connect_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) {
+ pseudo_debug(PDBGF_CLIENT, "Can't connect socket to pseudo.socket: (%s)\n", strerror(errno));
+ close(connect_fd);
+ if (fchdir(cwd_fd) == -1) {
+ pseudo_diag("return to previous directory failed: %s\n",
+ strerror(errno));
+ }
+ close(cwd_fd);
+ connect_fd = -1;
+ return 1;
+ }
+ if (fchdir(cwd_fd) == -1) {
+ pseudo_diag("return to previous directory failed: %s\n",
+ strerror(errno));
+ }
+ close(cwd_fd);
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "connected socket.\n");
+ return 0;
+}
+
+static int
+pseudo_client_setup(void) {
+ char * pseudo_pidfile;
+ FILE *fp;
+ server_pid = 0;
+
+ /* avoid descriptor leak, I hope */
+ if (connect_fd >= 0) {
+ close(connect_fd);
+ connect_fd = -1;
+ }
+
+ pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE);
+ fp = fopen(pseudo_pidfile, "r");
+ if (fp) {
+ if (fscanf(fp, "%d", &server_pid) != 1) {
+ pseudo_debug(PDBGF_CLIENT, "Opened server PID file, but didn't get a pid.\n");
+ }
+ fclose(fp);
+ }
+ if (server_pid) {
+ if (kill(server_pid, 0) == -1) {
+ pseudo_debug(PDBGF_CLIENT, "couldn't find server at pid %d: %s\n",
+ server_pid, strerror(errno));
+ server_pid = 0;
+ }
+ }
+ if (!server_pid) {
+ if (client_spawn_server()) {
+ return 1;
+ }
+ }
+ if (!client_connect() && !client_ping()) {
+ return 0;
+ }
+ pseudo_debug(PDBGF_CLIENT, "server seems to be gone, trying to restart\n");
+ if (client_spawn_server()) {
+ pseudo_debug(PDBGF_CLIENT, "failed to spawn server, giving up.\n");
+ return 1;
+ } else {
+ pseudo_debug(PDBGF_CLIENT, "restarted, new pid %d\n", server_pid);
+ if (!client_connect() && !client_ping()) {
+ return 0;
+ }
+ }
+ pseudo_debug(PDBGF_CLIENT, "couldn't get a server, giving up.\n");
+ return 1;
+}
+
+static pseudo_msg_t *
+pseudo_client_request(pseudo_msg_t *msg, size_t len, const char *path) {
+ pseudo_msg_t *response = 0;
+ int tries = 0;
+ int rc;
+
+ if (!msg)
+ return 0;
+
+ do {
+ do {
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending a message: ino %llu\n",
+ (unsigned long long) msg->ino);
+ if (connect_fd < 0) {
+ pseudo_debug(PDBGF_CLIENT, "trying to get server\n");
+ if (pseudo_client_setup()) {
+ return 0;
+ }
+ }
+ rc = pseudo_msg_send(connect_fd, msg, len, path);
+ if (rc != 0) {
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "msg_send: %d%s\n",
+ rc,
+ rc == -1 ? " (sigpipe)" :
+ " (short write)");
+ pseudo_client_setup();
+ ++tries;
+ if (tries > 3) {
+ pseudo_debug(PDBGF_CLIENT, "Can't get server going again.\n");
+ return 0;
+ }
+ }
+ } while (rc != 0);
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sent!\n");
+ if (msg->type != PSEUDO_MSG_FASTOP) {
+ response = pseudo_msg_receive(connect_fd);
+ if (!response) {
+ ++tries;
+ if (tries > 3) {
+ pseudo_debug(PDBGF_CLIENT, "Can't get responses.\n");
+ return 0;
+ }
+ }
+ } else {
+ return 0;
+ }
+ } while (response == 0);
+ if (response->type != PSEUDO_MSG_ACK) {
+ pseudo_debug(PDBGF_CLIENT, "got non-ack response %d\n", response->type);
+ return 0;
+ } else {
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "got response type %d\n", response->type);
+ }
+ return response;
+}
+
+int
+pseudo_client_shutdown(void) {
+ pseudo_msg_t msg;
+ pseudo_msg_t *ack;
+ char *pseudo_path;
+
+ pseudo_debug(PDBGF_INVOKE, "attempting to shut down server.\n");
+ pseudo_path = pseudo_prefix_path(NULL);
+ if (pseudo_prefix_dir_fd == -1) {
+ if (pseudo_path) {
+ pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY);
+ /* directory is missing? */
+ if (pseudo_prefix_dir_fd == -1 && errno == ENOENT) {
+ pseudo_debug(PDBGF_CLIENT, "prefix directory doesn't exist, trying to create\n");
+ mkdir_p(pseudo_path);
+ pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY);
+ }
+ pseudo_prefix_dir_fd = pseudo_fd(pseudo_prefix_dir_fd, COPY_FD);
+ free(pseudo_path);
+ } else {
+ pseudo_diag("No prefix available to to find server.\n");
+ exit(1);
+ }
+ if (pseudo_prefix_dir_fd == -1) {
+ pseudo_diag("Can't open prefix path (%s) for server. (%s)\n",
+ pseudo_prefix_path(NULL),
+ strerror(errno));
+ exit(1);
+ }
+ }
+ pseudo_path = pseudo_localstatedir_path(NULL);
+ mkdir_p(pseudo_path);
+ if (pseudo_localstate_dir_fd == -1) {
+ if (pseudo_path) {
+ pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY);
+ /* directory is missing? */
+ if (pseudo_localstate_dir_fd == -1 && errno == ENOENT) {
+ pseudo_debug(PDBGF_CLIENT, "local state dir doesn't exist, trying to create\n");
+ mkdir_p(pseudo_path);
+ pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY);
+ }
+ pseudo_localstate_dir_fd = pseudo_fd(pseudo_localstate_dir_fd, COPY_FD);
+ free(pseudo_path);
+ } else {
+ pseudo_diag("No prefix available to to find server.\n");
+ exit(1);
+ }
+ if (pseudo_localstate_dir_fd == -1) {
+ pseudo_diag("Can't open local state path (%s) for server. (%s)\n",
+ pseudo_localstatedir_path(NULL),
+ strerror(errno));
+ exit(1);
+ }
+ }
+ if (client_connect()) {
+ pseudo_debug(PDBGF_INVOKE, "Pseudo server seems to be already offline.\n");
+ return 0;
+ }
+ memset(&msg, 0, sizeof(pseudo_msg_t));
+ msg.type = PSEUDO_MSG_SHUTDOWN;
+ msg.op = OP_NONE;
+ msg.client = getpid();
+ pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "sending shutdown request\n");
+ if (pseudo_msg_send(connect_fd, &msg, 0, NULL)) {
+ pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "error requesting shutdown: %s\n", strerror(errno));
+ return 1;
+ }
+ ack = pseudo_msg_receive(connect_fd);
+ if (!ack) {
+ pseudo_diag("server did not respond to shutdown query.\n");
+ return 1;
+ }
+ if (ack->type == PSEUDO_MSG_ACK) {
+ return 0;
+ }
+ pseudo_diag("Server refused shutdown. Remaining client fds: %d\n", ack->fd);
+ pseudo_diag("Client pids: %s\n", ack->path);
+ pseudo_diag("Server will shut down after all clients exit.\n");
+ return 0;
+}
+
+static char *
+base_path(int dirfd, const char *path, int leave_last) {
+ char *basepath = 0;
+ size_t baselen = 0;
+ size_t minlen = 0;
+ char *newpath;
+
+ if (!path)
+ return NULL;
+ if (!*path)
+ return "";
+
+ if (path[0] != '/') {
+ if (dirfd != -1 && dirfd != AT_FDCWD) {
+ if (dirfd >= 0) {
+ basepath = fd_path(dirfd);
+ }
+ if (basepath) {
+ baselen = strlen(basepath);
+ } else {
+ pseudo_diag("got *at() syscall for unknown directory, fd %d\n", dirfd);
+ }
+ } else {
+ basepath = pseudo_cwd;
+ baselen = pseudo_cwd_len;
+ }
+ if (!basepath) {
+ pseudo_diag("unknown base path for fd %d, path %s\n", dirfd, path);
+ return 0;
+ }
+ /* if there's a chroot path, and it's the start of basepath,
+ * flag it for pseudo_fix_path
+ */
+ if (pseudo_chroot_len && baselen >= pseudo_chroot_len &&
+ !memcmp(basepath, pseudo_chroot, pseudo_chroot_len) &&
+ (basepath[pseudo_chroot_len] == '\0' || basepath[pseudo_chroot_len] == '/')) {
+
+ minlen = pseudo_chroot_len;
+ }
+ } else if (pseudo_chroot_len) {
+ /* "absolute" is really relative to chroot path */
+ basepath = pseudo_chroot;
+ baselen = pseudo_chroot_len;
+ minlen = pseudo_chroot_len;
+ }
+
+ newpath = pseudo_fix_path(basepath, path, minlen, baselen, NULL, leave_last);
+ pseudo_debug(PDBGF_PATH, "base_path: %s</>%s\n",
+ basepath ? basepath : "<nil>",
+ path ? path : "<nil>");
+ return newpath;
+}
+
+pseudo_msg_t *
+pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...) {
+ pseudo_msg_t *result = 0;
+ pseudo_msg_t msg = { .type = PSEUDO_MSG_OP };
+ size_t pathlen = -1;
+ int do_request = 0;
+ char *path_extra_1 = 0;
+ size_t path_extra_1len = 0;
+ char *path_extra_2 = 0;
+ size_t path_extra_2len = 0;
+ static char *alloced_path = 0;
+ static size_t alloced_len = 0;
+ int strip_slash;
+
+#ifdef PSEUDO_PROFILING
+ struct timeval tv1_op, tv2_op;
+
+ gettimeofday(&tv1_op, NULL);
+ ++profile_data.total_ops;
+#endif
+ /* disable wrappers */
+ pseudo_antimagic();
+
+ /* note: I am not entirely sure this should happen even if no
+ * messages have actually been sent. */
+ if (!sent_messages) {
+ sent_messages = 1;
+ atexit(void_client_ping);
+ }
+
+ /* if path isn't available, try to find one? */
+ if (!path && fd >= 0 && fd <= nfds) {
+ path = fd_path(fd);
+ if (!path) {
+ pathlen = 0;
+ } else {
+ pathlen = strlen(path) + 1;
+ }
+ }
+
+#ifdef PSEUDO_XATTRDB
+ if (buf) {
+ struct stat64 bufcopy = *buf;
+ int do_save = 0;
+ /* maybe use xattr instead */
+ /* note: if we use xattr, logging won't work reliably
+ * because the server won't get messages if these work.
+ */
+ switch (op) {
+ case OP_CHMOD:
+ case OP_FCHMOD:
+ case OP_CHOWN:
+ case OP_FCHOWN:
+ /* for these, we want to start with the existing db
+ * values.
+ */
+ bufcopy = *buf;
+ result = pseudo_xattrdb_load(fd, path, buf);
+ if (result && result->result == RESULT_SUCCEED) {
+ pseudo_debug(PDBGF_XATTR, "merging existing values for xattr\n");
+ switch (op) {
+ case OP_CHMOD:
+ case OP_FCHMOD:
+ bufcopy.st_uid = result->uid;
+ bufcopy.st_gid = result->gid;
+ break;
+ case OP_CHOWN:
+ case OP_FCHOWN:
+ bufcopy.st_rdev = result->rdev;
+ bufcopy.st_mode = result->mode;
+ break;
+ default:
+ break;
+ }
+
+ } else {
+ switch (op) {
+ case OP_CHMOD:
+ case OP_FCHMOD:
+ bufcopy.st_uid = pseudo_fuid;
+ bufcopy.st_gid = pseudo_fgid;
+ break;
+ default:
+ break;
+ }
+ }
+ result = NULL;
+ do_save = 1;
+ break;
+ case OP_CREAT:
+ case OP_MKDIR:
+ case OP_MKNOD:
+ bufcopy.st_uid = pseudo_fuid;
+ bufcopy.st_gid = pseudo_fgid;
+ do_save = 1;
+ break;
+ case OP_LINK:
+ do_save = 1;
+ break;
+ case OP_FSTAT:
+ case OP_STAT:
+ result = pseudo_xattrdb_load(fd, path, buf);
+ break;
+ default:
+ break;
+ }
+ if (do_save) {
+ result = pseudo_xattrdb_save(fd, path, &bufcopy);
+ }
+ if (result)
+ goto skip_path;
+ }
+#endif
+
+ if (op == OP_RENAME) {
+ va_list ap;
+ if (!path) {
+ pseudo_diag("rename (%s) without new path.\n",
+ path ? path : "<nil>");
+ pseudo_magic();
+ return 0;
+ }
+ va_start(ap, buf);
+ path_extra_1 = va_arg(ap, char *);
+ va_end(ap);
+ /* last argument is the previous path of the file */
+ if (!path_extra_1) {
+ pseudo_diag("rename (%s) without old path.\n",
+ path ? path : "<nil>");
+ pseudo_magic();
+ return 0;
+ }
+ path_extra_1len = strlen(path_extra_1);
+ pseudo_debug(PDBGF_PATH | PDBGF_FILE, "rename: %s -> %s\n",
+ path_extra_1, path);
+ }
+
+ /* we treat the "create" and "replace" flags as logically
+ * distinct operations, because they can fail when set can't.
+ */
+ if (op == OP_SET_XATTR || op == OP_CREATE_XATTR || op == OP_REPLACE_XATTR) {
+ va_list ap;
+ va_start(ap, buf);
+ path_extra_1 = va_arg(ap, char *);
+ path_extra_1len = strlen(path_extra_1);
+ path_extra_2 = va_arg(ap, char *);
+ path_extra_2len = va_arg(ap, size_t);
+ va_end(ap);
+ pseudo_debug(PDBGF_XATTR, "setxattr, name '%s', value %d bytes\n",
+ path_extra_1, (int) path_extra_2len);
+ pseudo_debug_call(PDBGF_XATTR | PDBGF_VERBOSE, pseudo_dump_data, "xattr value", path_extra_2, path_extra_2len);
+ }
+ if (op == OP_GET_XATTR || op == OP_REMOVE_XATTR) {
+ va_list ap;
+ va_start(ap, buf);
+ path_extra_1 = va_arg(ap, char *);
+ path_extra_1len = strlen(path_extra_1);
+ va_end(ap);
+ pseudo_debug(PDBGF_XATTR, "%sxattr, name '%s'\n",
+ op == OP_GET_XATTR ? "get" : "remove", path_extra_1);
+ }
+
+ if (path) {
+ if (pathlen == (size_t) -1) {
+ pathlen = strlen(path) + 1;
+ }
+ /* path fixup has to happen in the specific functions,
+ * because they may have to make calls which have to be
+ * fixed up for chroot stuff already.
+ * ... but wait! / in chroot should not have that
+ * trailing /.
+ * (no attempt is made to handle a rename of "/" occurring
+ * in a chroot...)
+ */
+ strip_slash = (pathlen > 2 && (path[pathlen - 2]) == '/');
+ } else {
+ path = "";
+ pathlen = 0;
+ strip_slash = 0;
+ }
+
+ /* f*xattr operations can result in needing to send a path
+ * value even though we don't have one available. We use an
+ * empty path for that.
+ */
+ if (path_extra_1) {
+ size_t full_len = path_extra_1len + 1 + pathlen;
+ size_t partial_len = pathlen - 1 - strip_slash;
+ if (path_extra_2) {
+ full_len += path_extra_2len + 1;
+ }
+ if (full_len > alloced_len) {
+ free(alloced_path);
+ alloced_path = malloc(full_len);
+ alloced_len = full_len;
+ if (!alloced_path) {
+ pseudo_diag("Can't allocate space for paths for a rename operation. Sorry.\n");
+ alloced_len = 0;
+ pseudo_magic();
+ return 0;
+ }
+ }
+ memcpy(alloced_path, path, partial_len);
+ alloced_path[partial_len] = '\0';
+ memcpy(alloced_path + partial_len + 1, path_extra_1, path_extra_1len);
+ alloced_path[partial_len + path_extra_1len + 1] = '\0';
+ if (path_extra_2) {
+ memcpy(alloced_path + partial_len + path_extra_1len + 2, path_extra_2, path_extra_2len);
+ }
+ alloced_path[full_len - 1] = '\0';
+ path = alloced_path;
+ pathlen = full_len;
+ pseudo_debug_call(PDBGF_IPC | PDBGF_VERBOSE, pseudo_dump_data, "combined path buffer", path, pathlen);
+ } else {
+ if (strip_slash) {
+ if (pathlen > alloced_len) {
+ free(alloced_path);
+ alloced_path = malloc(pathlen);
+ alloced_len = pathlen;
+ if (!alloced_path) {
+ pseudo_diag("Can't allocate space for paths for a rename operation. Sorry.\n");
+ alloced_len = 0;
+ pseudo_magic();
+ return 0;
+ }
+ }
+ memcpy(alloced_path, path, pathlen);
+ alloced_path[pathlen - 2] = '\0';
+ path = alloced_path;
+ }
+ }
+
+#ifdef PSEUDO_XATTRDB
+ /* If we were able to store things in xattr, we can easily skip
+ * most of the fancy path computations and such.
+ */
+ skip_path:
+#endif
+
+ if (buf)
+ pseudo_msg_stat(&msg, buf);
+
+ if (pseudo_util_debug_flags & PDBGF_OP) {
+ pseudo_debug(PDBGF_OP, "%s%s", pseudo_op_name(op),
+ (dirfd != -1 && dirfd != AT_FDCWD && op != OP_DUP) ? "at" : "");
+ if (path_extra_1) {
+ pseudo_debug(PDBGF_OP, " %s ->", path_extra_1);
+ }
+ if (path) {
+ pseudo_debug(PDBGF_OP, " %s", path);
+ }
+ /* for OP_RENAME in renameat, "fd" is also used for the
+ * second dirfd.
+ */
+ if (fd != -1 && op != OP_RENAME) {
+ pseudo_debug(PDBGF_OP, " [fd %d]", fd);
+ }
+ if (buf) {
+ pseudo_debug(PDBGF_OP, " (+buf)");
+ if (fd != -1) {
+ pseudo_debug(PDBGF_OP, " [dev/ino: %d/%llu]",
+ (int) buf->st_dev, (unsigned long long) buf->st_ino);
+ }
+ pseudo_debug(PDBGF_OP, " (0%o)", (int) buf->st_mode);
+ }
+ pseudo_debug(PDBGF_OP, ": ");
+ }
+ msg.type = PSEUDO_MSG_OP;
+ msg.op = op;
+ msg.fd = fd;
+ msg.access = access;
+ msg.result = RESULT_NONE;
+ msg.client = getpid();
+
+ /* do stuff */
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "processing request [ino %llu]\n", (unsigned long long) msg.ino);
+ switch (msg.op) {
+ case OP_CHDIR:
+ pseudo_client_getcwd();
+ do_request = 0;
+ break;
+ case OP_CHROOT:
+ if (pseudo_client_chroot(path) == 0) {
+ /* return a non-zero value to show non-failure */
+ result = &msg;
+ }
+ do_request = 0;
+ break;
+ case OP_OPEN:
+ pseudo_client_path(fd, path);
+ case OP_EXEC: /* fallthrough */
+ do_request = pseudo_client_logging;
+ break;
+ case OP_CLOSE:
+ /* no request needed */
+ if (fd >= 0) {
+ if (fd == connect_fd) {
+ connect_fd = pseudo_fd(connect_fd, COPY_FD);
+ if (connect_fd == -1) {
+ pseudo_diag("tried to close connection, couldn't dup: %s\n", strerror(errno));
+ }
+ } else if (fd == pseudo_util_debug_fd) {
+ pseudo_util_debug_fd = pseudo_fd(fd, COPY_FD);
+ } else if (fd == pseudo_prefix_dir_fd) {
+ pseudo_prefix_dir_fd = pseudo_fd(fd, COPY_FD);
+ } else if (fd == pseudo_localstate_dir_fd) {
+ pseudo_localstate_dir_fd = pseudo_fd(fd, COPY_FD);
+ } else if (fd == pseudo_pwd_fd) {
+ pseudo_pwd_fd = pseudo_fd(fd, COPY_FD);
+ /* since we have a FILE * on it, we close that... */
+ fclose(pseudo_pwd);
+ /* and open a new one on the copy */
+ pseudo_pwd = fdopen(pseudo_pwd_fd, "r");
+ } else if (fd == pseudo_grp_fd) {
+ pseudo_grp_fd = pseudo_fd(fd, COPY_FD);
+ /* since we have a FILE * on it, we close that... */
+ fclose(pseudo_grp);
+ /* and open a new one on the copy */
+ pseudo_grp = fdopen(pseudo_grp_fd, "r");
+ }
+ }
+ pseudo_client_close(fd);
+ do_request = 0;
+ break;
+ case OP_DUP:
+ /* just copy the path over */
+ pseudo_debug(PDBGF_CLIENT, "dup: fd_path(%d) = %p [%s], dup to %d\n",
+ fd, fd_path(fd), fd_path(fd) ? fd_path(fd) : "<nil>",
+ dirfd);
+ pseudo_client_path(dirfd, fd_path(fd));
+ break;
+ /* operations for which we should use the magic uid/gid */
+ case OP_CHMOD:
+ case OP_CREAT:
+ case OP_FCHMOD:
+ case OP_MKDIR:
+ case OP_MKNOD:
+ case OP_SYMLINK:
+ msg.uid = pseudo_fuid;
+ msg.gid = pseudo_fgid;
+ pseudo_debug(PDBGF_OP, "fuid: %d ", pseudo_fuid);
+ /* FALLTHROUGH */
+ /* chown/fchown uid/gid already calculated, and
+ * a link or rename should not change a file's ownership.
+ * (operations which can create should be CREAT or MKNOD
+ * or MKDIR)
+ */
+ case OP_CHOWN:
+ case OP_FCHOWN:
+ case OP_FSTAT:
+ case OP_LINK:
+ case OP_RENAME:
+ case OP_STAT:
+ case OP_UNLINK:
+ case OP_DID_UNLINK:
+ case OP_CANCEL_UNLINK:
+ case OP_MAY_UNLINK:
+ case OP_GET_XATTR:
+ case OP_LIST_XATTR:
+ case OP_SET_XATTR:
+ case OP_REMOVE_XATTR:
+ do_request = 1;
+ break;
+ default:
+ pseudo_diag("error: unknown or unimplemented operator %d (%s)", op, pseudo_op_name(op));
+ break;
+ }
+ /* result can only be set when PSEUDO_XATTRDB resulted in a
+ * successful store to or read from the local database.
+ */
+ if (do_request && !result) {
+#ifdef PSEUDO_PROFILING
+ struct timeval tv1_ipc, tv2_ipc;
+#endif
+ if (!pseudo_op_wait(msg.op))
+ msg.type = PSEUDO_MSG_FASTOP;
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending request [ino %llu]\n", (unsigned long long) msg.ino);
+#ifdef PSEUDO_PROFILING
+ gettimeofday(&tv1_ipc, NULL);
+#endif
+ if (pseudo_local_only) {
+ /* disable server */
+ result = NULL;
+ } else {
+ result = pseudo_client_request(&msg, pathlen, path);
+ }
+#ifdef PSEUDO_PROFILING
+ gettimeofday(&tv2_ipc, NULL);
+ ++profile_data.messages;
+ profile_data.ipc_time.tv_sec += (tv2_ipc.tv_sec - tv1_ipc.tv_sec);
+ profile_data.ipc_time.tv_usec += (tv2_ipc.tv_usec - tv1_ipc.tv_usec);
+#endif
+ if (result) {
+ pseudo_debug(PDBGF_OP, "(%d) %s", getpid(), pseudo_res_name(result->result));
+ if (op == OP_STAT || op == OP_FSTAT) {
+ pseudo_debug(PDBGF_OP, " mode 0%o uid %d:%d",
+ (int) result->mode,
+ (int) result->uid,
+ (int) result->gid);
+ } else if (op == OP_CHMOD || op == OP_FCHMOD) {
+ pseudo_debug(PDBGF_OP, " mode 0%o",
+ (int) result->mode);
+ } else if (op == OP_CHOWN || op == OP_FCHOWN) {
+ pseudo_debug(PDBGF_OP, " uid %d:%d",
+ (int) result->uid,
+ (int) result->gid);
+ }
+ } else if (msg.type != PSEUDO_MSG_FASTOP) {
+ pseudo_debug(PDBGF_OP, "(%d) no answer", getpid());
+ }
+ } else if (!result) {
+ pseudo_debug(PDBGF_OP, "(%d) (no request)", getpid());
+ }
+ #ifdef PSEUDO_XATTRDB
+ else {
+ pseudo_debug(PDBGF_OP, "(%d) (handled through xattrdb: %s)", getpid(), pseudo_res_name(result->result));
+ }
+ #endif
+ pseudo_debug(PDBGF_OP, "\n");
+
+#ifdef PSEUDO_PROFILING
+ gettimeofday(&tv2_op, NULL);
+ profile_data.op_time.tv_sec += (tv2_op.tv_sec - tv1_op.tv_sec);
+ profile_data.op_time.tv_usec += (tv2_op.tv_usec - tv1_op.tv_usec);
+ if (profile_data.total_ops % profile_interval == 0) {
+ pseudo_profile_report();
+ if (profile_interval < 100) {
+ profile_interval = profile_interval * 10;
+ }
+ }
+#endif
+ /* reenable wrappers */
+ pseudo_magic();
+
+ return result;
+}
+
+/* stuff for handling paths and execs */
+static char *previous_path;
+static char *previous_path_segs;
+static char **path_segs;
+static size_t *path_lens;
+
+/* Makes an array of strings which are the path components
+ * of previous_path. Does this by duping the path, then replacing
+ * colons with null terminators, and storing the addresses of the
+ * starts of the substrings.
+ */
+static void
+populate_path_segs(void) {
+ size_t len = 0;
+ char *s;
+ int c = 0;
+
+ free(path_segs);
+ free(previous_path_segs);
+ free(path_lens);
+ path_segs = NULL;
+ path_lens = NULL;
+ previous_path_segs = NULL;
+
+ if (!previous_path)
+ return;
+
+ for (s = previous_path; *s; ++s) {
+ if (*s == ':')
+ ++c;
+ }
+ path_segs = malloc((c+2) * sizeof(*path_segs));
+ if (!path_segs) {
+ pseudo_diag("warning: failed to allocate space for %d path segments.\n",
+ c);
+ return;
+ }
+ path_lens = malloc((c + 2) * sizeof(*path_lens));
+ if (!path_lens) {
+ pseudo_diag("warning: failed to allocate space for %d path lengths.\n",
+ c);
+ free(path_segs);
+ path_segs = 0;
+ return;
+ }
+ previous_path_segs = strdup(previous_path);
+ if (!previous_path_segs) {
+ pseudo_diag("warning: failed to allocate space for path copy.\n");
+ free(path_segs);
+ path_segs = NULL;
+ free(path_lens);
+ path_lens = NULL;
+ return;
+ }
+ c = 0;
+ path_segs[c++] = previous_path;
+ len = 0;
+ for (s = previous_path; *s; ++s) {
+ if (*s == ':') {
+ *s = '\0';
+ path_lens[c - 1] = len;
+ len = 0;
+ path_segs[c++] = s + 1;
+ } else {
+ ++len;
+ }
+ }
+ path_lens[c - 1] = len;
+ path_segs[c] = NULL;
+ path_lens[c] = 0;
+}
+
+const char *
+pseudo_exec_path(const char *filename, int search_path) {
+ char *path = getenv("PATH");
+ char *candidate;
+ int i;
+ struct stat buf;
+
+ if (!filename)
+ return NULL;
+
+ pseudo_antimagic();
+ if (!path) {
+ free(path_segs);
+ free(previous_path);
+ path_segs = 0;
+ previous_path = 0;
+ } else if (!previous_path || strcmp(previous_path, path)) {
+ free(previous_path);
+ previous_path = strdup(path);
+ populate_path_segs();
+ }
+
+ /* absolute paths just get canonicalized. */
+ if (*filename == '/') {
+ candidate = pseudo_fix_path(NULL, filename, 0, 0, NULL, 0);
+ pseudo_magic();
+ return candidate;
+ }
+
+ if (!search_path || !path_segs) {
+ candidate = pseudo_fix_path(pseudo_cwd, filename, 0, pseudo_cwd_len, NULL, 0);
+ /* executable or not, it's the only thing we can try */
+ pseudo_magic();
+ return candidate;
+ }
+
+ for (i = 0; path_segs[i]; ++i) {
+ path = path_segs[i];
+ pseudo_debug(PDBGF_CLIENT, "exec_path: checking %s for %s\n", path, filename);
+ if (!*path || (*path == '.' && path_lens[i] == 1)) {
+ /* empty path or . is cwd */
+ candidate = pseudo_fix_path(pseudo_cwd, filename, 0, pseudo_cwd_len, NULL, 0);
+ pseudo_debug(PDBGF_CLIENT, "exec_path: in cwd, got %s\n", candidate);
+ } else if (*path == '/') {
+ candidate = pseudo_fix_path(path, filename, 0, path_lens[i], NULL, 0);
+ pseudo_debug(PDBGF_CLIENT, "exec_path: got %s\n", candidate);
+ } else {
+ /* oh you jerk, making me do extra work */
+ size_t len;
+ char *dir = pseudo_fix_path(pseudo_cwd, path, 0, pseudo_cwd_len, &len, 0);
+ if (dir) {
+ candidate = pseudo_fix_path(dir, filename, 0, len, NULL, 0);
+ pseudo_debug(PDBGF_CLIENT, "exec_path: got %s for non-absolute path\n", candidate);
+ } else {
+ pseudo_diag("couldn't allocate intermediate path.\n");
+ candidate = NULL;
+ }
+ }
+ if (candidate && !stat(candidate, &buf) && !S_ISDIR(buf.st_mode) && (buf.st_mode & 0111)) {
+ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "exec_path: %s => %s\n", filename, candidate);
+ pseudo_magic();
+ return candidate;
+ }
+ }
+ /* blind guess being as good as anything */
+ pseudo_magic();
+ return filename;
+}
+
diff --git a/pseudo_client.h b/pseudo_client.h
new file mode 100644
index 0000000..e732bae
--- /dev/null
+++ b/pseudo_client.h
@@ -0,0 +1,89 @@
+/*
+ * pseudo_client.h, shared declarations for client
+ *
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+extern pseudo_msg_t *pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...);
+#if PSEUDO_STATBUF_64
+#define base_lstat real_lstat64
+#define base_fstat real_fstat64
+#define base_stat real_stat64
+#define base_fstatat(dirfd, path, buf, flags) real___fxstatat64(_STAT_VER, dirfd, path, buf, flags)
+#else
+#define base_lstat real_lstat
+#define base_fstat real_fstat
+#define base_stat real_stat
+#define base_fstatat(dirfd, path, buf, flags) real___fxstatat(_STAT_VER, dirfd, path, buf, flags)
+#endif
+extern void pseudo_antimagic(void);
+extern void pseudo_magic(void);
+extern void pseudo_client_touchuid(void);
+extern void pseudo_client_touchgid(void);
+extern char *pseudo_client_fdpath(int fd);
+extern int pseudo_client_shutdown(void);
+extern int pseudo_fd(int fd, int how);
+#define MOVE_FD 0
+#define COPY_FD 1
+#define PSEUDO_MIN_FD 20
+extern uid_t pseudo_euid;
+extern uid_t pseudo_fuid;
+extern uid_t pseudo_suid;
+extern uid_t pseudo_ruid;
+extern gid_t pseudo_egid;
+extern gid_t pseudo_sgid;
+extern gid_t pseudo_rgid;
+extern gid_t pseudo_fgid;
+extern int pseudo_prefix_dir_fd;
+extern int pseudo_localstate_dir_fd;
+extern FILE *pseudo_pwd_open(void);
+extern FILE *pseudo_grp_open(void);
+extern void pseudo_pwd_close(void);
+extern void pseudo_grp_close(void);
+extern int pseudo_pwd_lck_open(void);
+extern int pseudo_pwd_lck_close(void);
+extern FILE *pseudo_pwd;
+extern FILE *pseudo_grp;
+
+/* support related to chroot/getcwd/etc. */
+extern int pseudo_client_getcwd(void);
+extern int pseudo_client_chroot(const char *);
+extern char *pseudo_root_path(const char *, int, int, const char *, int);
+extern const char *pseudo_exec_path(const char *filename, int);
+#define PSEUDO_ROOT_PATH(x, y, z) pseudo_root_path(__func__, __LINE__, (x), (y), (z));
+extern char *pseudo_cwd;
+extern size_t pseudo_cwd_len;
+extern char *pseudo_cwd_rel;
+extern char *pseudo_chroot;
+extern char *pseudo_passwd;
+extern size_t pseudo_chroot_len;
+extern int pseudo_nosymlinkexp;
+
+extern int pseudo_umask;
+
+/* Root can read and write files, and enter directories which have no
+ * read, write, or execute permissions. (But can't execute files without
+ * execute permissions!)
+ *
+ * A non-root user can't.
+ *
+ * When doing anything which actually writes to the filesystem, we add in
+ * the user read/write/execute bits. When storing to the database, though,
+ * we mask out any such bits which weren't in the original mode.
+ */
+#define PSEUDO_FS_MODE(mode, isdir) (((mode) | S_IRUSR | S_IWUSR | ((isdir) ? S_IXUSR : 0)) & ~(S_IWGRP | S_IWOTH))
+#define PSEUDO_DB_MODE(fs_mode, user_mode) (((fs_mode) & ~0722) | ((user_mode & 0722)))
+
diff --git a/pseudo_db.c b/pseudo_db.c
new file mode 100644
index 0000000..f60f0ab
--- /dev/null
+++ b/pseudo_db.c
@@ -0,0 +1,2563 @@
+/*
+ * pseudo_db.c, sqlite3 interface
+ *
+ * Copyright (c) 2008-2010,2013 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sqlite3.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_db.h"
+
+/* #define NPROFILE */
+
+#ifdef NPROFILE
+void xProfile(void * pArg, const char * pQuery, sqlite3_uint64 pTimeTaken)
+{
+ pseudo_diag("profile: %lld %s\n", pTimeTaken, pQuery);
+}
+#endif
+
+struct log_history {
+ int rc;
+ unsigned long fields;
+ sqlite3_stmt *stmt;
+};
+
+struct pdb_file_list {
+ int rc;
+ sqlite3_stmt *stmt;
+};
+
+static int file_db_dirty = 0;
+#ifdef USE_MEMORY_DB
+static sqlite3 *real_file_db = 0;
+#endif
+static sqlite3 *file_db = 0;
+static sqlite3 *log_db = 0;
+
+/* What's going on here, you might well ask?
+ * This contains a template to build the database. I suppose maybe it
+ * should have been elegantly done as a big chunk of embedded SQL, but
+ * this looked like a good idea at the time.
+ */
+typedef struct { char *fmt; int arg; } id_row;
+
+/* This seemed like a really good idea at the time. The idea is that these
+ * structures let me write semi-abstract code to "create a database" without
+ * duplicating as much of the code.
+ */
+static struct sql_table {
+ char *name;
+ char *sql;
+ char **names;
+ id_row *values;
+} file_tables[] = {
+ { "files",
+ "id INTEGER PRIMARY KEY, "
+ "path VARCHAR, "
+ "dev INTEGER, "
+ "ino INTEGER, "
+ "uid INTEGER, "
+ "gid INTEGER, "
+ "mode INTEGER, "
+ "rdev INTEGER",
+ NULL,
+ NULL },
+ { "xattrs",
+ "id INTEGER PRIMARY KEY, "
+ "file_id INTEGER REFERENCES files(id) ON DELETE CASCADE, "
+ "name VARCHAR, "
+ "value VARCHAR",
+ NULL,
+ NULL },
+ { NULL, NULL, NULL, NULL },
+}, log_tables[] = {
+ { "logs",
+ "id INTEGER PRIMARY KEY, "
+ "stamp INTEGER, "
+ "op INTEGER, "
+ "client INTEGER, "
+ "fd INTEGER, "
+ "dev INTEGER, "
+ "ino INTEGER, "
+ "mode INTEGER, "
+ "path VARCHAR, "
+ "result INTEGER, "
+ "severity INTEGER, "
+ "text VARCHAR ",
+ NULL,
+ NULL },
+ { NULL, NULL, NULL, NULL },
+};
+
+/* similarly, this creates indexes generically. */
+static struct sql_index {
+ char *name;
+ char *table;
+ char *keys;
+} file_indexes[] = {
+/* { "files__path", "files", "path" }, */
+ { "files__path_dev_ino", "files", "path, dev, ino" },
+ { "files__dev_ino", "files", "dev, ino" },
+ { "xattrs__file", "xattrs", "file_id" },
+ { NULL, NULL, NULL },
+}, log_indexes[] = {
+ { NULL, NULL, NULL },
+};
+
+static char *file_pragmas[] = {
+ "PRAGMA legacy_file_format = OFF;",
+ "PRAGMA journal_mode = OFF;",
+ /* the default page size produces painfully bad behavior
+ * for memory databases with some versions of sqlite.
+ */
+ "PRAGMA page_size = 8192;",
+ "PRAGMA locking_mode = EXCLUSIVE;",
+ /* Setting this to NORMAL makes pseudo noticably slower
+ * than fakeroot, but is perhaps more secure. However,
+ * note that sqlite always flushes to the OS; what is lacking
+ * in non-synchronous mode is waiting for the OS to
+ * confirm delivery to media, and also a bunch of cache
+ * flushing and reloading which we probably don't really
+ * need.
+ */
+ "PRAGMA synchronous = OFF;",
+ "PRAGMA foreign_keys = ON;",
+ NULL
+};
+
+static char *log_pragmas[] = {
+ "PRAGMA legacy_file_format = OFF;",
+ "PRAGMA journal_mode = OFF;",
+ "PRAGMA locking_mode = EXCLUSIVE;",
+ "PRAGMA synchronous = OFF;",
+ NULL
+};
+
+/* table migrations: */
+/* If there is no migration table, we assume "version -1" -- the
+ * version shipped with wrlinux 3.0, which had no version
+ * number. Otherwise, we check it for the highest version recorded.
+ * We then perform, and then record, each migration in sequence.
+ * The first migration is the migration to create the migrations
+ * table; this way, it'll work on existing databases. It'll also
+ * work for new databases -- the migrations get performed in order
+ * before the databases are considered to be set up.
+ */
+
+static char create_migration_table[] =
+ "CREATE TABLE migrations ("
+ "id INTEGER PRIMARY KEY, "
+ "version INTEGER, "
+ "stamp INTEGER, "
+ "sql VARCHAR"
+ ");";
+static char index_migration_table[] =
+ "CREATE INDEX migration__version ON migrations (version)";
+
+/* This used to be a { version, sql } pair, but version was always
+ * the same as index into the table, so I removed it.
+ * The first migration in each database is migration #0 -- the
+ * creation of the migration table now being used for versioning.
+ * The second is indexing on version -- sqlite3 can grab MAX(version)
+ * faster if it's indexed. (Indexing this table is very cheap, since
+ * there are very few migrations and each one produces exactly
+ * one insert.)
+ */
+static struct sql_migration {
+ char *sql;
+} file_migrations[] = {
+ { create_migration_table },
+ { index_migration_table },
+ { "ALTER TABLE files ADD deleting INTEGER;" },
+ { NULL },
+}, log_migrations[] = {
+ { create_migration_table },
+ { index_migration_table },
+ /* support for hostdeps merge -- this allows us to log "tags"
+ * along with events.
+ */
+ { "ALTER TABLE logs ADD tag VARCHAR;" },
+ /* the logs table was defined so early I hadn't realized I cared
+ * about UID and GID.
+ */
+ { "ALTER TABLE logs ADD uid INTEGER;" },
+ { "ALTER TABLE logs ADD gid INTEGER;" },
+ /* track access types (read/write, etc) */
+ { "ALTER TABLE logs ADD access INTEGER;" },
+ /* client program/path */
+ { "ALTER TABLE logs ADD program VARCHAR;" },
+ /* message type (ping, op) */
+ { "ALTER TABLE logs ADD type INTEGER;" },
+ { NULL },
+};
+
+/* cleanup database before getting started
+ *
+ * On a large build, the logs database gets GIGANTIC... And
+ * we rarely-if-ever delete things from it. So instead of
+ * doing the vacuum operation on it at startup, which can impose
+ * a several-minute delay, we do it only on deletions.
+ *
+ * There's no setup for log database right now.
+ */
+char *file_setups[] = {
+ "VACUUM;",
+ NULL,
+};
+
+struct database_info {
+ char *pathname;
+ struct sql_index *indexes;
+ struct sql_table *tables;
+ struct sql_migration *migrations;
+ char **pragmas;
+ char **setups;
+ struct sqlite3 **db;
+};
+
+static struct database_info db_infos[] = {
+ {
+ "logs.db",
+ log_indexes,
+ log_tables,
+ log_migrations,
+ log_pragmas,
+ NULL,
+ &log_db
+ },
+ {
+ "files.db",
+ file_indexes,
+ file_tables,
+ file_migrations,
+ file_pragmas,
+ file_setups,
+#ifdef USE_MEMORY_DB
+ &real_file_db
+ },
+ {
+ ":memory:",
+ file_indexes,
+ file_tables,
+ file_migrations,
+ file_pragmas,
+ file_setups,
+#endif
+ &file_db
+ },
+ { 0, 0, 0, 0, 0, 0, 0 }
+};
+
+/* pretty-print error along with the underlying SQL error. */
+static void
+dberr(sqlite3 *db, char *fmt, ...) {
+ va_list ap;
+ char debuff[8192];
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(debuff, 8192, fmt, ap);
+ va_end(ap);
+ len = write(pseudo_util_debug_fd, debuff, len);
+ if (db) {
+ len = snprintf(debuff, 8192, ": %s\n", sqlite3_errmsg(db));
+ len = write(pseudo_util_debug_fd, debuff, len);
+ } else {
+ len = write(pseudo_util_debug_fd, " (no db)\n", 9);
+ }
+}
+
+#ifdef USE_MEMORY_DB
+
+static void
+pdb_backup() {
+ sqlite3_backup *pBackup;
+ /* no point in doing this if we don't have a database to back up,
+ * or nothing's changed.
+ */
+ if (!file_db || !real_file_db || !file_db_dirty)
+ return;
+
+ pBackup = sqlite3_backup_init(real_file_db, "main", file_db, "main");
+ if (pBackup) {
+ int rc;
+ (void)sqlite3_backup_step(pBackup, -1);
+ rc = sqlite3_backup_finish(pBackup);
+ if (rc != SQLITE_OK) {
+ dberr(real_file_db, "error during flush to disk");
+ }
+ }
+ file_db_dirty = 0;
+}
+
+static void
+pdb_restore() {
+ sqlite3_backup *pBackup;
+ /* no point in doing this if we don't have a database to back up */
+ if (!file_db || !real_file_db)
+ return;
+
+ pBackup = sqlite3_backup_init(file_db, "main", real_file_db, "main");
+ if (pBackup) {
+ int rc;
+ (void)sqlite3_backup_step(pBackup, -1);
+ rc = sqlite3_backup_finish(pBackup);
+ if (rc != SQLITE_OK) {
+ dberr(file_db, "error during load from disk");
+ }
+ }
+ file_db_dirty = 0;
+}
+
+int
+pdb_maybe_backup(void) {
+ static int occasional = 0;
+ if (file_db && real_file_db) {
+ occasional = (occasional + 1) % 10;
+ if (occasional == 0) {
+ pdb_backup();
+ return 1;
+ }
+ }
+ return 0;
+}
+#else /* USE_MEMORY_DB */
+int
+pdb_maybe_backup(void) {
+ return 0;
+}
+#endif
+
+/* those who enjoy children, sausages, and databases, should not watch
+ * them being made.
+ */
+static int
+make_tables(sqlite3 *db,
+ struct sql_table *sql_tables,
+ struct sql_index *sql_indexes,
+ struct sql_migration *sql_migrations,
+ char **existing, int rows) {
+
+ static sqlite3_stmt *stmt;
+ sqlite3_stmt *update_version = 0;
+ struct sql_migration *m;
+ int available_migrations;
+ int version = -1;
+ int i, j;
+ char *sql;
+ char *errmsg;
+ int rc;
+ int found = 0;
+
+ for (i = 0; sql_tables[i].name; ++i) {
+ found = 0;
+ printf("considering table %s\n", sql_tables[i].name);
+ for (j = 1; j <= rows; ++j) {
+ if (!strcmp(existing[j], sql_tables[i].name)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ continue;
+ /* now to create the table */
+ sql = sqlite3_mprintf("CREATE TABLE %s ( %s );",
+ sql_tables[i].name, sql_tables[i].sql);
+ rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
+ sqlite3_free(sql);
+ if (rc) {
+ dberr(db, "error trying to create %s", sql_tables[i].name);
+ return 1;
+ }
+ if (sql_tables[i].values) {
+ for (j = 0; sql_tables[i].values[j].fmt; ++j) {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), sql_tables[i].values[j].fmt, sql_tables[i].values[j].arg);
+ sql = sqlite3_mprintf("INSERT INTO %s ( %s ) VALUES ( %s );",
+ sql_tables[i].name,
+ *sql_tables[i].names,
+ buffer);
+ rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
+ sqlite3_free(sql);
+ if (rc) {
+ dberr(db, "error trying to populate %s",
+ sql_tables[i].name);
+ return 1;
+ }
+ }
+ }
+ for (j = 0; sql_indexes[j].name; ++j) {
+ if (strcmp(sql_indexes[j].table, sql_tables[i].name))
+ continue;
+ sql = sqlite3_mprintf("CREATE INDEX %s ON %s ( %s );",
+ sql_indexes[j].name,
+ sql_indexes[j].table,
+ sql_indexes[j].keys);
+ rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
+ sqlite3_free(sql);
+ if (rc) {
+ dberr(db, "error trying to index %s",
+ sql_tables[i].name);
+ return 1;
+ }
+ }
+ }
+ /* now, see about migrations */
+ found = 0;
+ for (j = 1; j <= rows; ++j) {
+ if (!strcmp(existing[j], "migrations")) {
+ found = 1;
+ break;
+ }
+ }
+ if (found) {
+ sql = "SELECT MAX(version) FROM migrations;";
+ rc = sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, NULL);
+ if (rc) {
+ dberr(db, "couldn't examine migrations table");
+ return 1;
+ }
+ rc = sqlite3_step(stmt);
+ if (rc == SQLITE_ROW) {
+ version = (unsigned long) sqlite3_column_int64(stmt, 0);
+ rc = sqlite3_step(stmt);
+ } else {
+ version = -1;
+ }
+ if (rc != SQLITE_DONE) {
+ dberr(db, "not done after the single row we expected?", rc);
+ return 1;
+ }
+ pseudo_debug(PDBGF_DB, "existing database version: %d\n", version);
+ rc = sqlite3_finalize(stmt);
+ if (rc) {
+ dberr(db, "couldn't finalize version check");
+ return 1;
+ }
+ } else {
+ pseudo_debug(PDBGF_DB, "no existing database version\n");
+ version = -1;
+ }
+ for (m = sql_migrations; m->sql; ++m)
+ ;
+ available_migrations = m - sql_migrations;
+ /* I am pretty sure this can never happen. */
+ if (version < -1)
+ version = -1;
+ /* I hope this can never happen. */
+ if (version >= available_migrations)
+ version = available_migrations - 1;
+ for (m = sql_migrations + (version + 1); m->sql; ++m) {
+ int migration = (m - sql_migrations);
+ pseudo_debug(PDBGF_DB, "considering migration %d\n", migration);
+ if (version >= migration)
+ continue;
+ pseudo_debug(PDBGF_DB, "running migration %d\n", migration);
+ rc = sqlite3_prepare_v2(db,
+ m->sql,
+ strlen(m->sql),
+ &stmt, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare migration %d (%s)",
+ migration, m->sql);
+ return 1;
+ }
+ rc = sqlite3_step(stmt);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "migration %d failed",
+ migration);
+ return 1;
+ }
+ sqlite3_finalize(stmt);
+ /* this has to occur here, because the first migration
+ * executed CREATES the migration table, so you can't
+ * prepare this statement if you haven't already executed
+ * the first migration.
+ *
+ * Lesson learned: Yes, it actually WILL be a sort of big
+ * deal to add versioning later.
+ */
+ static char *update_sql =
+ "INSERT INTO migrations ("
+ "version, stamp, sql"
+ ") VALUES (?, ?, ?);";
+ rc = sqlite3_prepare_v2(db,
+ update_sql,
+ strlen(update_sql),
+ &update_version, NULL);
+ if (rc) {
+ dberr(db, "couldn't prepare statement to update migrations");
+ return 1;
+ }
+ sqlite3_bind_int(update_version, 1, migration);
+ sqlite3_bind_int(update_version, 2, time(NULL));
+ sqlite3_bind_text(update_version, 3, m->sql, -1, SQLITE_STATIC);
+ rc = sqlite3_step(update_version);
+ if (rc != SQLITE_DONE) {
+ dberr(db, "couldn't update migrations table (after migration to version %d)",
+ migration);
+ sqlite3_finalize(update_version);
+ return 1;
+ } else {
+ pseudo_debug(PDBGF_DB, "update of migrations (after %d) fine.\n",
+ migration);
+ }
+ sqlite3_finalize(update_version);
+ update_version = 0;
+ version = migration;
+ }
+
+ return 0;
+}
+
+/* registered with atexit */
+static void
+cleanup_db(void) {
+ pseudo_debug(PDBGF_SERVER, "server exiting\n");
+#ifdef USE_MEMORY_DB
+ if (real_file_db) {
+ pdb_backup();
+ sqlite3_close(real_file_db);
+ }
+#endif
+ if (file_db)
+ sqlite3_close(file_db);
+ if (log_db)
+ sqlite3_close(log_db);
+}
+
+/* This function has been rewritten and I no longer hate it. I just feel
+ * like it's important to say that.
+ */
+static int
+get_db(struct database_info *dbinfo) {
+ int rc;
+ int i;
+ char *sql;
+ char **results;
+ int rows, columns;
+ char *errmsg;
+ static int registered_cleanup = 0;
+ char *dbfile;
+ sqlite3 *db;
+
+ if (!dbinfo)
+ return 1;
+ if (!dbinfo->db)
+ return 1;
+ /* this database is perhaps already initialized? */
+ if (*(dbinfo->db))
+ return 0;
+
+ dbfile = pseudo_localstatedir_path(dbinfo->pathname);
+#ifdef USE_MEMORY_DB
+ if (!strcmp(dbinfo->pathname, ":memory:")) {
+ rc = sqlite3_open(dbinfo->pathname, &db);
+ } else
+#endif
+ rc = sqlite3_open(dbfile, &db);
+ free(dbfile);
+ if (rc) {
+ pseudo_diag("Failed: %s\n", sqlite3_errmsg(db));
+ sqlite3_close(db);
+ *(dbinfo->db) = NULL;
+ return 1;
+ }
+ /* we store this in the database_info, but hereafter we'll just use
+ * the name db, because it is shorter.
+ */
+ *dbinfo->db = db;
+ if (!registered_cleanup) {
+ atexit(cleanup_db);
+ registered_cleanup = 1;
+ }
+ if (dbinfo->pragmas) {
+ for (i = 0; dbinfo->pragmas[i]; ++i) {
+ rc = sqlite3_exec(db, dbinfo->pragmas[i], NULL, NULL, &errmsg);
+ pseudo_debug(PDBGF_SQL | PDBGF_VERBOSE, "executed pragma: '%s', rc %d.\n",
+ dbinfo->pragmas[i], rc);
+ if (rc) {
+ dberr(db, dbinfo->pragmas[i]);
+ }
+ }
+ }
+ /* create database tables or die trying */
+ sql = "SELECT name FROM sqlite_master "
+ "WHERE type = 'table' "
+ "ORDER BY name;";
+ rc = sqlite3_get_table(db, sql, &results, &rows, &columns, &errmsg);
+ if (rc) {
+ pseudo_diag("Failed: %s\n", errmsg);
+ } else {
+ rc = make_tables(db, dbinfo->tables, dbinfo->indexes, dbinfo->migrations, results, rows);
+ sqlite3_free_table(results);
+ }
+ /* as of now, the only setup is a vacuum operation which we don't care about
+ * the results of.
+ */
+ if (dbinfo->setups) {
+ for (i = 0; dbinfo->setups[i]; ++i) {
+ sqlite3_exec(db, dbinfo->setups[i], NULL, NULL, &errmsg);
+ }
+ }
+ return rc;
+}
+
+static int
+get_dbs(void) {
+ int err = 0;
+ int i;
+#ifdef USE_MEMORY_DB
+ int already_loaded = 0;
+ if (file_db)
+ already_loaded = 1;
+#endif
+ for (i = 0; db_infos[i].db; ++i) {
+ if (get_db(&db_infos[i])) {
+ pseudo_diag("Error getting '%s' database.\n",
+ db_infos[i].pathname);
+ err = 1;
+ }
+ }
+#ifdef USE_MEMORY_DB
+ if (!already_loaded && file_db)
+ pdb_restore();
+#endif
+ return err;
+}
+
+/* put a prepared log entry into the database */
+int
+pdb_log_traits(pseudo_query_t *traits) {
+ pseudo_query_t *trait;
+ log_entry *e;
+ int rc;
+
+ if (!log_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 1;
+ }
+ e = calloc(sizeof(*e), 1);
+ if (!e) {
+ pseudo_diag("can't allocate space for log entry.");
+ return 1;
+ }
+ for (trait = traits; trait; trait = trait->next) {
+ switch (trait->field) {
+ case PSQF_ACCESS:
+ e->access = trait->data.ivalue;
+ break;
+ case PSQF_CLIENT:
+ e->client = trait->data.ivalue;
+ break;
+ case PSQF_DEV:
+ e->dev = trait->data.ivalue;
+ break;
+ case PSQF_FD:
+ e->fd = trait->data.ivalue;
+ break;
+ case PSQF_FTYPE:
+ e->mode |= (trait->data.ivalue & S_IFMT);
+ break;
+ case PSQF_GID:
+ e->gid = trait->data.ivalue;
+ break;
+ case PSQF_INODE:
+ e->ino = trait->data.ivalue;
+ break;
+ case PSQF_MODE:
+ e->mode = trait->data.ivalue;
+ break;
+ case PSQF_OP:
+ e->op = trait->data.ivalue;
+ break;
+ case PSQF_PATH:
+ e->path = trait->data.svalue ?
+ strdup(trait->data.svalue) : NULL;
+ break;
+ case PSQF_PERM:
+ e->mode |= (trait->data.ivalue & ~(S_IFMT) & 0177777);
+ break;
+ case PSQF_PROGRAM:
+ e->program = trait->data.svalue ?
+ strdup(trait->data.svalue) : NULL;
+ break;
+ case PSQF_RESULT:
+ e->result = trait->data.ivalue;
+ break;
+ case PSQF_SEVERITY:
+ e->severity = trait->data.ivalue;
+ break;
+ case PSQF_STAMP:
+ e->stamp = trait->data.ivalue;
+ break;
+ case PSQF_TAG:
+ e->tag = trait->data.svalue ?
+ strdup(trait->data.svalue) : NULL;
+ break;
+ case PSQF_TEXT:
+ e->text = trait->data.svalue ?
+ strdup(trait->data.svalue) : NULL;
+ break;
+ case PSQF_TYPE:
+ e->type = trait->data.ivalue;
+ break;
+ case PSQF_UID:
+ e->uid = trait->data.ivalue;
+ break;
+ case PSQF_ID:
+ case PSQF_ORDER:
+ default:
+ pseudo_diag("Invalid trait %s for log creation.\n",
+ pseudo_query_field_name(trait->field));
+ free(e);
+ return 1;
+ break;
+ }
+ }
+ rc = pdb_log_entry(e);
+ log_entry_free(e);
+ return rc;
+}
+
+/* create a log from a given log entry, with tag and text */
+int
+pdb_log_entry(log_entry *e) {
+ char *sql = "INSERT INTO logs "
+ "(stamp, op, access, client, dev, gid, ino, mode, path, result, severity, text, program, tag, type, uid)"
+ " VALUES "
+ "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+ static sqlite3_stmt *insert;
+ int field;
+ int rc;
+
+ if (!log_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 1;
+ }
+
+ if (!insert) {
+ rc = sqlite3_prepare_v2(log_db, sql, strlen(sql), &insert, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare INSERT statement");
+ return 1;
+ }
+ }
+
+ field = 1;
+ if (e) {
+ if (e->stamp) {
+ sqlite3_bind_int(insert, field++, e->stamp);
+ } else {
+ sqlite3_bind_int(insert, field++, (unsigned long) time(NULL));
+ }
+ sqlite3_bind_int(insert, field++, e->op);
+ sqlite3_bind_int(insert, field++, e->access);
+ sqlite3_bind_int(insert, field++, e->client);
+ sqlite3_bind_int(insert, field++, e->dev);
+ sqlite3_bind_int(insert, field++, e->gid);
+ sqlite3_bind_int64(insert, field++, e->ino);
+ sqlite3_bind_int(insert, field++, e->mode);
+ if (e->path) {
+ sqlite3_bind_text(insert, field++, e->path, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ sqlite3_bind_int(insert, field++, e->result);
+ sqlite3_bind_int(insert, field++, e->severity);
+ if (e->text) {
+ sqlite3_bind_text(insert, field++, e->text, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (e->program) {
+ sqlite3_bind_text(insert, field++, e->program, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (e->tag) {
+ sqlite3_bind_text(insert, field++, e->tag, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ sqlite3_bind_int(insert, field++, e->type);
+ sqlite3_bind_int(insert, field++, e->uid);
+ } else {
+ sqlite3_bind_int(insert, field++, (unsigned long) time(NULL));
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ }
+
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(log_db, "insert may have failed");
+ }
+ sqlite3_reset(insert);
+ sqlite3_clear_bindings(insert);
+ return rc != SQLITE_DONE;
+}
+/* create a log from a given message, with tag and text */
+int
+pdb_log_msg(pseudo_sev_t severity, pseudo_msg_t *msg, const char *program, const char *tag, const char *text, ...) {
+ char *sql = "INSERT INTO logs "
+ "(stamp, op, access, client, dev, gid, ino, mode, path, result, uid, severity, text, program, tag, type)"
+ " VALUES "
+ "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+ static sqlite3_stmt *insert;
+ char buffer[8192];
+ int field;
+ int rc;
+ va_list ap;
+
+ if (text) {
+ va_start(ap, text);
+ vsnprintf(buffer, 8192, text, ap);
+ va_end(ap);
+ text = buffer;
+ }
+
+ if (!log_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 1;
+ }
+
+ if (!insert) {
+ rc = sqlite3_prepare_v2(log_db, sql, strlen(sql), &insert, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare INSERT statement");
+ return 1;
+ }
+ }
+
+ field = 1;
+ sqlite3_bind_int(insert, field++, (unsigned long) time(NULL));
+ if (msg) {
+ sqlite3_bind_int(insert, field++, msg->op);
+ sqlite3_bind_int(insert, field++, msg->access);
+ sqlite3_bind_int(insert, field++, msg->client);
+ sqlite3_bind_int(insert, field++, msg->dev);
+ sqlite3_bind_int(insert, field++, msg->gid);
+ sqlite3_bind_int64(insert, field++, msg->ino);
+ sqlite3_bind_int(insert, field++, msg->mode);
+ if (msg->pathlen) {
+ sqlite3_bind_text(insert, field++, msg->path, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ sqlite3_bind_int(insert, field++, msg->result);
+ sqlite3_bind_int(insert, field++, msg->uid);
+ } else {
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ }
+ sqlite3_bind_int(insert, field++, severity);
+ if (text) {
+ sqlite3_bind_text(insert, field++, text, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (program) {
+ sqlite3_bind_text(insert, field++, program, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (tag) {
+ sqlite3_bind_text(insert, field++, tag, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (msg) {
+ sqlite3_bind_int(insert, field++, msg->type);
+ } else {
+ sqlite3_bind_int(insert, field++, 0);
+ }
+
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(log_db, "insert may have failed");
+ }
+ sqlite3_reset(insert);
+ sqlite3_clear_bindings(insert);
+ return rc != SQLITE_DONE;
+}
+
+#define BFSZ 8192
+typedef struct {
+ size_t buflen;
+ char *data;
+ char *tail;
+} buffer;
+
+static int
+frag(buffer *b, char *fmt, ...) {
+ va_list ap;
+ static size_t curlen;
+ int rc;
+
+ if (!b) {
+ pseudo_diag("frag called without buffer.\n");
+ return -1;
+ }
+ curlen = b->tail - b->data;
+ va_start(ap, fmt);
+ rc = vsnprintf(b->tail, b->buflen - curlen, fmt, ap);
+ va_end(ap);
+ if ((rc > 0) && ((size_t) rc >= (b->buflen - curlen))) {
+ size_t newlen = b->buflen;
+ while (newlen <= (rc + curlen))
+ newlen *= 2;
+ char *newbuf = malloc(newlen);
+ if (!newbuf) {
+ pseudo_diag("failed to allocate SQL buffer.\n");
+ return -1;
+ }
+ memcpy(newbuf, b->data, curlen + 1);
+ b->tail = newbuf + curlen;
+ free(b->data);
+ b->data = newbuf;
+ b->buflen = newlen;
+ /* try again */
+ va_start(ap, fmt);
+ rc = vsnprintf(b->tail, b->buflen - curlen, fmt, ap);
+ va_end(ap);
+ if ((rc > 0) && ((size_t) rc >= (b->buflen - curlen))) {
+ pseudo_diag("tried to reallocate larger buffer, failed. giving up.\n");
+ return -1;
+ }
+ }
+ if (rc >= 0)
+ b->tail += rc;
+ return rc;
+}
+
+sqlite3_stmt *
+pdb_query(char *stmt_type, pseudo_query_t *traits, unsigned long fields, int unique, int want_results) {
+ pseudo_query_t *trait;
+ sqlite3_stmt *stmt;
+ int done_any = 0;
+ int field = 0;
+ const char *order_by = "id";
+ char *order_dir = "ASC";
+ int rc;
+ pseudo_query_field_t f;
+ static buffer *sql;
+
+ if (!log_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return NULL;
+ }
+
+ if (!stmt_type) {
+ pseudo_diag("can't prepare a statement without a type.\n");
+ }
+
+ if (!sql) {
+ sql = malloc(sizeof *sql);
+ if (!sql) {
+ pseudo_diag("can't allocate SQL buffer.\n");
+ return NULL;
+ }
+ sql->buflen = 512;
+ sql->data = malloc(sql->buflen);
+ if (!sql->data) {
+ pseudo_diag("can't allocate SQL text buffer.\n");
+ free(sql);
+ sql = 0;
+ return NULL;
+ }
+ }
+ sql->tail = sql->data;
+ /* should be DELETE or SELECT */
+ frag(sql, "%s ", stmt_type);
+
+ if (want_results) {
+ if (unique)
+ frag(sql, "DISTINCT ");
+
+ done_any = 0;
+ for (f = PSQF_NONE + 1; f < PSQF_MAX; ++f) {
+ if (fields & (1 << f)) {
+ frag(sql, "%s%s",
+ done_any ? ", " : "",
+ pseudo_query_field_name(f));
+ done_any = 1;
+ }
+ }
+ }
+
+ frag(sql, " FROM logs ");
+ /* first, build up an SQL string with the fields and operators */
+ done_any = 0;
+ for (trait = traits; trait; trait = trait->next) {
+ if (trait->field != PSQF_ORDER) {
+ if (done_any) {
+ frag(sql, "AND ");
+ } else {
+ frag(sql, "WHERE ");
+ done_any = 1;
+ }
+ }
+ switch (trait->field) {
+ case PSQF_PROGRAM:
+ case PSQF_TEXT:
+ case PSQF_TAG:
+ case PSQF_PATH:
+ switch (trait->type) {
+ case PSQT_LIKE:
+ frag(sql, "%s %s ('%' || ? || '%')",
+ pseudo_query_field_name(trait->field),
+ pseudo_query_type_sql(trait->type));
+ break;
+ case PSQT_NOTLIKE:
+ case PSQT_SQLPAT:
+ frag(sql, "%s %s ?",
+ pseudo_query_field_name(trait->field),
+ pseudo_query_type_sql(trait->type));
+ break;
+ default:
+ frag(sql, "%s %s ? ",
+ pseudo_query_field_name(trait->field),
+ pseudo_query_type_sql(trait->type));
+ break;
+ }
+ break;
+ case PSQF_PERM:
+ switch (trait->type) {
+ case PSQT_LIKE:
+ case PSQT_NOTLIKE:
+ case PSQT_SQLPAT:
+ pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n");
+ return 0;
+ break;
+ default:
+ break;
+ }
+ /* mask out permission bits */
+ frag(sql, "(%s & %d) %s ? ",
+ "mode",
+ ~(S_IFMT) & 0177777,
+ pseudo_query_type_sql(trait->type));
+ break;
+ case PSQF_FTYPE:
+ switch (trait->type) {
+ case PSQT_LIKE:
+ case PSQT_NOTLIKE:
+ case PSQT_SQLPAT:
+ pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n");
+ return 0;
+ break;
+ default:
+ break;
+ }
+ /* mask out permission bits */
+ frag(sql, "(%s & %d) %s ? ",
+ "mode",
+ S_IFMT,
+ pseudo_query_type_sql(trait->type));
+ break;
+ case PSQF_ORDER:
+ order_by = pseudo_query_field_name(trait->data.ivalue);
+ switch (trait->type) {
+ case PSQT_LESS:
+ order_dir = "DESC";
+ break;
+ case PSQT_EXACT:
+ /* this was already the default */
+ break;
+ case PSQT_GREATER:
+ order_dir = "ASC";
+ break;
+ default:
+ pseudo_diag("Ordering must be < or >.\n");
+ return 0;
+ break;
+ }
+ break;
+ default:
+ switch (trait->type) {
+ case PSQT_LIKE:
+ case PSQT_NOTLIKE:
+ case PSQT_SQLPAT:
+ pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n");
+ return 0;
+ break;
+ default:
+ frag(sql, "%s %s ? ",
+ pseudo_query_field_name(trait->field),
+ pseudo_query_type_sql(trait->type));
+ break;
+ }
+ break;
+ }
+ }
+ if (want_results)
+ frag(sql, "ORDER BY %s %s;", order_by, order_dir);
+ pseudo_debug(PDBGF_SQL, "created SQL: <%s>\n", sql->data);
+
+ /* second, prepare it */
+ rc = sqlite3_prepare_v2(log_db, sql->data, strlen(sql->data), &stmt, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare %s statement", stmt_type);
+ return NULL;
+ }
+
+ /* third, bind the fields */
+ field = 1;
+ for (trait = traits; trait; trait = trait->next) {
+ switch (trait->field) {
+ case PSQF_ORDER:
+ /* this just creates a hunk of SQL above */
+ break;
+ case PSQF_PROGRAM:
+ case PSQF_PATH:
+ case PSQF_TAG:
+ case PSQF_TEXT:
+ sqlite3_bind_text(stmt, field++,
+ trait->data.svalue, -1, SQLITE_STATIC);
+ break;
+ case PSQF_ACCESS:
+ case PSQF_CLIENT:
+ case PSQF_DEV:
+ case PSQF_FD:
+ case PSQF_FTYPE:
+ case PSQF_INODE:
+ case PSQF_GID:
+ case PSQF_PERM:
+ case PSQF_MODE:
+ case PSQF_OP:
+ case PSQF_RESULT:
+ case PSQF_SEVERITY:
+ case PSQF_STAMP:
+ case PSQF_TYPE:
+ case PSQF_UID:
+ sqlite3_bind_int64(stmt, field++, trait->data.ivalue);
+ break;
+ default:
+ pseudo_diag("Inexplicably invalid field type %d\n", trait->field);
+ sqlite3_finalize(stmt);
+ return NULL;
+ }
+ }
+ return stmt;
+}
+
+int
+pdb_delete(pseudo_query_t *traits, unsigned long fields) {
+ sqlite3_stmt *stmt;
+
+ stmt = pdb_query("DELETE", traits, fields, 0, 0);
+
+ /* no need to return it, so... */
+ if (stmt) {
+ file_db_dirty = 1;
+ int rc = sqlite3_step(stmt);
+ if (rc != SQLITE_DONE) {
+ dberr(log_db, "deletion failed");
+ return -1;
+ } else {
+ pseudo_diag("Deleted records, vacuuming log database (may take a while).\n");
+ /* we can't do anything about it if this fails... */
+ sqlite3_exec(log_db, "VACUUM;", NULL, NULL, NULL);
+ }
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+ return -1;
+}
+
+log_history
+pdb_history(pseudo_query_t *traits, unsigned long fields, int unique) {
+ log_history h = NULL;
+ sqlite3_stmt *stmt;
+
+ stmt = pdb_query("SELECT", traits, fields, unique, 1);
+
+ if (stmt) {
+ /* fourth, return the statement, now ready to be stepped through */
+ h = malloc(sizeof(*h));
+ if (h) {
+ h->rc = 0;
+ h->fields = fields;
+ h->stmt = stmt;
+ } else {
+ pseudo_diag("failed to allocate memory for log_history\n");
+ sqlite3_finalize(stmt);
+ }
+ return h;
+ } else {
+ return NULL;
+ }
+}
+
+log_entry *
+pdb_history_entry(log_history h) {
+ log_entry *l;
+ const unsigned char *s;
+ int column;
+ pseudo_query_field_t f;
+
+ if (!h || !h->stmt)
+ return 0;
+ /* in case someone tries again after we're already done */
+ if (h->rc == SQLITE_DONE) {
+ return 0;
+ }
+ h->rc = sqlite3_step(h->stmt);
+ if (h->rc == SQLITE_DONE) {
+ return 0;
+ } else if (h->rc != SQLITE_ROW) {
+ dberr(log_db, "statement failed");
+ return 0;
+ }
+ l = calloc(sizeof(log_entry), 1);
+ if (!l) {
+ pseudo_diag("couldn't allocate log entry.\n");
+ return 0;
+ }
+
+ column = 0;
+ for (f = PSQF_NONE + 1; f < PSQF_MAX; ++f) {
+ if (!(h->fields & (1 << f)))
+ continue;
+ switch (f) {
+ case PSQF_ACCESS:
+ l->access = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_CLIENT:
+ l->client = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_DEV:
+ l->dev = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_FD:
+ l->fd = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_GID:
+ l->gid = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_INODE:
+ l->ino = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_MODE:
+ l->mode = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_OP:
+ l->op = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_PATH:
+ s = sqlite3_column_text(h->stmt, column++);
+ if (s)
+ l->path = strdup((char *) s);
+ break;
+ case PSQF_PROGRAM:
+ s = sqlite3_column_text(h->stmt, column++);
+ if (s)
+ l->program = strdup((char *) s);
+ break;
+ case PSQF_RESULT:
+ l->result = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_SEVERITY:
+ l->severity = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_STAMP:
+ l->stamp = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_TAG:
+ s = sqlite3_column_text(h->stmt, column++);
+ if (s)
+ l->tag = strdup((char *) s);
+ break;
+ case PSQF_TEXT:
+ s = sqlite3_column_text(h->stmt, column++);
+ if (s)
+ l->text = strdup((char *) s);
+ break;
+ case PSQF_TYPE:
+ l->type = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_UID:
+ l->uid = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_ORDER:
+ case PSQF_FTYPE:
+ case PSQF_PERM:
+ pseudo_diag("field %s should not be in the fields list.\n",
+ pseudo_query_field_name(f));
+ return 0;
+ break;
+ default:
+ pseudo_diag("unknown field %d\n", f);
+ return 0;
+ break;
+ }
+ }
+
+ return l;
+}
+
+void
+pdb_history_free(log_history h) {
+ if (!h)
+ return;
+ if (h->stmt) {
+ sqlite3_reset(h->stmt);
+ sqlite3_finalize(h->stmt);
+ }
+ free(h);
+}
+
+void
+log_entry_free(log_entry *e) {
+ if (!e)
+ return;
+ free(e->text);
+ free(e->path);
+ free(e->program);
+ free(e->tag);
+ free(e);
+}
+
+/* Now for the actual file handling code! */
+
+/* pdb_link_file: Creates a new file from msg, using the provided path
+ * or 'NAMELESS FILE'.
+ */
+int
+pdb_link_file(pseudo_msg_t *msg, long long *row) {
+ static sqlite3_stmt *insert;
+ int rc;
+ char *sql = "INSERT INTO files "
+ " ( path, dev, ino, uid, gid, mode, rdev, deleting ) "
+ " VALUES (?, ?, ?, ?, ?, ?, ?, 0);";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!insert) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &insert, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare INSERT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_text(insert, 1, msg->path, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_text(insert, 1, "NAMELESS FILE", -1, SQLITE_STATIC);
+ }
+ sqlite3_bind_int(insert, 2, msg->dev);
+ sqlite3_bind_int64(insert, 3, msg->ino);
+ sqlite3_bind_int(insert, 4, msg->uid);
+ sqlite3_bind_int(insert, 5, msg->gid);
+ sqlite3_bind_int(insert, 6, msg->mode);
+ sqlite3_bind_int(insert, 7, msg->rdev);
+ pseudo_debug(PDBGF_DB | PDBGF_FILE, "linking %s: dev %llu, ino %llu, mode %o, owner %d\n",
+ (msg->pathlen ? msg->path : "<nil> (as NAMELESS FILE)"),
+ (unsigned long long) msg->dev, (unsigned long long) msg->ino,
+ (int) msg->mode, msg->uid);
+ file_db_dirty = 1;
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "insert may have failed (rc %d)", rc);
+ }
+ /* some users care what the row ID is */
+ if (row) {
+ *row = sqlite3_last_insert_rowid(file_db);
+ }
+ sqlite3_reset(insert);
+ sqlite3_clear_bindings(insert);
+ return rc != SQLITE_DONE;
+}
+
+/* pdb_unlink_file_dev: Delete every instance of a dev/inode pair. */
+int
+pdb_unlink_file_dev(pseudo_msg_t *msg) {
+ static sqlite3_stmt *sql_delete;
+ int rc;
+ char *sql = "DELETE FROM files WHERE dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!sql_delete) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &sql_delete, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int(sql_delete, 1, msg->dev);
+ sqlite3_bind_int64(sql_delete, 2, msg->ino);
+ file_db_dirty = 1;
+ rc = sqlite3_step(sql_delete);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete by inode may have failed");
+ }
+ sqlite3_reset(sql_delete);
+ sqlite3_clear_bindings(sql_delete);
+ return rc != SQLITE_DONE;
+}
+
+/* provide a path for a 'NAMELESS FILE' entry */
+int
+pdb_update_file_path(pseudo_msg_t *msg) {
+ static sqlite3_stmt *update;
+ int rc;
+ char *sql = "UPDATE files SET path = ? "
+ "WHERE path = 'NAMELESS FILE' and dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!msg || !msg->pathlen) {
+ pseudo_debug(PDBGF_DB, "can't update a file without a message or path.\n");
+ return 1;
+ }
+ sqlite3_bind_text(update, 1, msg->path, -1, SQLITE_STATIC);
+ sqlite3_bind_int(update, 2, msg->dev);
+ sqlite3_bind_int64(update, 3, msg->ino);
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update path by inode may have failed");
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ return rc != SQLITE_DONE;
+}
+
+/* mark a file for pending deletion by a given client */
+int
+pdb_may_unlink_file(pseudo_msg_t *msg, int deleting) {
+ static sqlite3_stmt *mark_file;
+ int rc, exact;
+ char *sql_mark_file = "UPDATE files SET deleting = ? WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!mark_file) {
+ rc = sqlite3_prepare_v2(file_db, sql_mark_file, strlen(sql_mark_file), &mark_file, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_int(mark_file, 1, deleting);
+ sqlite3_bind_text(mark_file, 2, msg->path, -1, SQLITE_STATIC);
+ } else {
+ pseudo_debug(PDBGF_DB, "cannot mark a file for pending deletion without a path.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(mark_file);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "mark for deletion may have failed");
+ return 1;
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d) ", exact);
+ sqlite3_reset(mark_file);
+ sqlite3_clear_bindings(mark_file);
+ /* indicate whether we marked something */
+ if (exact > 0)
+ return 0;
+ else
+ return 1;
+}
+
+/* unmark a file for pending deletion */
+int
+pdb_cancel_unlink_file(pseudo_msg_t *msg) {
+ static sqlite3_stmt *mark_file;
+ int rc, exact;
+ char *sql_mark_file = "UPDATE files SET deleting = 0 WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!mark_file) {
+ rc = sqlite3_prepare_v2(file_db, sql_mark_file, strlen(sql_mark_file), &mark_file, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_text(mark_file, 1, msg->path, -1, SQLITE_STATIC);
+ } else {
+ pseudo_debug(PDBGF_DB, "cannot unmark a file for pending deletion without a path.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(mark_file);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "unmark for deletion may have failed");
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d) ", exact);
+ sqlite3_reset(mark_file);
+ sqlite3_clear_bindings(mark_file);
+ return rc != SQLITE_DONE;
+}
+
+/* delete all files attached to a given cookie;
+ * used for database fixup passes.
+ */
+int
+pdb_did_unlink_files(int deleting) {
+ static sqlite3_stmt *delete_exact;
+ int rc, exact;
+ char *sql_delete_exact = "DELETE FROM files WHERE deleting = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete_exact) {
+ rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (deleting == 0) {
+ pseudo_diag("did_unlink_files: deleting must be non-zero.\n");
+ return 0;
+ }
+ sqlite3_bind_int(delete_exact, 1, deleting);
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "cleanup of files marked for deletion may have failed");
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d)\n", exact);
+ sqlite3_reset(delete_exact);
+ sqlite3_clear_bindings(delete_exact);
+ return rc != SQLITE_DONE;
+}
+
+/* confirm deletion of a specific file by a given client */
+int
+pdb_did_unlink_file(char *path, int deleting) {
+ static sqlite3_stmt *delete_exact;
+ int rc, exact;
+ char *sql_delete_exact = "DELETE FROM files WHERE path = ? AND deleting = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete_exact) {
+ rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!path) {
+ pseudo_debug(PDBGF_DB, "cannot unlink a file without a path.");
+ return 1;
+ }
+ sqlite3_bind_text(delete_exact, 1, path, -1, SQLITE_STATIC);
+ sqlite3_bind_int(delete_exact, 2, deleting);
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "cleanup of file marked for deletion may have failed");
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d)\n", exact);
+ sqlite3_reset(delete_exact);
+ sqlite3_clear_bindings(delete_exact);
+ return rc != SQLITE_DONE;
+}
+
+/* unlink a file, by path */
+int
+pdb_unlink_file(pseudo_msg_t *msg) {
+ static sqlite3_stmt *delete_exact;
+ int rc, exact;
+ char *sql_delete_exact = "DELETE FROM files WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete_exact) {
+ rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_text(delete_exact, 1, msg->path, -1, SQLITE_STATIC);
+ } else {
+ pseudo_debug(PDBGF_DB, "cannot unlink a file without a path.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete exact by path may have failed");
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d) ", exact);
+ sqlite3_reset(delete_exact);
+ sqlite3_clear_bindings(delete_exact);
+ return rc != SQLITE_DONE;
+}
+
+/* Unlink all the contents of directory
+ * SQLite performance limitations:
+ * path LIKE foo '/%' -> can't use index
+ * path = A OR path = B -> can't use index
+ * Solution:
+ * 1. From web http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html
+ * Use > and < instead of a glob at the end.
+ */
+int
+pdb_unlink_contents(pseudo_msg_t *msg) {
+ static sqlite3_stmt *delete_sub;
+ int rc, sub;
+ char *sql_delete_sub = "DELETE FROM files WHERE "
+ "(path > (? || '/') AND path < (? || '0'));";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete_sub) {
+ rc = sqlite3_prepare_v2(file_db, sql_delete_sub, strlen(sql_delete_sub), &delete_sub, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_text(delete_sub, 1, msg->path, -1, SQLITE_STATIC);
+ sqlite3_bind_text(delete_sub, 2, msg->path, -1, SQLITE_STATIC);
+ } else {
+ pseudo_debug(PDBGF_DB, "cannot unlink a file without a path.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete_sub);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete sub by path may have failed");
+ }
+ sub = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(sub %d) ", sub);
+ sqlite3_reset(delete_sub);
+ sqlite3_clear_bindings(delete_sub);
+ return rc != SQLITE_DONE;
+}
+
+/* rename a file.
+ * If there are any other files with paths that are rooted in "file", then
+ * file must really be a directory, and they should be renamed.
+ *
+ * This is tricky:
+ * You have to rename everything starting with "path/", but also "path" itself
+ * with no slash. Luckily for us, SQL can replace the old path with the
+ * new path.
+ */
+int
+pdb_rename_file(const char *oldpath, pseudo_msg_t *msg) {
+ static sqlite3_stmt *update_exact, *update_sub;
+ int rc;
+ char *sql_update_exact = "UPDATE files SET path = ? WHERE path = ?;";
+ char *sql_update_sub = "UPDATE files SET path = replace(path, ?, ?) "
+ "WHERE (path > (? || '/') AND path < (? || '0'));";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update_exact) {
+ rc = sqlite3_prepare_v2(file_db, sql_update_exact, strlen(sql_update_exact), &update_exact, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!update_sub) {
+ rc = sqlite3_prepare_v2(file_db, sql_update_sub, strlen(sql_update_sub), &update_sub, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (!msg->pathlen) {
+ pseudo_debug(PDBGF_DB, "rename: No path provided (ino %llu)\n", (unsigned long long) msg->ino);
+ return 1;
+ }
+ if (!oldpath) {
+ pseudo_debug(PDBGF_DB, "rename: No old path for %s\n", msg->path);
+ return 1;
+ }
+ pseudo_debug(PDBGF_DB, "rename: Changing %s to %s\n", oldpath, msg->path);
+ rc = sqlite3_bind_text(update_exact, 1, msg->path, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_exact, 2, oldpath, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_sub, 1, oldpath, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_sub, 2, msg->path, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_sub, 3, oldpath, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_sub, 4, oldpath, -1, SQLITE_STATIC);
+
+ rc = sqlite3_exec(file_db, "BEGIN;", NULL, NULL, NULL);
+
+ file_db_dirty = 1;
+ rc = sqlite3_step(update_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update exact may have failed: rc %d", rc);
+ }
+ rc = sqlite3_step(update_sub);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update sub may have failed: rc %d", rc);
+ }
+ sqlite3_reset(update_exact);
+ sqlite3_reset(update_sub);
+
+ rc = sqlite3_exec(file_db, "END;", NULL, NULL, NULL);
+
+ sqlite3_clear_bindings(update_exact);
+ sqlite3_clear_bindings(update_sub);
+ return rc != SQLITE_DONE;
+}
+
+/* renumber device only.
+ * this is used if the filesystem moves to a new device, without changing
+ * inode allocations.
+ */
+int
+pdb_renumber_all(dev_t from, dev_t to) {
+ static sqlite3_stmt *update;
+ int rc;
+ char *sql = "UPDATE files "
+ " SET dev = ? "
+ " WHERE dev = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ rc = sqlite3_bind_int(update, 1, to);
+ if (rc) {
+ dberr(file_db, "error binding device numbers to update");
+ }
+ rc = sqlite3_bind_int(update, 2, from);
+ if (rc) {
+ dberr(file_db, "error binding device numbers to update");
+ }
+
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update may have failed: rc %d", rc);
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ pseudo_debug(PDBGF_DB, "updating device dev %llu to %llu\n",
+ (unsigned long long) from, (unsigned long long) to);
+ return rc != SQLITE_DONE;
+}
+
+/* change dev/inode for a given path -- used only by RENAME for now.
+ */
+int
+pdb_update_inode(pseudo_msg_t *msg) {
+ static sqlite3_stmt *update;
+ int rc;
+ char *sql = "UPDATE files "
+ " SET dev = ?, ino = ? "
+ " WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (!msg->pathlen) {
+ pseudo_diag("Can't update the inode of a file without its path.\n");
+ return 1;
+ }
+ sqlite3_bind_int(update, 1, msg->dev);
+ sqlite3_bind_int64(update, 2, msg->ino);
+ rc = sqlite3_bind_text(update, 3, msg->path, -1, SQLITE_STATIC);
+ if (rc) {
+ /* msg->path can never be null, and if msg didn't
+ * have a non-zero pathlen, we'd already have exited
+ * above
+ */
+ dberr(file_db, "error binding %s to select", msg->path);
+ }
+
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update may have failed: rc %d", rc);
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ pseudo_debug(PDBGF_DB, "updating path %s to dev %llu, ino %llu\n",
+ msg->path,
+ (unsigned long long) msg->dev, (unsigned long long) msg->ino);
+ return rc != SQLITE_DONE;
+}
+
+/* change uid/gid/mode/rdev in any existing entries matching a given
+ * dev/inode pair.
+ */
+int
+pdb_update_file(pseudo_msg_t *msg) {
+ static sqlite3_stmt *update;
+ int rc;
+ char *sql = "UPDATE files "
+ " SET uid = ?, gid = ?, mode = ?, rdev = ? "
+ " WHERE dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int(update, 1, msg->uid);
+ sqlite3_bind_int(update, 2, msg->gid);
+ sqlite3_bind_int(update, 3, msg->mode);
+ sqlite3_bind_int(update, 4, msg->rdev);
+ sqlite3_bind_int(update, 5, msg->dev);
+ sqlite3_bind_int64(update, 6, msg->ino);
+
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update may have failed: rc %d", rc);
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ pseudo_debug(PDBGF_DB, "updating dev %llu, ino %llu, new mode %o, owner %d\n",
+ (unsigned long long) msg->dev, (unsigned long long) msg->ino,
+ (int) msg->mode, msg->uid);
+ return rc != SQLITE_DONE;
+}
+
+/* find file using both path AND dev/inode as key */
+int
+pdb_find_file_exact(pseudo_msg_t *msg, long long *row) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE path = ? AND dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ rc = sqlite3_bind_text(select, 1, msg->path, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "error binding %s to select", msg->pathlen ? msg->path : "<nil>");
+ }
+ sqlite3_bind_int(select, 2, msg->dev);
+ sqlite3_bind_int64(select, 3, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ if (row) {
+ *row = sqlite3_column_int64(select, 0);
+ }
+ msg->uid = (unsigned long) sqlite3_column_int64(select, 4);
+ msg->gid = (unsigned long) sqlite3_column_int64(select, 5);
+ msg->mode = (unsigned long) sqlite3_column_int64(select, 6);
+ msg->rdev = (unsigned long) sqlite3_column_int64(select, 7);
+ msg->deleting = (int) sqlite3_column_int64(select, 8);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_exact: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_exact: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+/* find file using path as a key */
+int
+pdb_find_file_path(pseudo_msg_t *msg, long long *row) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 1;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (!msg->pathlen) {
+ return 1;
+ }
+ rc = sqlite3_bind_text(select, 1, msg->path, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "error binding %s to select", msg->pathlen ? msg->path : "<nil>");
+ }
+
+ rc = sqlite3_column_count(select);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ if (row) {
+ *row = sqlite3_column_int64(select, 0);
+ }
+ msg->dev = sqlite3_column_int64(select, 2);
+ msg->ino = sqlite3_column_int64(select, 3);
+ msg->uid = sqlite3_column_int64(select, 4);
+ msg->gid = sqlite3_column_int64(select, 5);
+ msg->mode = sqlite3_column_int64(select, 6);
+ msg->rdev = sqlite3_column_int64(select, 7);
+ msg->deleting = (int) sqlite3_column_int64(select, 8);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_path: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_path: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+/* find path for a file, given dev and inode as keys */
+char *
+pdb_get_file_path(pseudo_msg_t *msg) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT path FROM files WHERE dev = ? AND ino = ?;";
+ char *response;
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 0;
+ }
+ }
+ if (!msg) {
+ return 0;
+ }
+ sqlite3_bind_int(select, 1, msg->dev);
+ sqlite3_bind_int64(select, 2, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ response = (char *) sqlite3_column_text(select, 0);
+ if (response) {
+ if (strcmp(response, "NAMELESS FILE")) {
+ response = strdup(response);
+ } else {
+ response = 0;
+ }
+ }
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_dev: sqlite_done on first row\n");
+ response = 0;
+ break;
+ default:
+ dberr(file_db, "find_dev: select returned neither a row nor done");
+ response = 0;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return response;
+}
+
+/* find file using dev/inode as key */
+int
+pdb_find_file_dev(pseudo_msg_t *msg, long long *row, char **path) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int(select, 1, msg->dev);
+ sqlite3_bind_int64(select, 2, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ if (row) {
+ *row = sqlite3_column_int64(select, 0);
+ }
+ msg->uid = (unsigned long) sqlite3_column_int64(select, 4);
+ msg->gid = (unsigned long) sqlite3_column_int64(select, 5);
+ msg->mode = (unsigned long) sqlite3_column_int64(select, 6);
+ msg->rdev = (unsigned long) sqlite3_column_int64(select, 7);
+ msg->deleting = (int) sqlite3_column_int64(select, 8);
+ /* stash path */
+ if (path) {
+ *path = strdup((char *) sqlite3_column_text(select, 1));
+ pseudo_debug(PDBGF_FILE, "find_file_dev: path %s\n",
+ *path ? *path : "<nil>");
+ }
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_dev: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_dev: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+int
+pdb_get_xattr(long long file_id, char **value, size_t *len) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *response;
+ size_t length;
+ char *sql = "SELECT value FROM xattrs WHERE file_id = ? AND name = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ pseudo_debug(PDBGF_XATTR, "requested xattr named '%s' for file %lld\n", *value, file_id);
+ sqlite3_bind_int(select, 1, file_id);
+ rc = sqlite3_bind_text(select, 2, *value, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr name to SELECT.");
+ return 1;
+ }
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ response = (char *) sqlite3_column_text(select, 0);
+ length = sqlite3_column_bytes(select, 0);
+ pseudo_debug(PDBGF_XATTR, "got %d-byte results: '%s'\n",
+ (int) length, response);
+ if (response && length >= 1) {
+ /* not a strdup because the values can contain
+ * arbitrary bytes.
+ */
+ *value = malloc(length);
+ memcpy(*value, response, length);
+ *len = length;
+ rc = 0;
+ } else {
+ *value = NULL;
+ *len = 0;
+ rc = 1;
+ }
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_exact: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_exact: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+int
+pdb_list_xattr(long long file_id, char **value, size_t *len) {
+ static sqlite3_stmt *select;
+ size_t allocated = 0;
+ size_t used = 0;
+ char *buffer = 0;
+ int rc;
+ char *sql = "SELECT name FROM xattrs WHERE file_id = ?;";
+
+ /* if we don't have a record of the file, it must not have
+ * any extended attributes...
+ */
+ if (file_id == -1) {
+ *value = NULL;
+ *len = 0;
+ return 0;
+ }
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ sqlite3_bind_int(select, 1, file_id);
+ do {
+ rc = sqlite3_step(select);
+ if (rc == SQLITE_ROW) {
+ char *value = (char *) sqlite3_column_text(select, 0);
+ size_t len = sqlite3_column_bytes(select, 0);
+ if (!buffer) {
+ allocated = round_up(len, 256);
+ buffer = malloc(allocated);
+ }
+ if (used + len + 2 > allocated) {
+ size_t new_allocated = round_up(used + len + 2, 256);
+ char *new_buffer = malloc(new_allocated);
+ memcpy(new_buffer, buffer, used);
+ free(buffer);
+ allocated = new_allocated;
+ buffer = new_buffer;
+ }
+ memcpy(buffer + used, value, len);
+ buffer[used + len] = '\0';
+ used = used + len + 1;
+ } else if (rc == SQLITE_DONE) {
+ *value = buffer;
+ *len = used;
+ } else {
+ dberr(file_db, "non-row response from select?");
+ free(buffer);
+ *value = NULL;
+ *len = 0;
+ }
+ } while (rc == SQLITE_ROW);
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc != SQLITE_DONE;
+}
+
+int
+pdb_remove_xattr(long long file_id, char *value, size_t len) {
+ static sqlite3_stmt *delete;
+ int rc;
+ char *sql = "DELETE FROM xattrs WHERE file_id = ? AND name = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &delete, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ sqlite3_bind_int(delete, 1, file_id);
+ rc = sqlite3_bind_text(delete, 2, value, len, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr name to DELETE.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete xattr may have failed");
+ }
+ sqlite3_reset(delete);
+ sqlite3_clear_bindings(delete);
+ return rc != SQLITE_DONE;
+}
+
+int
+pdb_set_xattr(long long file_id, char *value, size_t len, int flags) {
+ static sqlite3_stmt *select, *update, *insert;
+ int rc;
+ long long existing_row = -1;
+ char *select_sql = "SELECT id FROM xattrs WHERE file_id = ? AND name = ?;";
+ char *insert_sql = "INSERT INTO xattrs "
+ " ( file_id, name, value ) "
+ " VALUES (?, ?, ?);";
+ char *update_sql = "UPDATE xattrs SET value = ? WHERE id = ?;";
+ char *vname = value;
+ size_t vlen;
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, select_sql, strlen(select_sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ sqlite3_bind_int(select, 1, file_id);
+ rc = sqlite3_bind_text(select, 2, value, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr name to SELECT.");
+ return 1;
+ }
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ existing_row = sqlite3_column_int64(select, 0);
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB | PDBGF_VERBOSE, "find_exact: sqlite_done on first row\n");
+ existing_row = -1;
+ break;
+ default:
+ dberr(file_db, "set_xattr: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ if (flags == XATTR_CREATE && existing_row != -1) {
+ pseudo_debug(PDBGF_DB, "XATTR_CREATE with an existing row, failing.");
+ return 1;
+ }
+ if (flags == XATTR_REPLACE && existing_row == -1) {
+ pseudo_debug(PDBGF_DB, "XATTR_REPLACE with no existing row, failing.");
+ return 1;
+ }
+ /* the material after the name is the value */
+ vlen = strlen(value);
+ len = len - (vlen + 1);
+ value = value + vlen + 1;
+ pseudo_debug(PDBGF_XATTR, "trying to set a value for %lld: name is '%s' [%d/%d bytes], value is '%s' [%d bytes]. Existing row %lld.\n",
+ file_id, vname, (int) vlen, (int) (len + vlen + 1), value, (int) len, existing_row);
+ if (existing_row != -1) {
+ /* update */
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, update_sql, strlen(update_sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ rc = sqlite3_bind_text(update, 1, value, len, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr value to UPDATE.");
+ return 1;
+ }
+ sqlite3_bind_int(update, 2, existing_row);
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update xattr may have failed");
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ return rc != SQLITE_DONE;
+ } else {
+ /* insert */
+ if (!insert) {
+ rc = sqlite3_prepare_v2(file_db, insert_sql, strlen(insert_sql), &insert, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare INSERT statement");
+ return 1;
+ }
+ }
+ pseudo_debug(PDBGF_XATTR, "insert should be getting ID %lld\n", file_id);
+ sqlite3_bind_int64(insert, 1, file_id);
+ rc = sqlite3_bind_text(insert, 2, vname, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr name to INSERT statement");
+ return 1;
+ }
+ rc = sqlite3_bind_text(insert, 3, value, len, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr value to INSERT statement");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "insert xattr may have failed");
+ }
+ sqlite3_reset(insert);
+ sqlite3_clear_bindings(insert);
+ return rc != SQLITE_DONE;
+ }
+ return rc;
+}
+
+/* find file using only inode as key. Unused for now, planned to come
+ * in for NFS usage.
+ */
+int
+pdb_find_file_ino(pseudo_msg_t *msg, long long *row) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int64(select, 1, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ if (row) {
+ *row = sqlite3_column_int64(select, 0);
+ }
+ msg->dev = (unsigned long) sqlite3_column_int64(select, 2);
+ msg->uid = (unsigned long) sqlite3_column_int64(select, 4);
+ msg->gid = (unsigned long) sqlite3_column_int64(select, 5);
+ msg->mode = (unsigned long) sqlite3_column_int64(select, 6);
+ msg->rdev = (unsigned long) sqlite3_column_int64(select, 7);
+ msg->deleting = (int) sqlite3_column_int64(select, 8);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_ino: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_ino: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+pdb_file_list
+pdb_files(void) {
+ pdb_file_list l;
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+
+ l = malloc(sizeof(*l));
+ if (!l)
+ return NULL;
+
+ l->rc = sqlite3_prepare_v2(file_db, "SELECT path, dev, ino, uid, gid, mode, rdev FROM files", -1, &l->stmt, NULL);
+ if (l->rc) {
+ dberr(file_db, "Couldn't start SELECT from files.\n");
+ free(l);
+ return NULL;
+ }
+ return l;
+}
+
+pseudo_msg_t *
+pdb_file(pdb_file_list l) {
+ const unsigned char *s;
+ pseudo_msg_t *m;
+ int column = 0;
+
+ if (!l || !l->stmt)
+ return 0;
+ /* in case someone tries again after we're already done */
+ if (l->rc == SQLITE_DONE) {
+ return 0;
+ }
+ l->rc = sqlite3_step(l->stmt);
+ if (l->rc == SQLITE_DONE) {
+ return 0;
+ } else if (l->rc != SQLITE_ROW) {
+ dberr(log_db, "statement failed");
+ return 0;
+ }
+ s = sqlite3_column_text(l->stmt, column++);
+ m = pseudo_msg_new(0, (const char *) s);
+ if (!m) {
+ pseudo_diag("couldn't allocate file message.\n");
+ return NULL;
+ }
+ pseudo_debug(PDBGF_DB, "pdb_file: '%s'\n", s ? (const char *) s : "<nil>");
+ m->dev = sqlite3_column_int64(l->stmt, column++);
+ m->ino = sqlite3_column_int64(l->stmt, column++);
+ m->uid = sqlite3_column_int64(l->stmt, column++);
+ m->gid = sqlite3_column_int64(l->stmt, column++);
+ m->mode = sqlite3_column_int64(l->stmt, column++);
+ m->rdev = sqlite3_column_int64(l->stmt, column++);
+ return m;
+}
+
+void
+pdb_files_done(pdb_file_list l) {
+ if (!l)
+ return;
+ if (l->stmt) {
+ sqlite3_reset(l->stmt);
+ sqlite3_finalize(l->stmt);
+ }
+ free(l);
+}
diff --git a/pseudo_db.h b/pseudo_db.h
new file mode 100644
index 0000000..07c547b
--- /dev/null
+++ b/pseudo_db.h
@@ -0,0 +1,94 @@
+/*
+ * pseudo_db.h, declarations and definitions for database use
+ *
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+typedef struct {
+ time_t stamp;
+ pseudo_msg_type_t type;
+ pseudo_op_t op;
+ int access;
+ unsigned long client;
+ unsigned long fd;
+ unsigned long long dev;
+ unsigned long long ino;
+ unsigned long mode;
+ unsigned long gid;
+ unsigned long uid;
+ char *path;
+ pseudo_res_t result;
+ pseudo_sev_t severity;
+ char *text;
+ char *tag;
+ char *program;
+} log_entry;
+
+extern int pdb_maybe_backup(void);
+extern int pdb_cancel_unlink_file(pseudo_msg_t *msg);
+extern int pdb_did_unlink_file(char *path, int deleting);
+extern int pdb_did_unlink_files(int deleting);
+extern int pdb_link_file(pseudo_msg_t *msg, long long *row);
+extern int pdb_may_unlink_file(pseudo_msg_t *msg, int deleting);
+extern int pdb_unlink_file(pseudo_msg_t *msg);
+extern int pdb_unlink_file_dev(pseudo_msg_t *msg);
+extern int pdb_update_file(pseudo_msg_t *msg);
+extern int pdb_update_file_path(pseudo_msg_t *msg);
+extern int pdb_update_inode(pseudo_msg_t *msg);
+extern int pdb_unlink_contents(pseudo_msg_t *msg);
+extern int pdb_rename_file(const char *oldpath, pseudo_msg_t *msg);
+extern int pdb_renumber_all(dev_t from, dev_t to);
+extern int pdb_find_file_exact(pseudo_msg_t *msg, long long *row);
+extern int pdb_find_file_path(pseudo_msg_t *msg, long long *row);
+extern int pdb_find_file_dev(pseudo_msg_t *msg, long long *row, char **path);
+extern int pdb_find_file_ino(pseudo_msg_t *msg, long long *row);
+extern char *pdb_get_file_path(pseudo_msg_t *msg);
+extern int pdb_get_xattr(long long file_id, char **value, size_t *len);
+extern int pdb_list_xattr(long long file_id, char **value, size_t *len);
+extern int pdb_remove_xattr(long long file_id, char *value, size_t len);
+extern int pdb_set_xattr(long long file_id, char *value, size_t len, int flags);
+
+struct log_history;
+typedef struct log_history *log_history;
+
+union pseudo_query_data {
+ unsigned long long ivalue;
+ char *svalue;
+};
+
+typedef struct pseudo_query {
+ pseudo_query_type_t type;
+ pseudo_query_field_t field;
+ union pseudo_query_data data;
+ struct pseudo_query *next;
+} pseudo_query_t;
+
+extern int pdb_log_entry(log_entry *e);
+extern int pdb_log_msg(pseudo_sev_t severity, pseudo_msg_t *msg, const char *program, const char *tag, const char *text, ...);
+extern int pdb_log_traits(pseudo_query_t *traits);
+
+struct pdb_file_list;
+typedef struct pdb_file_list *pdb_file_list;
+
+extern pdb_file_list pdb_files(void);
+extern pseudo_msg_t *pdb_file(pdb_file_list);
+extern void pdb_files_done(pdb_file_list);
+
+extern int pdb_delete(pseudo_query_t *traits, unsigned long fields);
+extern log_history pdb_history(pseudo_query_t *traits, unsigned long fields, int unique);
+extern log_entry *pdb_history_entry(log_history h);
+extern void pdb_history_free(log_history h);
+extern void log_entry_free(log_entry *);
diff --git a/pseudo_ipc.c b/pseudo_ipc.c
new file mode 100644
index 0000000..82c6f70
--- /dev/null
+++ b/pseudo_ipc.c
@@ -0,0 +1,243 @@
+/*
+ * pseudo_ipc.c, IPC code for pseudo client/server
+ *
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+
+/* Short reads or writes can cause a sigpipe, killing the program, so we
+ * trap it and report that something happened.
+ */
+static sig_atomic_t pipe_error = 0;
+static void (*old_handler)(int) = SIG_DFL;
+
+static void
+sigpipe_trap(int unused __attribute__((unused))) {
+ pipe_error = 1;
+}
+
+static void
+ignore_sigpipe(void) {
+ pipe_error = 0;
+ old_handler = signal(SIGPIPE, sigpipe_trap);
+}
+
+static void
+allow_sigpipe(void) {
+ signal(SIGPIPE, old_handler);
+}
+
+#if 0
+/* useful only when debugging crazy stuff */
+static void
+display_msg_header(pseudo_msg_t *msg) {
+ pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "type: %d\n", msg->type);
+ pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "inode: %llu\n", (unsigned long long) msg->ino);
+ pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "uid: %d\n", msg->uid);
+ pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "pathlen: %d\n", (int) msg->pathlen);
+ if (msg->pathlen) {
+ pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "path: %s\n", msg->path);
+ }
+}
+#endif
+
+/*
+ * send message on fd
+ * return:
+ * 0 on success
+ * >0 on error
+ * <0 on error suggesting other end is dead
+ */
+int
+pseudo_msg_send(int fd, pseudo_msg_t *msg, size_t len, const char *path) {
+ int r;
+
+ if (!msg)
+ return 1;
+
+ if (fd < 0)
+ return -1;
+
+ if (path) {
+ pseudo_debug(PDBGF_IPC, "msg type %d (%s), external path %s, mode 0%o\n",
+ msg->type, pseudo_op_name(msg->op), path, (int) msg->mode);
+ if (len == (size_t) -1)
+ len = strlen(path) + 1;
+ msg->pathlen = len;
+ ignore_sigpipe();
+ r = write(fd, msg, PSEUDO_HEADER_SIZE);
+ if (r == PSEUDO_HEADER_SIZE) {
+ r += write(fd, path, len);
+ }
+ allow_sigpipe();
+ pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "wrote %d bytes\n", r);
+ if (pipe_error || (r == -1 && errno == EBADF))
+ return -1;
+ return ((size_t) r != PSEUDO_HEADER_SIZE + len);
+ } else {
+ pseudo_debug(PDBGF_IPC, "msg type %d (%s), result %d (%s), path %.*s, mode 0%o\n",
+ msg->type, pseudo_op_name(msg->op),
+ msg->result, pseudo_res_name(msg->result),
+ msg->pathlen, msg->path, (int) msg->mode);
+ // display_msg_header(msg);
+ ignore_sigpipe();
+ r = write(fd, msg, PSEUDO_HEADER_SIZE + msg->pathlen);
+ allow_sigpipe();
+ pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "wrote %d bytes\n", r);
+ if (pipe_error || (r == -1 && errno == EBADF))
+ return -1;
+ return ((size_t) r != PSEUDO_HEADER_SIZE + msg->pathlen);
+ }
+}
+
+/* attempts to receive a message from fd
+ * return is allocated message if one is provided
+ */
+pseudo_msg_t *
+pseudo_msg_receive(int fd) {
+ static pseudo_msg_t *incoming;
+ static size_t incoming_pathlen;
+ pseudo_msg_t *newmsg, header;
+ int r;
+
+ if (fd < 0)
+ return 0;
+ errno = 0;
+ r = read(fd, &header, PSEUDO_HEADER_SIZE);
+ if (r == -1) {
+ pseudo_debug(PDBGF_IPC, "read failed: %s\n", strerror(errno));
+ return 0;
+ }
+ if (r < (int) PSEUDO_HEADER_SIZE) {
+ pseudo_debug(PDBGF_IPC, "got only %d bytes (%s)\n", r, strerror(errno));
+ return 0;
+ }
+ pseudo_debug(PDBGF_IPC, "got header, type %d, pathlen %d\n", header.type, (int) header.pathlen);
+ // display_msg_header(&header);
+ if (!incoming || header.pathlen >= incoming_pathlen) {
+ newmsg = pseudo_msg_new(header.pathlen + 128, 0);
+ if (!newmsg) {
+ pseudo_diag("Couldn't allocate header for path of %d bytes.\n",
+ (int) header.pathlen);
+ return 0;
+ }
+ free(incoming);
+ incoming = newmsg;
+ incoming_pathlen = header.pathlen + 128;
+ }
+ *incoming = header;
+ if (incoming->pathlen) {
+ r = read(fd, incoming->path, incoming->pathlen);
+ if (r < (int) incoming->pathlen) {
+ pseudo_debug(PDBGF_IPC, "short read on path, expecting %d, got %d\n",
+ (int) incoming->pathlen, r);
+ return 0;
+ }
+ /* ensure null termination */
+ incoming->path[r] = '\0';
+ }
+ // display_msg_header(incoming);
+ return incoming;
+}
+
+/* duplicate a message -- currently totally unused */
+pseudo_msg_t *
+pseudo_msg_dup(pseudo_msg_t *old) {
+ pseudo_msg_t *newmsg;
+ if (!old)
+ return NULL;
+ newmsg = malloc(sizeof(pseudo_msg_t) + old->pathlen);
+ if (!newmsg)
+ return NULL;
+ memcpy(newmsg, old, sizeof(pseudo_msg_t) + old->pathlen);
+ return newmsg;
+}
+
+/* allocate a message either with pathlen chars of storage or with enough
+ * storage for path
+ */
+pseudo_msg_t *
+pseudo_msg_new(size_t pathlen, const char *path) {
+ pseudo_msg_t *newmsg;
+ if (pathlen) {
+ newmsg = malloc(sizeof(pseudo_msg_t) + pathlen);
+ if (newmsg) {
+ newmsg->pathlen = pathlen;
+ if (path)
+ memcpy(newmsg->path, path, pathlen);
+ newmsg->path[pathlen - 1] = '\0';
+ }
+ return newmsg;
+ } else {
+ if (!path) {
+ /* no pathlen, no path == purely informational */
+ newmsg = malloc(sizeof(pseudo_msg_t));
+ if (newmsg) {
+ newmsg->pathlen = 0;
+ }
+ return newmsg;
+ } else {
+ pathlen = strlen(path) + 1;
+ newmsg = malloc(sizeof(pseudo_msg_t) + pathlen);
+ if (newmsg) {
+ memcpy(newmsg->path, path, pathlen);
+ newmsg->pathlen = pathlen;
+ }
+ return newmsg;
+ }
+ }
+}
+
+/* The following functions populate messages from statbufs and vice versa.
+ * It is intentional that copying a message into a stat doesn't touch nlink;
+ * the nlink value was not stored in the database (it is in the message at
+ * all only so the server can do sanity checks).
+ */
+
+void
+pseudo_msg_stat(pseudo_msg_t *msg, const PSEUDO_STATBUF *buf) {
+ if (!msg || !buf)
+ return;
+ msg->uid = buf->st_uid;
+ msg->gid = buf->st_gid;
+ msg->dev = buf->st_dev;
+ msg->ino = buf->st_ino;
+ msg->mode = buf->st_mode;
+ msg->rdev = buf->st_rdev;
+ msg->nlink = buf->st_nlink;
+}
+
+void
+pseudo_stat_msg(PSEUDO_STATBUF *buf, const pseudo_msg_t *msg) {
+ if (!msg || !buf)
+ return;
+ buf->st_uid = msg->uid;
+ buf->st_gid = msg->gid;
+ buf->st_mode = msg->mode;
+ buf->st_rdev = msg->rdev;
+}
diff --git a/pseudo_ipc.h b/pseudo_ipc.h
new file mode 100644
index 0000000..46277bc
--- /dev/null
+++ b/pseudo_ipc.h
@@ -0,0 +1,68 @@
+/*
+ * pseudo_ipc.h, definitions and declarations for pseudo IPC code
+ *
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/* The [] item at the end of the struct is a C99 feature, replacing the
+ * old (and unportable) "struct hack".
+ */
+typedef struct {
+ pseudo_msg_type_t type;
+ pseudo_op_t op;
+ pseudo_res_t result;
+ int access;
+ int client;
+ int fd;
+ dev_t dev;
+ unsigned long long ino;
+ uid_t uid;
+ gid_t gid;
+ unsigned long long mode;
+ dev_t rdev;
+ unsigned int pathlen;
+ int nlink;
+ int deleting;
+ char path[];
+} pseudo_msg_t;
+
+enum {
+ PSA_EXEC = 1,
+ PSA_WRITE = (PSA_EXEC << 1),
+ PSA_READ = (PSA_WRITE << 1),
+ PSA_APPEND = (PSA_READ << 1),
+} pseudo_access_t;
+
+#define PSEUDO_ACCESS_MAP(mode, fcntl_access, pseudo_access) ((((mode) & O_ACCMODE) == (fcntl_access)) ? (pseudo_access) : (0))
+#define PSEUDO_ACCESS_FLAG(mode, fcntl_access, pseudo_access) (((mode) & (fcntl_access)) ? (pseudo_access) : (0))
+#define PSEUDO_ACCESS(mode) ( \
+ PSEUDO_ACCESS_MAP(mode, O_RDONLY, PSA_READ) | \
+ PSEUDO_ACCESS_MAP(mode, O_WRONLY, PSA_WRITE) | \
+ PSEUDO_ACCESS_MAP(mode, O_RDWR, PSA_READ | PSA_WRITE) | \
+ PSEUDO_ACCESS_FLAG(mode, O_APPEND, PSA_APPEND))
+extern int pseudo_access_fopen(const char *);
+
+#define PSEUDO_HEADER_SIZE (offsetof(pseudo_msg_t, path))
+
+extern pseudo_msg_t *pseudo_msg_receive(int fd);
+extern pseudo_msg_t *pseudo_msg_dup(pseudo_msg_t *);
+extern pseudo_msg_t *pseudo_msg_dupheader(pseudo_msg_t *);
+extern pseudo_msg_t *pseudo_msg_new(size_t, const char *);
+extern int pseudo_msg_send(int fd, pseudo_msg_t *, size_t, const char *);
+
+void pseudo_msg_stat(pseudo_msg_t *msg, const PSEUDO_STATBUF *buf);
+void pseudo_stat_msg(PSEUDO_STATBUF *buf, const pseudo_msg_t *msg);
diff --git a/pseudo_profile.c b/pseudo_profile.c
new file mode 100644
index 0000000..c4af803
--- /dev/null
+++ b/pseudo_profile.c
@@ -0,0 +1,64 @@
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "pseudo.h"
+
+int
+main(int argc, char **argv) {
+ int fd;
+ pseudo_profile_t totals = { .total_ops = 0 }, item;
+ int count = 0;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: pseudo_profile <profiling_data>\n");
+ exit(1);
+ }
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Can't open '%s': %s.\n",
+ argv[1], strerror(errno));
+ exit(1);
+ }
+ while (read(fd, &item, sizeof(item)) == sizeof(item)) {
+ if (item.total_ops > 0) {
+ ++count;
+ totals.processes += item.processes;
+ totals.total_ops += item.total_ops;
+ totals.messages += item.messages;
+ totals.op_time.tv_sec += item.op_time.tv_sec;
+ totals.op_time.tv_usec += item.op_time.tv_usec;
+ if (totals.op_time.tv_usec >= 1000000) {
+ ++totals.op_time.tv_sec;
+ totals.op_time.tv_usec -= 1000000;
+ }
+ totals.ipc_time.tv_sec += item.ipc_time.tv_sec;
+ totals.ipc_time.tv_usec += item.ipc_time.tv_usec;
+ if (totals.ipc_time.tv_usec >= 1000000) {
+ ++totals.ipc_time.tv_sec;
+ totals.ipc_time.tv_usec -= 1000000;
+ }
+ totals.wrapper_time.tv_sec += item.wrapper_time.tv_sec;
+ totals.wrapper_time.tv_usec += item.wrapper_time.tv_usec;
+ if (totals.wrapper_time.tv_usec >= 1000000) {
+ ++totals.wrapper_time.tv_sec;
+ totals.wrapper_time.tv_usec -= 1000000;
+ }
+ }
+ }
+ printf("Found data for %d PIDs, %d processes.\n",
+ count, totals.processes);
+ printf("%lld messages for %lld ops.\n", totals.messages, totals.total_ops);
+ double otime = totals.op_time.tv_sec + (totals.op_time.tv_usec / 1000000.0);
+ double itime = totals.ipc_time.tv_sec + (totals.ipc_time.tv_usec / 1000000.0);
+ double wtime = totals.wrapper_time.tv_sec + (totals.wrapper_time.tv_usec / 1000000.0);
+ printf("%.4f msec wrapper time, %.4f msec op time, %.4f msec IPC time.\n",
+ wtime * 1000, otime * 1000, itime * 1000);
+
+ return 0;
+}
diff --git a/pseudo_server.c b/pseudo_server.c
new file mode 100644
index 0000000..1fdadcb
--- /dev/null
+++ b/pseudo_server.c
@@ -0,0 +1,529 @@
+/*
+ * pseudo_server.c, pseudo's server-side logic and message handling
+ *
+ * Copyright (c) 2008-2010, 2013 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_server.h"
+#include "pseudo_client.h"
+#include "pseudo_db.h"
+
+static int listen_fd = -1;
+
+typedef struct {
+ int fd;
+ pid_t pid;
+ char *tag;
+ char *program;
+} pseudo_client_t;
+
+pseudo_client_t *clients;
+
+/* active_clients: Number of clients we actually have right now.
+ * highest_client: Highest index into clients table of an active client.
+ * max_clients: Size of table.
+ */
+static int active_clients = 0, highest_client = 0, max_clients = 0;
+
+#define LOOP_DELAY 2
+int pseudo_server_timeout = 30;
+static int die_peacefully = 0;
+static int die_forcefully = 0;
+
+/* when the client is linked with pseudo_wrappers, these are defined there.
+ * when it is linked with pseudo_server, though, we have to provide different
+ * versions (pseudo_wrappers must not be linked with the server, or Bad Things
+ * happen).
+ */
+void pseudo_magic(void) { }
+void pseudo_antimagic(void) { }
+
+void
+quit_now(int signal) {
+ pseudo_diag("Received signal %d, quitting.\n", signal);
+ die_forcefully = 1;
+}
+
+static int messages = 0, responses = 0;
+static struct timeval message_time = { .tv_sec = 0 };
+
+static void pseudo_server_loop(void);
+
+static int
+pseudo_server_write_pid(pid_t pid) {
+ char *pseudo_path;
+ FILE *fp;
+
+ pseudo_path = pseudo_localstatedir_path(PSEUDO_PIDFILE);
+ if (!pseudo_path) {
+ pseudo_diag("Couldn't get path for prefix/%s\n", PSEUDO_PIDFILE);
+ return 1;
+ }
+ fp = fopen(pseudo_path, "w");
+ if (!fp) {
+ pseudo_diag("Couldn't open %s: %s\n",
+ pseudo_path, strerror(errno));
+ return 1;
+ }
+ fprintf(fp, "%lld\n", (long long) pid);
+ fclose(fp);
+ free(pseudo_path);
+ return 0;
+}
+
+int
+pseudo_server_start(int daemonize) {
+ struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET };
+ char *pseudo_path;
+ int rc, newfd;
+
+#if PSEUDO_PORT_DARWIN
+ sun.sun_len = strlen(PSEUDO_SOCKET) + 1;
+#endif
+
+ listen_fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (listen_fd < 0) {
+ pseudo_diag("couldn't create listening socket: %s\n", strerror(errno));
+ return 1;
+ }
+
+ if (listen_fd <= 2) {
+ newfd = fcntl(listen_fd, F_DUPFD, 3);
+ if (newfd < 0) {
+ pseudo_diag("couldn't dup listening socket: %s\n", strerror(errno));
+ close(listen_fd);
+ return 1;
+ } else {
+ close(listen_fd);
+ listen_fd = newfd;
+ }
+ }
+
+ /* cd to the data directory */
+ pseudo_path = pseudo_localstatedir_path(NULL);
+ if (!pseudo_path || chdir(pseudo_path) == -1) {
+ pseudo_diag("can't get to '%s': %s\n",
+ pseudo_path, strerror(errno));
+ return 1;
+ }
+ free(pseudo_path);
+ /* remove existing socket -- if it exists */
+ unlink(sun.sun_path);
+ if (bind(listen_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) {
+ pseudo_diag("couldn't bind listening socket: %s\n", strerror(errno));
+ return 1;
+ }
+ if (listen(listen_fd, 5) == -1) {
+ pseudo_diag("couldn't listen on socket: %s\n", strerror(errno));
+ return 1;
+ }
+ if (daemonize) {
+ if ((rc = fork()) != 0) {
+ if (rc == -1) {
+ pseudo_diag("couldn't spawn server: %s\n", strerror(errno));
+ return 0;
+ }
+ pseudo_debug(PDBGF_SERVER, "started server, pid %d\n", rc);
+ close(listen_fd);
+ /* Parent writes pid, that way it's always correct */
+ return pseudo_server_write_pid(rc);
+ }
+ /* In child */
+ pseudo_new_pid();
+ fclose(stdin);
+ fclose(stdout);
+ pseudo_logfile(PSEUDO_LOGFILE);
+ } else {
+ /* Write the pid if we don't daemonize */
+ pseudo_server_write_pid(getpid());
+ }
+
+ setsid();
+ signal(SIGHUP, quit_now);
+ signal(SIGINT, quit_now);
+ signal(SIGALRM, quit_now);
+ signal(SIGQUIT, quit_now);
+ signal(SIGTERM, quit_now);
+ pseudo_server_loop();
+ return 0;
+}
+
+/* mess with internal tables as needed */
+static void
+open_client(int fd) {
+ pseudo_client_t *new_clients;
+ int i;
+
+ /* if possible, use first open client slot */
+ for (i = 0; i < max_clients; ++i) {
+ if (clients[i].fd == -1) {
+ pseudo_debug(PDBGF_SERVER, "reusing client %d for fd %d\n", i, fd);
+ clients[i].fd = fd;
+ clients[i].pid = 0;
+ clients[i].tag = NULL;
+ clients[i].program = NULL;
+ ++active_clients;
+ if (i > highest_client)
+ highest_client = i;
+ return;
+ }
+ }
+
+ /* otherwise, allocate a new one */
+ new_clients = malloc(sizeof(*new_clients) * (max_clients + 16));
+ if (new_clients) {
+ memcpy(new_clients, clients, max_clients * sizeof(*clients));
+ free(clients);
+ for (i = max_clients; i < max_clients + 16; ++i) {
+ new_clients[i].fd = -1;
+ new_clients[i].pid = 0;
+ new_clients[i].tag = NULL;
+ new_clients[i].program = NULL;
+ }
+ clients = new_clients;
+ clients[max_clients].fd = fd;
+ clients[max_clients].pid = 0;
+ clients[max_clients].tag = NULL;
+ clients[max_clients].program = NULL;
+ highest_client = max_clients + 1;
+
+ max_clients += 16;
+ ++active_clients;
+ } else {
+ pseudo_diag("error allocating new client, fd %d\n", fd);
+ close(fd);
+ }
+}
+
+/* clear pid/fd. If this was the highest client, iterate downwards looking
+ * for a lower one to be the new highest client.
+ */
+static void
+close_client(int client) {
+ pseudo_debug(PDBGF_SERVER, "lost client %d [%d], closing fd %d\n", client,
+ clients[client].pid, clients[client].fd);
+ /* client went away... */
+ if (client > highest_client || client <= 0) {
+ pseudo_diag("tried to close client %d (highest is %d)\n",
+ client, highest_client);
+ return;
+ }
+ close(clients[client].fd);
+ clients[client].fd = -1;
+ free(clients[client].tag);
+ free(clients[client].program);
+ clients[client].pid = 0;
+ clients[client].tag = NULL;
+ clients[client].program = NULL;
+ --active_clients;
+ if (client == highest_client)
+ while (clients[highest_client].fd != -1 && highest_client > 0)
+ --highest_client;
+}
+
+/* Actually process a request.
+ */
+static int
+serve_client(int i) {
+ pseudo_msg_t *in;
+ int rc;
+
+ pseudo_debug(PDBGF_SERVER, "message from client %d [%d:%s - %s] fd %d\n",
+ i, (int) clients[i].pid,
+ clients[i].program ? clients[i].program : "???",
+ clients[i].tag ? clients[i].tag : "NO TAG",
+ clients[i].fd);
+ in = pseudo_msg_receive(clients[i].fd);
+ if (in) {
+ char *response_path = 0;
+ size_t response_pathlen;
+ int send_response = 1;
+ pseudo_debug(PDBGF_SERVER | PDBGF_VERBOSE, "got a message (%d): %s\n", in->type, (in->pathlen ? in->path : "<no path>"));
+ /* handle incoming ping */
+ if (in->type == PSEUDO_MSG_PING && !clients[i].pid) {
+ pseudo_debug(PDBGF_SERVER, "new client: %d -> %d",
+ i, in->client);
+ clients[i].pid = in->client;
+ if (in->pathlen) {
+ size_t proglen;
+ proglen = strlen(in->path);
+
+ pseudo_debug(PDBGF_SERVER, " <%s>", in->path);
+ free(clients[i].program);
+ clients[i].program = malloc(proglen + 1);
+ if (clients[i].program) {
+ snprintf(clients[i].program, proglen + 1, "%s", in->path);
+ }
+ if (in->pathlen > proglen) {
+ pseudo_debug(PDBGF_SERVER, " [%s]", in->path + proglen + 1);
+ clients[i].tag = malloc(in->pathlen - proglen);
+ if (clients[i].tag)
+ snprintf(clients[i].tag, in->pathlen - proglen,
+ "%s", in->path + proglen + 1);
+ }
+ }
+ pseudo_debug(PDBGF_SERVER, "\n");
+ }
+ /* sanity-check client ID */
+ if (in->client != clients[i].pid) {
+ pseudo_debug(PDBGF_SERVER, "uh-oh, expected pid %d for client %d, got %d\n",
+ (int) clients[i].pid, i, in->client);
+ }
+ /* regular requests are processed in place by
+ * pseudo_server_response.
+ */
+ if (in->type != PSEUDO_MSG_SHUTDOWN) {
+ if (in->type == PSEUDO_MSG_FASTOP)
+ send_response = 0;
+ /* most messages don't need these, but xattr may */
+ response_path = 0;
+ response_pathlen = -1;
+ if (pseudo_server_response(in, clients[i].program, clients[i].tag, &response_path, &response_pathlen)) {
+ in->type = PSEUDO_MSG_NAK;
+ } else {
+ in->type = PSEUDO_MSG_ACK;
+ pseudo_debug(PDBGF_SERVER | PDBGF_VERBOSE, "response: %d (%s)\n",
+ in->result, pseudo_res_name(in->result));
+ }
+ in->client = i;
+ if (response_path) {
+ in->pathlen = response_pathlen;
+ } else {
+ in->pathlen = 0;
+ }
+ } else {
+ /* the server's listen fd is "a client", and
+ * so is the program connecting to request a shutdown.
+ * it should never be less than 2, but crazy things
+ * happen. >2 implies some other active client,
+ * though.
+ */
+ if (active_clients > 2) {
+ int j;
+ char *s;
+
+ response_path = malloc(8 * active_clients);
+ in->type = PSEUDO_MSG_NAK;
+ in->fd = active_clients - 2;
+ s = response_path;
+ for (j = 1; j <= highest_client; ++j) {
+ if (clients[j].fd != -1 && j != i) {
+ s += snprintf(s, 8, "%d ", (int) clients[j].pid);
+ }
+ }
+ in->pathlen = (s - response_path) + 1;
+ /* exit quickly once clients go away, though */
+ pseudo_server_timeout = 1;
+ } else {
+ in->type = PSEUDO_MSG_ACK;
+ in->pathlen = 0;
+ in->client = i;
+ die_peacefully = 1;
+ }
+ }
+ if (send_response) {
+ if ((rc = pseudo_msg_send(clients[i].fd, in, in->pathlen, response_path)) != 0) {
+ pseudo_debug(PDBGF_SERVER, "failed to send response to client %d [%d]: %d (%s)\n",
+ i, (int) clients[i].pid, rc, strerror(errno));
+ }
+ } else {
+ rc = 1;
+ }
+ free(response_path);
+ return rc;
+ } else {
+ /* this should not be happening, but the exceptions aren't
+ * being detected in select() for some reason.
+ */
+ pseudo_debug(PDBGF_SERVER, "client %d: no message\n", (int) clients[i].pid);
+ close_client(i);
+ return 0;
+ }
+}
+
+/* get clients, handle messages, shut down.
+ * This doesn't actually do any work, it just calls a ton of things which
+ * do work.
+ */
+static void
+pseudo_server_loop(void) {
+ struct sockaddr_un client;
+ socklen_t len;
+ fd_set reads, writes, events;
+ int max_fd, current_clients;
+ struct timeval timeout;
+ int i;
+ int rc;
+ int fd;
+ int loop_timeout = pseudo_server_timeout;
+
+ clients = malloc(16 * sizeof(*clients));
+
+ clients[0].fd = listen_fd;
+ clients[0].pid = getpid();
+
+ for (i = 1; i < 16; ++i) {
+ clients[i].fd = -1;
+ clients[i].pid = 0;
+ clients[i].tag = NULL;
+ clients[i].program = NULL;
+ }
+
+ active_clients = 1;
+ max_clients = 16;
+ highest_client = 0;
+
+ pseudo_debug(PDBGF_SERVER, "server loop started.\n");
+ if (listen_fd < 0) {
+ pseudo_diag("got into loop with no valid listen fd.\n");
+ exit(1);
+ }
+ pdb_log_msg(SEVERITY_INFO, NULL, NULL, NULL, "server started (pid %d)", getpid());
+
+ FD_ZERO(&reads);
+ FD_ZERO(&writes);
+ FD_ZERO(&events);
+ FD_SET(clients[0].fd, &reads);
+ FD_SET(clients[0].fd, &events);
+ max_fd = clients[0].fd;
+ timeout = (struct timeval) { .tv_sec = LOOP_DELAY, .tv_usec = 0 };
+
+ /* EINTR tends to come from profiling, so it is not a good reason to
+ * exit; other signals are caught and set the flag causing a graceful
+ * exit. */
+ while ((rc = select(max_fd + 1, &reads, &writes, &events, &timeout)) >= 0 || (errno == EINTR)) {
+ if (rc == 0 || (rc == -1 && errno == EINTR)) {
+ /* If there's no clients, start timing out. If there
+ * are active clients, never time out.
+ */
+ if (active_clients == 1) {
+ loop_timeout -= LOOP_DELAY;
+ /* maybe flush database to disk */
+ pdb_maybe_backup();
+ if (loop_timeout <= 0) {
+ pseudo_debug(PDBGF_SERVER, "no more clients, got bored.\n");
+ die_peacefully = 1;
+ } else {
+ /* display this if not exiting */
+ pseudo_debug(PDBGF_SERVER | PDBGF_BENCHMARK, "%d messages handled in %.4f seconds, %d responses\n",
+ messages,
+ (double) message_time.tv_sec +
+ (double) message_time.tv_usec / 1000000.0,
+ responses);
+ }
+ }
+ } else if (rc > 0) {
+ loop_timeout = pseudo_server_timeout;
+ for (i = 1; i <= highest_client; ++i) {
+ if (clients[i].fd == -1)
+ continue;
+ if (FD_ISSET(clients[i].fd, &events)) {
+ /* this should happen but doesn't... */
+ close_client(i);
+ } else if (FD_ISSET(clients[i].fd, &reads)) {
+ struct timeval tv1, tv2;
+ int rc;
+ gettimeofday(&tv1, NULL);
+ rc = serve_client(i);
+ gettimeofday(&tv2, NULL);
+ ++messages;
+ if (rc == 0)
+ ++responses;
+ message_time.tv_sec += (tv2.tv_sec - tv1.tv_sec);
+ message_time.tv_usec += (tv2.tv_usec - tv1.tv_usec);
+ if (message_time.tv_usec < 0) {
+ message_time.tv_usec += 1000000;
+ --message_time.tv_sec;
+ } else while (message_time.tv_usec > 1000000) {
+ message_time.tv_usec -= 1000000;
+ ++message_time.tv_sec;
+ }
+ }
+ if (die_forcefully)
+ break;
+ }
+ if (!(die_peacefully || die_forcefully) &&
+ (FD_ISSET(clients[0].fd, &events) ||
+ FD_ISSET(clients[0].fd, &reads))) {
+ len = sizeof(client);
+ if ((fd = accept(listen_fd, (struct sockaddr *) &client, &len)) != -1) {
+ pseudo_debug(PDBGF_SERVER, "new client fd %d\n", fd);
+ open_client(fd);
+ }
+ }
+ pseudo_debug(PDBGF_SERVER, "server loop complete [%d clients left]\n", active_clients);
+ }
+ if (die_peacefully || die_forcefully) {
+ pseudo_debug(PDBGF_SERVER, "quitting.\n");
+ pseudo_debug(PDBGF_SERVER | PDBGF_BENCHMARK, "server %d exiting: handled %d messages in %.4f seconds\n",
+ getpid(), messages,
+ (double) message_time.tv_sec +
+ (double) message_time.tv_usec / 1000000.0);
+ pdb_log_msg(SEVERITY_INFO, NULL, NULL, NULL, "server %d exiting: handled %d messages in %.4f seconds",
+ getpid(), messages,
+ (double) message_time.tv_sec +
+ (double) message_time.tv_usec / 1000000.0);
+ close(clients[0].fd);
+ exit(0);
+ }
+ FD_ZERO(&reads);
+ FD_ZERO(&writes);
+ FD_ZERO(&events);
+ FD_SET(clients[0].fd, &reads);
+ FD_SET(clients[0].fd, &events);
+ max_fd = clients[0].fd;
+ /* current_clients is a sanity check; note that for
+ * purposes of select(), the server is one of the fds,
+ * and thus, "a client".
+ */
+ current_clients = 1;
+ for (i = 1; i <= highest_client; ++i) {
+ if (clients[i].fd != -1) {
+ ++current_clients;
+ FD_SET(clients[i].fd, &reads);
+ FD_SET(clients[i].fd, &events);
+ if (clients[i].fd > max_fd)
+ max_fd = clients[i].fd;
+ }
+ }
+ if (current_clients != active_clients) {
+ pseudo_debug(PDBGF_SERVER, "miscount of current clients (%d) against active_clients (%d)?\n",
+ current_clients, active_clients);
+ }
+ /* reinitialize timeout because Linux select alters it */
+ timeout = (struct timeval) { .tv_sec = LOOP_DELAY, .tv_usec = 0 };
+ }
+ pseudo_diag("select failed: %s\n", strerror(errno));
+}
diff --git a/pseudo_server.h b/pseudo_server.h
new file mode 100644
index 0000000..06598e7
--- /dev/null
+++ b/pseudo_server.h
@@ -0,0 +1,23 @@
+/*
+ * pseudo_server.h, pseudo server declarations and definitions
+ *
+ * Copyright (c) 2008-2009 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+extern int pseudo_server_start(int);
+extern int pseudo_server_response(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len);
+extern int pseudo_server_timeout;
+extern int opt_l;
diff --git a/pseudo_util.c b/pseudo_util.c
new file mode 100644
index 0000000..c81df5a
--- /dev/null
+++ b/pseudo_util.c
@@ -0,0 +1,1495 @@
+/*
+ * pseudo_util.c, miscellaneous utility functions
+ *
+ * Copyright (c) 2008-2013 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/* we need access to RTLD_NEXT for a horrible workaround */
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <regex.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* see the comments below about (*real_regcomp)() */
+#include <dlfcn.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_db.h"
+
+struct pseudo_variables {
+ char *key;
+ size_t key_len;
+ char *value;
+};
+
+/* The order below is not arbitrary, but based on an assumption
+ * of how often things will be used.
+ */
+static struct pseudo_variables pseudo_env[] = {
+ { "PSEUDO_PREFIX", 13, NULL },
+ { "PSEUDO_BINDIR", 13, NULL },
+ { "PSEUDO_LIBDIR", 13, NULL },
+ { "PSEUDO_LOCALSTATEDIR", 20, NULL },
+ { "PSEUDO_PASSWD", 13, NULL },
+ { "PSEUDO_CHROOT", 13, NULL },
+ { "PSEUDO_UIDS", 11, NULL },
+ { "PSEUDO_GIDS", 11, NULL },
+ { "PSEUDO_OPTS", 11, NULL },
+ { "PSEUDO_DEBUG", 12, NULL },
+ { "PSEUDO_DEBUG_FILE", 17, NULL },
+ { "PSEUDO_TAG", 10, NULL },
+ { "PSEUDO_ENOSYS_ABORT", 19, NULL },
+ { "PSEUDO_NOSYMLINKEXP", 19, NULL },
+ { "PSEUDO_DISABLED", 15, NULL },
+ { "PSEUDO_UNLOAD", 13, NULL },
+ { "PSEUDO_ALLOW_FSYNC", 18, NULL },
+#ifdef PSEUDO_PROFILING
+ { "PSEUDO_PROFILE_PATH", 19, NULL },
+#endif
+ { NULL, 0, NULL } /* Magic terminator */
+};
+
+/* -1 - init hasn't been run yet
+ * 0 - init has been run
+ * 1 - init is running
+ *
+ * There are cases where the constructor is run AFTER the
+ * program starts playing with things, so we need to do our
+ * best to handle that case.
+ */
+static int pseudo_util_initted = -1; /* Not yet run */
+
+/* bypass wrapper logic on path computations */
+int (*pseudo_real_lstat)(const char *path, PSEUDO_STATBUF *buf) = NULL;
+
+#if 0
+static void
+dump_env(char **envp) {
+ size_t i = 0;
+ for (i = 0; envp[i]; i++) {
+ pseudo_debug(PDBGF_ENV, "dump_envp: [%d]%s\n", (int) i, envp[i]);
+ }
+
+ for (i = 0; pseudo_env[i].key; i++) {
+ pseudo_debug(PDBGF_ENV, "dump_envp: {%d}%s=%s\n", (int) i, pseudo_env[i].key, pseudo_env[i].value);
+ }
+
+ pseudo_debug(PDBGF_ENV, "dump_envp: _in_init %d\n", pseudo_util_initted);
+}
+#endif
+
+int
+pseudo_has_unload(char * const *envp) {
+ static const char unload[] = "PSEUDO_UNLOAD";
+ static size_t unload_len = sizeof(unload) - 1;
+ size_t i = 0;
+
+ /* Is it in the caller environment? */
+ if (NULL != getenv(unload))
+ return 1;
+
+ /* Is it in the environment cache? */
+ if (pseudo_util_initted == -1)
+ pseudo_init_util();
+ while (pseudo_env[i].key && strcmp(pseudo_env[i].key, unload))
+ ++i;
+ if (pseudo_env[i].key && pseudo_env[i].value)
+ return 1;
+
+ /* Is it in the operational environment? */
+ while (envp && *envp) {
+ if ((!strncmp(*envp, unload, unload_len)) && ('=' == (*envp)[unload_len]))
+ return 1;
+ ++envp;
+ }
+ return 0;
+}
+
+/* Caller must free memory! */
+char *
+pseudo_get_value(const char *key) {
+ size_t i = 0;
+ char * value;
+
+ if (pseudo_util_initted == -1)
+ pseudo_init_util();
+
+ for (i = 0; pseudo_env[i].key && memcmp(pseudo_env[i].key, key, pseudo_env[i].key_len + 1); i++)
+ ;
+
+ /* Check if the environment has it and we don't ...
+ * if so, something went wrong... so we'll attempt to recover
+ */
+ if (pseudo_env[i].key && !pseudo_env[i].value && getenv(pseudo_env[i].key))
+ pseudo_init_util();
+
+ if (pseudo_env[i].value)
+ value = strdup(pseudo_env[i].value);
+ else
+ value = NULL;
+
+ if (!pseudo_env[i].key)
+ pseudo_diag("Unknown variable %s.\n", key);
+
+ return value;
+}
+
+/* We make a copy, so the original values should be freed. */
+int
+pseudo_set_value(const char *key, const char *value) {
+ int rc = 0;
+ size_t i = 0;
+
+ if (pseudo_util_initted == -1)
+ pseudo_init_util();
+
+ for (i = 0; pseudo_env[i].key && memcmp(pseudo_env[i].key, key, pseudo_env[i].key_len + 1); i++)
+ ;
+
+ if (pseudo_env[i].key) {
+ if (pseudo_env[i].value)
+ free(pseudo_env[i].value);
+ if (value) {
+ char *new = strdup(value);
+ if (new)
+ pseudo_env[i].value = new;
+ else
+ pseudo_diag("warning: failed to save new value (%s) for key %s\n",
+ value, key);
+ } else
+ pseudo_env[i].value = NULL;
+ } else {
+ if (!pseudo_util_initted) pseudo_diag("Unknown variable %s.\n", key);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+void
+pseudo_init_util(void) {
+ size_t i = 0;
+ char * env;
+
+ pseudo_util_initted = 1;
+
+ for (i = 0; pseudo_env[i].key; i++) {
+ if (getenv(pseudo_env[i].key))
+ pseudo_set_value(pseudo_env[i].key, getenv(pseudo_env[i].key));
+ }
+
+ pseudo_util_initted = 0;
+
+ /* Somewhere we have to set the debug level.. */
+ env = pseudo_get_value("PSEUDO_DEBUG");
+ if (env) {
+ int i;
+ int level = atoi(env);
+ if (level > 0) {
+ for (i = 0; i < level; ++i) {
+ pseudo_debug_verbose();
+ }
+ } else {
+ pseudo_debug_set(env);
+ }
+ pseudo_debug_flags_finalize();
+ }
+ free(env);
+}
+
+unsigned long pseudo_util_debug_flags = 0;
+int pseudo_util_debug_fd = 2;
+static int debugged_newline = 1;
+static char pid_text[32];
+static size_t pid_len;
+static int pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, int leave_this);
+static int pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *elements, size_t elen, int leave_last);
+extern char **environ;
+static ssize_t pseudo_max_pathlen = -1;
+static ssize_t pseudo_sys_max_pathlen = -1;
+
+/* in our installed system, we usually use a name of the form
+ * libpseudoCHECKSUM.so, where CHECKSUM is an md5 checksum of the host
+ * libc.so -- this forces rebuilds of the library when the C library
+ * changes. The problem is that the pseudo binary may be
+ * a prebuilt, in which case it doesn't know about CHECKSUM, so it
+ * has to determine whether a given PRELINK_LIBRARIES contains libpseudo.so
+ * or libpseudoCHECKSUM.so, without prior knowledge... Fancy!
+ *
+ * We search for anything matching libpseudo*.so, where * is any
+ * sequence of non-spaces (including an empty string), with either
+ * the beginning of the string or a space in front of it, and either
+ * the end of the string or a space after it.
+ */
+static char *libpseudo_name = "libpseudo.so";
+/* this used to look for a "libpseudo*.so", but it turns out you can
+ * specify a path even on Linux.
+ */
+static char *libpseudo_pattern = "(^|=| )[^ ]*libpseudo[^ ]*\\.so($| )";
+static regex_t libpseudo_regex;
+static int libpseudo_regex_compiled = 0;
+
+/* Okay, so, there's a funny story behind this. On one of the systems
+ * we need to run on, /usr/bin/find happens to provide its own
+ * definitions of regcomp and regexec which are INCOMPATIBLE with the
+ * ones in the C library, and not only that, but which have buggy and/or
+ * incompatible semantics, such that they trash elements of the pmatch
+ * array. So we do our best to call the "real" regcomp/regexec in the
+ * C library. If we can't find them, we just do our best and hope that
+ * no one called us from a program with incompatible variants.
+ *
+ */
+#if PSEUDO_PORT_LINUX
+static int (*real_regcomp)(regex_t *__restrict __preg, const char *__restrict __pattern, int __cflags);
+static int (*real_regexec)(const regex_t *__restrict __preg, const char *__restrict __string, size_t __nmatch, regmatch_t __pmatch[__restrict_arr], int __eflags);
+#else
+#define real_regcomp regcomp
+#define real_regexec regexec
+#endif /* PSEUDO_PORT_LINUX */
+
+static int
+libpseudo_regex_init(void) {
+ int rc;
+
+ if (libpseudo_regex_compiled)
+ return 0;
+#if PSEUDO_PORT_LINUX
+ real_regcomp = dlsym(RTLD_NEXT, "regcomp");
+ if (!real_regcomp)
+ real_regcomp = regcomp;
+ real_regexec = dlsym(RTLD_NEXT, "regexec");
+ if (!real_regexec)
+ real_regexec = regexec;
+#endif
+ rc = (*real_regcomp)(&libpseudo_regex, libpseudo_pattern, REG_EXTENDED);
+ if (rc == 0)
+ libpseudo_regex_compiled = 1;
+ return rc;
+}
+
+/* given a space-or-colon-separated list of files, ala PRELINK_LIBRARIES,
+ # return that list without any variants of libpseudo*.so.
+ */
+static char *
+without_libpseudo(char *list) {
+ regmatch_t pmatch[1];
+ int counter = 0;
+ int skip_start = 0;
+
+ if (libpseudo_regex_init())
+ return NULL;
+
+ if (list[0] == '=' || list[0] == PSEUDO_LINKPATH_SEPARATOR[0])
+ skip_start = 1;
+
+ if ((*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) {
+ return list;
+ }
+ list = strdup(list);
+ while (!(*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) {
+ char *start = list + pmatch[0].rm_so;
+ char *end = list + pmatch[0].rm_eo;
+ /* don't copy over the space or = */
+ start += skip_start;
+ memmove(start, end, strlen(end) + 1);
+ ++counter;
+ if (counter > 5) {
+ pseudo_diag("Found way too many libpseudo.so in environment, giving up.\n");
+ return list;
+ }
+ }
+ return list;
+}
+
+static char *
+with_libpseudo(char *list, char *libdir_path) {
+ regmatch_t pmatch[1];
+
+ if (libpseudo_regex_init())
+ return NULL;
+ if ((*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) {
+ size_t len;
+#if PSEUDO_PORT_DARWIN
+ /* <%s:%s/%s\0> */
+ len = strlen(list) + 1 + strlen(libdir_path) + 1 + strlen(libpseudo_name) + 1;
+#else
+ /* suppress warning */
+ (void) libdir_path;
+ /* <%s %s\0> */
+ len = strlen(list) + 1 + strlen(libpseudo_name) + 1;
+#endif
+ char *new = malloc(len);
+ if (new) {
+ /* insert space only if there were previous bits */
+ /* on Darwin, we have to provide the full path to
+ * libpseudo
+ */
+#if PSEUDO_PORT_DARWIN
+ snprintf(new, len, "%s%s%s/%s", list,
+ *list ? PSEUDO_LINKPATH_SEPARATOR : "",
+ libdir_path ? libdir_path : "",
+ libpseudo_name);
+#else
+ snprintf(new, len, "%s%s%s", list,
+ *list ? PSEUDO_LINKPATH_SEPARATOR : "",
+ libpseudo_name);
+#endif
+ }
+ return new;
+ } else {
+ return strdup(list);
+ }
+}
+
+char *pseudo_version = PSEUDO_VERSION;
+
+/* going away soon */
+static int max_debug_level = 0;
+
+void
+pseudo_debug_terse(void) {
+ char s[2] = { pseudo_debug_type_symbolic(max_debug_level) };
+
+ if (max_debug_level > 0) {
+ --max_debug_level;
+ pseudo_debug_clear(s);
+ }
+}
+
+void
+pseudo_debug_verbose(void) {
+ char s[2] = { pseudo_debug_type_symbolic(max_debug_level + 1) };
+
+ if (s[0]) {
+ pseudo_debug_set(s);
+ ++max_debug_level;
+ }
+}
+
+/* This exists because we don't want to allocate a bunch of strings
+ * and free them immediately if you have several flags set.
+ */
+void
+pseudo_debug_flags_finalize(void) {
+ char buf[PDBG_MAX + 1] = "", *s = buf;
+ for (int i = 0; i < PDBG_MAX; ++i) {
+ if (pseudo_util_debug_flags & (1 << i)) {
+ *s++ = pseudo_debug_type_symbolic(i);
+ }
+ }
+ pseudo_set_value("PSEUDO_DEBUG", buf);
+}
+
+void
+pseudo_debug_set(char *s) {
+ if (!s)
+ return;
+ for (; *s; ++s) {
+ int id = pseudo_debug_type_symbolic_id(*s);
+ if (id > 0) {
+ pseudo_util_debug_flags |= (1 << id);
+ }
+ }
+}
+
+void
+pseudo_debug_clear(char *s) {
+ if (!s)
+ return;
+ for (; *s; ++s) {
+ int id = pseudo_debug_type_symbolic_id(*s);
+ if (id > 0) {
+ pseudo_util_debug_flags &= ~(1 << id);
+ }
+ }
+}
+
+int
+pseudo_diag(char *fmt, ...) {
+ va_list ap;
+ char debuff[8192];
+ int len;
+ /* gcc on Ubuntu 8.10 requires that you examine the return from
+ * write(), and won't let you cast it to void. Of course, if you
+ * can't print error messages, there's nothing to do.
+ */
+ int wrote = 0;
+
+ va_start(ap, fmt);
+ len = vsnprintf(debuff, 8192, fmt, ap);
+ va_end(ap);
+
+ if (len > 8192)
+ len = 8192;
+
+ if (debugged_newline && (pseudo_util_debug_flags & PDBGF_PID)) {
+ wrote += write(pseudo_util_debug_fd, pid_text, pid_len);
+ }
+ debugged_newline = (debuff[len - 1] == '\n');
+
+ wrote += write(pseudo_util_debug_fd, debuff, len);
+ return wrote;
+}
+
+/* store pid in text form for prepending to messages */
+void
+pseudo_new_pid() {
+ int pid = getpid();
+ pid_len = snprintf(pid_text, 32, "%d: ", pid);
+ pseudo_debug(PDBGF_PID, "new pid: %d\n", pid);
+}
+
+/* helper function for pseudo_fix_path
+ * adds "element" to "newpath" at location current, if it can, then
+ * checks whether this now points to a symlink. If it does, expand
+ * the symlink, appending each element in turn the same way.
+ */
+static int
+pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, int leave_this) {
+ static int link_recursion = 0;
+ size_t curlen;
+ char *current;
+ PSEUDO_STATBUF buf;
+ if (!newpath ||
+ !pcurrent || !*pcurrent ||
+ !root || !element) {
+ pseudo_diag("pseudo_append_element: invalid args.\n");
+ return -1;
+ }
+ current = *pcurrent;
+ /* sanity-check: ignore // or /./ */
+ if (elen == 0 || (elen == 1 && *element == '.')) {
+ return 1;
+ }
+ /* backtrack for .. */
+ if (elen == 2 && element[0] == '.' && element[1] == '.') {
+ /* if newpath's whole contents are '/', do nothing */
+ if (current <= root + 1)
+ return 1;
+ /* backtrack to the character before the / */
+ current -= 2;
+ /* now find the previous slash */
+ while (current > root && *current != '/') {
+ --current;
+ }
+ /* and point to the nul just past it */
+ *(++current) = '\0';
+ *pcurrent = current;
+ return 1;
+ }
+ curlen = current - newpath;
+ /* current length, plus / <element> / \0 */
+ /* => curlen + elen + 3 */
+ if (curlen + elen + 3 > allocated) {
+ pseudo_diag("pseudo_append_element: path too long (wanted %lu bytes).\n", (unsigned long) curlen + elen + 3);
+ return -1;
+ }
+ memcpy(current, element, elen);
+ current += elen;
+ /* nul-terminate, and we now point to the nul after the slash */
+ *current = '\0';
+ /* now, the moment of truth... is that a symlink? */
+ /* if lstat fails, that's fine -- nonexistent files aren't symlinks */
+ if (!leave_this) {
+ int is_link;
+ is_link = (pseudo_real_lstat) && (pseudo_real_lstat(newpath, &buf) != -1) && S_ISLNK(buf.st_mode);
+ if (link_recursion >= PSEUDO_MAX_LINK_RECURSION && is_link) {
+ pseudo_diag("link recursion too deep, not expanding path '%s'.\n", newpath);
+ is_link = 0;
+ }
+ if (is_link) {
+ char linkbuf[pseudo_path_max() + 1];
+ ssize_t linklen;
+ int retval;
+
+ linklen = readlink(newpath, linkbuf, pseudo_path_max());
+ if (linklen == -1) {
+ pseudo_diag("uh-oh! '%s' seems to be a symlink, but I can't read it. Ignoring.", newpath);
+ return 0;
+ }
+ /* null-terminate buffer */
+ linkbuf[linklen] = '\0';
+ /* absolute symlink means start over! */
+ if (*linkbuf == '/') {
+ current = newpath + 1;
+ } else {
+ /* point back at the end of the previous path... */
+ current -= elen;
+ }
+ /* null terminate at the new pointer */
+ *current = '\0';
+ /* append all the elements in series */
+ *pcurrent = current;
+ ++link_recursion;
+ retval = pseudo_append_elements(newpath, root, allocated, pcurrent, linkbuf, linklen, 0);
+ --link_recursion;
+ return retval;
+ }
+ }
+ /* okay, not a symlink, go ahead and append a slash */
+ *(current++) = '/';
+ *current = '\0';
+ *pcurrent = current;
+ return 1;
+}
+
+static int
+pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *element, size_t elen, int leave_last) {
+ int retval = 1;
+ const char * start = element;
+ if (!newpath || !root ||
+ !current || !*current ||
+ !element) {
+ pseudo_diag("pseudo_append_elements: invalid arguments.");
+ return -1;
+ }
+ while (element < (start + elen) && *element) {
+ size_t this_elen;
+ int leave_this = 0;
+ char *next = strchr(element, '/');
+ if (!next) {
+ next = strchr(element, '\0');
+ leave_this = leave_last;
+ }
+ this_elen = next - element;
+ switch (this_elen) {
+ case 0: /* path => '/' */
+ break;
+ case 1: /* path => '?/' */
+ if (*element != '.') {
+ if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, leave_this) == -1) {
+ retval = -1;
+ }
+ }
+ break;
+ default:
+ if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, leave_this) == -1) {
+ retval = -1;
+ }
+ break;
+ }
+ /* and now move past the separator */
+ element += this_elen + 1;
+ }
+ return retval;
+}
+
+/* don't do so many allocations */
+#define PATHBUFS 16
+static char *pathbufs[PATHBUFS] = { 0 };
+static int pathbuf = 0;
+
+/* Canonicalize path. "base", if present, is an already-canonicalized
+ * path of baselen characters, presumed not to end in a /. path is
+ * the new path to be canonicalized. The tricky part is that path may
+ * contain symlinks, which must be resolved.
+ * if "path" starts with a /, then it is an absolute path, and
+ * we ignore base.
+ */
+char *
+pseudo_fix_path(const char *base, const char *path, size_t rootlen, size_t baselen, size_t *lenp, int leave_last) {
+ size_t newpathlen, pathlen;
+ char *newpath;
+ char *current;
+ char *effective_root;
+ int trailing_slash = 0;
+
+ if (!path) {
+ pseudo_diag("can't fix empty path.\n");
+ return 0;
+ }
+ newpathlen = pseudo_path_max();
+ if (!pathbufs[pathbuf]) {
+ pathbufs[pathbuf] = malloc(newpathlen);
+ }
+ newpath = pathbufs[pathbuf];
+ pathbuf = (pathbuf + 1) % PATHBUFS;
+ pathlen = strlen(path);
+ /* a trailing slash has special meaning */
+ if (pathlen > 0 && path[pathlen - 1] == '/') {
+ trailing_slash = 1;
+ }
+ /* allow a bit of slush. overallocating a bit won't
+ * hurt. rounding to 256's in the hopes that it makes life
+ * easier for the library.
+ */
+ if (!newpath) {
+ pseudo_diag("allocation failed seeking memory for path (%s).\n", path);
+ return 0;
+ }
+ newpath[0] = '\0';
+ current = newpath;
+ if (baselen && (path[0] != '/' || rootlen)) {
+ memcpy(current, base, baselen);
+ current += baselen;
+ }
+ /* "root" is a pointer to the beginning of the *modifiable*
+ * part of the string; you can't back up over it.
+ */
+ effective_root = newpath + rootlen;
+ *current++ = '/';
+ *current = '\0';
+ /* at any given point:
+ * current points to just after the last / of newpath
+ * path points to the next element of path
+ * newpathlen is the total allocated length of newpath
+ * (current - newpath) is the used length of newpath
+ */
+ if (pseudo_append_elements(newpath, effective_root, newpathlen, &current, path, pathlen, leave_last) != -1) {
+ --current;
+ if (*current == '/' && current > effective_root && !trailing_slash) {
+ *current = '\0';
+ }
+ pseudo_debug(PDBGF_PATH, "%s + %s => <%s>\n",
+ base ? base : "<nil>",
+ path ? path : "<nil>",
+ newpath ? newpath : "<nil>");
+ if (lenp) {
+ *lenp = current - newpath;
+ }
+ return newpath;
+ } else {
+ return 0;
+ }
+}
+
+/* remove the libpseudo stuff from the environment (leaving other preloads
+ * alone).
+ * There's an implicit memory leak here, but this is called only right
+ * before an exec(), or at most once in a given run.
+ *
+ * we don't try to fix the library path.
+ */
+void pseudo_dropenv() {
+ char *ld_preload = getenv(PRELINK_LIBRARIES);
+
+ if (ld_preload) {
+ ld_preload = without_libpseudo(ld_preload);
+ if (!ld_preload) {
+ pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES);
+ }
+ if (ld_preload && strlen(ld_preload)) {
+ pseudo_diag("ld_preload without: <%s>\n", ld_preload);
+ setenv(PRELINK_LIBRARIES, ld_preload, 1);
+ } else {
+ unsetenv(PRELINK_LIBRARIES);
+ }
+ }
+}
+
+char **
+pseudo_dropenvp(char * const *envp) {
+ char **new_envp;
+ int i, j;
+
+ for (i = 0; envp[i]; ++i) ;
+
+ new_envp = malloc((i + 1) * sizeof(*new_envp));
+ if (!new_envp) {
+ pseudo_diag("fatal: can't allocate new environment.\n");
+ return NULL;
+ }
+
+ j = 0;
+ for (i = 0; envp[i]; ++i) {
+ if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) {
+ char *new_val = without_libpseudo(envp[i]);
+ if (!new_val) {
+ pseudo_diag("fatal: can't allocate new environment variable.\n");
+ return 0;
+ } else {
+ /* don't keep an empty value; if the whole string is
+ * PRELINK_LIRBARIES=, we just drop it. */
+ if (strcmp(new_val, PRELINK_LIBRARIES "=")) {
+ new_envp[j++] = new_val;
+ }
+ }
+ } else {
+ new_envp[j++] = envp[i];
+ }
+ }
+ new_envp[j++] = NULL;
+ return new_envp;
+}
+
+/* add pseudo stuff to the environment.
+ */
+void
+pseudo_setupenv() {
+ size_t i = 0;
+
+ pseudo_debug(PDBGF_CLIENT, "setting up pseudo environment.\n");
+
+ /* Make sure everything has been evaluated */
+ free(pseudo_get_prefix(NULL));
+ free(pseudo_get_bindir());
+ free(pseudo_get_libdir());
+ free(pseudo_get_localstatedir());
+
+ while (pseudo_env[i].key) {
+ if (pseudo_env[i].value) {
+ setenv(pseudo_env[i].key, pseudo_env[i].value, 0);
+ pseudo_debug(PDBGF_ENV | PDBGF_VERBOSE, "pseudo_env: %s => %s\n",
+ pseudo_env[i].key, pseudo_env[i].value);
+ }
+ i++;
+ }
+
+ const char *ld_library_path = getenv(PRELINK_PATH);
+ char *libdir_path = pseudo_libdir_path(NULL);
+ if (!ld_library_path) {
+ size_t len = strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1;
+ char *newenv = malloc(len);
+ if (!newenv) {
+ pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH);
+ }
+ snprintf(newenv, len, "%s:%s64", libdir_path, libdir_path);
+ setenv(PRELINK_PATH, newenv, 1);
+ } else if (!strstr(ld_library_path, libdir_path)) {
+ size_t len = strlen(ld_library_path) + 1 + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1;
+ char *newenv = malloc(len);
+ if (!newenv) {
+ pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH);
+ }
+ snprintf(newenv, len, "%s:%s:%s64", ld_library_path, libdir_path, libdir_path);
+ setenv(PRELINK_PATH, newenv, 1);
+ } else {
+ /* nothing to do, ld_library_path exists and contains
+ * our preferred path */
+ }
+
+ char *ld_preload = getenv(PRELINK_LIBRARIES);
+ if (ld_preload) {
+ ld_preload = with_libpseudo(ld_preload, libdir_path);
+ if (!ld_preload) {
+ pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES);
+ }
+ setenv(PRELINK_LIBRARIES, ld_preload, 1);
+ free(ld_preload);
+ } else {
+ ld_preload = with_libpseudo("", libdir_path);
+ if (!ld_preload) {
+ pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES);
+ }
+ setenv(PRELINK_LIBRARIES, ld_preload, 1);
+ free(ld_preload);
+ }
+
+ /* we kept libdir path until now because with_libpseudo might
+ * need it
+ */
+ free(libdir_path);
+
+
+#if PSEUDO_PORT_DARWIN
+ char *force_flat = getenv("DYLD_FORCE_FLAT_NAMESPACE");
+ if (!force_flat) {
+ setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1);
+ }
+#endif
+}
+
+/* add pseudo stuff to the environment.
+ * We can't just use setenv(), because one use case is that we're trying
+ * to modify the environment of a process about to be forked through
+ * execve().
+ */
+char **
+pseudo_setupenvp(char * const *envp) {
+ char **new_envp;
+
+ size_t i, j, k;
+ size_t env_count = 0;
+
+ size_t size_pseudoenv = 0;
+
+ char *ld_preload = NULL, *ld_library_path = NULL;
+
+ pseudo_debug(PDBGF_ENV, "setting up envp environment.\n");
+
+ /* Make sure everything has been evaluated */
+ free(pseudo_get_prefix(NULL));
+ free(pseudo_get_bindir());
+ free(pseudo_get_libdir());
+ free(pseudo_get_localstatedir());
+
+ for (i = 0; envp[i]; ++i) {
+ if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) {
+ ld_preload = envp[i];
+ }
+ if (STARTSWITH(envp[i], PRELINK_PATH "=")) {
+ ld_library_path = envp[i];
+ }
+ ++env_count;
+ }
+
+ for (i = 0; pseudo_env[i].key; i++) {
+ size_pseudoenv++;
+ }
+
+ env_count += size_pseudoenv; /* We're going to over allocate */
+
+ j = 0;
+ new_envp = malloc((env_count + 1) * sizeof(*new_envp));
+ if (!new_envp) {
+ pseudo_diag("fatal: can't allocate new environment.\n");
+ return NULL;
+ }
+
+ char *libdir_path = pseudo_libdir_path(NULL);
+ if (!ld_library_path) {
+ size_t len = strlen(PRELINK_PATH "=") + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1;
+ char *newenv = malloc(len);
+ if (!newenv) {
+ pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH);
+ }
+ snprintf(newenv, len, PRELINK_PATH "=%s:%s64", libdir_path, libdir_path);
+ new_envp[j++] = newenv;
+ } else if (!strstr(ld_library_path, libdir_path)) {
+ size_t len = strlen(ld_library_path) + 1 + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1;
+ char *newenv = malloc(len);
+ if (!newenv) {
+ pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH);
+ }
+ snprintf(newenv, len, "%s:%s:%s64", ld_library_path, libdir_path, libdir_path);
+ new_envp[j++] = newenv;
+ } else {
+ /* keep old value */
+ new_envp[j++] = ld_library_path;
+ }
+
+ if (ld_preload) {
+ ld_preload = with_libpseudo(ld_preload, libdir_path);
+ if (!ld_preload) {
+ pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES);
+ }
+ new_envp[j++] = ld_preload;
+ } else {
+ ld_preload = with_libpseudo("", libdir_path);
+ size_t len = strlen(PRELINK_LIBRARIES "=") + strlen(ld_preload) + 1;
+ char *newenv = malloc(len);
+ snprintf(newenv, len, PRELINK_LIBRARIES "=%s", ld_preload);
+ new_envp[j++] = newenv;
+ free(ld_preload);
+ }
+
+ free(libdir_path);
+
+ for (i = 0; envp[i]; ++i) {
+ if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) continue;
+ if (STARTSWITH(envp[i], PRELINK_PATH "=")) continue;
+ new_envp[j++] = envp[i];
+ }
+
+ for (i = 0; pseudo_env[i].key; i++) {
+ int found = 0;
+ for (k = 0; k < j; k++) {
+ if (!strncmp(pseudo_env[i].key,new_envp[k],strlen(pseudo_env[i].key))) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found && pseudo_env[i].key && pseudo_env[i].value) {
+ size_t len = strlen(pseudo_env[i].key) + 1 + strlen(pseudo_env[i].value) + 1;
+ char *newenv = malloc(len);
+ if (!newenv) {
+ pseudo_diag("fatal: can't allocate new variable.\n");
+ }
+ snprintf(newenv, len, "%s=%s", pseudo_env[i].key, pseudo_env[i].value);
+ new_envp[j++] = newenv;
+ }
+ }
+ new_envp[j++] = NULL;
+ return new_envp;
+}
+
+/* Append the file value to the prefix value. */
+char *
+pseudo_append_path(const char * prefix, size_t prefix_len, char *file) {
+ char *path;
+
+ if (!file) {
+ return strdup(prefix);
+ } else {
+ size_t len = prefix_len + strlen(file) + 2;
+ path = malloc(len);
+ if (path) {
+ char *endptr;
+ int rc;
+
+ rc = snprintf(path, len, "%s", prefix);
+ /* this certainly SHOULD be impossible */
+ if ((size_t) rc >= len)
+ rc = len - 1;
+ endptr = path + rc;
+ /* strip extra slashes.
+ * This probably has no real effect, but I don't like
+ * seeing "//" in paths.
+ */
+ while ((endptr > path) && (endptr[-1] == '/'))
+ --endptr;
+ snprintf(endptr, len - (endptr - path), "/%s", file);
+ }
+ return path;
+ }
+}
+
+
+/* get the full path to a file under $PSEUDO_PREFIX. Other ways of
+ * setting the prefix all set it in the environment.
+ */
+char *
+pseudo_prefix_path(char *file) {
+ char * rc;
+ char * prefix = pseudo_get_prefix(NULL);
+
+ if (!prefix) {
+ pseudo_diag("You must set the PSEUDO_PREFIX environment variable to run pseudo.\n");
+ exit(1);
+ }
+
+ rc = pseudo_append_path(prefix, strlen(prefix), file);
+ free(prefix);
+
+ return rc;
+}
+
+/* get the full path to a file under $PSEUDO_BINDIR. */
+char *
+pseudo_bindir_path(char *file) {
+ char * rc;
+ char * bindir = pseudo_get_bindir();
+
+ if (!bindir) {
+ pseudo_diag("You must set the PSEUDO_BINDIR environment variable to run pseudo.\n");
+ exit(1);
+ }
+
+ rc = pseudo_append_path(bindir, strlen(bindir), file);
+ free(bindir);
+
+ return rc;
+}
+
+/* get the full path to a file under $PSEUDO_LIBDIR. */
+char *
+pseudo_libdir_path(char *file) {
+ char * rc;
+ char * libdir = pseudo_get_libdir();
+
+ if (!libdir) {
+ pseudo_diag("You must set the PSEUDO_LIBDIR environment variable to run pseudo.\n");
+ exit(1);
+ }
+
+ rc = pseudo_append_path(libdir, strlen(libdir), file);
+ free(libdir);
+
+ return rc;
+}
+
+/* get the full path to a file under $PSEUDO_LOCALSTATEDIR. */
+char *
+pseudo_localstatedir_path(char *file) {
+ char * rc;
+ char * localstatedir = pseudo_get_localstatedir();
+
+ if (!localstatedir) {
+ pseudo_diag("You must set the PSEUDO_LOCALSTATEDIR environment variable to run pseudo.\n");
+ exit(1);
+ }
+
+ rc = pseudo_append_path(localstatedir, strlen(localstatedir), file);
+ free(localstatedir);
+
+ return rc;
+}
+
+char *
+pseudo_get_prefix(char *pathname) {
+ char *s = pseudo_get_value("PSEUDO_PREFIX");
+
+ /* Generate the PSEUDO_PREFIX if necessary, and possible... */
+ if (!s && pathname) {
+ char mypath[pseudo_path_max()];
+ char *dir;
+ char *tmp_path;
+
+ if (pathname[0] == '/') {
+ snprintf(mypath, pseudo_path_max(), "%s", pathname);
+ s = mypath + strlen(mypath);
+ } else {
+ if (!getcwd(mypath, pseudo_path_max())) {
+ mypath[0] = '\0';
+ }
+ s = mypath + strlen(mypath);
+ s += snprintf(s, pseudo_path_max() - (s - mypath), "/%s",
+ pathname);
+ }
+ tmp_path = pseudo_fix_path(NULL, mypath, 0, 0, 0, AT_SYMLINK_NOFOLLOW);
+ /* point s to the end of the fixed path */
+ if ((int) strlen(tmp_path) >= pseudo_path_max()) {
+ pseudo_diag("Can't expand path '%s' -- expansion exceeds %d.\n",
+ mypath, (int) pseudo_path_max());
+ } else {
+ s = mypath + snprintf(mypath, pseudo_path_max(), "%s", tmp_path);
+ }
+
+ while (s > (mypath + 1) && *s != '/')
+ --s;
+ *s = '\0';
+ dir = s - 1;
+ while (dir > mypath && *dir != '/') {
+ --dir;
+ }
+ /* strip bin directory, if any */
+ if (!strncmp(dir, "/bin", 4)) {
+ *dir = '\0';
+ }
+ /* degenerate case: /bin/pseudo should yield a pseudo_prefix "/" */
+ if (*mypath == '\0') {
+ strcpy(mypath, "/");
+ }
+
+ pseudo_diag("Warning: PSEUDO_PREFIX unset, defaulting to %s.\n",
+ mypath);
+ pseudo_set_value("PSEUDO_PREFIX", mypath);
+ s = pseudo_get_value("PSEUDO_PREFIX");
+ }
+ return s;
+}
+
+char *
+pseudo_get_bindir(void) {
+ char *s = pseudo_get_value("PSEUDO_BINDIR");
+ if (!s) {
+ char *pseudo_bindir = pseudo_prefix_path(PSEUDO_BINDIR);
+ if (pseudo_bindir) {
+ pseudo_set_value("PSEUDO_BINDIR", pseudo_bindir);
+ s = pseudo_bindir;
+ }
+ }
+ return s;
+}
+
+char *
+pseudo_get_libdir(void) {
+ char *s = pseudo_get_value("PSEUDO_LIBDIR");
+ if (!s) {
+ char *pseudo_libdir;
+ pseudo_libdir = pseudo_prefix_path(PSEUDO_LIBDIR);
+ if (pseudo_libdir) {
+ pseudo_set_value("PSEUDO_LIBDIR", pseudo_libdir);
+ s = pseudo_libdir;
+ }
+ }
+#if PSEUDO_PORT_DARWIN
+ /* on Darwin, we need lib64, because dyld won't search */
+#else
+ /* If we somehow got lib64 in there, clean it down to just lib... */
+ if (s) {
+ size_t len = strlen(s);
+ if (s[len-2] == '6' && s[len-1] == '4') {
+ s[len-2] = '\0';
+ pseudo_set_value("PSEUDO_LIBDIR", s);
+ }
+ }
+#endif
+
+ return s;
+}
+
+char *
+pseudo_get_localstatedir() {
+ char *s = pseudo_get_value("PSEUDO_LOCALSTATEDIR");
+ if (!s) {
+ char *pseudo_localstatedir = pseudo_prefix_path(PSEUDO_LOCALSTATEDIR);
+ if (pseudo_localstatedir) {
+ pseudo_set_value("PSEUDO_LOCALSTATEDIR", pseudo_localstatedir);
+ s = pseudo_localstatedir;
+ }
+ }
+ return s;
+}
+
+/* these functions define the sizes pseudo will try to use
+ * when trying to allocate space, or guess how much space
+ * other people will have allocated; see the GNU man page
+ * for realpath(3) for an explanation of why the sys_path_max
+ * functions exists, approximately -- it's there to be a size
+ * that I'm pretty sure the user will have allocated if they
+ * provided a buffer to that defective function.
+ */
+/* I'm pretty sure this will be larger than real PATH_MAX */
+#define REALLY_BIG_PATH 16384
+/* A likely common value for PATH_MAX */
+#define SORTA_BIG_PATH 4096
+ssize_t
+pseudo_path_max(void) {
+ if (pseudo_max_pathlen == -1) {
+ long l = pathconf("/", _PC_PATH_MAX);
+ if (l < 0) {
+ if (_POSIX_PATH_MAX > 0) {
+ pseudo_max_pathlen = _POSIX_PATH_MAX;
+ } else {
+ pseudo_max_pathlen = REALLY_BIG_PATH;
+ }
+ } else {
+ if (l <= REALLY_BIG_PATH) {
+ pseudo_max_pathlen = l;
+ } else {
+ pseudo_max_pathlen = REALLY_BIG_PATH;
+ }
+ }
+ }
+ return pseudo_max_pathlen;
+}
+
+ssize_t
+pseudo_sys_path_max(void) {
+ if (pseudo_sys_max_pathlen == -1) {
+ long l = pathconf("/", _PC_PATH_MAX);
+ if (l < 0) {
+ if (_POSIX_PATH_MAX > 0) {
+ pseudo_sys_max_pathlen = _POSIX_PATH_MAX;
+ } else {
+ pseudo_sys_max_pathlen = SORTA_BIG_PATH;
+ }
+ } else {
+ if (l <= SORTA_BIG_PATH) {
+ pseudo_sys_max_pathlen = l;
+ } else {
+ pseudo_sys_max_pathlen = SORTA_BIG_PATH;
+ }
+ }
+ }
+ return pseudo_sys_max_pathlen;
+}
+
+/* complicated because in theory you can have modes like * 'ab+'
+ * which is the same as 'a+' in POSIX. The first letter really does have
+ * to be one of r, w, a, though.
+ */
+int
+pseudo_access_fopen(const char *mode) {
+ int access = 0;
+ for (; *mode; ++mode) {
+ switch (*mode) {
+ case 'a':
+ access |= (PSA_APPEND | PSA_WRITE);
+ break;
+ case 'r':
+ access |= PSA_READ;
+ break;
+ case 'w':
+ access |= PSA_WRITE;
+ break;
+ case 'x':
+ /* special case -- note that this conflicts with a
+ * rarely-used glibc extension
+ */
+ access |= PSA_EXEC;
+ break;
+ case 'b': /* binary mode */
+ break;
+ case 'c': case 'e': case 'm': /* glibc extensions */
+ break;
+ case '+':
+ /* one of these will already be set, presumably */
+ access |= (PSA_READ | PSA_WRITE);
+ break;
+ default:
+ access = -1;
+ break;
+ }
+ }
+ return access;
+}
+
+/* find a passwd/group file to use
+ * uses in order:
+ * - PSEUDO_CHROOT/etc/<file> (only if CHROOT is set)
+ * - PSEUDO_PASSWD/etc/<file>
+ * - /etc/<file>
+ */
+
+#if PSEUDO_PORT_DARWIN
+/* on Darwin, you can't just use /etc/passwd for system lookups,
+ * you have to use the real library calls because they know about
+ * Directory Services. So...
+ *
+ * We make up fake fds and FILE * objects that can't possibly be
+ * valid.
+ */
+int pseudo_host_etc_passwd_fd = -3;
+int pseudo_host_etc_group_fd = -4;
+static FILE pseudo_fake_passwd_file;
+static FILE pseudo_fake_group_file;
+FILE *pseudo_host_etc_passwd_file = &pseudo_fake_passwd_file;
+FILE *pseudo_host_etc_group_file = &pseudo_fake_group_file;
+
+#endif
+
+int
+pseudo_etc_file(const char *file, char *realname, int flags, const char **search_dirs, int dircount) {
+ char filename[pseudo_path_max()];
+ int rc = -1;
+
+ if (!file) {
+ pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: needs argument, usually passwd/group\n");
+ errno = ENOENT;
+ return -1;
+ }
+ int i;
+ if (!search_dirs || dircount == 0) {
+ pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: no search dirs.\n");
+ errno = ENOENT;
+ return -1;
+ }
+ for (i = 0; i < dircount; ++i) {
+ const char *s = search_dirs[i];
+ /* we used to pass in some paths as NULL when unset,
+ * so we skipped those. Now NULL entries don't get
+ * put in, so the only NULL should be the sentinel
+ * value, and this should never get hit.
+ *
+ * "should" is not comforting to me.
+ */
+ if (!s)
+ break;
+#if PSEUDO_PORT_DARWIN
+ /* special magic: empty string implies our emulation
+ * of the passwd/group files.
+ */
+ if (!*s) {
+ if (!strcmp("passwd", file)) {
+ pseudo_debug(PDBGF_CHROOT, "Darwin hackery: pseudo_etc_passwd returning magic passwd fd\n");
+ return pseudo_host_etc_passwd_fd;
+ } else if (!strcmp("group", file)) {
+ pseudo_debug(PDBGF_CHROOT, "Darwin hackery: pseudo_etc_passwd returning magic group fd\n");
+ return pseudo_host_etc_group_fd;
+ }
+ }
+#endif
+ snprintf(filename, pseudo_path_max(), "%s/etc/%s",
+ s, file);
+ rc = open(filename, flags, 0600);
+ if (rc >= 0) {
+ if (realname)
+ strcpy(realname, filename);
+ pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: using '%s' for '%s'.\n",
+ filename, file);
+ return rc;
+ } else {
+ pseudo_debug(PDBGF_CHROOT | PDBGF_VERBOSE, "didn't find <%s>\n",
+ filename);
+ }
+ }
+ return rc;
+}
+
+/* set up a log file */
+int
+pseudo_logfile(char *defname) {
+ char *pseudo_path;
+ char *filename = pseudo_get_value("PSEUDO_DEBUG_FILE");
+ char *s;
+#if PSEUDO_PORT_LINUX
+ extern char *program_invocation_short_name; /* glibcism */
+#else
+ char *program_invocation_short_name = "unknown";
+#endif
+ int fd;
+
+ if (!filename) {
+ if (!defname) {
+ pseudo_debug(PDBGF_INVOKE, "no special log file requested, using stderr.\n");
+ return -1;
+ }
+ pseudo_path = pseudo_localstatedir_path(defname);
+ if (!pseudo_path) {
+ pseudo_diag("can't get path for prefix/%s\n", PSEUDO_LOGFILE);
+ return -1;
+ }
+ } else {
+ char *pid = NULL, *prog = NULL;
+ size_t len;
+ for (s = filename; *s; ++s) {
+ if (s[0] == '%') {
+ switch (s[1]) {
+ case '%': /* skip the %% */
+ ++s;
+ break;
+ case 'd':
+ if (pid) {
+ pseudo_diag("found second %%d in PSEUDO_DEBUG_FILE, ignoring.\n");
+ return -1;
+ } else {
+ pid = s;
+ }
+ break;
+ case 's':
+ if (prog) {
+ pseudo_diag("found second %%s in PSEUDO_DEBUG_FILE, ignoring.\n");
+ return -1;
+ } else {
+ prog = s;
+ }
+ break;
+ default:
+ if (isprint(s[1])) {
+ pseudo_diag("found unknown format character '%c' in PSEUDO_DEBUG_FILE, ignoring.\n",
+ s[1]);
+ } else {
+ pseudo_diag("found unknown format character '\\x%02x' in PSEUDO_DEBUG_FILE, ignoring.\n",
+ (unsigned char) s[1]);
+ }
+ return -1;
+ break;
+ }
+ }
+ }
+ len = strlen(filename) + 1;
+ if (pid)
+ len += 8;
+ if (prog)
+ len += strlen(program_invocation_short_name);
+ pseudo_path = malloc(len);
+ if (!pseudo_path) {
+ pseudo_diag("can't allocate space for debug file name.\n");
+ return -1;
+ }
+ if (pid && prog) {
+ if (pid < prog) {
+ snprintf(pseudo_path, len, filename, getpid(), program_invocation_short_name);
+ } else {
+ snprintf(pseudo_path, len, filename, program_invocation_short_name, getpid());
+ }
+ } else if (pid) {
+ snprintf(pseudo_path, len, filename, getpid());
+ } else if (prog) {
+ snprintf(pseudo_path, len, filename, program_invocation_short_name);
+ } else {
+ strcpy(pseudo_path, filename);
+ }
+ free(filename);
+ }
+ fd = open(pseudo_path, O_WRONLY | O_APPEND | O_CREAT, 0644);
+ if (fd == -1) {
+ pseudo_diag("help: can't open log file %s: %s\n", pseudo_path, strerror(errno));
+ } else {
+ /* try to force fd to 2. We do this because glibc's malloc
+ * debug unconditionally writes to fd 2, and we don't want
+ * a client process ending op on fd 2, or server debugging
+ * becomes a nightmare.
+ */
+ if (fd != 2) {
+ int newfd;
+ close(2);
+ newfd = dup2(fd, 2);
+ if (newfd != -1) {
+ fd = newfd;
+ }
+ }
+ pseudo_util_debug_fd = fd;
+ }
+ free(pseudo_path);
+ if (fd == -1)
+ return -1;
+ else
+ return 0;
+}
+
+void
+pseudo_stat32_from64(struct stat *buf32, const struct stat64 *buf) {
+ buf32->st_dev = buf->st_dev;
+ buf32->st_ino = buf->st_ino;
+ buf32->st_mode = buf->st_mode;
+ buf32->st_nlink = buf->st_nlink;
+ buf32->st_uid = buf->st_uid;
+ buf32->st_gid = buf->st_gid;
+ buf32->st_rdev = buf->st_rdev;
+ buf32->st_size = buf->st_size;
+ buf32->st_blksize = buf->st_blksize;
+ buf32->st_blocks = buf->st_blocks;
+ buf32->st_atime = buf->st_atime;
+ buf32->st_mtime = buf->st_mtime;
+ buf32->st_ctime = buf->st_ctime;
+}
+
+void
+pseudo_stat64_from32(struct stat64 *buf64, const struct stat *buf) {
+ buf64->st_dev = buf->st_dev;
+ buf64->st_ino = buf->st_ino;
+ buf64->st_mode = buf->st_mode;
+ buf64->st_nlink = buf->st_nlink;
+ buf64->st_uid = buf->st_uid;
+ buf64->st_gid = buf->st_gid;
+ buf64->st_rdev = buf->st_rdev;
+ buf64->st_size = buf->st_size;
+ buf64->st_blksize = buf->st_blksize;
+ buf64->st_blocks = buf->st_blocks;
+ buf64->st_atime = buf->st_atime;
+ buf64->st_mtime = buf->st_mtime;
+ buf64->st_ctime = buf->st_ctime;
+}
+
+/* pretty-dump some data.
+ * expects to be called using pseudo_debug_call() so it doesn't have
+ * to do debug checks.
+ */
+void
+pseudo_dump_data(char *name, const void *v, size_t len) {
+ char hexbuf[128];
+ char asciibuf[32];
+ const unsigned char *base = v;
+ const unsigned char *data = base;
+ int remaining = len;
+ pseudo_diag("%s at %p [%d byte%s]:\n",
+ name ? name : "data", v, (int) len, len == 1 ? "" : "s");
+ while (remaining > 0) {
+ char *hexptr = hexbuf;
+ char *asciiptr = asciibuf;
+ for (int i = 0; i < 16 && i < remaining; ++i) {
+ hexptr += snprintf(hexptr, 4, "%02x ", data[i]);
+ if (isprint(data[i])) {
+ *asciiptr++ = data[i];
+ } else {
+ *asciiptr++ = '.';
+ }
+ if (i % 4 == 3) {
+ *hexptr++ = ' ';
+ }
+ }
+ *hexptr = '\0';
+ *asciiptr = '\0';
+ pseudo_diag("0x%06x %-50.50s '%.16s'\n",
+ (int) (data - base),
+ hexbuf, asciibuf);
+ remaining = remaining - 16;
+ data = data + 16;
+ }
+}
diff --git a/pseudo_wrappers.c b/pseudo_wrappers.c
new file mode 100644
index 0000000..34d7f23
--- /dev/null
+++ b/pseudo_wrappers.c
@@ -0,0 +1,292 @@
+/*
+ * pseudo_wrappers.c, shared code for wrapper functions
+ *
+ * Copyright (c) 2008-2012 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <assert.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <dlfcn.h>
+
+/* used for various specific function arguments */
+#include <dirent.h>
+#include <fts.h>
+#include <ftw.h>
+#include <glob.h>
+#include <grp.h>
+#include <pwd.h>
+#include <utime.h>
+
+#include "pseudo.h"
+#include "pseudo_wrapfuncs.h"
+#include "pseudo_ipc.h"
+#include "pseudo_client.h"
+
+/* Types and declarations we need in advance. */
+#include "pseudo_wrapper_table.c"
+
+static void pseudo_enosys(const char *);
+static int pseudo_check_wrappers(void);
+static volatile int antimagic = 0;
+static pthread_mutex_t pseudo_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_t pseudo_mutex_holder;
+static int pseudo_mutex_recursion = 0;
+static int pseudo_getlock(void);
+static void pseudo_droplock(void);
+static size_t pseudo_dechroot(char *, size_t);
+static void pseudo_sigblock(sigset_t *);
+
+extern char *program_invocation_short_name;
+static sigset_t pseudo_saved_sigmask;
+
+/* Constructor only exists in libpseudo */
+static void _libpseudo_init(void) __attribute__ ((constructor));
+
+static int _libpseudo_initted = 0;
+
+#ifdef PSEUDO_PROFILING
+extern struct timeval *pseudo_wrapper_time;
+/* profiling shared postamble */
+#define PROFILE_START \
+ struct timeval tv1, tv2; \
+ do { gettimeofday(&tv1, NULL); } while(0)
+#define PROFILE_DONE do { \
+ gettimeofday(&tv2, NULL); \
+ pseudo_wrapper_time->tv_sec += tv2.tv_sec - tv1.tv_sec; \
+ pseudo_wrapper_time->tv_usec += tv2.tv_usec - tv1.tv_usec; } while(0)
+#else
+#define PROFILE_START do {} while(0)
+#define PROFILE_DONE do {} while(0)
+#endif
+
+#ifdef PSEUDO_XATTRDB
+extern ssize_t (*pseudo_real_lgetxattr)(const char *, const char *, void *, size_t);
+extern ssize_t (*pseudo_real_fgetxattr)(int, const char *, void *, size_t);
+extern int (*pseudo_real_lsetxattr)(const char *, const char *, const void *, size_t, int);
+extern int (*pseudo_real_fsetxattr)(int, const char *, const void *, size_t, int);
+#endif
+/* later, the init code can change these to refer to the real calls and
+ * skip the wrappers.
+ */
+extern int (*pseudo_real_lstat)(const char *path, PSEUDO_STATBUF *buf);
+
+static void
+_libpseudo_init(void) {
+ pseudo_getlock();
+ pseudo_antimagic();
+ _libpseudo_initted = 1;
+
+ pseudo_init_util();
+ pseudo_init_wrappers();
+ pseudo_init_client();
+
+ pseudo_magic();
+ pseudo_droplock();
+}
+
+void
+pseudo_reinit_libpseudo(void) {
+ _libpseudo_init();
+}
+
+static void
+pseudo_init_one_wrapper(pseudo_function *func) {
+ int (*f)(void) = (int (*)(void)) NULL;
+
+ char *e;
+ if (*func->real != NULL) {
+ /* already initialized */
+ return;
+ }
+ dlerror();
+
+#if PSEUDO_PORT_LINUX
+ if (func->version)
+ f = dlvsym(RTLD_NEXT, func->name, func->version);
+ /* fall through to the general case, if that failed */
+ if (!f)
+#endif
+ f = dlsym(RTLD_NEXT, func->name);
+ if (f) {
+ *func->real = f;
+ } else {
+ e = dlerror();
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ char *s = func->name;
+ s += strlen(s) - 2;
+ /* *at() don't have to exist */
+ if (!strcmp(s, "at")) {
+ return;
+ }
+#else
+ if (e != NULL) {
+ pseudo_diag("No real function for %s: %s\n", func->name, e);
+ }
+#endif
+ }
+}
+
+void
+pseudo_init_wrappers(void) {
+ int i;
+ static int done = 0;
+
+ pseudo_getlock();
+ pseudo_antimagic();
+
+ /* We only ever want to run this once, even though we might want to
+ * "re-init" at specific times...
+ */
+ if (!done) {
+ for (i = 0; pseudo_functions[i].name; ++i) {
+ pseudo_init_one_wrapper(&pseudo_functions[i]);
+ }
+ done = 1;
+ }
+
+#ifdef PSEUDO_XATTRDB
+ pseudo_real_lgetxattr = real_lgetxattr;
+ pseudo_real_fgetxattr = real_fgetxattr;
+ pseudo_real_lsetxattr = real_lsetxattr;
+ pseudo_real_fsetxattr = real_fsetxattr;
+#endif
+ pseudo_real_lstat = base_lstat;
+
+ /* Once the wrappers are setup, we can now use open... so
+ * setup the logfile, if necessary...
+ */
+ pseudo_logfile(NULL);
+
+ pseudo_magic();
+ pseudo_droplock();
+}
+
+static void
+pseudo_sigblock(sigset_t *saved) {
+ sigset_t blocked;
+
+ /* these are signals for which the handlers often
+ * invoke operations, such as close(), which are handled
+ * by pseudo and could result in a deadlock.
+ */
+ sigemptyset(&blocked);
+ sigaddset(&blocked, SIGALRM); /* every-N-seconds tasks */
+ sigaddset(&blocked, SIGCHLD); /* reaping child processes */
+ sigaddset(&blocked, SIGHUP); /* idiomatically, reloading config */
+ sigaddset(&blocked, SIGTERM); /* shutdown/teardown operations */
+ sigaddset(&blocked, SIGUSR1); /* reopening log files, sometimes */
+ sigaddset(&blocked, SIGUSR2); /* who knows what people do */
+ sigprocmask(SIG_BLOCK, &blocked, saved);
+}
+
+static int
+pseudo_getlock(void) {
+ if (pthread_equal(pseudo_mutex_holder, pthread_self())) {
+ ++pseudo_mutex_recursion;
+ return 0;
+ } else {
+ if (pthread_mutex_lock(&pseudo_mutex) == 0) {
+ pseudo_mutex_recursion = 1;
+ pseudo_mutex_holder = pthread_self();
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+}
+
+static void
+pseudo_droplock(void) {
+ if (--pseudo_mutex_recursion == 0) {
+ pseudo_mutex_holder = 0;
+ pthread_mutex_unlock(&pseudo_mutex);
+ }
+}
+
+void
+pseudo_antimagic() {
+ ++antimagic;
+}
+
+void
+pseudo_magic() {
+ if (antimagic > 0)
+ --antimagic;
+}
+
+static void
+pseudo_enosys(const char *func) {
+ pseudo_diag("pseudo: ENOSYS for '%s'.\n", func ? func : "<nil>");
+ char * value = pseudo_get_value("PSEUDO_ENOSYS_ABORT");
+ if (value)
+ abort();
+ free(value);
+ errno = ENOSYS;
+}
+
+/* de-chroot a string.
+ * note that readlink() yields an unterminated buffer, so
+ * must pass in the correct length. Buffers are null-terminated
+ * unconditionally if they are modified -- the modification would
+ * shorten the string, so there will be space for the NUL, so
+ * this is safe even for stuff like readlink().
+ */
+static size_t
+pseudo_dechroot(char *s, size_t len) {
+ if (len == (size_t) -1)
+ len = strlen(s);
+ if (pseudo_chroot_len && len >= pseudo_chroot_len &&
+ !memcmp(s, pseudo_chroot, pseudo_chroot_len)) {
+ if (s[pseudo_chroot_len] == '/') {
+ memmove(s, s + pseudo_chroot_len, len - pseudo_chroot_len);
+ len -= pseudo_chroot_len;
+ s[len] = '\0';
+ } else if (s[pseudo_chroot_len] == '\0') {
+ s[0] = '/';
+ len = 1;
+ s[len] = '\0';
+ }
+ /* otherwise, it's not really a match... */
+ }
+ return len;
+}
+
+static int
+pseudo_check_wrappers(void) {
+ if (!_libpseudo_initted)
+ pseudo_reinit_libpseudo();
+
+ return _libpseudo_initted;
+}
+
+/* the generated code goes here */
+#include "port_wrappers.c"
+#include "pseudo_wrapfuncs.c"
+
diff --git a/pseudodb.c b/pseudodb.c
new file mode 100644
index 0000000..1be8651
--- /dev/null
+++ b/pseudodb.c
@@ -0,0 +1,49 @@
+/*
+ * pseudodb.c, (unimplemented) database maintenance utility.
+ *
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_db.h"
+
+int
+main(int argc, char **argv) {
+ pseudo_msg_t *msg;
+ int rc;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: pseudodb <filename>\n");
+ exit(1);
+ }
+ msg = pseudo_msg_new(0, argv[1]);
+ rc = pdb_find_file_path(msg, NULL);
+ if (rc) {
+ printf("error.\n");
+ return 1;
+ } else {
+ printf("%s: %o %x\n", msg->path, (int) msg->mode, (int) msg->rdev);
+ return 0;
+ }
+}
diff --git a/pseudolog.1 b/pseudolog.1
new file mode 100644
index 0000000..ebf8443
--- /dev/null
+++ b/pseudolog.1
@@ -0,0 +1,395 @@
+.\"
+.\" pseudolog(1) man page
+.\"
+.\" Copyright (c) 2010 Wind River Systems, Inc.
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the Lesser GNU General Public License version 2.1 as
+.\" published by the Free Software Foundation.
+.\"
+.\" This program 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 Lesser GNU General Public License for more details.
+.\"
+.\" You should have received a copy of the Lesser GNU General Public License
+.\" version 2.1 along with this program; if not, write to the Free Software
+.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+.TH pseudolog 1 "pseudo - pretending to be root"
+.SH NAME
+pseudolog \- pseudo log parser
+.SH SYNOPSIS
+.B pseudolog \-l
+.RB [ \-Pv ]
+[
+.B \-E
+.I timeformat
+]
+[
+.B \-x
+.I flags
+]
+.RI [ SPECIFICATIONS ]
+.PP
+.B pseudolog
+.RB [ \-UPv ]
+[
+.B \-E
+.I timeformat
+]
+[
+.B \-F
+.I format
+]
+[
+.B \-x
+.I flags
+]
+.PP
+.B pseudolog \-h
+.PP
+.B pseudolog \-D
+.RB [ \-Pv ]
+[
+.B \-E
+.I timeformat
+]
+[
+.B \-x
+.I flags
+]
+.RI [ SPECIFICATIONS ]
+.RI [ SPECIFICATIONS ]
+.SH DESCRIPTION
+The
+.I pseudolog
+utility displays, creates, or deletes log entries associated with the
+.I pseudo
+daemon. Creation of log entries is useful only to
+create timestamps or notes; for instance, you could create a log entry before
+beginning a process, so there would be a timestamp for the beginning of
+that process. There are a number of special options used to match or create
+the components of a log entry; these are called
+.IR specifications ,
+and are detailed in the
+.B SPECIFICATIONS
+section below.
+
+The following other options are supported:
+
+.TP 8
+.B \-h
+Print a usage message and exit.
+.TP 8
+.B \-D
+Delete rows selected by the query. This is not reversible.
+.TP 8
+.BI \-E \ timeformat
+Specify a format string (for
+.I strptime(3)
+or
+.I strftime(3)
+to use) for displaying or interpreting time stamps. The same format
+is used both for parsing and displaying stamps.
+.TP 8
+.BI \-F \ format
+Specifies a format string for displaying log entries. This format cannot
+be used to create log entries, only for display. The format string is
+a
+.I printf(3)
+type format string, with format specifiers matching the option characters
+used in specifications (see
+.BR SPECIFICATIONS ).
+There are some limitations on allowed formats, and misuse of this feature
+could cause interesting or surprising failures.
+.TP 8
+.B \-l
+Create a log entry. This option is mutually exclusive with the
+.B \-F
+option, or with any relative specifications (see below).
+.TP 8
+.BI \-P \ path
+Specify that
+.I path
+should be used as the
+.B PSEUDO_PREFIX
+value, overriding any environment setting.
+.TP 8
+.B \-U
+Restrict query output to unique rows. Rows will have members defined by
+the
+.B \-F
+(format) option. If all members are the same between two rows, only one
+is displayed. Applies only to queries.
+.TP 8
+.B \-v
+Increase verbosity (debug level). Not useful except when debugging pseudo.
+Deprecated; use
+.BR \-x .
+.TP 8
+.BI \-x flags
+Specify debugging flags of interest. Not useful except when debugging pseudo.
+
+Other option characters are defined as specifications, and all of those
+require arguments to specify their values.
+
+.SH SPECIFICATIONS
+
+The various components of a log entry can be specified, either as command-line
+options, or as format specifiers. In either case, the same character is used
+for a given component of a log entry. When querying values, one of the
+following prefixes may be prepended to a value; otherwise, the value is
+used for a literal match (an SQL
+.B =
+operator).
+
+.TP 8
+.B >
+Greater than; true if the related field is greater than the provided value.
+.TP 8
+.B <
+Less than; true if the related field is less than the provided value.
+.TP 8
+.B &
+Bitwise and; true if the related field, bitwise-and the provided value,
+is non-zero. (This is useful primarily for permissions or modes.)
+.TP 8
+.B =
+Equal to. (This is a no-op, as of this writing.)
+.TP 8
+.B !
+Not equal to.
+.TP 8
+.B %
+Similar to
+.BR ~ .
+This is valid only on text fields, and is equivalent to
+the SQL
+.B LIKE
+operator, with
+.B %
+patterns on the ends; it performs an unanchored, case-insensitive match.
+.TP 8
+.B ~
+Similar to
+.BR % .
+This is valid only on text fields, and is equivalent
+to the SQL
+.B LIKE
+operator, but performs an anchored match. The match is
+case-insensitive. The specifier
+.B ~%foo%
+is equivalent to the specifier
+.BR %foo .
+.TP 8
+.B ^
+Unlike. This is the inverse of ~; it specifies
+.BR NOT\ LIKE .
+.TP 8
+.B \\
+Escape the string. This is useful if you want to have one of the
+other modifiers at the beginning of the string.
+
+.PP
+Only
+.BR = and \\
+modifiers may be used in conjunction with the
+.B \-l
+option.
+
+The following characters correspond to specific fields in a log entry.
+In general, numeric values are parsed in the standard C idiom (where
+a leading
+.B 0
+indicates an octal value, and a leading
+.B 0x
+indicates a hexadecimal value, and any other number is decimal). A
+few fields are parsed or displayed in other ways, as detailed in their
+entries.
+
+.TP 8
+.B a
+Access mode. This is an access mode specified in the form used by
+.IR fopen(3) ,
+such as "r+" to indicate read/write access. Note that specifying
+.B \&a
+as an access mode will include non-append writes, as the "a" mode
+implies write and append both. This feature is slightly experimental
+and may not correctly identify the access type of every access. The
+string
+.B x
+may be specified to indicate execute access.
+.TP 8
+.B c
+Client ID (the PID of a client).
+.TP 8
+.B d
+Device number (from a stat buffer).
+.TP 8
+.B f
+File descriptor. In some cases, messages have an associated file descriptor
+identified.
+.TP 8
+.B g
+GID. The group ID associated with an entry.
+.TP 8
+.B G
+Tag. This is a text field. In log entries created by
+.IR pseudo ,
+this field holds the value that the environment variable
+.B PSEUDO_TAG
+had in the client's environment.
+.TP 8
+.B i
+Inode number (from a stat buffer).
+.TP 8
+.TP 8
+.B I
+ID. This is the database row number. Normally these are assigned
+as monotonically increasing values as rows are inserted, making them
+a more reliable sorting mechanism than timestamps. The default
+ordering is by ID.
+.B m
+Permissions. These can be entered as an octal value or as a symbolic
+mode string, similar to the output of
+.I ls(1)
+.BR -l.
+The file type component is ignored.
+.TP 8
+.B M
+Mode. This can be entered as an octal value or as a symbolic mode
+string, similar to the output of
+.I ls(1)
+.BR -l.
+This is tested against the whole file mode, including both the type
+and permissions bits. In general, it is more useful to use the
+.B m
+or
+.B t
+specifiers.
+.TP 8
+.B o
+Operation. This is the name of the file system operation
+(e.g., "open" or "rename").
+.TP 8
+.B O
+Order. This takes another specification character as the field
+on which to order results. A '<' implies a descending order sort,
+a '>' or no modifier specifies an ascending order sort.
+By default, records are sorted by ID.
+.TP 8
+.B p
+File path. This is a text field.
+.TP 8
+.B r
+Result. This is the
+.I pseudo
+result code, most often "fail" or
+"succeed". Note that "fail" doesn't mean that an underlying
+operation failed; for instance, if a "stat" operation fails, it
+usually means that there was no entry in the
+.I pseudo
+database.
+.TP 8
+.B R
+Program. This is the program name (as retrieved by glibc's
+.I program_invocation_name
+variable), which has the full path if and only if the program
+was invoked by full path name.
+.TP 8
+.B s
+Timestamp. The format of this field is controlled by the
+.B \-E
+format string, which is used with
+.I strftime(3)
+when displaying entries, or with
+.I strptime(3)
+when interpreting command line values. There is a small selection of
+common default time formats understood by the parser. Time fields not
+specified default to the current time. Note that specifying a time
+stamp when creating a log entry may yield confusing results.
+.TP 8
+.B S
+Severity. Log messages can have a severity, with the default for file
+operations being "info".
+.B t
+File type. This corresponds to the first letter of a mode string, or
+the values accepted by the
+.B \-type
+option to
+.IR find(1) .
+This is compared only against the file type bits of a mode.
+.TP 8
+.B T
+Text. This is an optional field available for user use when creating
+log entries, or to hold the text of an error message when an error is
+logged. It is, of course, a text field.
+.TP 8
+.B u
+UID. The user ID associated with an entry.
+.TP 8
+.B y
+Type. This is usually "op" for operations, or "ping" for the ping
+messages clients send to confirm server availability. Other types
+should rarely occur, but include "ack" and "nak" for server
+responses (which are never logged), and "halt" for shutdown messages
+(currently not logged).
+
+.SH EXAMPLES
+The following examples illustrate some of the likely usage patterns for
+.IR pseudolog .
+
+.TP 8
+.B pseudolog -m '&020' -t d
+Report on all directories which are group-writeable.
+.TP 8
+.B pseudolog -m 755 -t f
+Report on all plain files which have the mode rwxr-xr-x.
+.TP 8
+.B pseudolog -s '>03:19:00' -s '<03:20:00'
+Report on all entries created after 03:19:00 and before 03:20:00 on the
+current
+date.
+.TP 8
+.B pseudolog -p '~/usr/bin/%' -F '%-8o %p'
+Report on every entry with a path beginning with the string '/usr/bin',
+displaying the operation name (in a space-padded field of eight characters,
+left-adjusted) followed by the path.
+.TP 8
+.B pseudolog -l -T 'stamp test'
+Create an entry with all fields zero or blank, except for the
+text field, which is set to the text "stamp test", and the timestamp,
+which is set to the current time.
+.TP 8
+.B pseudolog -D -r succeed -F '%p' -O p
+Display all paths for which operations succeeded, sorted by path value.
+
+.SH ENVIRONMENT
+The only environment variable supported by
+.I pseudolog
+is:
+.TP 8
+.B PSEUDO_PREFIX
+If set, the variable
+.B PSEUDO_PREFIX
+is used to determine the path to use to find the
+.I logs.db
+database file, in
+.BR PSEUDO_PREFIX /var/pseudo.
+
+.SH BUGS
+The user might think our intent is to replace all of SQL. It's not. If the
+options here aren't enough, rather than adding more options to this already
+fairly elaborate program, just do raw SQL queries on the
+.I logs.db
+file.
+
+The formatting options are handled by converting them into
+.I printf(3)
+format strings, without much checking. As a result, it
+is possible for a malformed format string to cause
+.I printf()
+to explode unexpectedly.
+
+.SH SEE ALSO
+pseudo(1), sqlite3(1)
diff --git a/pseudolog.c b/pseudolog.c
new file mode 100644
index 0000000..ced421d
--- /dev/null
+++ b/pseudolog.c
@@ -0,0 +1,901 @@
+/*
+ * pseudolog.c, pseudo database viewer (preliminary)
+ *
+ * Copyright (c) 2008-2010 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This program 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/* We need _XOPEN_SOURCE for strptime(), but if we define that,
+ * we then don't get S_IFSOCK... _GNU_SOURCE turns on everything. */
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_client.h"
+#include "pseudo_db.h"
+
+static int opt_D = 0;
+static int opt_U = 0;
+static int opt_l = 0;
+
+/* when the client is linked with pseudo_wrappers, these are defined there.
+ * when it is linked with pseudo_server, though, we have to provide different
+ * versions (pseudo_wrappers must not be linked with the server, or Bad Things
+ * happen).
+ */
+void pseudo_magic(void) { }
+void pseudo_antimagic(void) { }
+
+static void display(log_entry *, char *format);
+static int format_scan(char *format);
+
+void
+usage(int status) {
+ static char *options[] = {
+ "a access (rwax)",
+ "c client pid",
+ "d device number",
+ "f file descriptor",
+ "g gid",
+ "G tag (text)",
+ "i inode number",
+ "I id (database row)",
+ "m permission bits (octal)",
+ "M file mode (octal)",
+ "o operation (e.g. 'open')",
+ "O order by (< DESC > ASC)",
+ "p file path",
+ "P program",
+ "r result (e.g. 'succeed')",
+ "s timestamp",
+ "S severity",
+ "t type (like find -type)",
+ "T text (text field)",
+ "u uid",
+ "y type (op/ping/shutown)",
+ NULL,
+ };
+ FILE *f = (status == EXIT_SUCCESS) ? stdout : stderr;
+ int i;
+
+ fputs("pseudolog: create or report log entries. usage:\n", f);
+ fputs("pseudolog -l [SPECIFIERS] -- create entries\n", f);
+ fputs("pseudolog [-U] [-F format] [SPECIFIERS] -- report entries\n", f);
+ fputs("pseudolog -D [SPECIFIERS] -- delete entries\n", f);
+ fputs("shared options: [-P prefix] [-E timeformat] [-x flags]\n", f);
+ fputs(" format is a printf-like format string using the option letters\n", f);
+ fputs(" listed below as format specifiers for the corresponding field.\n", f);
+ fputs(" timeformat is a strftime-like format string, the default is '%x %X'.\n", f);
+ fputs(" prefix is the PSEUDO_PREFIX in which to find the database.\n", f);
+ fputs(" flags are characters from the same debug flag set used by pseudo.\n", f);
+ fputs("\n", f);
+ fputs("SPECIFIERS are options of the form -X <value>, where X is one of\n", f);
+ fputs("the following option letters, and value is the value to match.\n", f);
+ fputs("values may be prefixed with ! (not equal to), > (greater than),\n", f);
+ fputs("< (less than), & (bitwise and), ~ (LIKE match, anchored at both\n", f);
+ fputs("ends, text fields only), ^ (NOT LIKE match, anchored at both\n", f);
+ fputs("ends, or % (LIKE match, text fields only).\n", f);
+ fputs("\n", f);
+ fputs("OPTION LETTERS:\n", f);
+ for (i = 0; options[i]; ++i) {
+ fprintf(f, " %-28s%s", options[i], (i % 2) ? "\n" : " ");
+ }
+ if (i % 2 == 1) {
+ fprintf(f, "\n");
+ }
+ exit(status);
+}
+
+pseudo_query_field_t opt_to_field[UCHAR_MAX + 1] = {
+ ['a'] = PSQF_ACCESS,
+ ['c'] = PSQF_CLIENT,
+ ['d'] = PSQF_DEV,
+ ['f'] = PSQF_FD,
+ ['g'] = PSQF_GID,
+ ['G'] = PSQF_TAG,
+ ['I'] = PSQF_ID,
+ ['i'] = PSQF_INODE,
+ ['m'] = PSQF_PERM,
+ ['M'] = PSQF_MODE,
+ ['o'] = PSQF_OP,
+ ['O'] = PSQF_ORDER,
+ ['p'] = PSQF_PATH,
+ ['r'] = PSQF_RESULT,
+ ['R'] = PSQF_PROGRAM,
+ ['s'] = PSQF_STAMP,
+ ['S'] = PSQF_SEVERITY,
+ ['t'] = PSQF_FTYPE,
+ ['T'] = PSQF_TEXT,
+ ['u'] = PSQF_UID,
+ ['y'] = PSQF_TYPE,
+};
+
+pseudo_query_type_t
+plog_query_type(char **string) {
+ pseudo_query_type_t type = PSQT_EXACT;
+ if (!string || !*string)
+ return PSQT_UNKNOWN;
+ switch (**string) {
+ case '\0':
+ pseudo_diag("Error: Value may not be an empty string.");
+ return PSQT_UNKNOWN;
+ break;
+ case '>':
+ type = PSQT_GREATER;
+ ++*string;
+ break;
+ case '<':
+ type = PSQT_LESS;
+ ++*string;
+ break;
+ case '!':
+ type = PSQT_NOTEQUAL;
+ ++*string;
+ break;
+ case '=':
+ ++*string;
+ break;
+ case '&':
+ type = PSQT_BITAND;
+ ++*string;
+ break;
+ case '%':
+ type = PSQT_LIKE;
+ ++*string;
+ break;
+ case '^':
+ type = PSQT_NOTLIKE;
+ ++*string;
+ break;
+ case '~':
+ type = PSQT_SQLPAT;
+ ++*string;
+ break;
+ case '\\':
+ /* no special type, but allows one of the others to be the
+ * first character of the effective string
+ */
+ ++*string;
+ break;
+ }
+ if (opt_l && type != PSQT_EXACT) {
+ pseudo_diag("Error: Non-exact match requested while trying to create a log entry.\n");
+ type = PSQT_UNKNOWN;
+ }
+ return type;
+}
+
+static char *time_formats[] = {
+ "%s",
+ "%F %r",
+ "%F %T",
+ "%m-%d %r",
+ "%m-%d %T",
+ "%r",
+ "%T",
+ NULL,
+};
+static char *timeformat = "%X";
+
+mode_t
+parse_file_type(char *string) {
+ switch (*string) {
+ case 'b':
+ return S_IFBLK;
+ break;
+ case 'c':
+ return S_IFCHR;
+ break;
+ case 'd':
+ return S_IFDIR;
+ break;
+ case '-': /* FALLTHROUGH */
+ case 'f':
+ return S_IFREG;
+ break;
+ case 'l':
+ return S_IFLNK;
+ break;
+ case 'p':
+ return S_IFIFO;
+ break;
+ case 's':
+ return S_IFSOCK;
+ break;
+ default:
+ pseudo_diag("unknown file type %c; should be one of [-bcdflps]\n",
+ isprint(*string) ? *string : '?');
+ return -1;
+ break;
+ }
+}
+
+mode_t
+parse_partial_mode(char *string) {
+ mode_t mode = 0;
+ switch (string[0]) {
+ case 'r':
+ mode |= 04;
+ break;
+ case '-':
+ break;
+ default:
+ pseudo_diag("unknown mode character: %c\n", string[0]);
+ return -1;
+ break;
+ }
+ switch (string[1]) {
+ case 'w':
+ mode |= 02;
+ break;
+ case '-':
+ break;
+ default:
+ pseudo_diag("unknown mode character: %c\n", string[1]);
+ return -1;
+ break;
+ }
+ switch (string[2]) {
+ case 'x':
+ mode |= 01;
+ break;
+ case 't': /* FALLTHROUGH */
+ case 's':
+ mode |= 011;
+ break;
+ case 'T': /* FALLTHROUGH */
+ case 'S':
+ mode |= 010;
+ break;
+ case '-':
+ break;
+ default:
+ pseudo_diag("unknown mode character: %c\n", string[2]);
+ return -1;
+ break;
+ }
+ return mode;
+}
+
+mode_t
+parse_mode_string(char *string) {
+ size_t len = strlen(string);
+ mode_t mode = 0;
+ mode_t bits = 0;
+
+ if (len != 9 && len != 10) {
+ pseudo_diag("mode strings must be of the form [-]rwxr-xr-x\n");
+ return -1;
+ }
+ if (len == 10) {
+ mode |= parse_file_type(string);
+ ++string;
+ if (mode == (mode_t) -1) {
+ pseudo_diag("mode strings with a file type must use a valid type [-bcdflps]\n");
+ return -1;
+ }
+ }
+ bits = parse_partial_mode(string);
+ if (bits == (mode_t) -1)
+ return -1;
+ if (bits & 010) {
+ mode |= S_ISUID;
+ bits &= ~010;
+ }
+ mode |= bits << 6;
+ string += 3;
+ bits = parse_partial_mode(string);
+ if (bits == (mode_t) -1)
+ return -1;
+ if (bits & 010) {
+ mode |= S_ISGID;
+ bits &= ~010;
+ }
+ mode |= bits << 3;
+ string += 3;
+ bits = parse_partial_mode(string);
+ if (bits == (mode_t) -1)
+ return -1;
+ if (bits & 010) {
+ mode |= S_ISVTX;
+ bits &= ~010;
+ }
+ mode |= bits;
+ return mode;
+}
+
+static time_t
+parse_timestamp(char *string) {
+ time_t stamp_sec;
+ struct tm stamp_tm;
+ int i;
+ char *s;
+ char timebuf[4096];
+
+ stamp_sec = time(0);
+
+ /* try the user's provided time format first, if there is one: */
+ localtime_r(&stamp_sec, &stamp_tm);
+ s = strptime(string, timeformat, &stamp_tm);
+ if (s && !*s) {
+ return mktime(&stamp_tm);
+ }
+
+ for (i = 0; time_formats[i]; ++i) {
+ char *s;
+ localtime_r(&stamp_sec, &stamp_tm);
+ s = strptime(string, time_formats[i], &stamp_tm);
+ if (s && !*s) {
+ break;
+ }
+ }
+ if (!time_formats[i]) {
+ pseudo_diag("Couldn't parse <%s> as a time. Current time in known formats is:\n",
+ string);
+ localtime_r(&stamp_sec, &stamp_tm);
+ for (i = 0; time_formats[i]; ++i) {
+ strftime(timebuf, sizeof(timebuf), time_formats[i], &stamp_tm);
+ pseudo_diag("\t%s\n", timebuf);
+ }
+ pseudo_diag("Or, specify your own with -E; see strptime(3).\n");
+ return -1;
+ }
+ return mktime(&stamp_tm);
+}
+
+pseudo_query_t *
+plog_trait(int opt, char *string) {
+ pseudo_query_t *new_trait;
+ char *endptr;
+
+ if (opt < 0 || opt > UCHAR_MAX) {
+ pseudo_diag("Unknown/invalid option value: %d\n", opt);
+ return 0;
+ }
+ if (!opt_to_field[opt]) {
+ if (isprint(opt)) {
+ pseudo_diag("Unknown option: -%c\n", opt);
+ } else {
+ pseudo_diag("Unknown option: 0x%02x\n", opt);
+ }
+ return 0;
+ }
+ if (!*string) {
+ pseudo_diag("invalid empty string for -%c\n", opt);
+ return 0;
+ }
+ new_trait = calloc(sizeof(*new_trait), 1);
+ if (!new_trait) {
+ pseudo_diag("Couldn't allocate requested trait (for -%c %s)\n",
+ opt, string ? string : "<nil>");
+ return 0;
+ }
+ new_trait->field = opt_to_field[opt];
+ new_trait->type = plog_query_type(&string);
+ if (new_trait->type == PSQT_UNKNOWN) {
+ pseudo_diag("Couldn't comprehend trait type for '%s'\n",
+ string ? string : "<nil>");
+ free(new_trait);
+ return 0;
+ }
+ switch (new_trait->field) {
+ case PSQF_ACCESS:
+ new_trait->data.ivalue = pseudo_access_fopen(string);
+ if (new_trait->data.ivalue == (unsigned long long) -1) {
+ pseudo_diag("access flags should be specified like fopen(3) mode strings (or x for exec).\n");
+ free(new_trait);
+ return 0;
+ }
+ break;
+ case PSQF_FTYPE:
+ /* special magic: allow file types ala find */
+ /* This is implemented by additional magic over in the database code */
+ /* must not be more than one character. The test against
+ * the first character is because in theory, if the
+ * first character is the terminating NUL, we may not
+ * access the second. */
+ if (string[0] && string[1]) {
+ pseudo_diag("file type must be a single character [-bcdflps].\n");
+ free(new_trait);
+ return 0;
+ }
+ new_trait->data.ivalue = parse_file_type(string);
+ if (new_trait->data.ivalue == (mode_t) -1) {
+ free(new_trait);
+ return 0;
+ }
+ break;
+ case PSQF_OP:
+ new_trait->data.ivalue = pseudo_op_id(string);
+ break;
+ case PSQF_ORDER:
+ if (string[0] && string[1]) {
+ pseudo_diag("order type must be a single specifier character.\n");
+ free(new_trait);
+ return 0;
+ }
+ new_trait->data.ivalue = opt_to_field[(unsigned char) string[0]];
+ if (!new_trait->data.ivalue) {
+ pseudo_diag("Unknown field type: %c\n", string[0]);
+ }
+ break;
+ case PSQF_RESULT:
+ new_trait->data.ivalue = pseudo_res_id(string);
+ break;
+ case PSQF_SEVERITY:
+ new_trait->data.ivalue = pseudo_sev_id(string);
+ break;
+ case PSQF_STAMP:
+ new_trait->data.ivalue = parse_timestamp(string);
+ if ((time_t) new_trait->data.ivalue == (time_t) -1) {
+ free(new_trait);
+ return 0;
+ }
+ break;
+ case PSQF_TYPE:
+ new_trait->data.ivalue = pseudo_msg_type_id(string);
+ break;
+ case PSQF_CLIENT:
+ case PSQF_DEV:
+ case PSQF_FD:
+ case PSQF_GID:
+ case PSQF_INODE:
+ case PSQF_UID:
+ new_trait->data.ivalue = strtoll(string, &endptr, 0);
+ if (*endptr) {
+ pseudo_diag("Unexpected garbage after number (%llu): '%s'\n",
+ new_trait->data.ivalue, endptr);
+ free(new_trait);
+ return 0;
+ }
+ break;
+ case PSQF_MODE:
+ case PSQF_PERM:
+ new_trait->data.ivalue = strtoll(string, &endptr, 8);
+ if (!*endptr) {
+ break;
+ }
+ /* maybe it's a mode string? */
+ new_trait->data.ivalue = parse_mode_string(string);
+ if (new_trait->data.ivalue == (mode_t) -1) {
+ free(new_trait);
+ return 0;
+ }
+ if (new_trait->field == PSQF_PERM) {
+ /* mask out file type */
+ new_trait->data.ivalue &= ~S_IFMT;
+ }
+ break;
+ case PSQF_PATH: /* FALLTHROUGH */
+ case PSQF_PROGRAM: /* FALLTHROUGH */
+ case PSQF_TEXT: /* FALLTHROUGH */
+ case PSQF_TAG:
+ /* Plain strings */
+ new_trait->data.svalue = strdup(string);
+ break;
+ default:
+ pseudo_diag("I don't know how I got here. Unknown field type %d.\n",
+ new_trait->field);
+ free(new_trait);
+ return 0;
+ break;
+ }
+ return new_trait;
+}
+
+/* You can either create a query or create a log entry. They use very
+ * similar syntax, but:
+ * - if you're making a query, you can use >, <, etc.
+ * - if you're logging, you can't.
+ * This is tracked by recording whether any non-exact relations
+ * have been requested ("query_only"), and refusing to set the -l
+ * flag if they have, and refusing to accept any such relation
+ * if the -l flag is already set.
+ */
+int
+main(int argc, char **argv) {
+ pseudo_query_t *traits = 0, *current = 0, *new_trait = 0;
+ char *s;
+ log_history history;
+ int query_only = 0;
+ int o;
+ int bad_args = 0;
+ char *format = "%s %-12.12R %-4y %7o: [mode %04m, %2a] %p %T";
+
+ while ((o = getopt(argc, argv, "vla:c:d:DE:f:F:g:G:hi:I:m:M:o:O:p:P:r:R:s:S:t:T:u:Ux:y:")) != -1) {
+ switch (o) {
+ case 'P':
+ s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW);
+ if (!s)
+ pseudo_diag("Can't resolve prefix path '%s'\n", optarg);
+ pseudo_set_value("PSEUDO_PREFIX", s);
+ break;
+ case 'v':
+ pseudo_debug_verbose();
+ break;
+ case 'x':
+ pseudo_debug_set(optarg);
+ break;
+ case 'l':
+ opt_l = 1;
+ break;
+ case 'D':
+ opt_D = 1;
+ query_only = 1;
+ break;
+ case 'E':
+ timeformat = strdup(optarg);
+ break;
+ case 'F':
+ /* disallow specifying -F with -l */
+ format = strdup(optarg);
+ query_only = 1;
+ break;
+ case 'U':
+ opt_U = 1;
+ query_only = 1;
+ break;
+ case 'I': /* PSQF_ID */
+ query_only = 1;
+ /* FALLTHROUGH */
+ case 'a': /* PSQF_ACCESS */
+ case 'c': /* PSQF_CLIENT */
+ case 'd': /* PSQF_DEV */
+ case 'f': /* PSQF_FD */
+ case 'g': /* PSQF_GID */
+ case 'G': /* PSQF_TAG */
+ case 'i': /* PSQF_INODE */
+ case 'm': /* PSQF_PERM */
+ case 'M': /* PSQF_MODE */
+ case 'o': /* PSQF_OP */
+ case 'O': /* PSQF_ORDER */
+ case 'p': /* PSQF_PATH */
+ case 'r': /* PSQF_RESULT */
+ case 'R': /* PSQF_PROGRAM */
+ case 's': /* PSQF_STAMP */
+ case 'S': /* PSQF_SEVERITY */
+ case 't': /* PSQF_FTYPE */
+ case 'T': /* PSQF_TEXT */
+ case 'u': /* PSQF_UID */
+ case 'y': /* PSQF_TYPE */
+ new_trait = plog_trait(o, optarg);
+ if (!new_trait) {
+ bad_args = 1;
+ }
+ break;
+ case 'h':
+ usage(EXIT_SUCCESS);
+ break;
+ case '?': /* FALLTHROUGH */
+ default:
+ fprintf(stderr, "unknown option '%c'\n", optopt);
+ usage(EXIT_FAILURE);
+ break;
+ }
+ if (new_trait) {
+ if (current) {
+ current->next = new_trait;
+ current = current->next;
+ } else {
+ traits = new_trait;
+ current = new_trait;
+ }
+ new_trait = 0;
+ }
+ }
+ pseudo_debug_flags_finalize();
+
+ if (optind < argc) {
+ pseudo_diag("Error: Extra arguments not associated with any option.\n");
+ usage(EXIT_FAILURE);
+ }
+
+ if (query_only && opt_l) {
+ pseudo_diag("Error: -l cannot be used with query-only options or flags.\n");
+ bad_args = 1;
+ }
+
+ /* should be set only if we have already diagnosed the bad arguments. */
+ if (bad_args)
+ exit(EXIT_FAILURE);
+
+ if (!pseudo_get_prefix(argv[0])) {
+ pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!pseudo_get_bindir()) {
+ pseudo_diag("Can't figure out bindir. Set PSEUDO_BINDIR.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!pseudo_get_libdir()) {
+ pseudo_diag("Can't figure out libdir. Set PSEUDO_LIBDIR.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!pseudo_get_localstatedir()) {
+ pseudo_diag("Can't figure out localstatedir. Set PSEUDO_LOCALSTATEDIR.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (opt_l) {
+ pdb_log_traits(traits);
+ } else {
+ int fields;
+ fields = format_scan(format);
+ if (fields == -1) {
+ pseudo_diag("couldn't parse format string (%s).\n", format);
+ return EXIT_FAILURE;
+ }
+ if (opt_D) {
+ if (pdb_delete(traits, fields)) {
+ pseudo_diag("errors occurred trying to delete entries.\n");
+ }
+ } else {
+ history = pdb_history(traits, fields, opt_U);
+ if (history) {
+ log_entry *e;
+ while ((e = pdb_history_entry(history)) != NULL) {
+ display(e, format);
+ log_entry_free(e);
+ }
+ pdb_history_free(history);
+ } else {
+ pseudo_diag("could not retrieve history.\n");
+ return EXIT_FAILURE;
+ }
+ }
+ }
+ return 0;
+}
+
+/* print a single member of log, based on a single format specifier;
+ * returns the address of the last character of the format specifier.
+ */
+static char *
+format_one(log_entry *e, char *format) {
+ char fmtbuf[256];
+ size_t len = strcspn(format, "acdfgGimMoprRsStTuy"), real_len;
+ char scratch[4096];
+ time_t stamp_sec;
+ struct tm stamp_tm;
+ char *s;
+
+ if (!e || !format) {
+ pseudo_diag("invalid log entry or format specifier.\n");
+ return 0;
+ }
+ real_len = snprintf(fmtbuf, sizeof(fmtbuf), "%.*s", (int) len + 1, format);
+ if (real_len >= sizeof(fmtbuf) - 1) {
+ pseudo_diag("Format string way too long starting at %.10s",
+ format - 1);
+ return 0;
+ }
+ /* point to the last character */
+ s = fmtbuf + real_len - 1;
+
+ /* The * modifier for width or precision requires additional
+ * parameters -- this doesn't make sense here.
+ */
+ if (strchr(fmtbuf, '*') || strchr(fmtbuf, 'l') || strchr(fmtbuf, 'h')) {
+ pseudo_diag("Sorry, you can't use *, h, or l format modifiers.\n");
+ return 0;
+ }
+
+ switch (*s) {
+ case 'a': /* PSQF_ACCESS */
+ *scratch = '\0';
+ if (e->access == -1) {
+ strcpy(scratch, "invalid");
+ } else if (e->access != 0) {
+ if (e->access & PSA_READ) {
+ strcpy(scratch, "r");
+ if (e->access & PSA_WRITE)
+ strcat(scratch, "+");
+ } else if (e->access & PSA_WRITE) {
+ if (e->access & PSA_APPEND) {
+ strcpy(scratch, "a");
+ } else {
+ strcpy(scratch, "w");
+ }
+ if (e->access & PSA_READ)
+ strcat(scratch, "+");
+ } else if (e->access & PSA_EXEC) {
+ strcpy(scratch, "x");
+ }
+ /* this should be impossible... should. */
+ if (e->access & PSA_APPEND && !(e->access & PSA_WRITE)) {
+ strcat(scratch, "?a");
+ }
+ } else {
+ strcpy(scratch, "-");
+ }
+ strcpy(s, "s");
+ printf(fmtbuf, scratch);
+ break;
+ case 'c': /* PSQF_CLIENT */
+ strcpy(s, "d");
+ printf(fmtbuf, (int) e->client);
+ break;
+ case 'd': /* PSQF_DEV */
+ strcpy(s, "d");
+ printf(fmtbuf, (int) e->dev);
+ break;
+ case 'f': /* PSQF_FD */
+ strcpy(s, "d");
+ printf(fmtbuf, (int) e->fd);
+ break;
+ case 'g': /* PSQF_GID */
+ strcpy(s, "d");
+ printf(fmtbuf, (int) e->gid);
+ break;
+ case 'G': /* PSQF_TAG */
+ strcpy(s, "s");
+ printf(fmtbuf, e->tag ? e->tag : "");
+ break;
+ case 'i': /* PSQF_INODE */
+ strcpy(s, "llu");
+ printf(fmtbuf, (unsigned long long) e->ino);
+ break;
+ case 'm': /* PSQF_PERM */
+ strcpy(s, "o");
+ printf(fmtbuf, (unsigned int) e->mode & ALLPERMS);
+ break;
+ case 'M': /* PSQF_MODE */
+ strcpy(s, "o");
+ printf(fmtbuf, (unsigned int) e->mode);
+ break;
+ case 'o': /* PSQF_OP */
+ strcpy(s, "s");
+ printf(fmtbuf, pseudo_op_name(e->op));
+ break;
+ case 'p': /* PSQF_PATH */
+ strcpy(s, "s");
+ printf(fmtbuf, e->path ? e->path : "");
+ break;
+ case 'r': /* PSQF_RESULT */
+ strcpy(s, "s");
+ printf(fmtbuf, pseudo_res_name(e->result));
+ break;
+ case 'R': /* PSQF_PROGRAM */
+ strcpy(s, "s");
+ printf(fmtbuf, e->program ? e->program : "");
+ break;
+ case 's': /* PSQF_STAMP */
+ strcpy(s, "s");
+ stamp_sec = e->stamp;
+ localtime_r(&stamp_sec, &stamp_tm);
+ strftime(scratch, sizeof(scratch), timeformat, &stamp_tm);
+ printf(fmtbuf, scratch);
+ break;
+ case 'S': /* PSQF_SEVERITY */
+ strcpy(s, "s");
+ printf(fmtbuf, pseudo_sev_name(e->severity));
+ break;
+ case 't': /* PSQF_FTYPE */
+ strcpy(s, "s");
+ if (S_ISREG(e->mode)) {
+ strcpy(scratch, "file");
+ } if (S_ISLNK(e->mode)) {
+ strcpy(scratch, "link");
+ } else if (S_ISDIR(e->mode)) {
+ strcpy(scratch, "dir");
+ } else if (S_ISFIFO(e->mode)) {
+ strcpy(scratch, "fifo");
+ } else if (S_ISBLK(e->mode)) {
+ strcpy(scratch, "block");
+ } else if (S_ISCHR(e->mode)) {
+ strcpy(scratch, "char");
+ } else {
+ snprintf(scratch, sizeof(scratch), "?%o", (unsigned int) e->mode & S_IFMT);
+ }
+ printf(fmtbuf, scratch);
+ break;
+ case 'T': /* PSQF_TEXT */
+ strcpy(s, "s");
+ printf(fmtbuf, e->text ? e->text : "");
+ break;
+ case 'u': /* PSQF_UID */
+ strcpy(s, "d");
+ printf(fmtbuf, (int) e->uid);
+ break;
+ case 'y': /* PSQF_TYPE */
+ strcpy(s, "s");
+ printf(fmtbuf, pseudo_msg_type_name(e->type));
+ break;
+ }
+ return format + len;
+}
+
+static int
+format_scan(char *format) {
+ char *s;
+ size_t len;
+ int fields = 0;
+ pseudo_query_field_t field;
+
+ for (s = format; (s = strchr(s, '%')) != NULL; ++s) {
+ len = strcspn(s, "acdfgGimMoprRsStTuy");
+ s += len;
+ if (!*s) {
+ pseudo_diag("Unknown format: '%.3s'\n",
+ (s - len));
+ return -1;
+ }
+ field = opt_to_field[(unsigned char) *s];
+ switch (field) {
+ case PSQF_PERM: /* FALLTHROUGH */
+ case PSQF_FTYPE:
+ fields |= (1 << PSQF_MODE);
+ break;
+ case PSQF_ACCESS: /* FALLTHROUGH */
+ case PSQF_CLIENT: /* FALLTHROUGH */
+ case PSQF_DEV: /* FALLTHROUGH */
+ case PSQF_FD: /* FALLTHROUGH */
+ case PSQF_GID: /* FALLTHROUGH */
+ case PSQF_TAG: /* FALLTHROUGH */
+ case PSQF_INODE: /* FALLTHROUGH */
+ case PSQF_MODE: /* FALLTHROUGH */
+ case PSQF_OP: /* FALLTHROUGH */
+ case PSQF_PATH: /* FALLTHROUGH */
+ case PSQF_PROGRAM: /* FALLTHROUGH */
+ case PSQF_RESULT: /* FALLTHROUGH */
+ case PSQF_STAMP: /* FALLTHROUGH */
+ case PSQF_SEVERITY: /* FALLTHROUGH */
+ case PSQF_TEXT: /* FALLTHROUGH */
+ case PSQF_TYPE: /* FALLTHROUGH */
+ case PSQF_UID:
+ fields |= (1 << field);
+ break;
+ case '\0':
+ /* if there are no more formats, that may be wrong, but
+ * we can ignore it here
+ */
+ break;
+ default:
+ pseudo_diag("error: invalid format specifier %c (at %s)\n", *s, s);
+ return -1;
+ break;
+ }
+ }
+ return fields;
+}
+
+static void
+display(log_entry *e, char *format) {
+ for (; *format; ++format) {
+ switch (*format) {
+ case '%':
+ format = format_one(e, format);
+ if (!format)
+ return;
+ break;
+ default:
+ putchar(*format);
+ break;
+ }
+ }
+ putchar('\n');
+}
diff --git a/run_tests.sh b/run_tests.sh
new file mode 100755
index 0000000..0eced9f
--- /dev/null
+++ b/run_tests.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+opt_verbose=
+
+usage()
+{
+ echo >&2 "usage:"
+ echo >&2 " run_tests [-v|--verbose]"
+ exit 1
+}
+
+for arg
+do
+ case $arg in
+ --) shift; break ;;
+ -v | --verbose)
+ opt_verbose=-v
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+
+#The tests will be run on the build dir, not the installed versions
+#This requires to following be set properly.
+export PSEUDO_PREFIX=${PWD}
+
+num_tests=0
+num_passed_tests=0
+
+for file in test/test*.sh
+do
+ filename=${file#test/}
+ let num_tests++
+ mkdir -p var/pseudo
+ ./bin/pseudo $file ${opt_verbose}
+ if [ "$?" -eq "0" ]; then
+ let num_passed_tests++
+ if [ "${opt_verbose}" == "-v" ]; then
+ echo "${filename%.sh}: Passed."
+ fi
+ else
+ echo "${filename/%.sh}: Failed."
+ fi
+ rm -rf var/pseudo/*
+done
+echo "${num_passed_tests}/${num_tests} test(s) passed."
+
diff --git a/table_templates/pseudo_tables.c b/table_templates/pseudo_tables.c
new file mode 100644
index 0000000..f802187
--- /dev/null
+++ b/table_templates/pseudo_tables.c
@@ -0,0 +1,43 @@
+@name pseudo_tables.c
+@header
+/* Tables matching enums to strings */
+
+/* This file is generated and should not be modified. See the maketables
+ * script if you want to modify this. */
+
+#include "pseudo_tables.h"
+
+@body
+/* tables for ${name} */
+
+static const char *${name}_id_to_name[] = {
+ "none",
+ ${names},
+ NULL
+};
+${column_names}
+
+/* functions for ${name} */
+extern const char *
+pseudo_${name}_name(pseudo_${name}_t id) {
+ if (id < 0 || id >= ${prefix}_MAX)
+ return "unknown";
+ return ${name}_id_to_name[id];
+}
+
+extern pseudo_${name}_t
+pseudo_${name}_id(const char *name) {
+ int id;
+
+ if (!name)
+ return -1;
+
+ for (id = 0; id < ${prefix}_MAX; ++id)
+ if (!strcmp(${name}_id_to_name[id], name))
+ return id;
+
+ return -1;
+}
+${column_funcs}
+
+@footer
diff --git a/table_templates/pseudo_tables.h b/table_templates/pseudo_tables.h
new file mode 100644
index 0000000..6d0e355
--- /dev/null
+++ b/table_templates/pseudo_tables.h
@@ -0,0 +1,24 @@
+@name pseudo_tables.h
+@header
+/* standard ranges/values/keys */
+
+/* This file is generated and should not be modified. See the maketables
+ * script if you want to modify this. */
+
+/* NULL, strcmp */
+#include <string.h>
+@body
+/* tables for ${name} */
+${comment}
+typedef enum {
+ ${prefix}_UNKNOWN = -1,
+ ${prefix}_NONE = 0,
+ ${enums},
+ ${prefix}_MAX
+} pseudo_${name}_t;
+${flag_enums}
+extern const char *pseudo_${name}_name(pseudo_${name}_t);
+extern pseudo_${name}_t pseudo_${name}_id(const char *);
+${column_protos}
+
+@footer
diff --git a/templatefile.py b/templatefile.py
new file mode 100644
index 0000000..2789b22
--- /dev/null
+++ b/templatefile.py
@@ -0,0 +1,111 @@
+from string import Template
+import os
+
+class TemplateFile:
+ """A template for creating a source file"""
+
+ def __init__(self, path):
+ # default values...
+ # no name or file yet
+ self.name = ''
+ self.sections = {}
+ self.file = None
+ self.path = None
+ # open a new file for each item
+ self.file_per_item = False
+
+ # empty footer if none specified:
+ self.sections['footer'] = []
+ # empty per-port if none specified:
+ self.sections['port'] = []
+
+ # lines appended to body by default
+ self.sections['body'] = []
+ current = self.sections['body']
+
+ self.template = open(path)
+ for line in self.template:
+ line = line.rstrip()
+ if line.startswith('@'):
+ if ' ' in line:
+ leading, trailing = line.split(' ', 1)
+ else:
+ leading, trailing = line, None
+
+ if leading == '@name':
+ if not trailing:
+ raise Exception("@name requires a file name.")
+ self.path = trailing
+ if '$' in self.path:
+ self.file_per_item = True
+ else:
+ section = leading[1:]
+ if section not in self.sections:
+ self.sections[section] = []
+ current = self.sections[section]
+ else:
+ current.append(line)
+ self.template.close()
+ for section, data in self.sections.items():
+ if len(data) > 0:
+ self.sections[section] = Template("\n".join(data))
+ else:
+ self.sections[section] = None
+
+ # You need a file if this isn't a file-per-item
+ if not self.file_per_item:
+ self.file = open(self.path, 'w')
+
+ def close(self):
+ """Close the associated file."""
+ if self.file:
+ self.file.close()
+ self.file = None
+
+ def __repr__(self):
+ strings = []
+ if self.file_per_item:
+ strings.append("path: %s (per item)" % self.path)
+ else:
+ strings.append("path: %s" % self.path)
+ for name, data in self.sections.items():
+ strings.append("%s:" % name)
+ strings.append(data.safe_substitute({}))
+ return "\n".join(strings)
+
+ def get_file(self, item):
+ if self.file_per_item:
+ if not item:
+ return
+ path = Template(self.path).safe_substitute(item)
+ if os.path.exists(path):
+ # print "We don't overwrite existing files."
+ return
+ self.file = open(path, 'w')
+ if not self.file:
+ print "Couldn't open '%s' (expanded from %s), " \
+ "not emitting '%s'." % \
+ (path, self.path, template)
+ return
+
+ def emit(self, template, item=None):
+ """Emit a template, with optional interpolation of an item."""
+ if template == "copyright":
+ # hey, at least it's not a global variable, amirite?
+ self.get_file(item)
+ if self.file:
+ self.file.write(TemplateFile.copyright)
+ elif template in self.sections:
+ templ = self.sections[template]
+ if templ:
+ self.get_file(item)
+ if self.file:
+ self.file.write(templ.safe_substitute(item))
+ self.file.write("\n")
+ else:
+ print "Warning: Unknown template '%s'." % template
+
+ if self.file_per_item:
+ if self.file:
+ self.file.close()
+ self.file = None
diff --git a/templates/func_deps b/templates/func_deps
new file mode 100644
index 0000000..ace0a2f
--- /dev/null
+++ b/templates/func_deps
@@ -0,0 +1,4 @@
+@name func_deps.mk
+${funcdeps}
+@header
+# Per-function extra dependencies
diff --git a/templates/guts b/templates/guts
new file mode 100644
index 0000000..e2c9880
--- /dev/null
+++ b/templates/guts
@@ -0,0 +1,16 @@
+@name ports/${port}/guts/${name}.c
+@header
+@body
+/*
+ * Copyright (c) ${date} Wind River Systems; see
+ * guts/COPYRIGHT for information.
+ *
+ * ${comment}
+ * ${rc_decl}
+ */
+
+ ${rc_assign} real_${name}(${call_args});
+
+/* ${rc_return}
+ * }
+ */
diff --git a/templates/port_deps b/templates/port_deps
new file mode 100644
index 0000000..8f53db7
--- /dev/null
+++ b/templates/port_deps
@@ -0,0 +1,5 @@
+@name port_deps.mk
+@port
+${portdeps}
+@header
+# port dependencies
diff --git a/templates/port_wrappers b/templates/port_wrappers
new file mode 100644
index 0000000..7ff7ff8
--- /dev/null
+++ b/templates/port_wrappers
@@ -0,0 +1,5 @@
+@name port_wrappers.c
+@header
+/* additional hand-written wrappers for ports which provide them */
+@port
+${include}
diff --git a/templates/pseudo_ports b/templates/pseudo_ports
new file mode 100644
index 0000000..113a2b6
--- /dev/null
+++ b/templates/pseudo_ports
@@ -0,0 +1,6 @@
+@name pseudo_ports.h
+@header
+/* #defines for port-specific hackery */
+@port
+${define}
+${portdefs}
diff --git a/templates/wrapfuncs.c b/templates/wrapfuncs.c
new file mode 100644
index 0000000..3859183
--- /dev/null
+++ b/templates/wrapfuncs.c
@@ -0,0 +1,95 @@
+@name pseudo_wrapfuncs.c
+@header
+/* wrapper functions. generated automatically. */
+
+/* IF YOU ARE SEEING COMPILER ERRORS IN THIS FILE:
+ * If you are seeing a whole lot of errors, make sure you aren't actually
+ * trying to compile pseudo_wrapfuncs.c directly. This file is #included
+ * from pseudo_wrappers.c, which has a lot of needed include files and
+ * static declarations.
+ */
+
+/* This file is generated and should not be modified. See the makewrappers
+ * script if you want to modify this. */
+@body
+
+static ${type} (*real_${name})(${decl_args}) = ${real_init};
+
+${maybe_skip}
+
+${type}
+${name}(${decl_args}) {
+ sigset_t saved;
+ ${variadic_decl}
+ ${rc_decl}
+ PROFILE_START;
+
+${maybe_async_skip}
+
+ if (!pseudo_check_wrappers() || !real_$name) {
+ /* rc was initialized to the "failure" value */
+ pseudo_enosys("${name}");
+ PROFILE_DONE;
+ ${rc_return}
+ }
+
+ ${variadic_start}
+
+ if (pseudo_disabled) {
+ ${rc_assign} (*real_${name})(${call_args});
+ ${variadic_end}
+ PROFILE_DONE;
+ ${rc_return}
+ }
+
+ pseudo_debug(PDBGF_WRAPPER, "wrapper called: ${name}\n");
+ pseudo_sigblock(&saved);
+ pseudo_debug(PDBGF_WRAPPER | PDBGF_VERBOSE, "${name} - signals blocked, obtaining lock\n");
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER, "${name} failed to get lock, giving EBUSY.\n");
+ PROFILE_DONE;
+ ${def_return}
+ }
+
+ int save_errno;
+ if (antimagic > 0) {
+ /* call the real syscall */
+ pseudo_debug(PDBGF_SYSCALL, "${name} calling real syscall.\n");
+ ${rc_assign} (*real_${name})(${call_args});
+ } else {
+ ${fix_paths}
+ /* exec*() use this to restore the sig mask */
+ pseudo_saved_sigmask = saved;
+ ${rc_assign} wrap_$name(${call_args});
+ }
+ ${variadic_end}
+ save_errno = errno;
+ pseudo_droplock();
+ sigprocmask(SIG_SETMASK, &saved, NULL);
+ pseudo_debug(PDBGF_WRAPPER | PDBGF_VERBOSE, "${name} - yielded lock, restored signals\n");
+#if 0
+/* This can cause hangs on some recentish systems which use locale
+ * stuff for strerror...
+ */
+ pseudo_debug(PDBGF_WRAPPER, "wrapper completed: ${name} returns ${rc_format} (errno: %s)\n", ${rc_value}, strerror(save_errno));
+#endif
+ pseudo_debug(PDBGF_WRAPPER, "wrapper completed: ${name} returns ${rc_format} (errno: %d)\n", ${rc_value}, save_errno);
+ errno = save_errno;
+ PROFILE_DONE;
+ ${rc_return}
+}
+
+static ${type}
+wrap_${name}(${wrap_args}) {
+ $rc_decl
+ ${maybe_variadic_decl}
+ ${maybe_variadic_start}
+
+#include "ports/${port}/guts/${name}.c"
+
+ ${rc_return}
+}
+
+${end_maybe_skip}
diff --git a/templates/wrapfuncs.h b/templates/wrapfuncs.h
new file mode 100644
index 0000000..1ce4fcc
--- /dev/null
+++ b/templates/wrapfuncs.h
@@ -0,0 +1,12 @@
+@name pseudo_wrapfuncs.h
+@header
+/* wrapper functions. generated automatically. */
+
+/* This file is generated and should not be modified. See the makewrappers
+ * script if you want to modify this. */
+@body
+/* ${comment} */
+static ${type} wrap_${name}(${wrap_args});
+static ${type} (*real_${name})(${decl_args});
+${real_predecl}
+@footer
diff --git a/templates/wrapper_table b/templates/wrapper_table
new file mode 100644
index 0000000..bb30530
--- /dev/null
+++ b/templates/wrapper_table
@@ -0,0 +1,24 @@
+@name pseudo_wrapper_table.c
+@header
+/* The table of wrapper functions to populate */
+
+/* This file is generated and should not be modified. See the makewrappers
+ * script if you want to modify this. */
+typedef struct {
+ char *name; /* the name */
+ int (**real)(void); /* the underlying syscall */
+ int (*wrapper)(void); /* the wrapper from guts/name.c */
+ char *version; /* the version, if we know and care */
+} pseudo_function;
+
+static pseudo_function pseudo_functions[] = {
+@body
+ { /* ${comment}; */
+ "${name}${maybe_inode64}",
+ (int (**)(void)) &real_${name},
+ (int (*)(void)) wrap_${name},
+ ${version}
+ },
+@footer
+ { NULL, NULL, NULL, NULL },
+};
diff --git a/test/test-chroot.sh b/test/test-chroot.sh
new file mode 100755
index 0000000..02c9ff6
--- /dev/null
+++ b/test/test-chroot.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# Return vals: 2 - invalid arg list
+# 1 - chroot failed
+# 0 - chroot succeeded
+cat > chroot_test.c << EOF
+#include <unistd.h>
+int main(int argc, char *argv[]) {
+ if (argc != 2)
+ return 2;
+ return (chroot(argv[1]) == -1);
+}
+EOF
+
+gcc -o chroot_test chroot_test.c
+
+./chroot_test `pwd`
+
+if [ "$?" = "0" ]
+then
+ #echo "Passed."
+ rm -f chroot_test chroot_test.c
+ exit 0
+fi
+#echo "Failed"
+rm -f chroot_test chroot_test.c
+exit 1
diff --git a/test/test-dir-move.sh b/test/test-dir-move.sh
new file mode 100755
index 0000000..37182f3
--- /dev/null
+++ b/test/test-dir-move.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+mkdir d1
+touch d1/f1
+mv d1 d2
+fileuid=`\ls -n1 d2/f1 | awk '{ print $3 }'`
+if [ "$fileuid" == "$UID" ]
+then
+ #echo "Passed."
+ rm -rf d2
+ exit 0
+fi
+rm -rf d2
+#echo "Failed"
+exit 1
diff --git a/test/test-env_i.sh b/test/test-env_i.sh
new file mode 100755
index 0000000..c38cf1d
--- /dev/null
+++ b/test/test-env_i.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+env -i A=A B=B C=C env | grep -q "PSEUDO_"
+
+if [ "$?" = "0" ]
+then
+ #echo "Passed."
+ exit 0
+fi
+#echo "Failed"
+exit 1
diff --git a/test/test-execl.sh b/test/test-execl.sh
new file mode 100755
index 0000000..1a1c580
--- /dev/null
+++ b/test/test-execl.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+cat > execl_test.c << EOF
+#include <unistd.h>
+int main() {
+ return execl("/usr/bin/env", "/usr/bin/env", "A=A", "B=B", "C=C", NULL);
+}
+EOF
+
+gcc -o execl_test execl_test.c
+
+./execl_test | grep -q "C=C"
+
+if [ "$?" = "0" ]
+then
+ #echo "Passed."
+ rm -f execl_test execl_test.c
+ exit 0
+fi
+#echo "Failed"
+rm -f execl_test execl_test.c
+exit 1
diff --git a/test/test-pseudo_disable-fork-env_i.sh b/test/test-pseudo_disable-fork-env_i.sh
new file mode 100755
index 0000000..1e679aa
--- /dev/null
+++ b/test/test-pseudo_disable-fork-env_i.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# Verify normal operation...
+uid=`env -i id -u`
+gid=`env -i id -g`
+if [ $uid -ne 0 -o $gid -ne 0 ]; then
+ exit 1
+fi
+
+export PSEUDO_DISABLED=1
+# Verify we dropped OUT of pseudo control, even with env -i
+# This checks that env -i replacement functionality still works
+# as expected
+uid=`env -i id -u`
+gid=`env -i id -g`
+if [ $uid -eq 0 -o $gid -eq 0 ]; then
+ exit 1
+fi
+
+export PSEUDO_DISABLED=1
+# Verify we can change PSEUDO_DISABLED, even with env -i
+# This checks that env -i replacement functionality still works
+# as expected
+uid=`env -i PSEUDO_DISABLED=0 id -u`
+gid=`env -i PSEUDO_DISABLED=0 id -g`
+if [ $uid -ne 0 -o $gid -ne 0 ]; then
+ exit 1
+fi
+
+exit 0
diff --git a/test/test-pseudo_disable-fork.sh b/test/test-pseudo_disable-fork.sh
new file mode 100755
index 0000000..13a42a4
--- /dev/null
+++ b/test/test-pseudo_disable-fork.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Verify normal operation...
+uid=`id -u`
+gid=`id -g`
+if [ $uid -ne 0 -o $gid -ne 0 ]; then
+ exit 1
+fi
+
+export PSEUDO_DISABLED=1
+# Verify we dropped OUT of pseudo control
+uid=`id -u`
+gid=`id -g`
+if [ $uid -eq 0 -o $gid -eq 0 ]; then
+ exit 1
+fi
+
+exit 0
diff --git a/test/test-pseudo_unload-fork-env_i.sh b/test/test-pseudo_unload-fork-env_i.sh
new file mode 100755
index 0000000..d6658e0
--- /dev/null
+++ b/test/test-pseudo_unload-fork-env_i.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# Verify normal operation...
+uid=`env -i id -u`
+gid=`env -i id -g`
+if [ $uid -ne 0 -o $gid -ne 0 ]; then
+ exit 1
+fi
+
+export PSEUDO_UNLOAD=1
+# Verify we dropped OUT of pseudo control, even with env -i
+# This checks that env -i replacement functionality still works
+# as expected
+uid=`env -i id -u`
+gid=`env -i id -g`
+if [ $uid -eq 0 -o $gid -eq 0 ]; then
+ exit 1
+fi
+
+export PSEUDO_UNLOAD=1
+# Verify that once PSEUDO_UNLOAD has been issued, that
+# we can't restore pseudo into memory
+uid=`env -i PSEUDO_DISABLED=0 id -u`
+gid=`env -i PSEUDO_DISABLED=0 id -g`
+if [ $uid -eq 0 -o $gid -eq 0 ]; then
+ exit 1
+fi
+
+exit 0
diff --git a/test/test-pseudo_unload-fork.sh b/test/test-pseudo_unload-fork.sh
new file mode 100755
index 0000000..9f79198
--- /dev/null
+++ b/test/test-pseudo_unload-fork.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Verify normal operation...
+uid=`id -u`
+gid=`id -g`
+if [ $uid -ne 0 -o $gid -ne 0 ]; then
+ exit 1
+fi
+
+export PSEUDO_UNLOAD=1
+# Verify we dropped OUT of pseudo control
+uid=`id -u`
+gid=`id -g`
+if [ $uid -eq 0 -o $gid -eq 0 ]; then
+ exit 1
+fi
+
+exit 0
diff --git a/test/test-reexec-chroot.sh b/test/test-reexec-chroot.sh
new file mode 100755
index 0000000..bde93f1
--- /dev/null
+++ b/test/test-reexec-chroot.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# Test if we re-invoke pseudo that chroot still works
+
+# Return vals: 2 - invalid arg list
+# 1 - chroot failed
+# 0 - chroot succeeded
+cat > chroot_test.c << EOF
+#include <unistd.h>
+int main(int argc, char *argv[]) {
+ if (argc != 2)
+ return 2;
+ return (chroot(argv[1]) == -1);
+}
+EOF
+
+gcc -o chroot_test chroot_test.c
+
+# The following should just run chroot_test since pseudo is already loaded
+./bin/pseudo ./chroot_test `pwd`
+
+if [ "$?" = "0" ]
+then
+ #echo "Passed."
+ rm -f chroot_test chroot_test.c
+ exit 0
+fi
+#echo "Failed"
+rm -f chroot_test chroot_test.c
+exit 1
diff --git a/test/test-umask.sh b/test/test-umask.sh
new file mode 100755
index 0000000..48cb3b1
--- /dev/null
+++ b/test/test-umask.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+mode() {
+ ls -l "$1" | awk '{ print $1 }'
+}
+
+# Verify normal operation...
+umask 022
+touch a
+umask 0
+touch b
+
+case $(mode a) in
+-rw-r--r--*) ;;
+*) exit 1;;
+esac
+case $(mode b) in
+-rw-rw-rw-*) ;;
+*) exit 1;;
+esac
+
+export PSEUDO_DISABLED=1
+
+case $(mode a) in
+-rw-r--r--*) ;;
+*) exit 1;;
+esac
+case $(mode b) in
+-rw-r--r--*) ;;
+*) exit 1;;
+esac
+
diff --git a/test/test-xattr.sh b/test/test-xattr.sh
new file mode 100755
index 0000000..7d818d2
--- /dev/null
+++ b/test/test-xattr.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+# Return vals: 2 - Unable to run xattr commands
+# 1 - Invalid return value
+# 0 - Pass
+
+touch f1
+attrs=`getfattr -d f1 | grep -v '^#'`
+if [ -n "$attrs" ]
+then
+ #echo "Fail, unexpected getfattr result '$attr'"
+ rm -f f1
+ exit 1
+fi
+
+setfattr -n "user.dummy" -v "test_f1" f1
+if [ $? -ne 0 ]
+then
+ #echo "Fail, unable to call setfattr"
+ rm -f f1
+ exit 2
+fi
+
+attrs=`getfattr -d f1 | grep -v '^#'`
+if [ "$attrs" != 'user.dummy="test_f1"' ]
+then
+ #echo "Fail, unexpected getfattr result '$attr'"
+ rm -f f1
+ exit 1
+fi
+
+setfattr -n "security.dummy" -v "test_f2" f1
+if [ $? -ne 0 ]
+then
+ #echo "Fail, unable to call setfattr"
+ rm -f f1
+ exit 2
+fi
+
+attrs=`getfattr -n "security.dummy" f1 | grep -v '^#'`
+if [ "$attrs" != 'security.dummy="test_f2"' ]
+then
+ #echo "Fail, unexpected getfattr result '$attr'"
+ rm -f f1
+ exit 1
+fi
+
+#echo "Passed."
+rm -f f1
+exit 0