diff options
96 files changed, 9868 insertions, 0 deletions
@@ -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)." @@ -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, ¤t, 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); |