aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Seebach <seebs@eee12.(none)>2010-03-16 19:26:24 -0500
committerPeter Seebach <seebs@eee12.(none)>2010-03-16 19:26:24 -0500
commit33d9386e8d818860ce603356eee074d2a2849085 (patch)
tree185f2c14b86df39403971f0651f4b6136dda147f
downloadpseudo-33d9386e8d818860ce603356eee074d2a2849085.tar.gz
pseudo-33d9386e8d818860ce603356eee074d2a2849085.tar.bz2
pseudo-33d9386e8d818860ce603356eee074d2a2849085.zip
initial public release
-rw-r--r--COPYING504
-rw-r--r--ChangeLog.txt4
-rw-r--r--Makefile.in126
-rw-r--r--README61
-rwxr-xr-xconfigure71
-rw-r--r--doc/database81
-rw-r--r--doc/overview96
-rw-r--r--doc/pseudo_ipc76
-rw-r--r--doc/utils33
-rw-r--r--guts/COPYRIGHT17
-rw-r--r--guts/README109
-rw-r--r--guts/__fxstat.c17
-rw-r--r--guts/__fxstat64.c28
-rw-r--r--guts/__fxstatat.c29
-rw-r--r--guts/__fxstatat64.c71
-rw-r--r--guts/__lxstat.c11
-rw-r--r--guts/__lxstat64.c11
-rw-r--r--guts/__openat64_2.c11
-rw-r--r--guts/__openat_2.c11
-rw-r--r--guts/__xmknod.c11
-rw-r--r--guts/__xmknodat.c68
-rw-r--r--guts/__xstat.c11
-rw-r--r--guts/__xstat64.c10
-rw-r--r--guts/chdir.c15
-rw-r--r--guts/chmod.c11
-rw-r--r--guts/chown.c11
-rw-r--r--guts/close.c14
-rw-r--r--guts/creat.c11
-rw-r--r--guts/creat64.c11
-rw-r--r--guts/dup.c16
-rw-r--r--guts/dup2.c19
-rw-r--r--guts/fchdir.c15
-rw-r--r--guts/fchmod.c30
-rw-r--r--guts/fchmodat.c70
-rw-r--r--guts/fchown.c54
-rw-r--r--guts/fchownat.c64
-rw-r--r--guts/fclose.c17
-rw-r--r--guts/fcntl.c68
-rw-r--r--guts/fopen.c32
-rw-r--r--guts/fopen64.c32
-rw-r--r--guts/fork.c13
-rw-r--r--guts/freopen.c32
-rw-r--r--guts/getegid.c11
-rw-r--r--guts/geteuid.c11
-rw-r--r--guts/getgid.c11
-rw-r--r--guts/getresgid.c20
-rw-r--r--guts/getresuid.c20
-rw-r--r--guts/getuid.c11
-rw-r--r--guts/lchown.c48
-rw-r--r--guts/link.c29
-rw-r--r--guts/mkdir.c11
-rw-r--r--guts/mkdirat.c34
-rw-r--r--guts/mkstemp.c25
-rw-r--r--guts/open.c11
-rw-r--r--guts/open64.c11
-rw-r--r--guts/openat.c64
-rw-r--r--guts/openat64.c11
-rw-r--r--guts/rename.c77
-rw-r--r--guts/renameat.c12
-rw-r--r--guts/rmdir.c22
-rw-r--r--guts/setegid.c17
-rw-r--r--guts/seteuid.c17
-rw-r--r--guts/setfsgid.c16
-rw-r--r--guts/setfsuid.c16
-rw-r--r--guts/setgid.c24
-rw-r--r--guts/setgroups.c12
-rw-r--r--guts/setregid.c27
-rw-r--r--guts/setresgid.c34
-rw-r--r--guts/setresuid.c34
-rw-r--r--guts/setreuid.c27
-rw-r--r--guts/setuid.c24
-rw-r--r--guts/symlink.c11
-rw-r--r--guts/symlinkat.c38
-rw-r--r--guts/unlink.c11
-rw-r--r--guts/unlinkat.c44
-rw-r--r--guts/vfork.c14
-rwxr-xr-xmakewrappers429
-rw-r--r--offsets.c52
-rw-r--r--pseudo.1353
-rw-r--r--pseudo.c667
-rw-r--r--pseudo.h128
-rw-r--r--pseudo_client.c836
-rw-r--r--pseudo_client.h82
-rw-r--r--pseudo_db.c1695
-rw-r--r--pseudo_db.h71
-rw-r--r--pseudo_ipc.c244
-rw-r--r--pseudo_ipc.h59
-rw-r--r--pseudo_server.c477
-rw-r--r--pseudo_server.h23
-rw-r--r--pseudo_table.c211
-rw-r--r--pseudo_util.c499
-rw-r--r--pseudodb.c49
-rw-r--r--pseudolog.1335
-rw-r--r--pseudolog.c783
-rw-r--r--wrapfuncs.in59
-rw-r--r--wrapfuncs64.in9
96 files changed, 9868 insertions, 0 deletions
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..2d40a7c
--- /dev/null
+++ b/ChangeLog.txt
@@ -0,0 +1,4 @@
+2010-03-16: 0.2
+ * first public release
+
+(There's no changelog before that, sorry.)
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..8852802
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,126 @@
+#
+# Makefile.in/Makefile, build rules for pseudo
+#
+# 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
+#
+
+# configuration flags
+PREFIX=@PREFIX@
+SUFFIX=@SUFFIX@
+SQLITE=@SQLITE@
+BITS=@BITS@
+MARK64=@MARK64@
+VERSION=0.2
+
+LIBDIR=$(PREFIX)/lib$(MARK64)
+BINDIR=$(PREFIX)/bin
+DATADIR=$(PREFIX)/var/pseudo
+
+CFLAGS_BASE=-pipe -std=gnu99 -Wall
+CFLAGS_CODE=-fPIC -D_LARGEFILE64_SOURCE -D_ATFILE_SOURCE -m$(BITS)
+CFLAGS_DEFS=-DPSEUDO_PREFIX='"$(PREFIX)"' -DPSEUDO_SUFFIX='"$(SUFFIX)"' -DPSEUDO_VERSION='"$(VERSION)"'
+CFLAGS_DEBUG=-O2 -g
+CFLAGS_SQL=-L$(SQLITE)/lib -I$(SQLITE)/include
+CFLAGS=$(CFLAGS_BASE) $(CFLAGS_CODE) $(CFLAGS_DEFS) \
+ $(CFLAGS_DEBUG) $(CFLAGS_SQL)
+
+GLOB_PATTERN=guts/*.c
+GUTS=$(filter-out "$(GLOB_PATTERN)",$(wildcard $(GLOB_PATTERN)))
+
+DBLDFLAGS=-lsqlite3
+USE_64=wrapfuncs64.in
+
+SHOBJS=pseudo_table.o pseudo_util.o
+DBOBJS=pseudo_db.o -ldl -lpthread
+WRAPOBJS=pseudo_wrappers.o
+
+test: install
+ @echo "No tests yet."
+
+all: libpseudo.so pseudo pseudodb pseudolog
+
+install-lib: libpseudo.so
+ mkdir -p $(DESTDIR)$(LIBDIR)
+ cp libpseudo*.so $(DESTDIR)$(LIBDIR)
+
+install-bin: pseudo pseudodb pseudolog
+ mkdir -p $(DESTDIR)$(BINDIR)
+ cp pseudo pseudodb pseudolog $(DESTDIR)$(BINDIR)
+
+install-data:
+ mkdir -p $(DESTDIR)$(DATADIR)
+
+install: all install-lib install-bin install-data
+
+pseudo: pseudo.o $(SHOBJS) $(DBOBJS) pseudo_server.o pseudo_ipc.o
+ $(CC) $(CFLAGS) -o pseudo \
+ pseudo.o pseudo_server.o pseudo_client.o pseudo_ipc.o \
+ $(DBOBJS) $(SHOBJS) $(DBLDFLAGS)
+
+pseudolog: pseudolog.o $(SHOBJS) $(DBOBJS)
+ $(CC) $(CFLAGS) -o pseudolog pseudolog.o \
+ $(DBOBJS) $(SHOBJS) $(DBLDFLAGS)
+
+pseudodb: pseudodb.o $(SHOBJS) $(DBOBJS) pseudo_ipc.o
+ $(CC) $(CFLAGS) -o pseudodb pseudodb.o \
+ $(DBOBJS) $(SHOBJS) $(DBLDFLAGS) pseudo_ipc.o
+
+libpseudo.so: $(WRAPOBJS) pseudo_client.o pseudo_ipc.o $(SHOBJS)
+ $(CC) $(CFLAGS) -shared -o libpseudo.so \
+ pseudo_client.o pseudo_ipc.o \
+ $(WRAPOBJS) $(SHOBJS) -ldl
+ if test -n "$(SUFFIX)"; then \
+ cp libpseudo.so libpseudo$(SUFFIX).so ; \
+ fi
+
+pseudo_client.o pseudo_server.o pseudo_ipc.o: pseudo_ipc.h
+
+pseudo_client.o: pseudo_client.h
+
+pseudo_server.o: pseudo_server.h
+
+pseudo_wrappers.o: $(GUTS)
+
+wrappers: wrapfuncs.in $(USE_64)
+ ./makewrappers wrapfuncs.in $(USE_64)
+
+.SECONDARY: wrappers
+
+pseudo_wrappers.c: wrappers
+
+# no-strict-aliasing is needed for the function pointer trickery.
+pseudo_wrappers.o: pseudo_wrappers.c
+ $(CC) -fno-strict-aliasing $(CFLAGS) -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
+
+clean:
+ rm -f *.o *.so pseudo pseudodb pseudolog \
+ pseudo_wrappers.h pseudo_wrappers.c \
+ pseudo_wrapper_table.c pseudo_wrappers.c.old \
+ pseudo_wrappers.h.old offsets32 offsets64
+
+distclean: clean
+ rm -f Makefile
+ @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)."
diff --git a/README b/README
new file mode 100644
index 0000000..cdf8873
--- /dev/null
+++ b/README
@@ -0,0 +1,61 @@
+pseudo -- an analogue to sudo
+
+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>
+
+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 -- it's never been tried on anything
+but Linux, although it runs on a pretty broad range of Linux 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.
+
+
+FUTURE DIRECTIONS:
+
+* We intend to add chroot() functionality to pseudo, as well as
+ functionality to support user name/uid lookups from an alternative
+ password file.
+* We have no immediate plans to make pseudo work on other targets, but
+ are not opposed to accepting patches to do so. (It isn't even CLOSE
+ on OS X, largely because of the Linux-specific _STAT_VER stuff.)
+* 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..20eaccb
--- /dev/null
+++ b/configure
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# configure, simulation of autoconf script, much simplified
+#
+# 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
+#
+# not a real configure script...
+opt_prefix=
+opt_suffix=
+opt_bits=32
+opt_sqlite=/usr
+
+usage()
+{
+ echo >&2 "usage:"
+ echo >&2 " configure --prefix=... [--suffix=...] [--with-sqlite=...] [--bits=32|64]"
+ exit 1
+}
+
+for arg
+do
+ case $arg in
+ --) shift; break ;;
+ --prefix=*)
+ opt_prefix=${arg#--prefix=}
+ ;;
+ --with-sqlite=*)
+ opt_sqlite=${arg#--with-sqlite=}
+ ;;
+ --suffix=*)
+ opt_suffix=${arg#--suffix=}
+ ;;
+ --bits=*)
+ opt_bits=${arg#--bits=}
+ case $opt_bits in
+ 64) opt_mark64=64;;
+ 32) opt_mark64=;;
+ *) echo >&2 "Unknown bit size $opt_bits (only 32 and 64 known)."
+ ;;
+ esac
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+
+if [ -z "$opt_prefix" ]; then
+ usage
+fi
+
+sed -e '
+ s,@PREFIX@,'"$opt_prefix"',g
+ s,@SUFFIX@,'"$opt_suffix"',g
+ s,@SQLITE@,'"$opt_sqlite"',g
+ s,@MARK64@,'"$opt_mark64"',g
+ s,@BITS@,'"$opt_bits"',g
+' < Makefile.in > Makefile
diff --git a/doc/database b/doc/database
new file mode 100644
index 0000000..cac7a3a
--- /dev/null
+++ b/doc/database
@@ -0,0 +1,81 @@
+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)
+
+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 later
+request comes in with a valid name, the 'NAMELESS FILE' is renamed to it so
+it can be unlinked later.
+
+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)
+
+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.
diff --git a/doc/overview b/doc/overview
new file mode 100644
index 0000000..7341e3c
--- /dev/null
+++ b/doc/overview
@@ -0,0 +1,96 @@
+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. At this time, this does not extend to
+emulating chroot functions or a virtual password database, but these features
+may be added.
+
+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!
+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/pseudo_ipc b/doc/pseudo_ipc
new file mode 100644
index 0000000..6a73ec8
--- /dev/null
+++ b/doc/pseudo_ipc
@@ -0,0 +1,76 @@
+MESSAGE PASSING
+
+typedef struct {
+ pseudo_msg_type_t type;
+ op_id_t op;
+ res_id_t result;
+ int xerrno;
+ 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, SHUTDOWN, ACK, or NAK. The client only sends PING or
+OP. 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.
+
+xerrno is used to contain a changed errno if, at some point, the server wants
+to override the default errno. Normally, the client just uses its existing
+errno.
+
+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/guts/COPYRIGHT b/guts/COPYRIGHT
new file mode 100644
index 0000000..c96e1b1
--- /dev/null
+++ b/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/guts/README b/guts/README
new file mode 100644
index 0000000..be00d30
--- /dev/null
+++ b/guts/README
@@ -0,0 +1,109 @@
+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:
+
+ chmod: fchmodat
+ chown: fchownat
+ creat64: openat
+ creat: openat
+ __fxstatat: __fxstatat64
+ __fxstat: __fxstat64
+ __lxstat64: __fxstatat64
+ __lxstat: __fxstatat
+ mkdir: mkdirat
+ open64: openat
+ __openat_2: openat
+ __openat64_2: openat
+ openat64: openat
+ open: openat
+ 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
+ lchown
+ mkdirat
+ openat
+ renameat
+ 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
+ fclose
+ fopen
+ fopen64
+ freopen
+ mkstemp
+ fcntl
+ fork
+ link
+ vfork
+
+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
diff --git a/guts/__fxstat.c b/guts/__fxstat.c
new file mode 100644
index 0000000..e715f6c
--- /dev/null
+++ b/guts/__fxstat.c
@@ -0,0 +1,17 @@
+/*
+ * 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/guts/__fxstat64.c b/guts/__fxstat64.c
new file mode 100644
index 0000000..59f8a59
--- /dev/null
+++ b/guts/__fxstat64.c
@@ -0,0 +1,28 @@
+/*
+ * 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(1, "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) {
+ if (msg->result == RESULT_SUCCEED)
+ pseudo_stat_msg(buf, msg);
+ }
+
+ errno = save_errno;
+/* return rc;
+ * }
+ */
diff --git a/guts/__fxstatat.c b/guts/__fxstatat.c
new file mode 100644
index 0000000..4bc2454
--- /dev/null
+++ b/guts/__fxstatat.c
@@ -0,0 +1,29 @@
+/*
+ * 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/guts/__fxstatat64.c b/guts/__fxstatat64.c
new file mode 100644
index 0000000..3edd8c7
--- /dev/null
+++ b/guts/__fxstatat64.c
@@ -0,0 +1,71 @@
+/*
+ * 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;
+ mode_t save_mode = 0;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+#endif
+ /* If the file is actually a symlink, we grab the db values
+ * for the underlying target, then mask in the size and mode
+ * from the link. Otherwise, we just use the db values (if
+ * any).
+ */
+ 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;
+ }
+ /* it's a symlink, stash its mode */
+ if (S_ISLNK(buf->st_mode)) {
+ save_mode = buf->st_mode;
+ }
+ } 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;
+ }
+ }
+ /* if we got here: we have valid stat data, for either the symlink
+ * (if it is a symlink, and we have NOFOLLOW on) or the target.
+ */
+ save_errno = errno;
+
+ if (ver != _STAT_VER) {
+ pseudo_debug(1, "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, flags, -1, dirfd, path, buf);
+ if (msg) {
+ pseudo_stat_msg(buf, msg);
+ if (save_mode) {
+ buf->st_mode = save_mode;
+ }
+ }
+
+ errno = save_errno;
+
+/* return rc;
+ * }
+ */
diff --git a/guts/__lxstat.c b/guts/__lxstat.c
new file mode 100644
index 0000000..4678f28
--- /dev/null
+++ b/guts/__lxstat.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/__lxstat64.c b/guts/__lxstat64.c
new file mode 100644
index 0000000..36ac18b
--- /dev/null
+++ b/guts/__lxstat64.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/__openat64_2.c b/guts/__openat64_2.c
new file mode 100644
index 0000000..85b950b
--- /dev/null
+++ b/guts/__openat64_2.c
@@ -0,0 +1,11 @@
+/*
+ * static int
+ * wrap___openat64_2(int dirfd, const char *path, int flags) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(dirfd, path, flags, O_LARGEFILE);
+
+/* return rc;
+ * }
+ */
diff --git a/guts/__openat_2.c b/guts/__openat_2.c
new file mode 100644
index 0000000..ef8d7ad
--- /dev/null
+++ b/guts/__openat_2.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/__xmknod.c b/guts/__xmknod.c
new file mode 100644
index 0000000..5fb395d
--- /dev/null
+++ b/guts/__xmknod.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/__xmknodat.c b/guts/__xmknodat.c
new file mode 100644
index 0000000..a86d6aa
--- /dev/null
+++ b/guts/__xmknodat.c
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+#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));
+#else
+ rc = real_openat(dirfd, path, O_CREAT | O_WRONLY | O_EXCL,
+ PSEUDO_FS_MODE(mode));
+#endif
+ if (rc == -1)
+ return -1;
+ 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, AT_SYMLINK_NOFOLLOW, -1, dirfd, path, &buf);
+ if (!msg) {
+ errno = ENOSYS;
+ rc = -1;
+ } else if (msg->result != RESULT_SUCCEED) {
+ errno = msg->xerrno;
+ rc = -1;
+ } else {
+ 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/guts/__xstat.c b/guts/__xstat.c
new file mode 100644
index 0000000..ef2e363
--- /dev/null
+++ b/guts/__xstat.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/__xstat64.c b/guts/__xstat64.c
new file mode 100644
index 0000000..f02358b
--- /dev/null
+++ b/guts/__xstat64.c
@@ -0,0 +1,10 @@
+/*
+ * 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/guts/chdir.c b/guts/chdir.c
new file mode 100644
index 0000000..b910060
--- /dev/null
+++ b/guts/chdir.c
@@ -0,0 +1,15 @@
+/*
+ * static int
+ * wrap_chdir(const char *path) {
+ * int rc = -1;
+ */
+ pseudo_debug(3, "chdir: %s\n", path ? path : "<nil>");
+ rc = real_chdir(path);
+
+ if (rc != -1) {
+ pseudo_client_op(OP_CHDIR, 0, -1, -1, path, 0);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/chmod.c b/guts/chmod.c
new file mode 100644
index 0000000..2d5de91
--- /dev/null
+++ b/guts/chmod.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/chown.c b/guts/chown.c
new file mode 100644
index 0000000..0a82989
--- /dev/null
+++ b/guts/chown.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/close.c b/guts/close.c
new file mode 100644
index 0000000..8edbee9
--- /dev/null
+++ b/guts/close.c
@@ -0,0 +1,14 @@
+/*
+ * 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/guts/creat.c b/guts/creat.c
new file mode 100644
index 0000000..e4c0b60
--- /dev/null
+++ b/guts/creat.c
@@ -0,0 +1,11 @@
+/*
+ * static int
+ * wrap_creat(const char *path, mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(AT_FDCWD, path, O_CREAT|O_WRONLY|O_TRUNC, mode);
+
+/* return rc;
+ * }
+ */
diff --git a/guts/creat64.c b/guts/creat64.c
new file mode 100644
index 0000000..655c166
--- /dev/null
+++ b/guts/creat64.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/dup.c b/guts/dup.c
new file mode 100644
index 0000000..941a5d9
--- /dev/null
+++ b/guts/dup.c
@@ -0,0 +1,16 @@
+/*
+ * static int
+ * wrap_dup(int fd) {
+ * int rc = -1;
+ */
+ int save_errno;
+
+ rc = real_dup(fd);
+ save_errno = errno;
+ pseudo_debug(2, "dup: %d->%d\n", fd, rc);
+ pseudo_client_op(OP_DUP, 0, fd, rc, 0, 0);
+
+ errno = save_errno;
+/* return rc;
+ * }
+ */
diff --git a/guts/dup2.c b/guts/dup2.c
new file mode 100644
index 0000000..360c3ad
--- /dev/null
+++ b/guts/dup2.c
@@ -0,0 +1,19 @@
+/*
+ * 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(2, "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/guts/fchdir.c b/guts/fchdir.c
new file mode 100644
index 0000000..5289f4c
--- /dev/null
+++ b/guts/fchdir.c
@@ -0,0 +1,15 @@
+/*
+ * 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/guts/fchmod.c b/guts/fchmod.c
new file mode 100644
index 0000000..df2f9e9
--- /dev/null
+++ b/guts/fchmod.c
@@ -0,0 +1,30 @@
+/*
+ * static int
+ * wrap_fchmod(int fd, mode_t mode) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ struct stat64 buf;
+ int save_errno = errno;
+
+ if (real___fxstat64(_STAT_VER, fd, &buf) == -1) {
+ /* can't stat it, can't chmod it */
+ return -1;
+ }
+ buf.st_mode = (buf.st_mode & ~07777) | (mode & 07777);
+ msg = pseudo_client_op(OP_FCHMOD, 0, fd, -1, 0, &buf);
+ real_fchmod(fd, PSEUDO_FS_MODE(mode));
+ if (!msg) {
+ errno = ENOSYS;
+ rc = -1;
+ } else if (msg->result != RESULT_SUCCEED) {
+ errno = msg->xerrno;
+ rc = -1;
+ } else {
+ errno = save_errno;
+ rc = 0;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/fchmodat.c b/guts/fchmodat.c
new file mode 100644
index 0000000..36bd4d1
--- /dev/null
+++ b/guts/fchmodat.c
@@ -0,0 +1,70 @@
+/*
+ * static int
+ * wrap_fchmodat(int dirfd, const char *path, mode_t mode, int flags) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ struct stat64 buf;
+ int save_errno;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (flags & AT_SYMLINK_NOFOLLOW) {
+ rc = real___lxstat64(_STAT_VER, path, &buf);
+ } else {
+ rc = real___xstat64(_STAT_VER, path, &buf);
+ }
+#else
+ rc = real___fxstatat64(_STAT_VER, 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;
+
+ /* purely for debugging purposes: check whether file
+ * is already in database.
+ */
+ msg = pseudo_client_op(OP_STAT, flags, -1, -1, path, &buf);
+ if (!msg || msg->result != RESULT_SUCCEED) {
+ pseudo_debug(2, "chmodat to 0%o on %d/%s, ino %llu, new file.\n",
+ mode, dirfd, path, (unsigned long long) buf.st_ino);
+
+ }
+
+ /* user bits added so "root" can always access files. */
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ /* note: if path was a symlink, and AT_NOFOLLOW_SYMLINKS was
+ * specified, we already bailed previously. */
+ real_chmod(path, PSEUDO_FS_MODE(mode));
+#else
+ real_fchmodat(dirfd, path, PSEUDO_FS_MODE(mode), flags);
+#endif
+ /* we ignore a failure from underlying fchmod, because pseudo
+ * may believe you are permitted to change modes that the filesystem
+ * doesn't.
+ */
+
+ buf.st_mode = (buf.st_mode & ~07777) | (mode & 07777);
+ msg = pseudo_client_op(OP_CHMOD, flags, -1, dirfd, path, &buf);
+ if (!msg) {
+ errno = ENOSYS;
+ rc = -1;
+ } else if (msg->result != RESULT_SUCCEED) {
+ errno = msg->xerrno;
+ rc = -1;
+ } else {
+ rc = 0;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/fchown.c b/guts/fchown.c
new file mode 100644
index 0000000..4d420c8
--- /dev/null
+++ b/guts/fchown.c
@@ -0,0 +1,54 @@
+/*
+ * static int
+ * wrap_fchown(int fd, uid_t owner, gid_t group) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ struct stat64 buf;
+ int save_errno;
+
+ if (real___fxstat64(_STAT_VER, fd, &buf) == -1) {
+ save_errno = errno;
+ pseudo_debug(2, "fchown failing because fxstat failed: %s\n",
+ strerror(errno));
+ errno = save_errno;
+ return -1;
+ }
+ if (owner == -1 || group == -1) {
+ msg = pseudo_client_op(OP_STAT, 0, fd, -1, NULL, &buf);
+ /* copy in any existing values... */
+ if (msg) {
+ if (msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(&buf, msg);
+ } else {
+ pseudo_debug(2, "fchown fd %d, ino %llu, unknown file.\n",
+ fd, (unsigned long long) buf.st_ino);
+ }
+ } else {
+ pseudo_diag("stat within chown of fd %d [%llu] failed.\n",
+ fd, (unsigned long long) buf.st_ino);
+ }
+ }
+ /* now override with arguments */
+ if (owner != -1) {
+ buf.st_uid = owner;
+ }
+ if (group != -1) {
+ buf.st_gid = group;
+ }
+ pseudo_debug(2, "fchown, fd %d: %d:%d -> %d:%d\n",
+ fd, owner, group, buf.st_uid, buf.st_gid);
+ msg = pseudo_client_op(OP_FCHOWN, 0, fd, -1, 0, &buf);
+ if (!msg) {
+ errno = ENOSYS;
+ rc = -1;
+ } else if (msg->result != RESULT_SUCCEED) {
+ errno = msg->xerrno;
+ rc = -1;
+ } else {
+ rc = 0;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/fchownat.c b/guts/fchownat.c
new file mode 100644
index 0000000..8d24d3e
--- /dev/null
+++ b/guts/fchownat.c
@@ -0,0 +1,64 @@
+/*
+ * static int
+ * wrap_fchownat(int dirfd, const char *path, uid_t owner, gid_t group, int flags) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ struct stat64 buf;
+ int save_errno;
+ int doing_link = 0;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (flags & AT_SYMLINK_NOFOLLOW) {
+ rc = real___lxstat64(_STAT_VER, path, &buf);
+ } else {
+ rc = real___xstat64(_STAT_VER, path, &buf);
+ }
+#else
+ rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, flags);
+#endif
+ if (rc == -1) {
+ return rc;
+ }
+ /* pseudo won't track the ownership, here */
+ if (S_ISLNK(buf.st_mode)) {
+ doing_link = 1;
+ }
+ save_errno = errno;
+
+ msg = pseudo_client_op(OP_STAT, flags, -1, -1, path, &buf);
+ /* copy in any existing values... */
+ if (msg) {
+ if (msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(&buf, msg);
+ } else {
+ pseudo_debug(2, "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 != -1) {
+ buf.st_uid = owner;
+ }
+ if (group != -1) {
+ buf.st_gid = group;
+ }
+ msg = pseudo_client_op(OP_CHOWN, flags, -1, dirfd, path, &buf);
+ if (!msg) {
+ errno = ENOSYS;
+ rc = -1;
+ } else if (msg->result != RESULT_SUCCEED) {
+ errno = msg->xerrno;
+ rc = -1;
+ } else {
+ rc = 0;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/fclose.c b/guts/fclose.c
new file mode 100644
index 0000000..e0c5681
--- /dev/null
+++ b/guts/fclose.c
@@ -0,0 +1,17 @@
+/*
+ * 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/guts/fcntl.c b/guts/fcntl.c
new file mode 100644
index 0000000..d03d40c
--- /dev/null
+++ b/guts/fcntl.c
@@ -0,0 +1,68 @@
+/*
+ * 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(2, "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/guts/fopen.c b/guts/fopen.c
new file mode 100644
index 0000000..2aae54c
--- /dev/null
+++ b/guts/fopen.c
@@ -0,0 +1,32 @@
+/*
+ * static FILE *
+ * wrap_fopen(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_fopen(path, mode);
+ save_errno = errno;
+
+ if (rc) {
+ int fd = fileno(rc);
+
+ pseudo_debug(2, "fopen '%s': fd %d\n", path, fd);
+ if (real___fxstat64(_STAT_VER, fd, &buf) != -1) {
+ if (!existed) {
+ pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, 0, fd, -1, path, &buf);
+ } else {
+ pseudo_debug(1, "fopen (fd %d) succeeded, but fstat failed (%s).\n",
+ fd, strerror(errno));
+ pseudo_client_op(OP_OPEN, 0, fd, -1, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/fopen64.c b/guts/fopen64.c
new file mode 100644
index 0000000..0b0ab58
--- /dev/null
+++ b/guts/fopen64.c
@@ -0,0 +1,32 @@
+/*
+ * 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(2, "fopen64 '%s': fd %d\n", path, fd);
+ if (real___fxstat64(_STAT_VER, fd, &buf) != -1) {
+ if (!existed) {
+ pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, 0, fd, -1, path, &buf);
+ } else {
+ pseudo_debug(1, "fopen64 (fd %d) succeeded, but fstat failed (%s).\n",
+ fd, strerror(errno));
+ pseudo_client_op(OP_OPEN, 0, fd, -1, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/fork.c b/guts/fork.c
new file mode 100644
index 0000000..a49694f
--- /dev/null
+++ b/guts/fork.c
@@ -0,0 +1,13 @@
+/*
+ * static int
+ * wrap_fork(void) {
+ * int rc = -1;
+ */
+
+ rc = real_fork();
+ if (rc == 0)
+ pseudo_client_reset();
+
+/* return rc;
+ * }
+ */
diff --git a/guts/freopen.c b/guts/freopen.c
new file mode 100644
index 0000000..312bc0a
--- /dev/null
+++ b/guts/freopen.c
@@ -0,0 +1,32 @@
+/*
+ * static FILE *
+ * wrap_freopen(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_freopen(path, mode, stream);
+ save_errno = errno;
+
+ if (rc) {
+ int fd = fileno(rc);
+
+ pseudo_debug(2, "freopen '%s': fd %d\n", path, fd);
+ if (real___fxstat64(_STAT_VER, fd, &buf) != -1) {
+ if (!existed) {
+ pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, 0, fd, -1, path, &buf);
+ } else {
+ pseudo_debug(1, "fopen (fd %d) succeeded, but stat failed (%s).\n",
+ fd, strerror(errno));
+ pseudo_client_op(OP_OPEN, 0, fd, -1, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/getegid.c b/guts/getegid.c
new file mode 100644
index 0000000..4a3c929
--- /dev/null
+++ b/guts/getegid.c
@@ -0,0 +1,11 @@
+/*
+ * static gid_t
+ * wrap_getegid(void) {
+ * gid_t rc = 0;
+ */
+
+ rc = pseudo_egid;
+
+/* return rc;
+ * }
+ */
diff --git a/guts/geteuid.c b/guts/geteuid.c
new file mode 100644
index 0000000..508cc83
--- /dev/null
+++ b/guts/geteuid.c
@@ -0,0 +1,11 @@
+/*
+ * static uid_t
+ * wrap_geteuid(void) {
+ * uid_t rc = 0;
+ */
+
+ rc = pseudo_euid;
+
+/* return rc;
+ * }
+ */
diff --git a/guts/getgid.c b/guts/getgid.c
new file mode 100644
index 0000000..415e3f0
--- /dev/null
+++ b/guts/getgid.c
@@ -0,0 +1,11 @@
+/*
+ * static gid_t
+ * wrap_getgid(void) {
+ * gid_t rc = 0;
+ */
+
+ rc = pseudo_rgid;
+
+/* return rc;
+ * }
+ */
diff --git a/guts/getresgid.c b/guts/getresgid.c
new file mode 100644
index 0000000..3eb4fac
--- /dev/null
+++ b/guts/getresgid.c
@@ -0,0 +1,20 @@
+/*
+ * 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/guts/getresuid.c b/guts/getresuid.c
new file mode 100644
index 0000000..2976f78
--- /dev/null
+++ b/guts/getresuid.c
@@ -0,0 +1,20 @@
+/*
+ * 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/guts/getuid.c b/guts/getuid.c
new file mode 100644
index 0000000..48294e2
--- /dev/null
+++ b/guts/getuid.c
@@ -0,0 +1,11 @@
+/*
+ * static uid_t
+ * wrap_getuid(void) {
+ * uid_t rc = 0;
+ */
+
+ rc = pseudo_ruid;
+
+/* return rc;
+ * }
+ */
diff --git a/guts/lchown.c b/guts/lchown.c
new file mode 100644
index 0000000..6f7e53a
--- /dev/null
+++ b/guts/lchown.c
@@ -0,0 +1,48 @@
+/*
+ * static int
+ * wrap_lchown(const char *path, uid_t owner, gid_t group) {
+ */
+ pseudo_msg_t *msg;
+ struct stat64 buf;
+
+ pseudo_debug(2, "lchown(%s, %d, %d)\n",
+ path ? path : "<null>", owner, group);
+ if (real___lxstat64(_STAT_VER, path, &buf) == -1) {
+ return -1;
+ }
+ if (owner == -1 || group == -1) {
+ msg = pseudo_client_op(OP_STAT, AT_SYMLINK_NOFOLLOW, -1, -1, path, &buf);
+ /* copy in any existing values... */
+ if (msg) {
+ if (msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(&buf, msg);
+ } else {
+ pseudo_debug(2, "lchown to %d:%d on %s, ino %llu, new file.\n",
+ owner, group, path,
+ (unsigned long long) buf.st_ino);
+ }
+ } else {
+ pseudo_diag("stat within lchown of '%s' [%llu] failed.\n",
+ path, (unsigned long long) buf.st_ino);
+ }
+ }
+ if (owner != -1) {
+ buf.st_uid = owner;
+ }
+ if (group != -1) {
+ buf.st_gid = group;
+ }
+ msg = pseudo_client_op(OP_CHOWN, AT_SYMLINK_NOFOLLOW, -1, -1, path, &buf);
+ if (!msg) {
+ errno = ENOSYS;
+ rc = -1;
+ } else if (msg->result != RESULT_SUCCEED) {
+ errno = msg->xerrno;
+ rc = -1;
+ } else {
+ rc = 0;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/link.c b/guts/link.c
new file mode 100644
index 0000000..278edd7
--- /dev/null
+++ b/guts/link.c
@@ -0,0 +1,29 @@
+/*
+ * static int
+ * wrap_link(const char *oldpath, const char *newpath) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ struct stat64 buf;
+
+ rc = real_link(oldpath, newpath);
+ if (rc == 0) {
+ /* link(2) will not overwrite; if it succeeded, we know
+ * that there was no previous file with this name, so we
+ * shove it into the database.
+ */
+ real___xstat64(_STAT_VER, oldpath, &buf);
+ /* a link should copy the existing database entry, if
+ * there is one. OP_LINK is also used to insert unseen
+ * files, though, so it can't be implicit.
+ */
+ msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, &buf);
+ if (msg) {
+ pseudo_stat_msg(&buf, msg);
+ }
+ pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &buf);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/mkdir.c b/guts/mkdir.c
new file mode 100644
index 0000000..3177e4f
--- /dev/null
+++ b/guts/mkdir.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/mkdirat.c b/guts/mkdirat.c
new file mode 100644
index 0000000..a5ae5d8
--- /dev/null
+++ b/guts/mkdirat.c
@@ -0,0 +1,34 @@
+/*
+ * static int
+ * wrap_mkdirat(int dirfd, const char *path, mode_t mode) {
+ * int rc = -1;
+ */
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ rc = real_mkdir(path, PSEUDO_FS_MODE(mode));
+#else
+ rc = real_mkdirat(dirfd, path, PSEUDO_FS_MODE(mode));
+#endif
+ if (rc != -1) {
+ struct stat64 buf;
+ int stat_rc;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ stat_rc = real___lxstat64(_STAT_VER, path, &buf);
+#else
+ stat_rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, AT_SYMLINK_NOFOLLOW);
+#endif
+ if (stat_rc != -1) {
+ pseudo_client_op(OP_MKDIR, AT_SYMLINK_NOFOLLOW, -1, dirfd, path, &buf);
+ } else {
+ pseudo_debug(1, "mkdir of %s succeeded, but stat failed: %s\n",
+ path, strerror(errno));
+ }
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/mkstemp.c b/guts/mkstemp.c
new file mode 100644
index 0000000..b339b5c
--- /dev/null
+++ b/guts/mkstemp.c
@@ -0,0 +1,25 @@
+/*
+ * static int
+ * wrap_mkstemp(char *template) {
+ * int rc = -1;
+ */
+ struct stat64 buf;
+ int save_errno;
+
+ rc = real_mkstemp(template);
+
+ if (rc != -1) {
+ save_errno = errno;
+ if (real___fxstat64(_STAT_VER, rc, &buf) != -1) {
+ pseudo_client_op(OP_CREAT, 0, -1, -1, template, &buf);
+ pseudo_client_op(OP_OPEN, 0, rc, -1, template, &buf);
+ } else {
+ pseudo_debug(1, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n",
+ rc, strerror(errno));
+ pseudo_client_op(OP_OPEN, 0, rc, -1, template, 0);
+ }
+ errno = save_errno;
+ }
+/* return rc;
+ * }
+ */
diff --git a/guts/open.c b/guts/open.c
new file mode 100644
index 0000000..7348daf
--- /dev/null
+++ b/guts/open.c
@@ -0,0 +1,11 @@
+/*
+ * 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/guts/open64.c b/guts/open64.c
new file mode 100644
index 0000000..0898c78
--- /dev/null
+++ b/guts/open64.c
@@ -0,0 +1,11 @@
+/*
+ * static int
+ * wrap_open64(const char *path, int flags, ...mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(AT_FDCWD, path, flags, mode | O_LARGEFILE);
+
+/* return rc;
+ * }
+ */
diff --git a/guts/openat.c b/guts/openat.c
new file mode 100644
index 0000000..9475aeb
--- /dev/null
+++ b/guts/openat.c
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+#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(2, "openat_creat: %s -> 0%o\n", path, mode);
+ errno = save_errno;
+ }
+
+ /* because we are not actually root, secretly mask in 0700 to the
+ * underlying mode
+ */
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real_open(path, flags, PSEUDO_FS_MODE(mode));
+#else
+ rc = real_openat(dirfd, path, flags, PSEUDO_FS_MODE(mode));
+#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) {
+ pseudo_client_op(OP_CREAT, 0, -1, dirfd, path, &buf);
+ }
+ pseudo_client_op(OP_OPEN, 0, rc, dirfd, path, &buf);
+ } else {
+ pseudo_debug(1, "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, 0, rc, dirfd, path, 0);
+ }
+ errno = save_errno;
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/openat64.c b/guts/openat64.c
new file mode 100644
index 0000000..926d9c8
--- /dev/null
+++ b/guts/openat64.c
@@ -0,0 +1,11 @@
+/*
+ * static int
+ * wrap_openat64(int dirfd, const char *path, int flags, ...mode_t mode) {
+ * int rc = -1;
+ */
+
+ rc = wrap_openat(dirfd, path, flags, mode | O_LARGEFILE);
+
+/* return rc;
+ * }
+ */
diff --git a/guts/rename.c b/guts/rename.c
new file mode 100644
index 0000000..9dd6d99
--- /dev/null
+++ b/guts/rename.c
@@ -0,0 +1,77 @@
+/*
+ * static int
+ * wrap_rename(const char *oldpath, const char *newpath) {
+ * int rc = -1;
+ */
+ pseudo_msg_t *msg;
+ struct stat64 oldbuf, newbuf;
+ int oldrc, newrc;
+ int save_errno;
+
+ pseudo_debug(1, "rename: %s->%s\n",
+ oldpath ? oldpath : "<nil>",
+ newpath ? newpath : "<nil>");
+
+ if (!oldpath || !newpath) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ save_errno = errno;
+
+ newrc = real___lxstat64(_STAT_VER, newpath, &newbuf);
+ oldrc = real___lxstat64(_STAT_VER, oldpath, &oldbuf);
+
+ errno = save_errno;
+
+ rc = real_rename(oldpath, newpath);
+ if (rc == -1) {
+ /* we failed, and we don't care why */
+ 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.
+ */
+
+ /* newpath must be removed. */
+ pseudo_client_op(OP_UNLINK, AT_SYMLINK_NOFOLLOW, -1, -1, newpath, &newbuf);
+
+ /* fill in "correct" details from server */
+ msg = pseudo_client_op(OP_STAT, AT_SYMLINK_NOFOLLOW, -1, -1, oldpath, &oldbuf);
+ if (msg && msg->result == RESULT_SUCCEED) {
+ pseudo_stat_msg(&oldbuf, msg);
+ pseudo_debug(1, "renaming %s, got old mode of 0%o\n", oldpath, (int) msg->mode);
+ } else {
+ /* create an entry under the old name, which will then be
+ * renamed; this way, children would get renamed too, if there
+ * were any.
+ */
+ pseudo_debug(1, "renaming new '%s' [%llu]\n",
+ oldpath, (unsigned long long) oldbuf.st_ino);
+ pseudo_client_op(OP_LINK, AT_SYMLINK_NOFOLLOW, -1, -1, oldpath, &oldbuf);
+ }
+ pseudo_client_op(OP_RENAME, AT_SYMLINK_NOFOLLOW, -1, -1, newpath, &oldbuf, oldpath);
+
+ errno = save_errno;
+/* return rc;
+ * }
+ */
diff --git a/guts/renameat.c b/guts/renameat.c
new file mode 100644
index 0000000..c5d295f
--- /dev/null
+++ b/guts/renameat.c
@@ -0,0 +1,12 @@
+/*
+ * static int
+ * wrap_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
+ * int rc = -1;
+ */
+
+ pseudo_diag("help! unimplemented renameat [%s -> %s].\n", oldpath, newpath);
+ rc = real_renameat(olddirfd, oldpath, newdirfd, newpath);
+
+/* return rc;
+ * }
+ */
diff --git a/guts/rmdir.c b/guts/rmdir.c
new file mode 100644
index 0000000..029e5a2
--- /dev/null
+++ b/guts/rmdir.c
@@ -0,0 +1,22 @@
+/*
+ * static int
+ * wrap_rmdir(const char *path) {
+ * int rc = -1;
+ */
+ struct stat64 buf;
+ int save_errno;
+
+ rc = real___lxstat64(_STAT_VER, path, &buf);
+ if (rc == -1) {
+ return rc;
+ }
+ rc = real_rmdir(path);
+ save_errno = errno;
+ if (rc != -1) {
+ pseudo_client_op(OP_UNLINK, AT_SYMLINK_NOFOLLOW, -1, -1, path, &buf);
+ }
+
+ errno = save_errno;
+/* return rc;
+ * }
+ */
diff --git a/guts/setegid.c b/guts/setegid.c
new file mode 100644
index 0000000..a24be76
--- /dev/null
+++ b/guts/setegid.c
@@ -0,0 +1,17 @@
+/*
+ * 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/guts/seteuid.c b/guts/seteuid.c
new file mode 100644
index 0000000..42cb3db
--- /dev/null
+++ b/guts/seteuid.c
@@ -0,0 +1,17 @@
+/*
+ * 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/guts/setfsgid.c b/guts/setfsgid.c
new file mode 100644
index 0000000..b046c6f
--- /dev/null
+++ b/guts/setfsgid.c
@@ -0,0 +1,16 @@
+/*
+ * 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/guts/setfsuid.c b/guts/setfsuid.c
new file mode 100644
index 0000000..1b58ce8
--- /dev/null
+++ b/guts/setfsuid.c
@@ -0,0 +1,16 @@
+/*
+ * 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/guts/setgid.c b/guts/setgid.c
new file mode 100644
index 0000000..0bb5f27
--- /dev/null
+++ b/guts/setgid.c
@@ -0,0 +1,24 @@
+/*
+ * 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/guts/setgroups.c b/guts/setgroups.c
new file mode 100644
index 0000000..d6856f2
--- /dev/null
+++ b/guts/setgroups.c
@@ -0,0 +1,12 @@
+/*
+ * static int
+ * wrap_setgroups(size_t size, const gid_t *list) {
+ * int rc = -1;
+ */
+
+ /* you always have all group privileges. we're like magic! */
+ rc = 0;
+
+/* return rc;
+ * }
+ */
diff --git a/guts/setregid.c b/guts/setregid.c
new file mode 100644
index 0000000..2444390
--- /dev/null
+++ b/guts/setregid.c
@@ -0,0 +1,27 @@
+/*
+ * static int
+ * wrap_setregid(gid_t rgid, gid_t egid) {
+ * int rc = -1;
+ */
+ rc = 0;
+ if (pseudo_euid != 0 && rgid != -1 &&
+ rgid != pseudo_egid && rgid != pseudo_rgid && rgid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && egid != -1 &&
+ egid != pseudo_egid && egid != pseudo_rgid && egid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (rc != -1) {
+ if (rgid != -1)
+ pseudo_rgid = rgid;
+ if (egid != -1)
+ pseudo_egid = egid;
+ pseudo_fgid = pseudo_egid;
+ pseudo_client_touchuid();
+ }
+/* return rc;
+ * }
+ */
diff --git a/guts/setresgid.c b/guts/setresgid.c
new file mode 100644
index 0000000..455fe62
--- /dev/null
+++ b/guts/setresgid.c
@@ -0,0 +1,34 @@
+/*
+ * static int
+ * wrap_setresgid(gid_t rgid, gid_t egid, gid_t sgid) {
+ * int rc = -1;
+ */
+ rc = 0;
+ if (pseudo_euid != 0 && rgid != -1 &&
+ rgid != pseudo_egid && rgid != pseudo_rgid && rgid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && egid != -1 &&
+ egid != pseudo_egid && egid != pseudo_rgid && egid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && sgid != -1 &&
+ sgid != pseudo_egid && sgid != pseudo_rgid && sgid != pseudo_sgid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (rc != -1) {
+ if (rgid != -1)
+ pseudo_rgid = rgid;
+ if (egid != -1)
+ pseudo_egid = egid;
+ if (sgid != -1)
+ pseudo_sgid = sgid;
+ pseudo_fgid = pseudo_egid;
+ pseudo_client_touchuid();
+ }
+/* return rc;
+ * }
+ */
diff --git a/guts/setresuid.c b/guts/setresuid.c
new file mode 100644
index 0000000..41dd81d
--- /dev/null
+++ b/guts/setresuid.c
@@ -0,0 +1,34 @@
+/*
+ * static int
+ * wrap_setresuid(uid_t ruid, uid_t euid, uid_t suid) {
+ * int rc = -1;
+ */
+ rc = 0;
+ if (pseudo_euid != 0 && ruid != -1 &&
+ ruid != pseudo_euid && ruid != pseudo_ruid && ruid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && euid != -1 &&
+ euid != pseudo_euid && euid != pseudo_ruid && euid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && suid != -1 &&
+ suid != pseudo_euid && suid != pseudo_ruid && suid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (rc != -1) {
+ if (ruid != -1)
+ pseudo_ruid = ruid;
+ if (euid != -1)
+ pseudo_euid = euid;
+ if (suid != -1)
+ pseudo_suid = suid;
+ pseudo_fuid = pseudo_euid;
+ pseudo_client_touchuid();
+ }
+/* return rc;
+ * }
+ */
diff --git a/guts/setreuid.c b/guts/setreuid.c
new file mode 100644
index 0000000..3669581
--- /dev/null
+++ b/guts/setreuid.c
@@ -0,0 +1,27 @@
+/*
+ * static int
+ * wrap_setreuid(uid_t ruid, uid_t euid) {
+ * int rc = -1;
+ */
+ rc = 0;
+ if (pseudo_euid != 0 && ruid != -1 &&
+ ruid != pseudo_euid && ruid != pseudo_ruid && ruid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (pseudo_euid != 0 && euid != -1 &&
+ euid != pseudo_euid && euid != pseudo_ruid && euid != pseudo_suid) {
+ rc = -1;
+ errno = EPERM;
+ }
+ if (rc != -1) {
+ if (ruid != -1)
+ pseudo_ruid = ruid;
+ if (euid != -1)
+ pseudo_euid = euid;
+ pseudo_fuid = pseudo_euid;
+ pseudo_client_touchuid();
+ }
+/* return rc;
+ * }
+ */
diff --git a/guts/setuid.c b/guts/setuid.c
new file mode 100644
index 0000000..bedebb6
--- /dev/null
+++ b/guts/setuid.c
@@ -0,0 +1,24 @@
+/*
+ * 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/guts/symlink.c b/guts/symlink.c
new file mode 100644
index 0000000..bc1c4be
--- /dev/null
+++ b/guts/symlink.c
@@ -0,0 +1,11 @@
+/*
+ * static int
+ * wrap_symlink(const char *oldpath, const char *newpath) {
+ * int rc = -1;
+ */
+
+ rc = wrap_symlinkat(oldpath, AT_FDCWD, newpath);
+
+/* return rc;
+ * }
+ */
diff --git a/guts/symlinkat.c b/guts/symlinkat.c
new file mode 100644
index 0000000..5f1cc59
--- /dev/null
+++ b/guts/symlinkat.c
@@ -0,0 +1,38 @@
+/*
+ * static int
+ * wrap_symlinkat(const char *oldpath, int dirfd, const char *newpath) {
+ * int rc = -1;
+ */
+ struct stat64 buf;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ rc = real_symlink(oldpath, newpath);
+#else
+ rc = real_symlinkat(oldpath, dirfd, newpath);
+#endif
+
+ if (rc == -1) {
+ return rc;
+ }
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real___lxstat64(_STAT_VER, newpath, &buf);
+#else
+ rc = real___fxstatat64(_STAT_VER, 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;
+ return rc;
+ }
+ /* just record the entry */
+ pseudo_client_op(OP_SYMLINK, AT_SYMLINK_NOFOLLOW, -1, dirfd, newpath, &buf);
+
+/* return rc;
+ * }
+ */
diff --git a/guts/unlink.c b/guts/unlink.c
new file mode 100644
index 0000000..369089b
--- /dev/null
+++ b/guts/unlink.c
@@ -0,0 +1,11 @@
+/*
+ * static int
+ * wrap_unlink(const char *path) {
+ * int rc = -1;
+ */
+
+ rc = wrap_unlinkat(AT_FDCWD, path, 0);
+
+/* return rc;
+ * }
+ */
diff --git a/guts/unlinkat.c b/guts/unlinkat.c
new file mode 100644
index 0000000..cfa71e7
--- /dev/null
+++ b/guts/unlinkat.c
@@ -0,0 +1,44 @@
+/*
+ * static int
+ * wrap_unlinkat(int dirfd, const char *path, int flags) {
+ * int rc = -1;
+ */
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ if (dirfd != AT_FDCWD) {
+ errno = ENOSYS;
+ return -1;
+ }
+ if (flags) {
+ /* 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",
+ flags, path ? path : "<nil>");
+ errno = ENOSYS;
+ return -1;
+ }
+#endif
+
+ struct stat64 buf;
+
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real___lxstat64(_STAT_VER, path, &buf);
+#else
+ rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, AT_SYMLINK_NOFOLLOW);
+#endif
+ if (rc == -1) {
+ return rc;
+ }
+#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS
+ rc = real_unlink(path);
+#else
+ rc = real_unlinkat(dirfd, path, flags);
+#endif
+ if (rc != -1) {
+ pseudo_client_op(OP_UNLINK, AT_SYMLINK_NOFOLLOW, -1, dirfd, path, &buf);
+ }
+
+/* return rc;
+ * }
+ */
diff --git a/guts/vfork.c b/guts/vfork.c
new file mode 100644
index 0000000..7d234da
--- /dev/null
+++ b/guts/vfork.c
@@ -0,0 +1,14 @@
+/*
+ * static int
+ * wrap_vfork(void) {
+ * int rc = -1;
+ */
+
+ /* like fakeroot, we really can't handle vfork's implications */
+ rc = real_fork();
+ if (rc == 0)
+ pseudo_client_reset();
+
+/* return rc;
+ * }
+ */
diff --git a/makewrappers b/makewrappers
new file mode 100755
index 0000000..11ef407
--- /dev/null
+++ b/makewrappers
@@ -0,0 +1,429 @@
+#!/bin/sh
+#
+# makewrappers, script to auto-generate wrapper functions
+#
+# 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
+#
+
+case $# in
+0) echo >&2 "Usage: makewrappers file [...]"
+ exit 1
+ ;;
+esac
+
+# save old versions
+test -f "pseudo_wrappers.c" && mv pseudo_wrappers.c pseudo_wrappers.c.old
+test -f "pseudo_wrappers.h" && mv pseudo_wrappers.h pseudo_wrappers.h.old
+
+# create files
+exec 5>pseudo_wrappers.c
+exec 6>pseudo_wrappers.h
+exec 7>pseudo_wrapper_table.c
+
+# "cat >&N <<EOF" populates the file on &N with the here-document.
+
+# pseudo_wrappers.c has to have all the hunks used by the wrapper functions,
+# including guts/*.c.
+cat >&5 <<EOF
+`cat guts/COPYRIGHT`
+/* wrapper functions. generated automatically. */
+
+/* This file is generated and should not be modified. See the makewrappers
+ * script if you want to modify this. */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+
+#include "pseudo.h"
+#include "pseudo_wrappers.h"
+#include "pseudo_ipc.h"
+#include "pseudo_client.h"
+
+static void pseudo_enosys(const char *);
+static int pseudo_populate_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;
+
+int
+pseudo_getlock() {
+ 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;
+ }
+ }
+}
+
+void
+pseudo_droplock() {
+ 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;
+}
+
+EOF
+
+cat >&6 <<EOF
+`cat guts/COPYRIGHT`
+EOF
+
+# the wrapper function table is also #included, it is a separate file so it
+# can be written as we go, but still be a single table.
+cat >&7 <<EOF
+`cat guts/COPYRIGHT`
+/* 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. */
+static struct {
+ char *name; /* the name */
+ int (**real)(void); /* the underlying syscall */
+ int (*dummy)(void); /* the always-fails form */
+ int (*wrapper)(void); /* the wrapper from guts/name.c */
+} pseudo_functions[] = {
+EOF
+
+printf >&2 'Reading signatures...\n'
+for file in "$@"
+do
+ # read lines containing wrappable functions, write wrapper
+ # declarations, definitions, and so on.
+ printf >&2 '[%s]' "$file"
+ while read signature
+ do
+ # skip comments
+ case $signature in
+ \#*) continue;;
+ esac
+ # obtain return type, name, and arguments
+ args=`expr "$signature" : '.*(\(.*\));'`
+ return_and_name=`expr "$signature" : '\(.*\)('`
+ name=`expr "$return_and_name" : '.*[^a-zA-Z0-9_]\([a-zA-Z0-9_]*\)$'`
+ type=`expr "$return_and_name" : '\(.*[^ ]\) *'"$name"'$'`
+ printf >&2 ' %s' "$name"
+ argnames=''
+ save_IFS=$IFS
+ IFS=,
+ set -- $args
+ IFS=$save_ifs
+ args=''
+ optional_arg=false
+ for arg
+ do
+ # strip whitespace
+ arg=${arg# }
+ arg=${arg% }
+
+ # handle optional arguments, like the third arg
+ # to open()
+ case $arg in
+ ...*)
+ optional_arg=true
+ args="$args${args:+, }..."
+ arg=`expr "$arg" : '\.\.\.{\(.*\)}'`
+ argname=`expr "$arg" : '.*[^a-zA-Z0-9_]\([a-zA-Z0-9_]*\)$'`
+ argnames="$argnames${argnames:+, }$argname"
+
+ # we need this to extract and pass the argument
+ optional_decl=$arg
+ optional_prev=$prev_argname
+ optional_name=$argname
+ optional_type=`expr "$arg" : '\(.*[^ ]\) *'"$argname"'$'`
+ ;;
+ *)
+ argname=`expr "$arg" : '.*[^a-zA-Z0-9_]\([a-zA-Z0-9_]*\)$'`
+ args="$args${args:+, }$arg"
+ argnames="$argnames${argnames:+, }$argname"
+ prev_argname=$argname
+ ;;
+ esac
+ done
+
+ # determine default return value.
+ case $type in
+ int)
+ default_value=-1;;
+ uid_t|gid_t)
+ default_value=0;;
+ 'FILE *')
+ default_value=NULL;;
+ *) echo >&2 "Unknown type '$type'." ; exit 1 ;;
+ esac
+ # create the wrappers
+ # first the dummy, and the function pointer:
+ cat >&5 <<EOF
+static $type
+dummy_$name($args) {
+ pseudo_enosys("$name");
+ errno = ENOSYS;
+ return $default_value;
+}
+
+static $type (*real_$name)($args) = dummy_$name;
+
+EOF
+ # then the wrapper signature and args:
+ if $optional_arg; then
+ cat >&5 <<EOF
+$type
+$name($args) {
+ $optional_decl;
+ va_list ap;
+ va_start(ap, $optional_prev);
+ $optional_name = va_arg(ap, $optional_type);
+ va_end(ap);
+
+EOF
+ else
+ cat >&5 <<EOF
+$type
+$name($args) {
+EOF
+ fi
+ # and now the body of the wrapper:
+ cat >&5 <<EOF
+ if (pseudo_getlock()) {
+ errno = EBUSY;
+ return $default_value;
+ }
+ if (pseudo_populate_wrappers()) {
+ $type rc = $default_value;
+ int save_errno;
+ if (antimagic > 0) {
+ if (real_$name) {
+ rc = (*real_$name)($argnames);
+ } else {
+ rc = dummy_$name($argnames);
+ }
+ } else {
+ rc = wrap_$name($argnames);
+ }
+ save_errno = errno;
+ pseudo_droplock();
+ errno = save_errno;
+ return rc;
+ } else {
+ pseudo_droplock();
+ return dummy_$name($argnames);
+ }
+}
+
+EOF
+ # and now the signature part for the actual implementation:
+ # and the guts include file.
+
+ # the wrapper function is actually declared in
+ # pseudo_wrapper.c, with guts implemented in a separate
+ # file with comments indicating the signature.
+ guts="guts/$name.c"
+
+ # the actual wrapper function, including argument setup
+ if $optional_arg; then
+ cat >&5 << EOF
+static $type
+wrap_$name($args) {
+ $type rc = $default_value;
+ $optional_decl;
+
+ va_list ap;
+ va_start(ap, $optional_prev);
+ $optional_name = va_arg(ap, $optional_type);
+ va_end(ap);
+
+#include "$guts"
+
+ return rc;
+}
+EOF
+ else
+ cat >&5 << EOF
+static $type
+wrap_$name($args) {
+ $type rc = $default_value;
+
+#include "$guts"
+
+ return rc;
+}
+EOF
+ fi
+
+ # if the guts file didn't already exist, create a default.
+ if test ! -f "$guts"; then
+ if $optional_arg; then
+ cat > "$guts" <<EOF
+/*
+ * static $type
+ * wrap_$name($args$optional_decl) {
+ * $type rc = $default_value;
+ */
+
+ rc = real_$name($argnames);
+
+/* return rc;
+ * }
+ */
+EOF
+ else
+ cat > "$guts" <<EOF
+/*
+ * static $type
+ * wrap_$name($args) {
+ * $type rc = $default_value;
+ */
+
+ rc = real_$name($argnames);
+
+/* return rc;
+ * }
+ */
+EOF
+ fi
+ fi
+ # prototypes for pseudo_wrappers.h
+ cat >&6 <<EOF
+/* $type $name($args); */
+static $type dummy_$name($args);
+static $type wrap_$name($args);
+static $type (*real_$name)($args);
+
+EOF
+
+ # and entries in the Big Table
+ cat >&7 <<EOF
+ { /* $type $name($args); */
+ "$name",
+ (int (**)(void)) &real_$name,
+ (int (*)(void)) dummy_$name,
+ (int (*)(void)) wrap_$name
+ },
+EOF
+ done < $file
+done
+printf >&2 '.\n'
+
+# sentinel values
+cat >&7 <<EOF
+ { NULL, NULL, NULL, NULL },
+};
+EOF
+
+# and now the actual populate_wrappers function.
+cat >&5 <<EOF
+#include "pseudo_wrapper_table.c"
+
+extern char *program_invocation_short_name;
+
+static void
+pseudo_enosys(const char *func) {
+ pseudo_diag("pseudo: ENOSYS for '%s'.\n", func ? func : "<nil>");
+ if (getenv("PSEUDO_ENOSYS_ABORT"))
+ abort();
+}
+
+static int
+pseudo_populate_wrappers(void) {
+ int i;
+ char *debug;
+ static int done = 0;
+ char *pseudo_path = 0;
+
+ if (done)
+ return done;
+ pseudo_getlock();
+ pseudo_antimagic();
+ for (i = 0; pseudo_functions[i].name; ++i) {
+ if (*pseudo_functions[i].real == pseudo_functions[i].dummy) {
+ int (*f)(void);
+ char *e;
+ dlerror();
+ f = dlsym(RTLD_NEXT, pseudo_functions[i].name);
+ if ((e = dlerror()) != NULL) {
+ /* leave it pointed to dummy */
+ pseudo_diag("No wrapper for %s: %s\n", pseudo_functions[i].name, e);
+ } else {
+ if (f)
+ *pseudo_functions[i].real = f;
+ }
+ }
+ }
+ done = 1;
+ debug = getenv("PSEUDO_DEBUG");
+ if (debug) {
+ int level = atoi(debug);
+ for (i = 0; i < level; ++i) {
+ pseudo_debug_verbose();
+ }
+ }
+ /* must happen after wrappers are set up, because it can call
+ * getcwd(), which needs wrappers, but must happen here so that
+ * any attempt to use a path in a wrapper function will have a
+ * value for cwd.
+ */
+ pseudo_client_reset();
+ pseudo_path = pseudo_prefix_path(NULL);
+ if (pseudo_dir_fd == -1) {
+ if (pseudo_path) {
+ pseudo_dir_fd = open(pseudo_path, O_RDONLY);
+ pseudo_dir_fd = pseudo_fd(pseudo_dir_fd, MOVE_FD);
+ free(pseudo_path);
+ } else {
+ pseudo_diag("No prefix available to to find server.\n");
+ exit(1);
+ }
+ if (pseudo_dir_fd == -1) {
+ pseudo_diag("Can't open prefix path (%s) for server.\n",
+ strerror(errno));
+ exit(1);
+ }
+ }
+ pseudo_debug(2, "(%s) set up wrappers\n", program_invocation_short_name);
+ pseudo_magic();
+ pseudo_droplock();
+ return done;
+}
+EOF
diff --git a/offsets.c b/offsets.c
new file mode 100644
index 0000000..baf63da
--- /dev/null
+++ b/offsets.c
@@ -0,0 +1,52 @@
+/*
+ * 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
+ *
+ */
+#define _LARGEFILE64_SOURCE
+#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", offsetof(pseudo_msg_t, type));
+ printf("op: %d\n", offsetof(pseudo_msg_t, op));
+ printf("result: %d\n", offsetof(pseudo_msg_t, result));
+ printf("xerrno: %d\n", offsetof(pseudo_msg_t, xerrno));
+ printf("client: %d\n", offsetof(pseudo_msg_t, client));
+ printf("dev: %d\n", offsetof(pseudo_msg_t, dev));
+ printf("ino: %d\n", offsetof(pseudo_msg_t, ino));
+ printf("uid: %d\n", offsetof(pseudo_msg_t, uid));
+ printf("gid: %d\n", offsetof(pseudo_msg_t, gid));
+ printf("mode: %d\n", offsetof(pseudo_msg_t, mode));
+ printf("rdev: %d\n", offsetof(pseudo_msg_t, rdev));
+ printf("pathlen: %d\n", offsetof(pseudo_msg_t, pathlen));
+ printf("path: %d\n", offsetof(pseudo_msg_t, path));
+ printf("size: %d\n", sizeof(pseudo_msg_t));
+ return 0;
+}
+
diff --git a/pseudo.1 b/pseudo.1
new file mode 100644
index 0000000..35c3c69
--- /dev/null
+++ b/pseudo.1
@@ -0,0 +1,353 @@
+.\"
+.\" 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 SYNOPSIS
+.B pseudo
+.RB [ \-dflv ]
+.RB [ \-P\ prefix ]
+.RB [ \-t\ timeout ]
+.RB [ command ]
+.PP
+.B pseudo
+.RB [ \-P\ prefix ]
+.B \-S
+.PP
+.B pseudo
+.RB [ \-P\ 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 four possible modes:
+
+.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.
+
+.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.
+
+.BI \-l\ (log)
+Enable logging. The
+.I pseudo
+daemon will log every filesystem transaction in the log database.
+
+.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.
+
+.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
+.B PSEUDO_DEBUG
+environment variable; multiple
+.B \-v
+options increase the debugging level.
+
+.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.
+
+.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. 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_DEBUG
+This variable holds the "debug level" for
+.I pseudo
+to run at. In general, this is useful only for debugging
+.I pseudo
+itself.
+.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
+.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_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_RELOADED
+This purely internal variable is used to track state while trying
+to re-execute to get rid of the
+.B LD_PRELOAD
+value when spawning a server. (The
+.I pseudo
+server itself cannot function running in the
+.I pseudo environment.)
+.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 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..515f447
--- /dev/null
+++ b/pseudo.c
@@ -0,0 +1,667 @@
+/*
+ * pseudo.c, main pseudo utility program
+ *
+ * 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 <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 "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_client.h"
+#include "pseudo_server.h"
+#include "pseudo_db.h"
+
+int opt_d = 0;
+int opt_f = 0;
+int opt_l = 0;
+long opt_p = 0;
+int opt_S = 0;
+
+static int pseudo_op(pseudo_msg_t *msg, const char *tag);
+
+void
+usage(void) {
+ pseudo_diag("Usage: pseudo [-dflv] [-P prefix] [-t timeout] [command]\n");
+ pseudo_diag(" pseudo [-dflv] [-P prefix] -S\n");
+ pseudo_diag(" pseudo [-dflv] [-P prefix] -V\n");
+ exit(1);
+}
+
+/* main server process */
+int
+main(int argc, char *argv[]) {
+ int o;
+ char *s;
+ int lockfd, newfd;
+ char *ld_env = getenv("LD_PRELOAD");
+ int rc;
+ char opts[PATH_MAX] = "", *optptr = opts;
+ char *lockname;
+
+ s = getenv("PSEUDO_DEBUG");
+ if (s) {
+ int level = atoi(s);
+ for (o = 0; o < level; ++o) {
+ pseudo_debug_verbose();
+ }
+ }
+
+ if (ld_env && strstr(ld_env, "libpseudo")) {
+ extern char **environ;
+
+ pseudo_debug(2, "can't run daemon with libpseudo in LD_PRELOAD\n");
+ if (getenv("PSEUDO_RELOADED")) {
+ pseudo_diag("I can't seem to make LD_PRELOAD go away. Sorry.\n");
+ exit(1);
+ }
+ setenv("PSEUDO_RELOADED", "YES", 1);
+ pseudo_dropenv();
+ execve(argv[0], argv, environ);
+ exit(1);
+ }
+ unsetenv("PSEUDO_RELOADED");
+
+ /* 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, "+dflP:St:vV")) != -1) {
+ switch (o) {
+ case 'd':
+ /* run as daemon */
+ opt_d = 1;
+ break;
+ case 'f':
+ /* run foregrounded */
+ opt_f = 1;
+ break;
+ case 'l':
+ optptr += snprintf(optptr, PATH_MAX - (optptr - opts),
+ "%s-l", optptr > opts ? " " : "");
+ opt_l = 1;
+ break;
+ case 'P':
+ setenv("PSEUDO_PREFIX", optarg, 1);
+ break;
+ case 'S':
+ opt_S = 1;
+ break;
+ case 't':
+ pseudo_server_timeout = strtol(optarg, &s, 10);
+ if (*s && !isspace(*s)) {
+ pseudo_diag("Timeout must be an integer value.\n");
+ usage();
+ }
+ optptr += snprintf(optptr, PATH_MAX - (optptr - opts),
+ "%s-t %d", optptr > opts ? " " : "",
+ pseudo_server_timeout);
+ break;
+ case 'v':
+ pseudo_debug_verbose();
+ break;
+ case 'V':
+ printf("pseudo version %s\n", pseudo_version ? pseudo_version : "<undefined>");
+ printf("pseudo configuration flags:\n prefix: %s\n suffix: %s\n",
+ PSEUDO_PREFIX,
+ PSEUDO_SUFFIX);
+ printf("Set PSEUDO_PREFIX to run with a different prefix.\n");
+ exit(0);
+ break;
+ case '?':
+ pseudo_diag("unknown/invalid argument (option '%c').\n", optopt);
+ usage();
+ break;
+ }
+ }
+
+ if (!pseudo_get_prefix(argv[0])) {
+ pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n");
+ exit(1);
+ }
+
+ if (opt_S) {
+ return pseudo_client_shutdown();
+ }
+
+ if (opt_d && opt_f) {
+ pseudo_diag("You cannot run a foregrounded daemon.\n");
+ exit(1);
+ }
+
+ if (opt_f || opt_d) {
+ if (argc > optind) {
+ pseudo_diag("pseudo: running program implies spawning background daemon.\n");
+ exit(1);
+ }
+ } else {
+ if (argc > optind) {
+ pseudo_debug(2, "running command: %s\n",
+ argv[optind]);
+ argc -= optind;
+ argv += optind;
+ } else {
+ static char *newargv[2];
+ argv = newargv;
+ pseudo_debug(2, "running shell.\n");
+ argv[0] = getenv("SHELL");
+ if (!argv[0])
+ argv[0] = "/bin/sh";
+ argv[1] = NULL;
+ }
+ /* build an environment containing libpseudo */
+ pseudo_debug(2, "setting up pseudo environment.\n");
+ pseudo_setupenv(opts);
+ rc = execvp(argv[0], argv);
+ if (rc == -1) {
+ pseudo_diag("pseudo: can't run %s: %s\n",
+ argv[0], strerror(errno));
+ }
+ exit(1);
+ }
+ /* if we got here, we are not running a command, and we are not in
+ * a pseudo environment.
+ */
+ pseudo_new_pid();
+
+ pseudo_debug(3, "opening lock.\n");
+ lockname = pseudo_prefix_path(PSEUDO_LOCKFILE);
+ if (!lockname) {
+ pseudo_diag("Couldn't allocate a file path.\n");
+ exit(1);
+ }
+ 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(1);
+ }
+ 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(3, "acquiring lock.\n");
+ if (flock(lockfd, LOCK_EX | LOCK_NB) < 0) {
+ if (errno == EACCES || errno == EAGAIN) {
+ pseudo_debug(1, "Existing server has lock. Exiting.\n");
+ } else {
+ pseudo_diag("Error obtaining lock: %s\n", strerror(errno));
+ }
+ exit(0);
+ } else {
+ pseudo_debug(2, "Acquired lock.\n");
+ }
+ pseudo_debug(3, "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 *tag) {
+ pseudo_msg_t msg_header;
+ pseudo_msg_t by_path = { 0 }, by_ino = { 0 };
+ pseudo_msg_t db_header;
+ char *path_by_ino = 0;
+ char *oldpath = 0;
+ int found_path = 0, found_ino = 0;
+ int prefer_ino = 0;
+
+ if (!msg)
+ return 1;
+
+ msg->result = RESULT_SUCCEED;
+ msg->xerrno = 0;
+
+ /* debugging message. Primary key first. */
+ switch (msg->op) {
+ case OP_FCHOWN:
+ case OP_FCHMOD:
+ case OP_FSTAT:
+ prefer_ino = 1;
+ pseudo_debug(2, "%s %llu [%s]: ", pseudo_op_name(msg->op),
+ (unsigned long long) msg->ino,
+ msg->pathlen ? msg->path : "no path");
+ break;
+ default:
+ pseudo_debug(2, "%s %s [%llu]: ", pseudo_op_name(msg->op),
+ msg->pathlen ? msg->path : "no path",
+ (unsigned long long) msg->ino);
+ break;
+ }
+
+ /* stash original header, in case we need it later */
+ msg_header = *msg;
+
+ /* There should usually be a path. Even for f* ops, the client
+ * tries to provide a path from its table of known fd paths.
+ */
+ if (msg->pathlen) {
+ if (msg->op == OP_RENAME) {
+ oldpath = msg->path + strlen(msg->path) + 1;
+ pseudo_debug(2, "rename: path %s, oldpath %s\n",
+ msg->path, oldpath);
+ }
+ /* for now, don't canonicalize paths anymore */
+ /* used to do it here, but now doing it in client */
+ if (!pdb_find_file_path(msg)) {
+ by_path = *msg;
+ found_path = 1;
+ } else {
+ if (msg->op != OP_RENAME && msg->op != OP_LINK) {
+ pseudo_debug(3, "(new?) ");
+ }
+ }
+ }
+
+ /* search on original inode -- in case of mismatch */
+ by_ino = msg_header;
+ if (msg_header.ino != 0) {
+ if (msg->pathlen && !pdb_find_file_exact(msg)) {
+ /* restore header contents */
+ by_ino = *msg;
+ *msg = msg_header;
+ found_ino = 1;
+ /* note: we have to avoid freeing this later */
+ path_by_ino = msg->path;
+ } else if (!pdb_find_file_dev(&by_ino)) {
+ found_ino = 1;
+ path_by_ino = pdb_get_file_path(&by_ino);
+ }
+ }
+
+ pseudo_debug(3, "incoming: '%s'%s [%llu]%s\n",
+ msg->pathlen ? msg->path : "no path",
+ found_path ? "+" : "-",
+ (unsigned long long) msg_header.ino,
+ found_ino ? "+" : "-");
+
+ if (found_path) {
+ /* This is a bad sign. We should never have a different entry
+ * for the inode...
+ */
+ if (by_path.ino != msg_header.ino) {
+ 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 (found_ino) {
+ /* 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(2, "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(2, "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:
+ 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) {
+ 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(1, "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:
+ 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_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) msg_header.ino,
+ path_by_ino ? path_by_ino : "no path");
+ pdb_unlink_file_dev(&by_ino);
+ }
+ if (!found_path) {
+ pdb_link_file(msg);
+ } 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:
+ case OP_FCHMOD:
+ pseudo_debug(2, "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 the path is not known, link it */
+ if (!found_path) {
+ pseudo_debug(2, "(new) ");
+ pdb_link_file(msg);
+ }
+ break;
+ case OP_CHOWN:
+ case OP_FCHOWN:
+ pseudo_debug(2, "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 the path is not known, link it */
+ if (!found_path) {
+ pdb_link_file(msg);
+ }
+ break;
+ case OP_STAT:
+ 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) {
+ *msg = db_header;
+ } else {
+ msg->result = RESULT_FAIL;
+ }
+ pseudo_debug(3, "%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:
+ 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(2, "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(2, "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(2, "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(2, "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;
+ }
+ pdb_link_file(msg);
+ break;
+ case OP_RENAME:
+ /* a rename implies renaming an existing entry... and every
+ * database entry rooted in it.
+ */
+ pdb_rename_file(oldpath, 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);
+ /* 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(2, "unlink, link count 1, unlinking anything with ino %llu.\n",
+ (unsigned long long) msg->ino);
+ pdb_unlink_file_dev(msg);
+ }
+ break;
+ case OP_MKDIR:
+ case OP_MKNOD:
+ pseudo_debug(2, "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;
+ pdb_link_file(msg);
+ 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(2, "completed %s.\n", pseudo_op_name(msg->op));
+ if (opt_l)
+ pdb_log_msg(SEVERITY_INFO, msg, 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 *tag) {
+ switch (msg->type) {
+ case PSEUDO_MSG_PING:
+ msg->result = RESULT_SUCCEED;
+ if (opt_l)
+ pdb_log_msg(SEVERITY_INFO, msg, tag, "ping");
+ return 0;
+ break;
+ case PSEUDO_MSG_OP:
+ return pseudo_op(msg, tag);
+ break;
+ case PSEUDO_MSG_ACK:
+ case PSEUDO_MSG_NAK:
+ default:
+ pdb_log_msg(SEVERITY_WARN, msg, tag, "invalid message");
+ return 1;
+ }
+}
diff --git a/pseudo.h b/pseudo.h
new file mode 100644
index 0000000..560991f
--- /dev/null
+++ b/pseudo.h
@@ -0,0 +1,128 @@
+/*
+ * pseudo.h, shared definitions and structures for pseudo
+ *
+ * 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 <stdlib.h>
+
+typedef enum {
+ OP_UNKNOWN = -1,
+ OP_NONE = 0,
+ OP_CHDIR,
+ OP_CHMOD,
+ OP_CHOWN,
+ OP_CLOSE,
+ OP_CREAT,
+ OP_DUP,
+ OP_FCHMOD,
+ OP_FCHOWN,
+ OP_FSTAT,
+ OP_LINK,
+ OP_MKDIR,
+ OP_MKNOD,
+ OP_OPEN,
+ OP_RENAME,
+ OP_STAT,
+ OP_UNLINK,
+ OP_SYMLINK,
+ OP_MAX
+} op_id_t;
+extern char *pseudo_op_name(op_id_t id);
+extern op_id_t pseudo_op_id(char *name);
+
+typedef enum {
+ RESULT_UNKNOWN = -1,
+ RESULT_NONE = 0,
+ RESULT_SUCCEED,
+ RESULT_FAIL,
+ RESULT_ERROR,
+ RESULT_MAX
+} res_id_t;
+extern char *pseudo_res_name(res_id_t id);
+extern res_id_t pseudo_res_id(char *name);
+
+typedef enum {
+ SEVERITY_UNKNOWN = -1,
+ SEVERITY_NONE = 0,
+ SEVERITY_DEBUG,
+ SEVERITY_INFO,
+ SEVERITY_WARN,
+ SEVERITY_ERROR,
+ SEVERITY_CRITICAL,
+ SEVERITY_MAX
+} sev_id_t;
+extern char *pseudo_sev_name(sev_id_t id);
+extern sev_id_t pseudo_sev_id(char *name);
+
+typedef enum pseudo_query_type {
+ PSQT_UNKNOWN = -1,
+ PSQT_NONE,
+ PSQT_EXACT, PSQT_LESS, PSQT_GREATER, PSQT_BITAND,
+ PSQT_NOTEQUAL, PSQT_LIKE, PSQT_NOTLIKE, PSQT_SQLPAT,
+ PSQT_MAX
+} pseudo_query_type_t;
+extern char *pseudo_query_type_name(pseudo_query_type_t id);
+extern char *pseudo_query_type_sql(pseudo_query_type_t id);
+extern pseudo_query_type_t pseudo_query_type_id(char *name);
+
+/* 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.
+ */
+typedef enum pseudo_query_field {
+ PSQF_UNKNOWN = -1,
+ PSQF_NONE, /* so that these are always non-zero */
+ PSQF_CLIENT, PSQF_DEV, PSQF_FD, PSQF_FTYPE,
+ PSQF_GID, PSQF_ID, PSQF_INODE, PSQF_MODE,
+ PSQF_OP, PSQF_ORDER, PSQF_PATH, PSQF_PERM,
+ PSQF_RESULT, PSQF_SEVERITY, PSQF_STAMP, PSQF_TAG,
+ PSQF_TEXT, PSQF_UID,
+ PSQF_MAX
+} pseudo_query_field_t;
+extern char *pseudo_query_field_name(pseudo_query_field_t id);
+extern pseudo_query_field_t pseudo_query_field_id(char *name);
+
+extern void pseudo_debug_verbose(void);
+extern void pseudo_debug_terse(void);
+extern int pseudo_util_debug_fd;
+#ifndef NDEBUG
+extern int pseudo_debug_real(int, char *, ...) __attribute__ ((format (printf, 2, 3)));
+#define pseudo_debug pseudo_debug_real
+#else
+/* oh no, mister compiler, please don't optimize me away! */
+static inline void pseudo_debug(int level, char *text, ...) { }
+#endif
+extern int pseudo_diag(char *, ...) __attribute__ ((format (printf, 1, 2)));
+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(char *, const char *, size_t, size_t *, int);
+extern void pseudo_dropenv(void);
+extern void pseudo_setupenv(char *);
+extern char *pseudo_prefix_path(char *);
+extern char *pseudo_get_prefix(char *);
+
+extern char *pseudo_version;
+
+#define PSEUDO_DATA "var/pseudo/"
+#define PSEUDO_LOCKFILE PSEUDO_DATA "pseudo.lock"
+#define PSEUDO_LOGFILE PSEUDO_DATA "pseudo.log"
+#define PSEUDO_PIDFILE PSEUDO_DATA "pseudo.pid"
+#define PSEUDO_SOCKET PSEUDO_DATA "pseudo.socket"
diff --git a/pseudo_client.c b/pseudo_client.c
new file mode 100644
index 0000000..eb3cf81
--- /dev/null
+++ b/pseudo_client.c
@@ -0,0 +1,836 @@
+/*
+ * pseudo_client.c, pseudo client library 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
+ *
+ */
+#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 "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_client.h"
+
+static int connect_fd = -1;
+static int server_pid = 0;
+int pseudo_dir_fd = -1;
+
+static char **fd_paths = 0;
+static int nfds = 0;
+static char cwdbuf[PATH_MAX * 2], *cwd;
+static int cwdlen = 0;
+static int messages = 0;
+static struct timeval message_time = { 0 };
+static int pseudo_inited = 0;
+
+/* note: these are int, not uid_t/gid_t, so I can use 'em with scanf */
+int pseudo_ruid;
+int pseudo_euid;
+int pseudo_suid;
+int pseudo_fuid;
+int pseudo_rgid;
+int pseudo_egid;
+int pseudo_sgid;
+int pseudo_fgid;
+
+void
+pseudo_client_touchuid(void) {
+ static char uidbuf[256];
+ snprintf(uidbuf, 256, "%d,%d,%d,%d",
+ pseudo_ruid, pseudo_euid, pseudo_suid, pseudo_fuid);
+ setenv("PSEUDO_UIDS", uidbuf, 1);
+}
+
+void
+pseudo_client_touchgid(void) {
+ static char gidbuf[256];
+ snprintf(gidbuf, 256, "%d,%d,%d,%d",
+ pseudo_rgid, pseudo_egid, pseudo_sgid, pseudo_fgid);
+ setenv("PSEUDO_GIDS", gidbuf, 1);
+}
+
+static char *
+fd_path(int fd) {
+ if (fd >= 0 && fd < nfds) {
+ return fd_paths[fd];
+ }
+ if (fd == AT_FDCWD) {
+ return cwd;
+ }
+ return 0;
+}
+
+static void
+pseudo_client_path(int fd, const char *path) {
+ if (fd < 0)
+ return;
+
+ if (fd >= nfds) {
+ int i;
+ pseudo_debug(2, "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(2, "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;
+}
+
+void
+pseudo_client_reset() {
+ pseudo_antimagic();
+ pseudo_new_pid();
+ if (connect_fd != -1) {
+ close(connect_fd);
+ connect_fd = -1;
+ }
+ cwd = getcwd(cwdbuf, PATH_MAX);
+ cwdlen = strlen(cwd);
+ if (!pseudo_inited) {
+ char *env;
+
+ env = getenv("PSEUDO_UIDS");
+ if (env)
+ sscanf(env, "%d,%d,%d,%d",
+ &pseudo_ruid, &pseudo_euid,
+ &pseudo_suid, &pseudo_fuid);
+
+ env = getenv("PSEUDO_GIDS");
+ if (env)
+ sscanf(env, "%d,%d,%d,%d",
+ &pseudo_rgid, &pseudo_egid,
+ &pseudo_sgid, &pseudo_fuid);
+
+ pseudo_inited = 1;
+ }
+ pseudo_magic();
+}
+
+/* spawn server */
+static int
+client_spawn_server(void) {
+ int status;
+ FILE *fp;
+ extern char **environ;
+ int cwd_fd;
+
+ if ((server_pid = fork()) != 0) {
+ if (server_pid == -1) {
+ pseudo_diag("couldn't fork server: errno %d\n", errno);
+ return 1;
+ }
+ pseudo_debug(4, "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;
+ cwd_fd = open(".", O_RDONLY);
+ if (cwd_fd == -1) {
+ pseudo_diag("Couldn't stash directory before opening pidfile: %s",
+ strerror(errno));
+ close(connect_fd);
+ connect_fd = -1;
+ return 1;
+ }
+ if (fchdir(pseudo_dir_fd)) {
+ pseudo_diag("Couldn't change to server dir [%d]: %s\n",
+ pseudo_dir_fd, strerror(errno));
+ }
+ fp = fopen(PSEUDO_PIDFILE, "r");
+ if (fchdir(cwd_fd) == -1) {
+ pseudo_diag("return to previous directory failed: %s\n",
+ strerror(errno));
+ }
+ close(cwd_fd);
+ if (fp) {
+ if (fscanf(fp, "%d", &server_pid) != 1) {
+ pseudo_debug(1, "Opened server PID file, but didn't get a pid.\n");
+ }
+ fclose(fp);
+ } else {
+ pseudo_diag("no pid file (%s): %s\n",
+ PSEUDO_PIDFILE, strerror(errno));
+ }
+ pseudo_debug(2, "read new pid file: %d\n", server_pid);
+ /* at this point, we should have a new server_pid */
+ return 0;
+ } else {
+ char *base_args[] = { NULL, NULL, NULL };
+ char **argv;
+ int args;
+ int fd;
+
+ pseudo_new_pid();
+ base_args[0] = "bin/pseudo";
+ base_args[1] = "-d";
+ if (getenv("PSEUDO_OPTS")) {
+ char *option_string = strdup(getenv("PSEUDO_OPTS"));
+ 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;
+ }
+ pseudo_dropenv();
+ pseudo_debug(4, "calling execve on %s\n", argv[0]);
+ /* and now, execute the server */
+ if (fchdir(pseudo_dir_fd)) {
+ pseudo_diag("Couldn't change to server dir [%d]: %s\n",
+ pseudo_dir_fd, strerror(errno));
+ }
+ /* 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);
+ }
+ execve(argv[0], argv, environ);
+ 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 *tag = getenv("PSEUDO_TAG");
+
+ ping.type = PSEUDO_MSG_PING;
+ ping.op = OP_NONE;
+ if (tag) {
+ ping.pathlen = strlen(tag);
+ } else {
+ ping.pathlen = 0;
+ }
+ ping.client = getpid();
+ ping.result = 0;
+ errno = 0;
+ pseudo_debug(4, "sending ping\n");
+ if (pseudo_msg_send(connect_fd, &ping, ping.pathlen, tag)) {
+ pseudo_debug(3, "error pinging server: errno %d\n", errno);
+ return 1;
+ }
+ ack = pseudo_msg_receive(connect_fd);
+ if (!ack) {
+ pseudo_debug(2, "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(1, "invalid ping response from server: expected ack, got %d\n", ack->type);
+ /* and that's not good, so... */
+ server_pid = 0;
+ return 1;
+ }
+ pseudo_debug(5, "ping ok\n");
+ return 0;
+}
+
+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 = { AF_UNIX, PSEUDO_SOCKET };
+ int cwd_fd;
+
+ 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\n", strerror(errno));
+ return 1;
+ }
+
+ pseudo_debug(3, "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_dir_fd) == -1) {
+ pseudo_diag("Couldn't chdir to server directory [%d]: %s\n",
+ pseudo_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(3, "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(4, "connected socket.\n");
+ return 0;
+}
+
+static int
+pseudo_client_setup(void) {
+ FILE *fp;
+ server_pid = 0;
+ int cwd_fd;
+
+ /* avoid descriptor leak, I hope */
+ if (connect_fd >= 0) {
+ close(connect_fd);
+ connect_fd = -1;
+ }
+
+ cwd_fd = open(".", O_RDONLY);
+ if (cwd_fd == -1) {
+ pseudo_diag("Couldn't stash directory before opening pidfile: %s",
+ strerror(errno));
+ close(connect_fd);
+ connect_fd = -1;
+ return 1;
+ }
+ if (fchdir(pseudo_dir_fd) != 1) {
+ fp = fopen(PSEUDO_PIDFILE, "r");
+ if (fchdir(cwd_fd) == -1) {
+ pseudo_diag("return to previous directory failed: %s\n",
+ strerror(errno));
+ }
+ } else {
+ fp = NULL;
+ pseudo_diag("couldn't change to pseudo working directory for pid file.\n");
+ }
+ close(cwd_fd);
+ if (fp) {
+ if (fscanf(fp, "%d", &server_pid) != 1) {
+ pseudo_debug(1, "Opened server PID file, but didn't get a pid.\n");
+ }
+ fclose(fp);
+ }
+ if (server_pid) {
+ if (kill(server_pid, 0) == -1) {
+ pseudo_debug(1, "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(2, "server seems to be gone, trying to restart\n");
+ if (client_spawn_server()) {
+ pseudo_debug(1, "failed to spawn server, giving up.\n");
+ return 1;
+ } else {
+ pseudo_debug_verbose();
+ pseudo_debug(2, "restarted, new pid %d\n", server_pid);
+ if (!client_connect() && !client_ping()) {
+ pseudo_debug_terse();
+ return 0;
+ }
+ pseudo_debug_terse();
+ }
+ pseudo_debug(1, "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(4, "sending a message: ino %llu\n",
+ (unsigned long long) msg->ino);
+ if (connect_fd < 0) {
+ pseudo_debug(2, "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(2, "msg_send: %d%s\n",
+ rc,
+ rc == -1 ? " (sigpipe)" :
+ " (short write)");
+ pseudo_client_setup();
+ ++tries;
+ if (tries > 3) {
+ pseudo_debug(1, "can't get server going again.\n");
+ return 0;
+ }
+ }
+ } while (rc != 0);
+ pseudo_debug(5, "sent!\n");
+ response = pseudo_msg_receive(connect_fd);
+ if (!response) {
+ ++tries;
+ if (tries > 3) {
+ pseudo_debug(1, "can't get responses.\n");
+ return 0;
+ }
+ }
+ } while (response == 0);
+ if (response->type != PSEUDO_MSG_ACK) {
+ pseudo_debug(2, "got non-ack response %d\n", response->type);
+ return 0;
+ } else {
+ pseudo_debug(4, "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_path = pseudo_prefix_path(NULL);
+ if (pseudo_dir_fd == -1) {
+ if (pseudo_path) {
+ pseudo_dir_fd = open(pseudo_path, O_RDONLY);
+ pseudo_dir_fd = pseudo_fd(pseudo_dir_fd, COPY_FD);
+ free(pseudo_path);
+ } else {
+ pseudo_diag("No prefix available to to find server.\n");
+ exit(1);
+ }
+ if (pseudo_dir_fd == -1) {
+ pseudo_diag("Can't open prefix path (%s) for server.\n",
+ strerror(errno));
+ exit(1);
+ }
+ }
+ if (client_connect()) {
+ pseudo_diag("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(2, "sending shutdown request\n");
+ if (pseudo_msg_send(connect_fd, &msg, 0, NULL)) {
+ pseudo_debug(1, "error requesting shutdown: errno %d\n", 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 pathlen = -1;
+ size_t baselen;
+ char *newpath;
+
+ if (dirfd != -1 && dirfd != AT_FDCWD) {
+ if (dirfd >= 0) {
+ basepath = fd_path(dirfd);
+ baselen = strlen(basepath);
+ } else {
+ pseudo_diag("got *at() syscall for unknown directory, fd %d\n", dirfd);
+ }
+ } else {
+ basepath = cwd;
+ baselen = cwdlen;
+ }
+ if (!basepath) {
+ pseudo_diag("unknown base path for fd %d, path %s\n", dirfd, path);
+ return 0;
+ }
+
+ pathlen = baselen + strlen(path) + 2;
+ newpath = pseudo_fix_path(basepath, path, baselen, NULL, leave_last);
+ return newpath;
+}
+
+pseudo_msg_t *
+pseudo_client_op(op_id_t op, int flags, int fd, int dirfd, const char *path, const struct stat64 *buf, ...) {
+ pseudo_msg_t *result = 0;
+ pseudo_msg_t msg = { .type = PSEUDO_MSG_OP };
+ char *newpath = 0;
+ size_t pathlen = -1;
+ int do_request = 0;
+ char *oldpath = 0;
+
+ /* disable wrappers */
+ pseudo_antimagic();
+
+ if (op == OP_RENAME) {
+ va_list ap;
+ va_start(ap, buf);
+ oldpath = va_arg(ap, char *);
+ va_end(ap);
+ /* last argument is the previous path of the file */
+ if (!oldpath) {
+ pseudo_diag("rename (%s) without old path.\n",
+ path ? path : "<nil>");
+ pseudo_magic();
+ return 0;
+ }
+ if (!path) {
+ pseudo_diag("rename (%s) without new path.\n",
+ path ? path : "<nil>");
+ pseudo_magic();
+ return 0;
+ }
+ if (oldpath[0] != '/') {
+ oldpath = base_path(dirfd, oldpath, 1);
+ } else {
+ oldpath = pseudo_fix_path(NULL, oldpath, 0, NULL, 1);
+ }
+ }
+
+ if (path) {
+ /* fixup relative path */
+ if (*path != '/') {
+ newpath = base_path(dirfd, path, flags);
+ } else {
+ newpath = pseudo_fix_path(NULL, path, 0, NULL, flags);
+ }
+ if (newpath) {
+ pathlen = strlen(newpath) + 1;
+ } else {
+ pseudo_diag("couldn't allocate space for a path (%s). Sorry.\n", path);
+ free(oldpath);
+ pseudo_magic();
+ return 0;
+ }
+ if (oldpath) {
+ size_t full_len = strlen(oldpath) + 1 + pathlen;
+ char *both_paths = malloc(full_len);
+ if (!both_paths) {
+ pseudo_diag("can't allocate space for paths for a rename operation. Sorry.\n");
+ free(newpath);
+ free(oldpath);
+ pseudo_magic();
+ return 0;
+ }
+ snprintf(both_paths, full_len, "%s%c%s",
+ newpath, 0, oldpath);
+ pseudo_debug(2, "rename: %s -> %s [%d]\n",
+ both_paths + pathlen, both_paths, (int) full_len);
+ free(newpath);
+ newpath = both_paths;
+ pathlen = full_len;
+ }
+ } else if (fd >= 0 && fd <= nfds) {
+ path = fd_path(fd);
+ if (!path)
+ msg.pathlen = 0;
+ else
+ msg.pathlen = strlen(path) + 1;
+ } else {
+ path = 0;
+ msg.pathlen = 0;
+ }
+ pseudo_debug(2, "%s%s", pseudo_op_name(op),
+ (dirfd != -1 && dirfd != AT_FDCWD && op != OP_DUP) ? "at" : "");
+ if (oldpath) {
+ pseudo_debug(2, " %s ->", (char *) oldpath);
+ free(oldpath);
+ }
+ if (newpath || path) {
+ pseudo_debug(2, " %s", newpath ? newpath : path);
+ }
+ if (fd != -1) {
+ pseudo_debug(2, " [fd %d]", fd);
+ }
+ if (buf) {
+ pseudo_debug(2, " (+buf)");
+ pseudo_msg_stat(&msg, buf);
+ if (buf && fd != -1) {
+ pseudo_debug(2, " [dev/ino: %d/%llu]",
+ (int) buf->st_dev, (unsigned long long) buf->st_ino);
+ }
+ pseudo_debug(2, " (0%o)", (int) buf->st_mode);
+ }
+ pseudo_debug(2, ": ");
+ msg.type = PSEUDO_MSG_OP;
+ msg.op = op;
+ msg.fd = fd;
+ msg.result = RESULT_NONE;
+ msg.client = getpid();
+
+ /* do stuff */
+ pseudo_debug(4, "processing request [ino %llu]\n", (unsigned long long) msg.ino);
+ switch (msg.op) {
+ case OP_CHDIR:
+ cwd = getcwd(cwdbuf, PATH_MAX);
+ cwdlen = strlen(cwd);
+ do_request = 0;
+ break;
+ case OP_OPEN:
+ pseudo_client_path(fd, newpath ? newpath : path);
+ do_request = 1;
+ 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: errno %d\n", errno);
+ }
+ } else if (fd == pseudo_util_debug_fd) {
+ pseudo_util_debug_fd = pseudo_fd(fd, COPY_FD);
+ } else if (fd == pseudo_dir_fd) {
+ pseudo_dir_fd = pseudo_fd(fd, COPY_FD);
+ }
+ }
+ pseudo_client_close(fd);
+ do_request = 0;
+ break;
+ case OP_DUP:
+ /* just copy the path over */
+ pseudo_debug(2, "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(2, "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_LINK:
+ case OP_CHOWN:
+ case OP_FCHOWN:
+ case OP_FSTAT:
+ case OP_RENAME:
+ case OP_STAT:
+ case OP_UNLINK:
+ do_request = 1;
+ break;
+ default:
+ pseudo_diag("error: unknown or unimplemented operator %d (%s)", op, pseudo_op_name(op));
+ break;
+ }
+ if (do_request) {
+ struct timeval tv1, tv2;
+ pseudo_debug(4, "sending request [ino %llu]\n", (unsigned long long) msg.ino);
+ gettimeofday(&tv1, NULL);
+ result = pseudo_client_request(&msg, pathlen, newpath ? newpath : path);
+ gettimeofday(&tv2, NULL);
+ ++messages;
+ 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 (result) {
+ pseudo_debug(2, "(%d) %s", getpid(), pseudo_res_name(result->result));
+ if (op == OP_STAT || op == OP_FSTAT) {
+ pseudo_debug(2, " 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(2, " mode 0%o",
+ (int) result->mode);
+ } else if (op == OP_CHOWN || op == OP_FCHOWN) {
+ pseudo_debug(2, " uid %d:%d",
+ (int) result->uid,
+ (int) result->gid);
+ }
+ } else {
+ pseudo_debug(2, "(%d) no answer", getpid());
+ }
+ } else {
+ pseudo_debug(2, "(%d) (no request)", getpid());
+ }
+ pseudo_debug(2, "\n");
+
+ free(newpath);
+
+ if (do_request && (messages % 1000 == 0)) {
+ pseudo_debug(2, "%d messages handled in %.4f seconds\n",
+ messages,
+ (double) message_time.tv_sec +
+ (double) message_time.tv_usec / 1000000.0);
+ }
+ /* reenable wrappers */
+ pseudo_magic();
+
+ return result;
+}
+
+void
+pseudo_stat32_from64(struct stat *buf32, 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, 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;
+}
diff --git a/pseudo_client.h b/pseudo_client.h
new file mode 100644
index 0000000..b5a6075
--- /dev/null
+++ b/pseudo_client.h
@@ -0,0 +1,82 @@
+/*
+ * 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(op_id_t op, int flags, int fd, int dirfd, const char *path, const struct stat64 *buf, ...);
+extern void pseudo_antimagic(void);
+extern void pseudo_magic(void);
+extern void pseudo_client_reset(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);
+extern void pseudo_stat32_from64(struct stat *, struct stat64 *);
+extern void pseudo_stat64_from32(struct stat64 *, struct stat *);
+#define MOVE_FD 0
+#define COPY_FD 1
+#define PSEUDO_MIN_FD 20
+extern int pseudo_euid;
+extern int pseudo_fuid;
+extern int pseudo_suid;
+extern int pseudo_ruid;
+extern int pseudo_egid;
+extern int pseudo_sgid;
+extern int pseudo_rgid;
+extern int pseudo_fgid;
+extern int pseudo_dir_fd;
+
+/* Root can read, write, and execute files which have no read, write,
+ * or 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.
+ *
+ * None of this will behave very sensibly if umask has 0700 bits in it;
+ * this is a known limitation.
+ */
+#define PSEUDO_FS_MODE(mode) ((mode) | S_IRUSR | S_IWUSR | S_IXUSR)
+#define PSEUDO_DB_MODE(fs_mode, user_mode) (((fs_mode) & ~0700) | ((user_mode & 0700)))
+
+/* 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. Similarly, any value which isn't
+ * zero will do to fake AT_SYMLINK_NOFOLLOW. 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 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
+
diff --git a/pseudo_db.c b/pseudo_db.c
new file mode 100644
index 0000000..665f823
--- /dev/null
+++ b/pseudo_db.c
@@ -0,0 +1,1695 @@
+/*
+ * pseudo_db.c, sqlite3 interface
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sqlite3.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_db.h"
+
+struct log_history {
+ int rc;
+ unsigned long fields;
+ sqlite3_stmt *stmt;
+};
+
+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;
+
+/* op_columns and op_rows are used to initialize the table of operations,
+ * which exists so that the databases are self-consistent even if somehow
+ * someone else's version of pseudo is out of sync.
+ * The same applies to other tables like this.
+ */
+#define OP_ROW(id, name) { "%d, '" name "'", id }
+char *op_columns = "id, name";
+id_row op_rows[] = {
+ OP_ROW(OP_UNKNOWN, "unknown"),
+ OP_ROW(OP_NONE, "none"),
+ OP_ROW(OP_CHDIR, "chdir"),
+ OP_ROW(OP_CHMOD, "chmod"),
+ OP_ROW(OP_CHOWN, "chown"),
+ OP_ROW(OP_CLOSE, "close"),
+ OP_ROW(OP_CREAT, "creat"),
+ OP_ROW(OP_DUP, "dup"),
+ OP_ROW(OP_FCHMOD, "fchmod"),
+ OP_ROW(OP_FCHOWN, "fchown"),
+ OP_ROW(OP_FSTAT, "fstat"),
+ OP_ROW(OP_LINK, "link"),
+ OP_ROW(OP_MKDIR, "mkdir"),
+ OP_ROW(OP_MKNOD, "mknod"),
+ OP_ROW(OP_OPEN, "open"),
+ OP_ROW(OP_RENAME, "rename"),
+ OP_ROW(OP_STAT, "stat"),
+ OP_ROW(OP_UNLINK, "unlink"),
+ OP_ROW(OP_SYMLINK, "symlink"),
+ OP_ROW(OP_MAX, "max"),
+ { NULL, 0 }
+};
+
+/* same as for ops; defined so the values in the database are consistent */
+#define SEV_ROW(id, name) { "%d, '" name "'", id }
+char *sev_columns = "id, name";
+id_row sev_rows[] = {
+ SEV_ROW(SEVERITY_UNKNOWN, "unknown"),
+ SEV_ROW(SEVERITY_NONE, "none"),
+ SEV_ROW(SEVERITY_DEBUG, "debug"),
+ SEV_ROW(SEVERITY_INFO, "info"),
+ SEV_ROW(SEVERITY_WARN, "warn"),
+ SEV_ROW(SEVERITY_ERROR, "error"),
+ SEV_ROW(SEVERITY_CRITICAL, "critical"),
+ SEV_ROW(SEVERITY_MAX, "max"),
+ { NULL, 0 }
+};
+
+/* same as for ops; defined so the values in the database are consistent */
+#define RES_ROW(id, name, ok) { "%d, '" name "' , " #ok, id }
+char *res_columns = "id, name, ok";
+id_row res_rows[] = {
+ RES_ROW(RESULT_UNKNOWN, "unknown", 0),
+ RES_ROW(RESULT_NONE, "none", 0),
+ RES_ROW(RESULT_SUCCEED, "succeed", 1),
+ RES_ROW(RESULT_FAIL, "fail", 1),
+ RES_ROW(RESULT_ERROR, "error", 0),
+ RES_ROW(RESULT_MAX, "max", 0),
+ { NULL, 0 }
+};
+
+/* 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 },
+ { NULL, NULL, NULL, NULL },
+}, log_tables[] = {
+ { "operations",
+ "id INTEGER PRIMARY KEY, name VARCHAR",
+ &op_columns,
+ op_rows },
+ { "results",
+ "id INTEGER PRIMARY KEY, name VARCHAR, ok INTEGER",
+ &res_columns,
+ res_rows },
+ { "severities",
+ "id INTEGER PRIMARY KEY, name VARCHAR",
+ &sev_columns,
+ sev_rows },
+ { "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__dev_ino", "files", "dev, ino" },
+ { NULL, NULL, NULL },
+}, log_indexes[] = {
+ { NULL, NULL, 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 },
+ { 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;" },
+ { NULL },
+};
+
+/* 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);
+ }
+}
+
+/* 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;
+ 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];
+ sprintf(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(2, "existing database version: %d\n", version);
+ rc = sqlite3_finalize(stmt);
+ if (rc) {
+ dberr(db, "couldn't finalize version check");
+ return 1;
+ }
+ } else {
+ pseudo_debug(2, "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(3, "considering migration %d\n", migration);
+ if (version >= migration)
+ continue;
+ pseudo_debug(2, "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(3, "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(1, "server exiting\n");
+ if (file_db)
+ sqlite3_close(file_db);
+ if (log_db)
+ sqlite3_close(log_db);
+}
+
+/* I hate this function.
+ * The need to separate logs and files into separate database (performance
+ * and stability suffered when they were together) was discovered after
+ * this was written, and it is still full of half-considered code and
+ * unreasonable tests. The test for whether db == &file_db is particularly
+ * odious.
+ *
+ * The basic idea is to open the database, and make sure the tables exist
+ * (using the make_tables function above). Options are set to make sqlite
+ * run reasonably efficiently.
+ */
+static int
+get_db(sqlite3 **db) {
+ int rc;
+ char *sql;
+ char **results;
+ int rows, columns;
+ char *errmsg;
+ static int registered_cleanup = 0;
+ char *dbfile;
+
+ if (!db)
+ return 1;
+ if (*db)
+ return 0;
+ if (db == &file_db) {
+ dbfile = pseudo_prefix_path(PSEUDO_DATA "files.db");
+ rc = sqlite3_open(dbfile, db);
+ free(dbfile);
+ } else {
+ dbfile = pseudo_prefix_path(PSEUDO_DATA "logs.db");
+ rc = sqlite3_open(dbfile, db);
+ free(dbfile);
+ }
+ if (rc) {
+ pseudo_diag("Failed: %s\n", sqlite3_errmsg(*db));
+ sqlite3_close(*db);
+ *db = NULL;
+ return 1;
+ }
+ if (!registered_cleanup) {
+ atexit(cleanup_db);
+ registered_cleanup = 1;
+ }
+ if (db == &file_db) {
+ rc = sqlite3_exec(*db, "PRAGMA legacy_file_format = OFF;", NULL, NULL, &errmsg);
+ if (rc) {
+ dberr(*db, "file_db legacy_file_format");
+ }
+ rc = sqlite3_exec(*db, "PRAGMA journal_mode = PERSIST;", NULL, NULL, &errmsg);
+ if (rc) {
+ dberr(*db, "file_db journal_mode");
+ }
+ rc = sqlite3_exec(*db, "PRAGMA locking_mode = EXCLUSIVE;", NULL, NULL, &errmsg);
+ if (rc) {
+ dberr(*db, "file_db locking_mode");
+ }
+ /* 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.
+ */
+ rc = sqlite3_exec(*db, "PRAGMA synchronous = OFF;", NULL, NULL, &errmsg);
+ if (rc) {
+ dberr(*db, "file_db synchronous");
+ }
+ } else if (db == &log_db) {
+ rc = sqlite3_exec(*db, "PRAGMA legacy_file_format = OFF;", NULL, NULL, &errmsg);
+ if (rc) {
+ dberr(*db, "log_db legacy_file_format");
+ }
+ rc = sqlite3_exec(*db, "PRAGMA journal_mode = OFF;", NULL, NULL, &errmsg);
+ if (rc) {
+ dberr(*db, "log_db journal_mode");
+ }
+ rc = sqlite3_exec(*db, "PRAGMA locking_mode = EXCLUSIVE;", NULL, NULL, &errmsg);
+ if (rc) {
+ dberr(*db, "log_db locking_mode");
+ }
+ rc = sqlite3_exec(*db, "PRAGMA synchronous = OFF;", NULL, NULL, &errmsg);
+ if (rc) {
+ dberr(*db, "log_db synchronous");
+ }
+ }
+ /* 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 {
+ if (db == &file_db) {
+ rc = make_tables(*db, file_tables, file_indexes, file_migrations, results, rows);
+ } else if (db == &log_db) {
+ rc = make_tables(*db, log_tables, log_indexes, log_migrations, results, rows);
+ }
+ sqlite3_free_table(results);
+ }
+ /* cleanup database before getting started */
+ sqlite3_exec(*db, "VACUUM;", NULL, NULL, &errmsg);
+ return rc;
+}
+
+/* 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_db(&log_db)) {
+ pseudo_diag("database error.\n");
+ 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_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_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_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, client, dev, gid, ino, mode, path, result, severity, text, tag, uid)"
+ " VALUES "
+ "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+ static sqlite3_stmt *insert;
+ int rc;
+
+ if (!log_db && get_db(&log_db)) {
+ pseudo_diag("database error.\n");
+ 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;
+ }
+ }
+
+ if (e) {
+ if (e->stamp) {
+ sqlite3_bind_int(insert, 1, e->stamp);
+ } else {
+ sqlite3_bind_int(insert, 1, (unsigned long) time(NULL));
+ }
+ sqlite3_bind_int(insert, 2, e->op);
+ sqlite3_bind_int(insert, 3, e->client);
+ sqlite3_bind_int(insert, 4, e->dev);
+ sqlite3_bind_int(insert, 5, e->gid);
+ sqlite3_bind_int(insert, 6, e->ino);
+ sqlite3_bind_int(insert, 7, e->mode);
+ if (e->path) {
+ sqlite3_bind_text(insert, 8, e->path, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, 8);
+ }
+ sqlite3_bind_int(insert, 9, e->result);
+ sqlite3_bind_int(insert, 10, e->severity);
+ if (e->text) {
+ sqlite3_bind_text(insert, 11, e->text, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, 11);
+ }
+ if (e->tag) {
+ sqlite3_bind_text(insert, 12, e->tag, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, 12);
+ }
+ sqlite3_bind_int(insert, 13, e->uid);
+ } else {
+ sqlite3_bind_int(insert, 1, (unsigned long) time(NULL));
+ sqlite3_bind_int(insert, 2, 0);
+ sqlite3_bind_int(insert, 3, 0);
+ sqlite3_bind_int(insert, 4, 0);
+ sqlite3_bind_int(insert, 5, 0);
+ sqlite3_bind_int(insert, 6, 0);
+ sqlite3_bind_int(insert, 7, 0);
+ sqlite3_bind_null(insert, 8);
+ sqlite3_bind_int(insert, 9, 0);
+ sqlite3_bind_int(insert, 10, 0);
+ sqlite3_bind_null(insert, 11);
+ sqlite3_bind_null(insert, 12);
+ sqlite3_bind_int(insert, 13, 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(sev_id_t severity, pseudo_msg_t *msg, const char *tag, const char *text, ...) {
+ char *sql = "INSERT INTO logs "
+ "(stamp, op, client, dev, gid, ino, mode, path, result, severity, text, tag, uid)"
+ " VALUES "
+ "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+ static sqlite3_stmt *insert;
+ char buffer[8192];
+ 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_db(&log_db)) {
+ pseudo_diag("database error.\n");
+ 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;
+ }
+ }
+
+ if (msg) {
+ sqlite3_bind_int(insert, 2, msg->op);
+ sqlite3_bind_int(insert, 3, msg->client);
+ sqlite3_bind_int(insert, 4, msg->dev);
+ sqlite3_bind_int(insert, 5, msg->gid);
+ sqlite3_bind_int(insert, 6, msg->ino);
+ sqlite3_bind_int(insert, 7, msg->mode);
+ if (msg->pathlen) {
+ sqlite3_bind_text(insert, 8, msg->path, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, 8);
+ }
+ sqlite3_bind_int(insert, 9, msg->result);
+ sqlite3_bind_int(insert, 13, msg->uid);
+ } else {
+ sqlite3_bind_int(insert, 2, 0);
+ sqlite3_bind_int(insert, 3, 0);
+ sqlite3_bind_int(insert, 4, 0);
+ sqlite3_bind_int(insert, 5, 0);
+ sqlite3_bind_int(insert, 6, 0);
+ sqlite3_bind_int(insert, 7, 0);
+ sqlite3_bind_null(insert, 8);
+ sqlite3_bind_int(insert, 9, 0);
+ sqlite3_bind_int(insert, 13, 0);
+ }
+ sqlite3_bind_int(insert, 1, (unsigned long) time(NULL));
+ sqlite3_bind_int(insert, 10, severity);
+ if (text) {
+ sqlite3_bind_text(insert, 11, text, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, 11);
+ }
+ if (tag) {
+ sqlite3_bind_text(insert, 12, tag, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, 12);
+ }
+
+ 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 >= (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 >= (b->buflen - curlen)) {
+ pseudo_diag("tried to reallocate larger buffer, failed. giving up.\n");
+ return -1;
+ }
+ }
+ if (rc >= 0)
+ b->tail += rc;
+ return rc;
+}
+
+log_history
+pdb_history(pseudo_query_t *traits, unsigned long fields, int distinct) {
+ log_history h = NULL;
+ pseudo_query_t *trait;
+ sqlite3_stmt *select;
+ int done_any = 0;
+ int field = 0;
+ char *order_by = "id";
+ char *order_dir = "ASC";
+ int rc;
+ pseudo_query_field_t f;
+ static buffer *sql;
+
+ /* this column arrangement is used by pdb_history_entry() */
+ if (!sql) {
+ sql = malloc(sizeof *sql);
+ if (!sql) {
+ pseudo_diag("can't allocate SQL buffer.\n");
+ return 0;
+ }
+ sql->buflen = 512;
+ sql->data = malloc(sql->buflen);
+ if (!sql->data) {
+ pseudo_diag("can't allocate SQL text buffer.\n");
+ free(sql);
+ return 0;
+ }
+ }
+ sql->tail = sql->data;
+ frag(sql, "SELECT ");
+
+ if (!log_db && get_db(&log_db)) {
+ pseudo_diag("database error.\n");
+ return 0;
+ }
+
+ if (distinct)
+ 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_TEXT: /* FALLTHROUGH */
+ case PSQF_TAG: /* FALLTHROUGH */
+ 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: /* FALLTHROUGH */
+ case PSQT_NOTLIKE: /* FALLTHROUGH */
+ 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: /* FALLTHROUGH */
+ case PSQT_NOTLIKE: /* FALLTHROUGH */
+ 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: /* FALLTHROUGH */
+ /* 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: /* FALLTHROUGH */
+ case PSQT_NOTLIKE: /* FALLTHROUGH */
+ 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;
+ }
+ }
+ frag(sql, "ORDER BY %s %s;", order_by, order_dir);
+ pseudo_debug(1, "created SQL: <%s>\n", sql->data);
+
+ /* second, prepare it */
+ rc = sqlite3_prepare_v2(log_db, sql->data, strlen(sql->data), &select, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare SELECT statement");
+ return 0;
+ }
+
+ /* 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_PATH: /* FALLTHROUGH */
+ case PSQF_TAG: /* FALLTHROUGH */
+ case PSQF_TEXT:
+ sqlite3_bind_text(select, field++,
+ trait->data.svalue, -1, SQLITE_STATIC);
+ break;
+ case PSQF_FTYPE: /* FALLTHROUGH */
+ case PSQF_CLIENT: /* FALLTHROUGH */
+ case PSQF_DEV: /* FALLTHROUGH */
+ case PSQF_FD: /* FALLTHROUGH */
+ case PSQF_INODE: /* FALLTHROUGH */
+ case PSQF_GID: /* FALLTHROUGH */
+ case PSQF_PERM: /* FALLTHROUGH */
+ case PSQF_MODE: /* FALLTHROUGH */
+ case PSQF_OP: /* FALLTHROUGH */
+ case PSQF_RESULT: /* FALLTHROUGH */
+ case PSQF_SEVERITY: /* FALLTHROUGH */
+ case PSQF_STAMP: /* FALLTHROUGH */
+ case PSQF_UID: /* FALLTHROUGH */
+ sqlite3_bind_int(select, field++, trait->data.ivalue);
+ break;
+ default:
+ pseudo_diag("Inexplicably invalid field type %d\n", trait->field);
+ sqlite3_finalize(select);
+ return 0;
+ }
+ }
+
+ /* fourth, return the statement, now ready to be stepped through */
+ h = malloc(sizeof(*h));
+ if (h) {
+ h->rc = 0;
+ h->fields = fields;
+ h->stmt = select;
+ } else {
+ pseudo_diag("failed to allocate memory for log_history\n");
+ }
+ return h;
+}
+
+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_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_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_UID:
+ l->uid = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_ORDER: /* FALLTHROUGH */
+ case PSQF_FTYPE: /* FALLTHROUGH */
+ 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->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) {
+ static sqlite3_stmt *insert;
+ int rc;
+ char *sql = "INSERT INTO files "
+ " ( path, dev, ino, uid, gid, mode, rdev ) "
+ " VALUES (?, ?, ?, ?, ?, ?, ?);";
+
+ if (!file_db && get_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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_int(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(2, "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);
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "insert may have failed (rc %d)", rc);
+ }
+ 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 *delete;
+ int rc;
+ char *sql = "DELETE FROM files WHERE dev = ? AND ino = ?;";
+
+ if (!file_db && get_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int(delete, 1, msg->dev);
+ sqlite3_bind_int(delete, 2, msg->ino);
+ rc = sqlite3_step(delete);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete by inode may have failed");
+ }
+ sqlite3_reset(delete);
+ sqlite3_clear_bindings(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 dev = ? AND ino = ? AND path = 'NAMELESS FILE';";
+
+ if (!file_db && get_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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(1, "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_int(update, 3, msg->ino);
+ 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;
+}
+
+/* unlink a file, by path */
+/* SQLite limitations:
+ * path LIKE foo '/%' -> can't use index
+ * path = A OR path = B -> can't use index
+ * Solution:
+ * 1. Use two separate queries, so there's no OR.
+ * 2. (From the SQLite page): Use > and < instead of a glob at the end.
+ */
+int
+pdb_unlink_file(pseudo_msg_t *msg) {
+ static sqlite3_stmt *delete_exact, *delete_sub;
+ int rc;
+ char *sql_delete_exact = "DELETE FROM files WHERE path = ?;";
+ char *sql_delete_sub = "DELETE FROM files WHERE "
+ "(path > (? || '/') AND path < (? || '0'));";
+
+ if (!file_db && get_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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 (!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_exact, 1, msg->path, -1, SQLITE_STATIC);
+ 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(1, "cannot unlink a file without a path.");
+ return 1;
+ }
+ rc = sqlite3_step(delete_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete exact by path may have failed");
+ }
+ rc = sqlite3_step(delete_sub);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete sub by path may have failed");
+ }
+ sqlite3_reset(delete_exact);
+ sqlite3_reset(delete_sub);
+ sqlite3_clear_bindings(delete_exact);
+ 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_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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(1, "rename: No path provided (ino %llu)\n", (unsigned long long) msg->ino);
+ return 1;
+ }
+ if (!oldpath) {
+ pseudo_debug(1, "rename: No old path for %s\n", msg->path);
+ return 1;
+ }
+ pseudo_debug(2, "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_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);
+ sqlite3_clear_bindings(update_exact);
+ sqlite3_clear_bindings(update_sub);
+ 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_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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_int(update, 6, msg->ino);
+
+ 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(2, "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) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE dev = ? AND ino = ? AND path = ?;";
+
+ if (!file_db && get_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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_int(select, 2, msg->ino);
+ rc = sqlite3_bind_text(select, 3, msg->path, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "error binding %s to select", msg->pathlen ? msg->path : "<nil>");
+ }
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ 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);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(3, "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) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE path = ?;";
+
+ if (!file_db && get_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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:
+ msg->dev = (unsigned long) sqlite3_column_int64(select, 2);
+ msg->ino = (unsigned long) sqlite3_column_int64(select, 3);
+ 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);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(3, "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_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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_int(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(3, "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) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE dev = ? AND ino = ?;";
+
+ if (!file_db && get_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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_int(select, 2, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ 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);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(3, "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;
+}
+
+/* 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) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE ino = ?;";
+
+ if (!file_db && get_db(&file_db)) {
+ pseudo_diag("database error.\n");
+ 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->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ 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);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(3, "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;
+}
diff --git a/pseudo_db.h b/pseudo_db.h
new file mode 100644
index 0000000..2085335
--- /dev/null
+++ b/pseudo_db.h
@@ -0,0 +1,71 @@
+/*
+ * 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;
+ op_id_t op;
+ 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;
+ res_id_t result;
+ sev_id_t severity;
+ char *text;
+ char *tag;
+} log_entry;
+
+extern int pdb_link_file(pseudo_msg_t *msg);
+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_rename_file(const char *oldpath, pseudo_msg_t *msg);
+extern int pdb_find_file_exact(pseudo_msg_t *msg);
+extern int pdb_find_file_path(pseudo_msg_t *msg);
+extern int pdb_find_file_dev(pseudo_msg_t *msg);
+extern int pdb_find_file_ino(pseudo_msg_t *msg);
+extern char *pdb_get_file_path(pseudo_msg_t *msg);
+
+struct log_history;
+typedef struct log_history *log_history;
+
+union pseudo_query_data {
+ unsigned long long ivalue;
+ char *svalue;
+};
+
+typedef struct pseudo_query {
+ enum pseudo_query_type type;
+ enum pseudo_query_field 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(sev_id_t severity, pseudo_msg_t *msg, const char *tag, const char *text, ...);
+extern int pdb_log_traits(pseudo_query_t *traits);
+
+extern log_history pdb_history(pseudo_query_t *traits, unsigned long fields, int distinct);
+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..cd18140
--- /dev/null
+++ b/pseudo_ipc.c
@@ -0,0 +1,244 @@
+/*
+ * 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) {
+ 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(4, "type: %d\n", msg->type);
+ pseudo_debug(4, "inode: %llu\n", (unsigned long long) msg->ino);
+ pseudo_debug(4, "uid: %d\n", msg->uid);
+ pseudo_debug(4, "pathlen: %d\n", (int) msg->pathlen);
+ if (msg->pathlen) {
+ pseudo_debug(4, "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(4, "msg type %d (%s), external path %s, mode 0%o\n",
+ msg->type, pseudo_op_name(msg->op), path, (int) msg->mode);
+ if (len == -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(5, "wrote %d bytes\n", r);
+ if (pipe_error || (r == -1 && errno == EBADF))
+ return -1;
+ return (r != PSEUDO_HEADER_SIZE + len);
+ } else {
+ pseudo_debug(4, "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(5, "wrote %d bytes\n", r);
+ if (pipe_error || (r == -1 && errno == EBADF))
+ return -1;
+ return (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(2, "read failed: %s\n", strerror(errno));
+ return 0;
+ }
+ if (r < (int) PSEUDO_HEADER_SIZE) {
+ pseudo_debug(2, "got only %d bytes (%s)\n", r, strerror(errno));
+ return 0;
+ }
+ pseudo_debug(4, "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 < incoming->pathlen) {
+ pseudo_debug(2, "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 struct stat64 *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(struct stat64 *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..50794a1
--- /dev/null
+++ b/pseudo_ipc.h
@@ -0,0 +1,59 @@
+/*
+ * 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
+ *
+ */
+typedef enum {
+ PSEUDO_MSG_PING,
+ PSEUDO_MSG_SHUTDOWN,
+ PSEUDO_MSG_OP,
+ PSEUDO_MSG_ACK,
+ PSEUDO_MSG_NAK,
+ PSEUDO_MSG_MAX
+} pseudo_msg_type_t;
+
+/* 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;
+ op_id_t op;
+ res_id_t result;
+ int xerrno;
+ 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;
+ char path[];
+} pseudo_msg_t;
+
+#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 struct stat64 *buf);
+void pseudo_stat_msg(struct stat64 *buf, const pseudo_msg_t *msg);
diff --git a/pseudo_server.c b/pseudo_server.c
new file mode 100644
index 0000000..962c7ce
--- /dev/null
+++ b/pseudo_server.c
@@ -0,0 +1,477 @@
+/*
+ * pseudo_server.c, pseudo's server-side logic and message handling
+ *
+ * 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 <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;
+
+int *client_fds;
+pid_t *client_pids;
+char **client_tags;
+/* 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;
+static struct timeval message_time = { 0 };
+
+static void pseudo_server_loop(void);
+
+int
+pseudo_server_start(int daemonize) {
+ struct sockaddr_un sun = { AF_UNIX, "pseudo.socket" };
+ char *pseudo_path;
+ int rc, newfd;
+ FILE *fp;
+
+ 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_prefix_path(PSEUDO_DATA);
+ if (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 */
+ 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 && ((rc = fork()) != 0)) {
+ if (rc == -1) {
+ pseudo_diag("couldn't spawn server: errno %d\n", errno);
+ return 0;
+ }
+ pseudo_debug(2, "started server, pid %d\n", rc);
+ close(listen_fd);
+ return 0;
+ }
+ setsid();
+ pseudo_path = pseudo_prefix_path(PSEUDO_PIDFILE);
+ fp = fopen(pseudo_path, "w");
+ fprintf(fp, "%d\n", getpid());
+ fclose(fp);
+ free(pseudo_path);
+ if (daemonize) {
+ int fd;
+
+ pseudo_new_pid();
+ fclose(stdin);
+ fclose(stdout);
+ pseudo_path = pseudo_prefix_path(PSEUDO_LOGFILE);
+ fd = open(pseudo_path, O_WRONLY | O_APPEND | O_CREAT, 0644);
+ if (fd == -1) {
+ pseudo_diag("help: can't open pseudo.log: %s\n", strerror(errno));
+ } else {
+ pseudo_util_debug_fd = fd;
+ fclose(stderr);
+ }
+ free(pseudo_path);
+ }
+ 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) {
+ int *new_client_fds;
+ pid_t *new_client_pids;
+ char **new_client_tags;
+ int i;
+
+ /* if possible, use first open client slot */
+ for (i = 0; i < max_clients; ++i) {
+ if (client_fds[i] == -1) {
+ pseudo_debug(2, "reusing client %d for fd %d\n", i, fd);
+ client_fds[i] = fd;
+ client_pids[i] = 0;
+ client_tags[i] = 0;
+ ++active_clients;
+ if (i > highest_client)
+ highest_client = i;
+ return;
+ }
+ }
+
+ /* otherwise, allocate a new one */
+ new_client_fds = malloc(sizeof(int) * (max_clients + 16));
+ new_client_pids = malloc(sizeof(pid_t) * (max_clients + 16));
+ new_client_tags = malloc(sizeof(char *) * (max_clients + 16));
+ if (new_client_fds && new_client_pids && new_client_tags) {
+ memcpy(new_client_fds, client_fds, max_clients * sizeof(int));
+ memcpy(new_client_pids, client_pids, max_clients * sizeof(pid_t));
+ memcpy(new_client_tags, client_tags, max_clients * sizeof(char *));
+ free(client_fds);
+ free(client_pids);
+ free(client_tags);
+ for (i = max_clients; i < max_clients + 16; ++i) {
+ new_client_fds[i] = -1;
+ new_client_pids[i] = 0;
+ new_client_tags[i] = 0;
+ }
+ client_fds = new_client_fds;
+ client_pids = new_client_pids;
+ client_tags = new_client_tags;
+
+ client_fds[max_clients] = fd;
+ client_pids[max_clients] = 0;
+ client_tags[max_clients] = 0;
+ highest_client = max_clients + 1;
+
+ max_clients += 16;
+ ++active_clients;
+ } else {
+ /* if something got allocated, free it */
+ free(new_client_fds);
+ free(new_client_pids);
+ free(new_client_tags);
+ 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(2, "lost client %d [%d], closing fd %d\n", client, client_pids[client], client_fds[client]);
+ /* client went away... */
+ close(client_fds[client]);
+ client_fds[client] = -1;
+ free(client_tags[client]);
+ client_tags[client] = 0;
+ client_pids[client] = 0;
+ --active_clients;
+ if (client == highest_client)
+ while (client_fds[highest_client] != -1 && highest_client > 0)
+ --highest_client;
+}
+
+/* Actually process a request.
+ */
+static int
+serve_client(int i) {
+ pseudo_msg_t *in;
+ int rc;
+
+ pseudo_debug(2, "message from client %d [%d:%s] fd %d\n",
+ i, (int) client_pids[i],
+ client_tags[i] ? client_tags[i] : "NO TAG",
+ client_fds[i]);
+ in = pseudo_msg_receive(client_fds[i]);
+ if (in) {
+ char *response_path = 0;
+ pseudo_debug(4, "got a message (%d): %s\n", in->type, (in->pathlen ? in->path : "<no path>"));
+ /* handle incoming ping */
+ if (in->type == PSEUDO_MSG_PING && !client_pids[i]) {
+ pseudo_debug(2, "new client: %d -> %d\n",
+ i, in->client);
+ client_pids[i] = in->client;
+ if (in->pathlen) {
+ free(client_tags[i]);
+ client_tags[i] = strdup(in->path);
+ }
+ }
+ /* sanity-check client ID */
+ if (in->client != client_pids[i]) {
+ pseudo_debug(1, "uh-oh, expected pid %d for client %d, got %d\n",
+ (int) client_pids[i], i, in->client);
+ }
+ /* regular requests are processed in place by
+ * pseudo_server_response.
+ */
+ if (in->type != PSEUDO_MSG_SHUTDOWN) {
+ if (pseudo_server_response(in, client_tags[i])) {
+ in->type = PSEUDO_MSG_NAK;
+ } else {
+ in->type = PSEUDO_MSG_ACK;
+ pseudo_debug(4, "response: %d (%s)\n",
+ in->result, pseudo_res_name(in->result));
+ }
+ /* no path in response */
+ in->pathlen = 0;
+ in->client = i;
+ } 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 (client_fds[j] != -1 && j != i) {
+ s += snprintf(s, 8, "%d ", (int) client_pids[j]);
+ }
+ }
+ 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 ((rc = pseudo_msg_send(client_fds[i], in, 0, NULL)) != 0)
+ pseudo_debug(1, "failed to send response to client %d [%d]: %d (%s)\n",
+ i, (int) client_pids[i], rc, strerror(errno));
+ rc = in->op;
+ 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(2, "client %d: no message\n", (int) client_pids[i]);
+ 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;
+
+ client_fds = malloc(sizeof(int) * 16);
+ client_pids = malloc(sizeof(pid_t) * 16);
+ client_tags = malloc(sizeof(char *) * 16);
+
+ client_fds[0] = listen_fd;
+ client_pids[0] = getpid();
+
+ for (i = 1; i < 16; ++i) {
+ client_fds[i] = -1;
+ client_pids[i] = 0;
+ client_tags[i] = 0;
+ }
+
+ active_clients = 1;
+ max_clients = 16;
+ highest_client = 0;
+
+ pseudo_debug(2, "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, "server started (pid %d)", getpid());
+
+ FD_ZERO(&reads);
+ FD_ZERO(&writes);
+ FD_ZERO(&events);
+ FD_SET(client_fds[0], &reads);
+ FD_SET(client_fds[0], &events);
+ max_fd = client_fds[0];
+ 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;
+ if (loop_timeout <= 0) {
+ pseudo_debug(1, "no more clients, got bored.\n");
+ die_peacefully = 1;
+ } else {
+ /* display this if not exiting */
+ pseudo_debug(1, "%d messages handled in %.4f seconds\n",
+ messages,
+ (double) message_time.tv_sec +
+ (double) message_time.tv_usec / 1000000.0);
+ }
+ }
+ } else if (rc > 0) {
+ loop_timeout = pseudo_server_timeout;
+ for (i = 1; i <= highest_client; ++i) {
+ if (client_fds[i] == -1)
+ continue;
+ if (FD_ISSET(client_fds[i], &events)) {
+ /* this should happen but doesn't... */
+ close_client(i);
+ } else if (FD_ISSET(client_fds[i], &reads)) {
+ struct timeval tv1, tv2;
+ int op;
+ gettimeofday(&tv1, NULL);
+ op = serve_client(i);
+ gettimeofday(&tv2, NULL);
+ ++messages;
+ 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(client_fds[0], &events) ||
+ FD_ISSET(client_fds[0], &reads))) {
+ len = sizeof(client);
+ if ((fd = accept(listen_fd, (struct sockaddr *) &client, &len)) != -1) {
+ pseudo_debug(2, "new client fd %d\n", fd);
+ open_client(fd);
+ }
+ }
+ pseudo_debug(2, "server loop complete [%d clients left]\n", active_clients);
+ }
+ if (die_peacefully || die_forcefully) {
+ pseudo_debug(2, "quitting.\n");
+ pseudo_debug(1, "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, "server %d exiting: handled %d messages in %.4f seconds",
+ getpid(), messages,
+ (double) message_time.tv_sec +
+ (double) message_time.tv_usec / 1000000.0);
+ close(client_fds[0]);
+ exit(0);
+ }
+ FD_ZERO(&reads);
+ FD_ZERO(&writes);
+ FD_ZERO(&events);
+ FD_SET(client_fds[0], &reads);
+ FD_SET(client_fds[0], &events);
+ max_fd = client_fds[0];
+ /* 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 (client_fds[i] != -1) {
+ ++current_clients;
+ FD_SET(client_fds[i], &reads);
+ FD_SET(client_fds[i], &events);
+ if (client_fds[i] > max_fd)
+ max_fd = client_fds[i];
+ }
+ }
+ if (current_clients != active_clients) {
+ pseudo_debug(1, "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..5e2ea6b
--- /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 *tag);
+extern int pseudo_server_timeout;
+extern int opt_l;
diff --git a/pseudo_table.c b/pseudo_table.c
new file mode 100644
index 0000000..7c69037
--- /dev/null
+++ b/pseudo_table.c
@@ -0,0 +1,211 @@
+/*
+ * pseudo_table.c, data definitions and related utilities
+ *
+ * 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 "pseudo.h"
+#include <stdlib.h>
+#include <string.h>
+
+/* just a bunch of handy lookups for pretty-printing stuff */
+
+static char *operation_names[] = {
+ "none",
+ "chdir",
+ "chmod",
+ "chown",
+ "close",
+ "creat",
+ "dup",
+ "fchmod",
+ "fchown",
+ "fstat",
+ "link",
+ "mkdir",
+ "mknod",
+ "open",
+ "rename",
+ "stat",
+ "unlink",
+ "symlink",
+ NULL
+};
+
+static char *result_names[] = {
+ "none",
+ "succeed",
+ "fail",
+ "error",
+ NULL
+};
+
+static char *severity_names[] = {
+ "none",
+ "debug",
+ "info",
+ "warn",
+ "error",
+ "critical",
+ NULL
+};
+
+static char *query_type_names[] = {
+ "none",
+ "exact",
+ "less",
+ "greater",
+ "bitand",
+ "notequal",
+ "like",
+ "notlike",
+ "sqlpat",
+ NULL
+};
+
+static char *query_type_sql[] = {
+ "LITTLE BOBBY TABLES",
+ "=",
+ "<",
+ ">",
+ "&",
+ "!=",
+ "LIKE",
+ "NOT LIKE",
+ "LIKE",
+ NULL
+};
+
+static char *query_field_names[] = {
+ "zero",
+ "client",
+ "dev",
+ "fd",
+ "ftype",
+ "gid",
+ "id",
+ "ino",
+ "mode",
+ "op",
+ "order",
+ "path",
+ "perm",
+ "result",
+ "severity",
+ "stamp",
+ "tag",
+ "text",
+ "uid",
+ NULL
+};
+
+char *
+pseudo_op_name(op_id_t id) {
+ if (id >= OP_NONE && id < OP_MAX) {
+ return operation_names[id];
+ }
+ return "unknown";
+}
+
+char *
+pseudo_res_name(res_id_t id) {
+ if (id >= RESULT_NONE && id < RESULT_MAX) {
+ return result_names[id];
+ }
+ return "unknown";
+}
+
+char *
+pseudo_sev_name(sev_id_t id) {
+ if (id >= SEVERITY_NONE && id < SEVERITY_MAX) {
+ return severity_names[id];
+ }
+ return "unknown";
+}
+
+char *
+pseudo_query_type_name(pseudo_query_type_t id) {
+ if (id >= PSQT_NONE && id < PSQT_MAX) {
+ return query_type_names[id];
+ }
+ return "unknown";
+}
+
+char *
+pseudo_query_type_sql(pseudo_query_type_t id) {
+ if (id >= PSQT_NONE && id < PSQT_MAX) {
+ return query_type_sql[id];
+ }
+ return "LITTLE BOBBY TABLES";
+}
+
+char *
+pseudo_query_field_name(pseudo_query_field_t id) {
+ if (id >= PSQF_NONE && id < PSQF_MAX) {
+ return query_field_names[id];
+ }
+ return "unknown";
+}
+
+op_id_t
+pseudo_op_id(char *name) {
+ int id;
+ for (id = OP_NONE; id < OP_MAX; ++id) {
+ if (!strcmp(name, operation_names[id]))
+ return id;
+ }
+ return OP_UNKNOWN;
+}
+
+res_id_t
+pseudo_res_id(char *name) {
+ int id;
+ for (id = RESULT_NONE; id < RESULT_MAX; ++id) {
+ if (!strcmp(name, result_names[id]))
+ return id;
+ }
+ return RESULT_UNKNOWN;
+}
+
+sev_id_t
+pseudo_sev_id(char *name) {
+ int id;
+ for (id = SEVERITY_NONE; id < SEVERITY_MAX; ++id) {
+ if (!strcmp(name, severity_names[id]))
+ return id;
+ }
+ return SEVERITY_UNKNOWN;
+}
+
+pseudo_query_type_t
+pseudo_query_type(char *name) {
+ int id;
+ for (id = PSQT_NONE; id < PSQT_MAX; ++id) {
+ if (!strcmp(name, query_type_names[id]))
+ return id;
+ }
+ return PSQT_UNKNOWN;
+}
+
+pseudo_query_field_t
+pseudo_query_field(char *name) {
+ int id;
+ for (id = PSQF_NONE; id < PSQF_MAX; ++id) {
+ if (!strcmp(name, query_field_names[id]))
+ return id;
+ }
+ return PSQF_UNKNOWN;
+}
diff --git a/pseudo_util.c b/pseudo_util.c
new file mode 100644
index 0000000..2e21824
--- /dev/null
+++ b/pseudo_util.c
@@ -0,0 +1,499 @@
+/*
+ * pseudo_util.c, miscellaneous utility functions
+ *
+ * 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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_db.h"
+
+/* 3 = detailed protocol analysis
+ * 2 = higher-level protocol analysis
+ * 1 = stuff that might go wrong
+ * 0 = fire and arterial bleeding
+ */
+static int max_debug_level = 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, size_t *allocated, char **current, const char *element, size_t elen, int leave_last);
+static int pseudo_append_elements(char **newpath, size_t *allocated, char **current, const char *elements, size_t elen, int leave_last);
+extern char **environ;
+
+char *pseudo_version = PSEUDO_VERSION;
+
+void
+pseudo_debug_terse(void) {
+ if (max_debug_level > 0)
+ --max_debug_level;
+}
+
+void
+pseudo_debug_verbose(void) {
+ ++max_debug_level;
+}
+
+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 && max_debug_level > 1) {
+ wrote += write(pseudo_util_debug_fd, pid_text, pid_len);
+ }
+ debugged_newline = (debuff[len - 1] == '\n');
+
+ wrote += write(pseudo_util_debug_fd, "pseudo: ", 8);
+ wrote += write(pseudo_util_debug_fd, debuff, len);
+ return wrote;
+}
+
+int
+pseudo_debug_real(int level, char *fmt, ...) {
+ va_list ap;
+ char debuff[8192];
+ int len;
+ int wrote = 0;
+
+ if (max_debug_level < level)
+ return 0;
+
+ va_start(ap, fmt);
+ len = vsnprintf(debuff, 8192, fmt, ap);
+ va_end(ap);
+
+ if (len > 8192)
+ len = 8192;
+
+ if (debugged_newline && max_debug_level > 1) {
+ 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;
+}
+
+/* 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);
+}
+
+/* store pid in text form for prepending to messages */
+void
+pseudo_new_pid() {
+ pid_len = snprintf(pid_text, 32, "%d: ", getpid());
+ pseudo_debug(2, "new pid.\n");
+}
+
+/* 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 **pnewpath, size_t *pallocated, char **pcurrent, const char *element, size_t elen, int leave_this) {
+ static int link_recursion = 0;
+ size_t curlen, allocated;
+ char *newpath, *current;
+ struct stat64 buf;
+ if (!pnewpath || !*pnewpath || !pallocated || !pcurrent || !*pcurrent || !element) {
+ pseudo_diag("pseudo_append_element: invalid args.\n");
+ return -1;
+ }
+ newpath = *pnewpath;
+ allocated = *pallocated;
+ 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 <= newpath + 1)
+ return 1;
+ /* backtrack to the character before the / */
+ current -= 2;
+ /* now find the previous slash */
+ while (current > newpath && *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) {
+ char *bigger;
+ size_t big = round_up(allocated + elen, 256);
+ bigger = malloc(big);
+ if (!bigger) {
+ pseudo_diag("pseudo_append_element: couldn't allocate space (wanted %lu bytes).\n", (unsigned long) big);
+ return -1;
+ }
+ memcpy(bigger, newpath, curlen);
+ current = bigger + curlen;
+ free(newpath);
+ newpath = bigger;
+ allocated = big;
+ *pnewpath = newpath;
+ *pcurrent = current;
+ *pallocated = allocated;
+ }
+ 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 = (lstat64(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[PATH_MAX + 1];
+ ssize_t linklen;
+ int retval;
+
+ linklen = readlink(newpath, linkbuf, 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(pnewpath, pallocated, 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, size_t *allocated, char **current, const char *element, size_t elen, int leave_last) {
+ int retval = 1;
+ const char * start = element;
+ if (!newpath || !current || !element || !*newpath || !*current) {
+ 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, allocated, current, element, this_elen, leave_this) == -1) {
+ retval = -1;
+ }
+ }
+ break;
+ default:
+ if (pseudo_append_element(newpath, allocated, current, element, this_elen, leave_this) == -1) {
+ retval = -1;
+ }
+ break;
+ }
+ /* and now move past the separator */
+ element += this_elen + 1;
+ }
+ return retval;
+}
+
+/* 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(char *base, const char *path, size_t baselen, size_t *lenp, int leave_last) {
+ size_t newpathlen, pathlen;
+ char *newpath;
+ char *current;
+
+ if (!path) {
+ pseudo_diag("can't fix empty path.\n");
+ return 0;
+ }
+ pathlen = strlen(path);
+ /* 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 (path[0] == '/') {
+ newpathlen = round_up(pathlen + 1, 256);
+ newpath = malloc(newpathlen);
+ if (!newpath) {
+ pseudo_diag("allocation failed seeking memory for path (%s).\n", path);
+ return 0;
+ }
+ current = newpath;
+ ++path;
+ } else {
+ newpathlen = round_up(baselen + pathlen + 2, 256);
+ newpath = malloc(newpathlen);
+ memcpy(newpath, base, baselen);
+ current = newpath + baselen;
+ }
+ *current++ = '/';
+ *current = '\0';
+ /* at any given point:
+ * current points to just after the last / of newpath
+ * path points to the next path element of path
+ * newpathlen is the total allocated length of newpath
+ * (current - newpathlen) is the used length of newpath
+ * oldpath is the starting point of path
+ * (path - oldpath) is how far into path we are
+ */
+ if (pseudo_append_elements(&newpath, &newpathlen, &current, path, pathlen, leave_last) != -1) {
+ --current;
+ if (*current == '/') {
+ *current = '\0';
+ }
+ pseudo_debug(3, "%s + %s => <%s>\n",
+ base ? base : "<nil>",
+ path ? path : "<nil>",
+ newpath ? newpath : "<nil>");
+ if (lenp) {
+ *lenp = current - newpath;
+ }
+ return newpath;
+ } else {
+ free(newpath);
+ return 0;
+ }
+}
+
+/* remove the pseudo stuff from the environment (leaving other preloads
+ * alone).
+ */
+void
+pseudo_dropenv(void) {
+ char *ld_env = getenv("LD_PRELOAD");
+
+ /* why do this two ways? Because calling setenv(), then execing,
+ * in bash seems to result in the variables still being set in the
+ * new environment.
+ * we remove the entire LD_PRELOAD, because our use case would have
+ * fakechroot and fakepasswd in it too -- and we don't want that.
+ * we don't touch LD_LIBRARY_PATH because it might be being used for
+ * other system libraries...
+ */
+ if (ld_env) {
+ unsetenv("LD_PRELOAD");
+ }
+}
+
+/* add pseudo stuff to the environment.
+ */
+void
+pseudo_setupenv(char *opts) {
+ char *ld_env;
+ char *newenv;
+ size_t len;
+ char debugvalue[64];
+
+ newenv = "libpseudo.so";
+ setenv("LD_PRELOAD", newenv, 1);
+
+ ld_env = getenv("LD_LIBRARY_PATH");
+ if (ld_env) {
+ char *prefix = pseudo_prefix_path(NULL);
+ if (!strstr(ld_env, prefix)) {
+ char *e1, *e2;
+ e1 = pseudo_prefix_path("lib");
+ e2 = pseudo_prefix_path("lib64");
+ len = strlen(ld_env) + strlen(e1) + strlen(e2) + 3;
+ newenv = malloc(len);
+ snprintf(newenv, len, "%s:%s:%s", ld_env, e1, e2);
+ free(e1);
+ free(e2);
+ setenv("LD_LIBRARY_PATH", newenv, 1);
+ free(newenv);
+ }
+ free(prefix);
+ } else {
+ char *e1, *e2;
+ e1 = pseudo_prefix_path("lib");
+ e2 = pseudo_prefix_path("lib64");
+ len = strlen(e1) + strlen(e2) + 2;
+ newenv = malloc(len);
+ snprintf(newenv, len, "%s:%s", e1, e2);
+ setenv("LD_LIBRARY_PATH", newenv, 1);
+ free(newenv);
+ }
+
+ if (max_debug_level) {
+ sprintf(debugvalue, "%d", max_debug_level);
+ setenv("PSEUDO_DEBUG", debugvalue, 1);
+ } else {
+ unsetenv("PSEUDO_DEBUG");
+ }
+
+ if (opts) {
+ setenv("PSEUDO_OPTS", opts, 1);
+ } else {
+ unsetenv("PSEUDO_OPTS");
+ }
+}
+
+/* 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 *s) {
+ static char *prefix = NULL;
+ static size_t prefix_len;
+ char *path;
+
+ if (!prefix) {
+ prefix = getenv("PSEUDO_PREFIX");
+ if (!prefix) {
+ pseudo_diag("You must set the PSEUDO_PREFIX environment variable to run pseudo.\n");
+ exit(1);
+ }
+ prefix_len = strlen(prefix);
+ while ((prefix[prefix_len - 1] == '/') && (prefix_len > 0)) {
+ prefix[--prefix_len] = '\0';
+ }
+ }
+ if (!s) {
+ return strdup(prefix);
+ } else {
+ size_t len = prefix_len + strlen(s) + 2;
+ path = malloc(len);
+ if (path) {
+ snprintf(path, len, "%s/%s", prefix, s);
+ }
+ return path;
+ }
+}
+
+char *
+pseudo_get_prefix(char *pathname) {
+ char *s;
+ s = getenv("PSEUDO_PREFIX");
+ if (!s) {
+ char mypath[PATH_MAX];
+ char *dir;
+ char *tmp_path;
+
+ if (pathname[0] == '/') {
+ snprintf(mypath, PATH_MAX, "%s", pathname);
+ s = mypath + strlen(mypath);
+ } else {
+ if (!getcwd(mypath, PATH_MAX)) {
+ mypath[0] = '\0';
+ }
+ s = mypath + strlen(mypath);
+ s += snprintf(s, PATH_MAX - (s - mypath), "/%s",
+ pathname);
+ }
+ tmp_path = pseudo_fix_path(NULL, mypath, 0, 0, 0);
+ /* point s to the end of the fixed path */
+ if (strlen(tmp_path) >= PATH_MAX) {
+ pseudo_diag("Can't expand path '%s' -- expansion exceeds PATH_MAX.\n",
+ mypath);
+ free(tmp_path);
+ } else {
+ s = mypath + snprintf(mypath, PATH_MAX, "%s", tmp_path);
+ free(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);
+ setenv("PSEUDO_PREFIX", mypath, 1);
+ s = getenv("PSEUDO_PREFIX");
+ }
+ return s;
+}
diff --git a/pseudodb.c b/pseudodb.c
new file mode 100644
index 0000000..15bc158
--- /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 (!argv[1]) {
+ fprintf(stderr, "Usage: pseudodb <filename>\n");
+ exit(1);
+ }
+ msg = pseudo_msg_new(0, argv[1]);
+ rc = pdb_find_file_path(msg);
+ 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..6bb0bd9
--- /dev/null
+++ b/pseudolog.1
@@ -0,0 +1,335 @@
+.\"
+.\" 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 SYNOPSIS
+.B pseudolog -l
+.RB [ \-Pv ]
+[
+.B \-E
+.I timeformat
+]
+.RI [ SPECIFICATIONS ]
+.PP
+.B pseudolog
+.RB [ \-DPv ]
+[
+.B \-E
+.I timeformat
+]
+[
+.B \-F
+.I format
+]
+.RI [ SPECIFICATIONS ]
+.SH DESCRIPTION
+The
+.I pseudolog
+utility displays log entries created by the
+.I pseudo
+daemon, or creates log entries. 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 \-D
+Restrict query output to distinct 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
+.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 \-v
+Increase verbosity (debug level). 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 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 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.
+
+.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..33ee3c7
--- /dev/null
+++ b/pseudolog.c
@@ -0,0 +1,783 @@
+/*
+ * 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_db.h"
+
+static int opt_D = 0;
+static int opt_l = 0;
+
+static void display(log_entry *, char *format);
+static unsigned long format_scan(char *format);
+
+void
+usage(int status) {
+ static char *options[] = {
+ "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",
+ "r result (e.g. 'succeed')",
+ "s timestamp",
+ "S severity",
+ "t type (like find -type)",
+ "T text (text field)",
+ "u uid",
+ NULL,
+ };
+ FILE *f = (status == EXIT_SUCCESS) ? stdout : stderr;
+ int i;
+
+ fputs("pseudolog: create or report log entries. usage:\n", f);
+ fputs("pseudolog -l [-E timeformat] [SPECIFIERS] -- create entries\n", f);
+ fputs("pseudolog [-D] [-F format] [-E timeformat] [SPECIFIERS] -- report entries\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("\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), 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" : " ");
+ }
+ exit(status);
+}
+
+pseudo_query_field_t opt_to_field[UCHAR_MAX + 1] = {
+ ['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,
+ ['s'] = PSQF_STAMP,
+ ['S'] = PSQF_SEVERITY,
+ ['t'] = PSQF_FTYPE,
+ ['T'] = PSQF_TEXT,
+ ['u'] = PSQF_UID,
+};
+
+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 %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 == -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 == -1)
+ return -1;
+ if (bits & 010) {
+ mode |= S_ISUID;
+ bits &= ~010;
+ }
+ mode |= bits << 6;
+ string += 3;
+ bits = parse_partial_mode(string);
+ if (bits == -1)
+ return -1;
+ if (bits & 010) {
+ mode |= S_ISGID;
+ bits &= ~010;
+ }
+ mode |= bits << 3;
+ string += 3;
+ bits = parse_partial_mode(string);
+ if (bits == -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_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 == -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 (new_trait->data.ivalue == (time_t) -1) {
+ free(new_trait);
+ return 0;
+ }
+ 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 == -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_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;
+ log_history history;
+ int query_only = 0;
+ int o;
+ int bad_args = 0;
+ char *format = "%s %-5o %7r: [mode %04m] %p %T";
+
+ while ((o = getopt(argc, argv, "vlc:d:DE:f:F:g:G:hi:I:m:M:o:O:p:r:s:S:t:T:u:")) != -1) {
+ switch (o) {
+ case 'P':
+ setenv("PSEUDO_PREFIX", optarg, 1);
+ break;
+ case 'v':
+ pseudo_debug_verbose();
+ 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 'I': /* PSQF_ID */
+ query_only = 1;
+ /* FALLTHROUGH */
+ 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 's': /* PSQF_STAMP */
+ case 'S': /* PSQF_SEVERITY */
+ case 't': /* PSQF_FTYPE */
+ case 'T': /* PSQF_TEXT */
+ case 'u': /* PSQF_UID */
+ 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;
+ }
+ }
+
+ 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 (opt_l) {
+ pdb_log_traits(traits);
+ } else {
+ unsigned long fields;
+ fields = format_scan(format);
+ if (fields == -1) {
+ pseudo_diag("couldn't parse format string (%s).\n", format);
+ return EXIT_FAILURE;
+ }
+ history = pdb_history(traits, fields, opt_D);
+ 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, "cdfgGimMoprsStTu"), 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", 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 '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 '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;
+ }
+ return format + len;
+}
+
+static unsigned long
+format_scan(char *format) {
+ char *s;
+ size_t len;
+ unsigned long fields = 0;
+ pseudo_query_field_t field;
+
+ for (s = format; (s = strchr(s, '%')) != NULL; ++s) {
+ len = strcspn(s, "cdfgGimMoprsStTu");
+ s += len;
+ field = opt_to_field[(unsigned char) *s];
+ switch (field) {
+ case PSQF_PERM:
+ case PSQF_FTYPE:
+ fields |= (1 << PSQF_MODE);
+ break;
+ case PSQF_CLIENT:
+ case PSQF_DEV:
+ case PSQF_FD:
+ case PSQF_GID:
+ case PSQF_TAG:
+ case PSQF_INODE:
+ case PSQF_MODE:
+ case PSQF_OP:
+ case PSQF_PATH:
+ case PSQF_RESULT:
+ case PSQF_STAMP:
+ case PSQF_SEVERITY:
+ case PSQF_TEXT:
+ 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/wrapfuncs.in b/wrapfuncs.in
new file mode 100644
index 0000000..e92de5e
--- /dev/null
+++ b/wrapfuncs.in
@@ -0,0 +1,59 @@
+int open(const char *path, int flags, ...{mode_t mode});
+int close(int fd);
+int link(const char *oldpath, const char *newpath);
+int rename(const char *oldpath, const char *newpath);
+int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
+int unlink(const char *path);
+int unlinkat(int dirfd, const char *path, int flags);
+int creat(const char *path, mode_t mode);
+int __xstat(int ver, const char *path, struct stat *buf);
+int __lxstat(int ver, const char *path, struct stat *buf);
+int __fxstat(int ver, int fd, struct stat *buf);
+int chmod(const char *path, mode_t mode);
+int fchmod(int fd, mode_t mode);
+int chown(const char *path, uid_t owner, gid_t group);
+int fchown(int fd, uid_t owner, gid_t group);
+int lchown(const char *path, uid_t owner, gid_t group);
+int __fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags);
+int fchownat(int dirfd, const char *path, uid_t owner, gid_t group, int flags);
+int fchmodat(int dirfd, const char *path, mode_t mode, 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);
+int __xmknodat(int ver, int dirfd, const char *path, mode_t mode, dev_t *dev);
+int dup2(int oldfd, int newfd);
+int dup(int fd);
+int mkdir(const char *path, mode_t mode);
+int mkdirat(int dirfd, const char *path, mode_t mode);
+int rmdir(const char *path);
+int chdir(const char *path);
+int fchdir(int dirfd);
+int fcntl(int fd, int cmd, ...{struct flock *lock});
+int fork(void);
+int vfork(void);
+# just so we know the inums of symlinks
+int symlink(const char *oldpath, const char *newpath);
+int symlinkat(const char *oldpath, int dirfd, const char *newpath);
+# needed because glibc 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 mkstemp(char *template);
+# I bet you never knew there were this many of these!
+int setfsuid(uid_t fsuid);
+int setfsgid(gid_t fsgid);
+int seteuid(uid_t euid);
+int setegid(gid_t egid);
+uid_t getuid(void);
+uid_t geteuid(void);
+gid_t getgid(void);
+gid_t getegid(void);
+int setresuid(uid_t ruid, uid_t euid, uid_t suid);
+int setresgid(gid_t rgid, gid_t egid, gid_t sgid);
+int setuid(uid_t uid);
+int setgid(gid_t gid);
+int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
+int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);
+int setreuid(uid_t ruid, uid_t euid);
+int setregid(gid_t rgid, gid_t egid);
+int setgroups(size_t size, const gid_t *list);
diff --git a/wrapfuncs64.in b/wrapfuncs64.in
new file mode 100644
index 0000000..94c4744
--- /dev/null
+++ b/wrapfuncs64.in
@@ -0,0 +1,9 @@
+int open64(const char *path, int flags, ...{mode_t mode});
+int openat64(int dirfd, const char *path, int flags, ...{mode_t mode});
+int __openat64_2(int dirfd, const char *path, int flags);
+int creat64(const char *path, mode_t mode);
+int __xstat64(int ver, const char *path, struct stat64 *buf);
+int __lxstat64(int ver, const char *path, struct stat64 *buf);
+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);