diff options
author | Andrew Shadura <andrewsh@debian.org> | 2015-11-22 21:25:49 +0100 |
---|---|---|
committer | Andrew Shadura <andrewsh@debian.org> | 2015-11-22 21:25:49 +0100 |
commit | 6e7205c8f65c8bc03af8619c04b7179218eb1520 (patch) | |
tree | 0957dfac7dbf4a5a39adfe2777666037b7bdd0f3 |
pseudo (1.7.4-1) unstable; urgency=low
* Initial release (Closes: #796973).
# imported from the archive
311 files changed, 22420 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6e11d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.o +Makefile +libpseudo.so +pseudo_wrapfuncs.* +pseudo_wrapper_table.c +pseudo +pseudodb +pseudolog +pseudo_profile +pseudo_tables.c +pseudo_tables.h +port_wrappers.c +pseudo_ports.h +templatefile.pyc +func_deps.mk +port_deps.mk @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog.txt b/ChangeLog.txt new file mode 100644 index 0000000..5431f1e --- /dev/null +++ b/ChangeLog.txt @@ -0,0 +1,664 @@ +2015-09-22: + * (seebs) Fix modes after fopen/fopen64. + +2015-09-04: + * (seebs) add return value printing to wrapper debug! + * (seebs) make mkdirat() restore errno, also don't pass + AT_SYMLINK_NOFOLLOW to fchmodat() since it is ignored, and + we know that if the thing exists at all, it is a directory + and thus AT_SYMLINK_NOFOLLOW would be irrelevant anyway. + * (seebs) and 1.7.3, of course. + +2015-09-03: + * (seebs) Use original mode, not 0600, for the chmods used to + ensure that an 0700 umask didn't prevent writing. + * (seebs) remove the comment saying that 0700 umasks were expected + to break things. + * (seebs) and call this 1.7.2. + +2015-09-02: + * (seebs) call this 1.7.1, since that seems to be all the bugs + I can immediately find. + +2015-09-01: + * (seebs) use PSEUDO_STATBUF and base_lstat in path resolution, because + plain lstat can fail on XFS if an inode number is out of the 32-bit + range. + * (seebs) if no pointer was provided to realpath, provide allocated + dup. + +2015-08-24: + * (seebs) drop unused/unimplemented "picky_fchmodat", since I + currently have no systems which implement it, and the implementation + for NO_REAL_AT_FUNCTIONS was difficult to define. + * (seebs) drop unused "doing_link" in fchownat, which I think predates + the current database entirely. + * (seebs) drop unused "old_trailing_slash". + * (seebs) tag this as 1.7.0. + +2015-08-22: + * (seebs) Prevent files from getting created with real filesystem + mode 0, even with umask. + * (seebs) xattrdb logic fixes. + * (seebs) Debugging messages. + +2015-08-21: + * (seebs) don't put incorrect host UIDs in dummy entries in db. + * (seebs) merge existing values for chown/chmod with xattrdb. + +2015-08-20: + * (seebs) don't send open/exec messages unless server is logging. + +2015-08-19: + * (seebs) Reduce alloc/free cycles in path computations. + +2015-08-17: + * (seebs) profiling improvements + +2015-08-14: + * (seebs) profiling cleanup, add profiler to gitignore + * Start using 1.7.0 version for internal work. + +2015-08-13: + * (seebs) client profiling stuff (first pass) + +2015-07-17: + * (seebs) allow actually making fifos without randomly closing fd 0 + * 1.6.7 + +2015-07-16: + * (seebs) don't truncate xattr attributes that end with a slash. + * (seebs) allow actually making fifos + * 1.6.6 + +2015-05-04: + * (seebs) don't give spurious trailing slash diagnostics + * 1.6.5 + +2015-01-22: + * (seebs) work when --prefix doesn't exist yet + * 1.6.4 + +2015-01-15: + * (seebs) Rework PSEUDO_PASSWD path allocation for clarity, + I hope. + * 1.6.3 + +2015-01-14: + * (seebs) Merge pabigot's fix for a missing antimagic() in + etc_file code. + * (seebs) Make configure reject a "prefix" of the current + directory. It fails in strange ways and is easy to detect. + * (seebs) handle colon-separated lists in PSEUDO_PASSWD. + Derived in part from contribution from pabigot. + * (seebs) change allocation order in password file locking. + +2015-01-05: + * (seebs) First try at handling trailing slashes in path names + correctly. (Which is to say, a trailing slash on a file name + which is not a directory name should yield ENOTDIR from a lot + of things.) + +2014-10-03: + * (seebs) in fact, suppress a lot of sanity checks entirely for + did_unlink. + * (seebs) merge get_file_path functionality into find_file_dev, + since you never call find_file_dev unless you want to do that. + * (seebs) If a file is in the database by inode but not path, + don't try to create a new link for it. + * (seebs) when renaming, the link of the "old" file name should be + contingent on whether *it* was in the database, not whether + the *new* name was in the database. Whoops. + * (seebs) 1.6.2 + +2014-10-02: + * (seebs) use sqlite3_bind_int64 for inodes. + * (seebs) suppress path mismatch warnings for did_unlink. + +2014-07-18: + * (seebs) Make "server already offline" message into a debugging + message (PDBGF_INVOKE). + * (seebs) 1.6.1 + +2014-07-17: + * (seebs) Restrict symbol version restrictions to x86-64 and i386 + rather than x86-64 and any old thing. Fixes build problems on ARM. + +2014-07-11: + * (seebs) merge in symbol version restrictions (slightly tweaked) + for Linux. + +2014-07-10: + * (seebs) Don't pass -L/usr/lib* for sqlite, because that's probably + the default. + * (seebs) include sys/xattr.h explicitly for Darwin, also fix things + up so we aren't relying on seeing a declaration for our dummy + wrapper for fgetgrent_r which doesn't exist here. + * (seebs) fix typo in xattr message + * (seebs) make xattr work on Darwin, too. + +2014-06-13: + * (seebs) don't follow symlinks for lutimes. + * (seebs) Use sizeof instead of strlen to initialize static value. + +2014-05-27: + * (seebs) start noticing umask, mask it out from open or mkdir + calls rather than relying on underlying open/mkdir to do it. + * (seebs) add a test for umask and filesystem-vs-db modes. + +2014-05-16: + * (seebs) fchmodat: don't drop flags, report failures, to improve + compatibility/consistency. Cache the knowledge that + AT_SYMLINK_NOFOLLOW gets ENOTSUP. + * (seebs) mask out group/other write bits in real filesystem to + reduce risks when assembling a rootfs including world-writeable + directories. + * (seebs) send a round-trip message to the server before exiting + if any messages have been sent, so the program won't exit before + the server processed those messages. + * (pabigot) Don't memory-leak on checks for PSEUDO_UNLOAD, also + check the correct environment when spawning new things. (Contributed + by Peter A. Bigot). + * (seebs) mask 022 back in too, when restoring a database mode. + +2014-05-15: + * (seebs) drop flags when calling fchmodat() to appease GNU tar. + +2014-04-24: + * (seebs) extended attribute support + +2014-01-23: + * (seebs) mknod wasn't calling mknodat. + * (seebs) mkdirat wasn't recording the logical DB mode for directories + +2014-01-22: + * (seebs) notes on some Futures planning. + * (seebs) Typo/formatting issue in man page. + * (seebs) message formatting/clarity cleanup and typo fixes. + * (seebs) First draft of passwd_fallback implementation. + * (seebs) bump this all to 1.6.0. + +2014-01-15: + * (seebs) performance test/test case updates. + +2013-06-20: + * (seebs) refactor debugging code + * (seebs) start using 1.5.2 numbering + +2013-06-18: + * (seebs) Fix bug in oldclone (reported by rich@noir.com). + +2013-02-27: + * (seebs) Oh, hey, what if I took out my debug messages? + * (seebs) update docs a bit to reduce bitrot + +2013-02-26: + * (seebs) When built -DNDEBUG, completely drop pseudo_debug calls. + * (seebs) Add PSEUDO_ALLOW_FSYNC to allow temporary re-enabling of + fsync to work around filesystem bugs. + * (seebs) call that 1.5.1. + +2013-02-17: + * (seebs) Fix uninitialized variable in unlink, fix force-fsync for + Darwin (off64_t is not a distinct type there). + +2013-02-16: + * (seebs) Add wrapper setting which automatically returns a fixed + value from some wrappers. Add fixed-value wrappers for all the + *sync() functions. These are all contingent on --enable-force-async. + +2013-02-15: + * (seebs) Add support for in-memory DB. This, plus upcoming + fsync-related changes, are expected to be big enough to justify + calling this 1.5. + +2013-02-13: + * (seebs) calling link while chrooted could in some cases result + in the root path not being prepended at all. One more try! + * (seebs) 1.4.5. + +2013-02-12: + * (seebs) calling link while chrooted could in some cases result + in the root path being double-appended. + * (seebs) and tag 1.4.4 so that can get out as a clean update. + +2013-01-31: + * (seebs) tag 1.4.3 (to avoid any problems with the changes since + 1.4.2 and old tarballs) + +2013-01-30: + * (seebs) Subtle tweaks to avoid GLIBC_2.7 dependencies which + can cause trouble trying to copy libpseudo.so to older hosts. + * (seebs) add "with-sqlite-lib" to simplify cases where sqlite's + libdir computation differs from what we otherwise want; for + instance, with bitbake, we often end up wanting $lib = lib64, + but $sqlite_lib = lib. + +2012-12-13: + * (seebs) tag 1.4.2. + +2012-12-12: + * (seebs) Remove extra tab from the path alloc code in + makewrappers. (Which has no effect since I changed my mind about + the implementation which would have made it show up.) + * (seebs) linkat() implementation. as a side effect, clear up + the documentation and behavior of link(2) to reflect host + OS semantics: Linux will hardlink symlinks, Darwin will only + hardlink their targets. + * (seebs) make linkat() implementation compile/run on Darwin, + fix header bitrot for Mountain Lion. + +2012-08-09: + * (seebs) base_stat should be real_stat64, not stat64 + * (seebs) add stat64/lstat64/fstat64 wrappers to Linux (not + previously needed because the libc versions call stuff we + already wrap). + +2012-08-02: + * (seebs) fix some Darwin-specific bitrot for clang/llvm. + * (seebs) Drop the _plain thing, convert unix/guts/* to use + PSEUDO_STATBUF. + * (seebs) Tag 1.4.1. + +2012-07-27: + * (seebs) Convert from .tgz to tar.bz2 since that's handier for + Yocto. + +2012-07-24: + * (seebs) Fix a couple of bitrots from the update. + +2012-07-20: + * (seebs) Add --cflags, deprecate --arch. + * (seebs) tag 1.4 (since this should now work on arbitrary targets) + +2012-06-28: + * (seebs) Tag 1.3.1. + +2012-06-27: + * (seebs) Fix chroot coredump with long root path. + +2012-04-30: + * (seebs) Update README about new upstream. + +2012-04-09: + * (seebs) Improvements to rpath logic for sqlite, etc. + * (seebs) Improvements to the logic for picking options + like -m32, -m64 (which is to say, on ARM: Don't.) + +2012-03-28: + * (seebs) Cleanup unused variables, stray semicolons, add + comments to some unused functions which exist because the + wrapper generator makes them anyway. + * (seebs) Make system() drop environment if PSEUDO_UNLOAD is + set. + +2012-03-27: + * (seebs) Merge in: + * (mhatle) Improve configuration compatibility with OE-Core. + * (seebs) Provide option to statically link libsqlite. + * (seebs) 1.3 branch + +2012-03-26: + * (seebs) Add popen() call to set up environment. + +2012-02-06: + * (seebs) Merge O_LARGEFILE into flags, not mode (thanks to Lei + Liu at Wind River for the fix). + +2012-02-02: + * (seebs) stash dir name for DIR * from opendir using dirfd. + * (seebs) add closedir. + * (seebs) add initial pass at renameat() + * (seebs) update makewrappers with smarter *dirfd handling. + * (seebs) in base_path, don't try to strlen the result if + fd_path() returns NULL. + +2011-11-02: + * (seebs) Call this 1.2 because the UNLOAD change is moderately + significant, and so's the clone change. + +2011-11-01: + * (mhatle) Stop valgrind from reporting use of uninitialized + memory from pseudo_client:client_ping() + +2011-10-26: + * (mhatle) update clone wrapper to add an intermediate function + to avoid setting environment variables in the parent. + +2011-10-20: + * (mhatle) change from internal PSEUDO_RELOADED to external + PSEUDO_UNLOAD environment variable. Enable external programs + to have a safe and reliable way to unload pseudo on the next + exec*. PSEUDO_UNLOAD also will disable pseudo if we're in a + fork/clone situation in the same way PSEUDO_DISABLED=1 would. + +2011-07-19: + * (seebs) initialize a variable in that "realpath" code. + +2011-06-08: + * (seebs) Get the modern realpath from glibc instead of the old + one inexplicably proferred by RTLD_NEXT. Fixes realpath(path, NULL) + when PSEUDO_DISABLED=1. + +2011-06-06: + * (seebs) revise system() handler substantially. It now + pollutes the environment but works. + * (seebs) Call it "1.1.1" so the nice folks doing Yocto + can have an official branch and not need to use git. + * (seebs) add "tarball" make target. + +2011-06-02: + * (seebs) intercept system() so the pseudo environment is + properly set for it. + * (seebs) call this "1.1" since the cumulative result of all + of these changes is pretty big. + +2011-05-31: + * (seebs) Don't mask in 0100 to filesystem modes for things which + are not actually directories, because this breaks the special + logic in euidaccess for X_OK. + +2011-05-25: + * (seebs) fix for ulckpwdf() + +2011-04-21: + * (seebs) don't use strerror in wrappers, because it can + lead to malloc deadlocks if part of setting up a malloc + operation falls into strerror which uses locale... Curse + you, Fedora 13. You and your perfectly reasonable and + standards-conforming behavior which happened to inconvenience + me. + +2011-04-16: + * (seebs) remove duplicate definition of real_clone() from + the oldclone port. + +2011-04-13: + * (seebs) base_path of an empty string should be an empty string, + not $pwd. + +2011-04-04: + * (seebs) whitespace cleanup for Python code + +2011-04-01: + * (seebs) update README + +2011-03-25: + * (seebs) don't try to search path when you don't have one + * (seebs) merge in ports branch + * (seebs) fix permissions on subports/preports + * (seebs) try to force debug fd to 2 + +2011-03-24: + * (seebs) more work on OS X port. + * (seebs) include errno in the verbose debug output + * (seebs) fix darwin fcntl. + * (seebs) fix *xattr for darwin (they take more arguments) + +2011-02-18: + * (seebs) moving things to Unix port, cleanup for Darwin + +2011-02-14: + * (seebs) first pass on splitting out ports + * (seebs) various cleanup + +2011-02-10: + * (seebs) pseudo_client_shutdown(), and the pseudo server, have to + be smart enough to make the local state directory in case the + pseudo binary is invoked directly by a user before being spawned + by the client. + +2011-02-09: + * (seebs) the long-awaited cleanup of the database initialization + code. it's not really beautiful but it's quite a bit better. + +2011-02-08: + * (seebs) Get full paths for exec*() + +2011-01-24: + * (mhatle) Revert last result cache and related commits. + caching proved to be unreliable. + +2011-01-14: + * (seebs) Automatically create prefix/state directories. + * (mhatle) Avoid caching OP_EXEC calls + +2011-01-13: + * (seebs) Subtle cache fixup. + +2010-12-17: + * (mhatle) Disabled additional early setup if pseudo is disabled + +2010-12-16: + * (mhatle) change the journal from PERSIST to OFF + * (seebs) update docs now that fakeroot and password support are in, + this being long overdue + * (seebs) fix parallel build issue introduced with maketables + +2010-12-15: + * (mhatle) add sqlite call profiling, enable with NPROFILE + * (mhatle) as a result of profiling, optimize inode search + * (mhatle) rearrange the pseudo_op file data operations to reduce + the number of selects. + * (mhatle) add the ability to cache the last select result + * (mhatle) change the indexing in pseudo_db.c + +2010-12-14: + * (mhatle) restructure wrapfuncs.c + +2010-12-09: + * (mhatle) Add doc/program_flow to attempt to explain startup/running + * (mhatle) guts/* minor cleanup + * (mhatle) Reorganize into a new constructor for libpseudo ONLY + pseudo main() now manually calls the util init + new / revised init for client, wrappers and utils + * (mhatle) Add central "reinit" function + * (mhatle) Add manul execv* functions + * (mhatle) rename pseudo_populate_wrappers to pseudo_check_wrappers + +2010-12-08: + * (mhatle) Add guts/clone.c to cleanup the clone support + * (mhatle) guts/clone.c only run setupenv and reinit when NOT PSEUDO_RELOADED + * (mhatle) guts/execve.c whitespace fixe + * (mhatle) guts/fork.c similar to guts/clone.c change + * (mhatle) pseudo_client.c add reinit function + * (mhatle) pseudo_client.c revise client reset, include code from pseudo_wrappers.c + * (mhatle) pseudo_server.c move the pid writing to the parent + * (mhatle) pseudo_wrappers.c clone cleanup and populate cleanup + +2010-12-07: + * (seebs) whitespace fixes + * (seebs) improve fork and PSEUDO_DISABLED + * (seebs) add support for clone(2) + * (mhatle) rework/improve clone(2) support + * (mhatle) add test code for PSEUDO_DISABLED + +2010-12-02: + * (seebs) rework of fork/exec, add PSEUDO_DISABLED + +2010-11-30: + * (seebs) move *_t types to a separate file. + * (seebs) remove unused tables from pseudo_db.c + * (seebs) cleanup .gitignore + +2010-11-17: + * (seebs) add "Futures.txt" notes about future development plans + * (seebs) split some of the templating code out of makewrappers + +2010-11-16: + * (seebs) database move functionality (first pass) + +2010-10-25: + * (seebs) various makewrappers cleanups (pylint, mostly) + +2010-10-12: + * (seebs) add missing copyright to Python makewrappers. + * (seebs) detab makewrappers + +2010-10-11: + * (seebs) do the other *xattr() wrappers. + * (seebs) Replace makewrappers with Python implementation and some + template files. + +2010-10-06: + * (mhatle) Add the fsetxattr wrapper to return ENOTSUP + (note: workaround gnu coreutils 'cp') + +2010-09-16: + * (seebs) change exec*() to use file, rather than path or filename, + also add OP_EXEC where it was missing. + +2010-09-15: + * (seebs) allow setting default RPATH entry distinct from sqlite3 + directory. + +2010-09-08: + * (seebs) handle mkfifo without guaranteeing an EINVAL response. + +2010-09-02: + * (seebs) fix errno for getcwd() with insufficient size + * (seebs) Add an RPATH entry to the pseudo binary to find the sqlite3 + library. + +2010-09-01: + * (seebs) add missing casts to even more printf arguments + +2010-08-31: + * (seebs) add missing casts to printf arguments, after being warned + about them a mere twenty or thirty thousand times. + +2010-08-27: + * (seebs) fix a bug caused by memcmp with wrong length + * (seebs) stop hand-coding lengths of memcmp (torek was right...) + +2010-08-26: + * (seebs) make offsets.c slightly less useless + * (seebs) don't overwrite LD_LIBRARY_PATH values that include us + +2010-08-25: + * (seebs) fix the signal mask restore + +2010-08-24: + * (seebs) try to restore signal mask before calling exec() + * (seebs) move errno restoration after code which could set errno + +2010-08-19: + * (seebs) handle insane edge case involving regcomp/regexec + +2010-08-17: + * (seebs) create speculative-deletion logic + * (seebs) remove crackpot theories about cross-device renames + +2010-08-16: + * (rp) Fix ld_preload/ld_library_path mixup. + * (seebs) Handle failed allocations. + * (seebs) Fix logic for dropping empty LD_PRELOAD. + +2010-08-12: + * (seebs) Fix install of libpseudo so the plain library is created + when using $(SUFFIX), this is needed so pseudo daemons don't + need to know $(SUFFIX) so you can use prebuilts. + * (seebs) Remove spurious "const" from modifiable table. + +2010-08-11: + * (seebs) document the new variables. + +2010-08-10: + * (mhatle) add execl, execle, execlp, execv, and execvp wrappers + * (seebs) handle ... for execl, etc. + * (mhatle) add a local cache of variables, instead of using environ + * (mhatle) rewrite pseudo_setupenv, pseudo_dropenv routines + we now support running "/usr/bin/env -i env" in pseudo! + +2010-08-06: + * (mhatle) Fix an exec program with an empty environment + +2010-08-03: + * (mhatle) Fix parallel build problem + * (mhatle) allow both environment CFLAGS and internals CFLAGS + * (mhatle) add PSEUDO_BINDIR, PSEUDO_LIBDIR, PSEUDO_LOCALSTATEDIR + to allow specific overrides above and beyond PSEUDO_PREFIX + +2010-07-30: + * (kscherer) added .gitignore file + * (kscherer) added sqlite version check to configure script + * (kscherer) added basic test harness + * (kscherer) fixed bug that when moving a directory the contents + of the dir were removed from the db + +2010-06-29: + * (seebs) handle the other half of the suffix case -- when + libpseudo is in LD_PRELOAD under another name. + * (seebs) remove a couple of debugging messages. + +2010-06-28: 0.3 + * (seebs) back out PSEUDO_SUFFIX -- it causes problem when + rebuilding the library but not the server, but this is + a permissible use case. + +2010-06-21: + * (seebs) add mkstemp64 + +2010-06-02: + * (seebs) add PSEUDO_NOSYMLINKEXP feature and documentation. + +2010-04-30: + * (seebs) rework pdb_history + * (seebs) small cleanups and bulletproofing. + * (seebs) fix up PSEUDO_DEBUG_FILE, use it for server as well. + +2010-04-27: + * (seebs) fix -P in pseudolog + * (seebs) document PSEUDO_DEBUG_FILE + +2010-04-26: + * (seebs) many bug fixes and updates + * (seebs) allow deleting entries in pseudolog + * (seebs) correct race conditions and related bugs + +2010-04-20: + * (seebs) add quick sanity-check option for pseudo + * (seebs) report on rows deleted + * (seebs) unlink after removing db entry to reduce race conditions + +2010-04-19: + * (seebs) fix crash if xstat() or similar routine called with null path + * (seebs) fix list of client processes still running + * (seebs) fix pathname mismatches introduced by chroot() support + +2010-04-16: + * (seebs) add tracking of program names + * (seebs) track message types + * (seebs) small bug fixes and improvements galore + +2010-04-06: + * (seebs) implement various passwd-related utilities, various + bugfixes. + +2010-03-25: + * (seebs) fix return values. + +2010-03-24: + * (seebs) add chroot syscall + * (seebs) add chroot handling to path canonicalization + * (seebs) add many calls just to get path fixups + * (seebs) handle virtualizing rootness of absolute symlinks + +2010-03-24: + * (seebs) regenerate wrappers when makewrappers is changed. + * (seebs) begin prep for chroot + * (seebs) standardize path expansion + * (seebs) extend makewrappers to handle function pointer args + +2010-03-17: + * (seebs) fixup help options + * (seebs) use strerror() in a couple more places + * (seebs) mention ^ specification in pseudolog -h output. + +2010-03-16: + * (seebs) Fix missing error checking noted by comp.lang.c reader. + +2010-03-16: 0.2 + * first public release + +(There's no changelog before that, sorry.) diff --git a/Futures.txt b/Futures.txt new file mode 100644 index 0000000..284005a --- /dev/null +++ b/Futures.txt @@ -0,0 +1,54 @@ +Some notes on features under discussion or consideration, and some +vague implementation thoughts: + +* Add some kind of filter for "the directory we care about" + - pseudo operations would only go to the server for items + in this directory + - optionally, logging could be generated for accesses + *outside* this directory + - intent is to reduce server load dramatically, and improve + and/or streamline the logging path when, e.g., doing + host contamination checking + - probably implies a new message type, MSG_LOG. + +* Database recovery/reconstruction + - capability for failing harder in the event of apparent + corruption + +* Log database performance improvements + - some way to do "live" queries against the log database + while the server is running + - some way to flush unwanted data + - possibly a way to separate logging into multiple files + or otherwise restrict the amount of stuff produced + - logging filters of some sort + +* Canonicalization speedups + - possibly implement some kind of cache of directory names + and known canonicalizations + +* Possibly limited handling for extended attributes + - specifically, the case where they're used to manpulate or + query the plain old modes ala chmod/stat. + +* Test cases + - boy, do we need test cases! + +* Clean up *at() functions + - The *at() function implementations are gratuitously complicated, + I believe because they predate the auto-canonicalization that came + with the rewritten pseudo wrappers. + +* Memory allocation/deallocation issues. + - there's a number of places where results from pseudo_get_value() are + strdup'd, then the originals freed, which makes no sense. + - there's at least one unchecked realloc() to do with the fd table. + +* Benchmarking/performance work. + - It'd be nice to get some kind of measurement of how much time is + going to which parts of pseudo (database access, filesystem access, + IPC overhead, logic, client processing, and so on). + - Maybe some work on finding ways to make profiling work, since I was + having issues getting good profiling data. + - Some moderately-canonical benchmarks would be nice to have for evaluating + the costs of various common use cases. diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..7fa5149 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,213 @@ +# +# Makefile.in/Makefile, build rules for pseudo +# +# Copyright (c) 2008-2015 Wind River Systems, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License version 2.1 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# version 2.1 along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# configuration flags +PREFIX=@PREFIX@ +LIBDIR=@LIBDIR@ +SUFFIX=@SUFFIX@ +SQLITE=@SQLITE@ +SQLITE_LIB=@SQLITE_LIB@ +SQLITE_MEMORY=@SQLITE_MEMORY@ +FORCE_ASYNC=@FORCE_ASYNC@ +XATTR=@XATTR@ +XATTRDB=@XATTRDB@ +PROFILING=@PROFILING@ +ifeq (true,$(PROFILING)) + PSEUDO_PROFILE=$(BIN)/pseudo_profile +else + PSEUDO_PROFILE= +endif +define cond + $(if $(filter true,$($(1))),-D$(2)) +endef +OPTDEFS=$(call cond,XATTR,PSEUDO_XATTR_SUPPORT) \ + $(call cond,XATTRDB,PSEUDO_XATTRDB) \ + $(call cond,PROFILING,PSEUDO_PROFILING) +PASSWD_FALLBACK=@PASSWD_FALLBACK@ +BITS=@BITS@ +ARCH_FLAGS=@ARCH_FLAGS@ +MARK64=@MARK64@ +RPATH=@RPATH@ +VERSION=1.7.4 + +LIB=@LIB@ +BIN=bin +LOCALSTATE=var/pseudo +BINDIR=$(PREFIX)/$(BIN) +LOCALSTATEDIR=$(PREFIX)/$(LOCALSTATE) + +CFLAGS_BASE=-pipe -std=gnu99 -Wall -W -Wextra +CFLAGS_CODE=-fPIC -D_LARGEFILE64_SOURCE -D_ATFILE_SOURCE $(ARCH_FLAGS) +CFLAGS_DEFS=-DPSEUDO_PREFIX='"$(PREFIX)"' -DPSEUDO_SUFFIX='"$(SUFFIX)"' -DPSEUDO_BINDIR='"$(BIN)"' -DPSEUDO_LIBDIR='"$(LIB)"' -DPSEUDO_LOCALSTATEDIR='"$(LOCALSTATE)"' -DPSEUDO_VERSION='"$(VERSION)"' $(SQLITE_MEMORY) $(FORCE_ASYNC) -DPSEUDO_PASSWD_FALLBACK='$(PASSWD_FALLBACK)' $(OPTDEFS) +CFLAGS_DEBUG=-O2 -g +@DEFAULT_SQLITE@CFLAGS_SQL=-L$(SQLITE)/$(SQLITE_LIB) -I$(SQLITE)/include $(RPATH) +CFLAGS_PSEUDO=$(CFLAGS_BASE) $(CFLAGS_CODE) $(CFLAGS_DEFS) \ + $(CFLAGS_DEBUG) $(CFLAGS_SQL) + +GLOB_PATTERN=guts/*.c +GUTS=$(filter-out "$(GLOB_PATTERN)",$(wildcard $(GLOB_PATTERN))) + +SOURCES=$(wildcard *.c) +OBJS=$(subst .c,.o,$(SOURCES)) + +SHOBJS=pseudo_tables.o pseudo_util.o +DBOBJS=pseudo_db.o +WRAPOBJS=pseudo_wrappers.o + +# needed for anything that links with pseduo_client.o, pretty much +CLIENT_LDFLAGS=-ldl -lpthread +DB_LDFLAGS=@SQLITE_LDARG@ -lpthread + +PSEUDO=$(BIN)/pseudo +PSEUDODB=$(BIN)/pseudodb +PSEUDOLOG=$(BIN)/pseudolog +LIBPSEUDO=$(LIB)/libpseudo.so + +TEMPLATES=templates/guts templates/wrapfuncs.c templates/wrapfuncs.h templates/wrapper_table +TABLES=table_templates/pseudo_tables.c table_templates/pseudo_tables.h + +all: $(LIBPSEUDO) $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE) + +test: all $(BIN) $(LIB) $(LOCALSTATE) + @./run_tests.sh -v + +install-lib: $(LIBPSEUDO) + mkdir -p $(DESTDIR)$(LIBDIR) + cp $(LIBPSEUDO) $(DESTDIR)$(LIBDIR) + $(if $(SUFFIX),cp $(LIBPSEUDO) $(DESTDIR)$(LIBDIR)/libpseudo$(SUFFIX).so,:) + +install-bin: $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE) + mkdir -p $(DESTDIR)$(BINDIR) + cp $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) $(PSEUDO_PROFILE) $(DESTDIR)$(BINDIR) + +install-data: + mkdir -p $(DESTDIR)$(LOCALSTATEDIR) + +install: all install-lib install-bin install-data + +$(BIN) $(LIB) $(LOCALSTATE): + mkdir -p $@ + +pseudo: $(PSEUDO) + +$(PSEUDO): $(BIN) pseudo.o $(SHOBJS) $(DBOBJS) pseudo_client.o pseudo_server.o pseudo_ipc.o + $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $(PSEUDO) \ + pseudo.o pseudo_server.o pseudo_client.o pseudo_ipc.o \ + $(DBOBJS) $(SHOBJS) $(DB_LDFLAGS) $(CLIENT_LDFLAGS) + +pseudolog: $(PSEUDOLOG) + +$(PSEUDOLOG): $(BIN) pseudolog.o $(SHOBJS) $(DBOBJS) pseudo_client.o pseudo_ipc.o + $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $(PSEUDOLOG) pseudolog.o pseudo_client.o pseudo_ipc.o \ + $(DBOBJS) $(SHOBJS) $(DB_LDFLAGS) $(CLIENT_LDFLAGS) + +pseudodb: $(PSEUDODB) + +$(PSEUDODB): $(BIN) pseudodb.o $(SHOBJS) $(DBOBJS) pseudo_ipc.o + $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o $(PSEUDODB) pseudodb.o \ + $(DBOBJS) $(SHOBJS) pseudo_ipc.o $(DB_LDFLAGS) $(CLIENT_LDFLAGS) + +libpseudo: $(LIBPSEUDO) + +$(LIBPSEUDO): $(LIB) $(WRAPOBJS) pseudo_client.o pseudo_ipc.o $(SHOBJS) + $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -shared -o $(LIBPSEUDO) \ + pseudo_client.o pseudo_ipc.o \ + $(WRAPOBJS) $(SHOBJS) $(CLIENT_LDFLAGS) + +# *everything* now relies on stuff that's generated in the +# wrapper process. +%.o: %.c pseudo_wrapfuncs.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(CFLAGS_PSEUDO) $< + +$(OBJS): pseudo_tables.h + +pseudo_client.o pseudo_server.o pseudo_ipc.o: pseudo_ipc.h + +pseudo_client.o: pseudo_client.h + +pseudo_server.o: pseudo_server.h + +tables: enums/*.in maketables templatefile.py $(TABLES) + ./maketables enums/*.in + +wrappers: makewrappers templatefile.py $(TEMPLATES) ports/*/wrapfuncs.in + CC="$(CC) $(CFLAGS) $(CFLAGS_PSEUDO)" ./makewrappers "xattr=$(XATTR)" + +.SECONDARY: tables wrappers + +pseudo_wrapfuncs.c pseudo_wrapfuncs.h: wrappers + +pseudo_tables.c pseudo_tables.h: tables + +pseudo_tables.o: pseudo_tables.c + $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -c -o pseudo_tables.o pseudo_tables.c + +# no-strict-aliasing is needed for the function pointer trickery. +pseudo_wrappers.o: $(GUTS) pseudo_wrappers.c pseudo_wrapfuncs.c pseudo_wrapfuncs.h pseudo_tables.h + $(CC) -fno-strict-aliasing $(CFLAGS) $(CFLAGS_PSEUDO) -D_GNU_SOURCE -c -o pseudo_wrappers.o pseudo_wrappers.c + +offsets32: + $(CC) -m32 -o offsets32 offsets.c + +offsets64: + $(CC) -m64 -o offsets64 offsets.c + +$(PSEUDO_PROFILE): $(BIN) pseudo_profile + cp pseudo_profile $(BIN) + +pseudo_profile: Makefile pseudo_profile.c tables wrappers + $(CC) $(CFLAGS) $(CFLAGS_PSEUDO) -o pseudo_profile pseudo_profile.c + +clean: + rm -f *.o *.so $(PSEUDO) $(PSEUDODB) $(PSEUDOLOG) \ + pseudo_wrapfuncs.h pseudo_wrapfuncs.c \ + pseudo_wrapper_table.c \ + pseudo_tables.c pseudo_tables.h \ + pseudo_ports.h port_wrappers.c \ + offsets32 offsets64 \ + pseudo_profile \ + port_deps.mk func_deps.mk + touch port_deps.mk func_deps.mk + +distclean: clean + rm -f Makefile + rm -rf ./$(BIN) ./$(LIB) ./$(LOCALSTATE) + @echo "WARNING: Makefile has been removed. You must reconfigure to do anything else." + +nuke: distclean + case "$(PREFIX)" in "`pwd`"/*) rm -rf "$(PREFIX)";; esac + @echo "WARNING: Removed $(PREFIX)." + +tarball: + @test -d .git || ( echo >&2 "Tarball can only be made from git tree."; exit 1) + rm -rf pseudo-$(VERSION) + mkdir -p pseudo-$(VERSION) + ( parent=$(PWD); \ + cd pseudo-$(VERSION) && \ + git clone $$parent && \ + mv pseudo/* . && \ + rm -rf pseudo/.git* && \ + rmdir pseudo \ + ) + tar cjf pseudo-$(VERSION).tar.bz2 pseudo-$(VERSION) + +# Note when we need to rebuild pseudo_wrappers.o +include port_deps.mk +include func_deps.mk + @@ -0,0 +1,87 @@ +pseudo -- an analogue to sudo + +IMPORTANT NOTE: + +As of this writing, the official home for pseudo's git repository has +changed to: + git://git.yoctoproject.org/pseudo + +The old site at: + https://github.com/wrpseudo/pseudo + +is no longer the "real" master, although I'll probably try to keep it in +sync. + +OVERVIEW: + +The pseudo utility offers a way to run commands in a virtualized "root" +environment, allowing ordinary users to run commands which give the illusion +of creating device nodes, changing file ownership, and otherwise doing +things necessary for creating distribution packages or filesystems. + +To configure, run the provided configure script. Note that this is +NOT an autoconf script. + +Configure options: + --prefix=/path/to/install/dir + --with-sqlite=/path/to/sqlite/dir + --bits={32,64} + --suffix=<text> + --enable-static-sqlite + --with-sqlite-lib + --libdir=... + --enable-memory-db + --enable-force-async + --with-sqlite-lib=... + --enable-static-sqlite + --with-static-sqlite=... + --with-rpath=...|--without-rpath + --cflags='' + +There is no default prefix. The default for sqlite is /usr, and for +bits is 32. You need a reasonably modern sqlite3 -- it has to have +sqlite3_clear_bindings(), which at least one default sqlite3 install +did not. (But that was dated 2006, so I'm not sure this will affect +most readers.) + +The suffix value can be used to append a particular text string to file +names (such as libpseudo<suffix>.so). This was used in the WR environment +to create libpseudo-<host_libc_md5sum>.so, to ensure that libpseudo was +rebuilt if the host libc changed. + +This code is not particularly portable, but works on Linux and also +on 64-bit Intel Darwin systems. + +Limited user documentation is provided in the given man page files (these +are not currently installed, merely provided in the source directory), and +some documentation on internals is provided in the doc/ directory. + +The memory-db option uses an in-memory SQLite database, which is flushed +to disk on failures. It's disabled by default with SQLite 3.6 and earlier +due to performance weirdness. + +The --enable-force-async option causes wrappers for fsync() and friends to +all return "success" instantly unless PSEUDO_ALLOW_FSYNC was set in the +environment when the client initialized (usually after a fork or exec). + + +FUTURE DIRECTIONS: + +* The chroot() functionality is incomplete, though now sufficient for + the real-world use cases we've tried. +* I have no intention of converting to autoconf. It is the wrong tool + for the job. + +Please feel free to send bug feedback, change requests, or general +commentary. + + +ACKNOWLEDGEMENTS: + +My various coworkers, both engineering and management, made this possible. +While I did most of the actual typing, this code has benefitted greatly +from detailed code reviews, excellent reproducers for bugs, and the +consistent support of the whole group for the project. It's been a great +deal of fun, and I'm pretty happy that we're finally ready to make it +available for other people to look at. + diff --git a/configure b/configure new file mode 100755 index 0000000..e5ef9ce --- /dev/null +++ b/configure @@ -0,0 +1,341 @@ +#!/bin/sh +# +# configure, simulation of autoconf script, much simplified +# +# Copyright (c) 2008-2014 Wind River Systems, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License version 2.1 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# version 2.1 along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# not a real configure script... +opt_prefix= +opt_libdir= +opt_suffix= +opt_arch=x86 +opt_bits= +opt_sqlite=/usr +opt_rpath= +opt_memory= +opt_async= +opt_xattr= +opt_xattrdb=false +opt_profile=false +opt_passwd_fallback='""' + +compile_x86_32=-m32 +compile_x86_64=-m64 + +usage() +{ + echo >&2 "usage:" + echo >&2 " configure --prefix=..." + echo >&2 " [--libdir=...]" + echo >&2 " [--suffix=...]" + echo >&2 " [--enable-memory-db]" + echo >&2 " [--enable-xattr]" + echo >&2 " [--enable-xattrdb]" + echo >&2 " [--enable-profiling]" + echo >&2 " [--enable-force-async]" + echo >&2 " [--with-sqlite=...]" + echo >&2 " [--with-sqlite-lib=...]" + echo >&2 " [--enable-static-sqlite]" + echo >&2 " [--with-static-sqlite=...]" + echo >&2 " [--with-rpath=...|--without-rpath]" + echo >&2 " [--with-passwd-fallback=...|--without-passwd-fallback]" + echo >&2 " [--cflags='']" + echo >&2 " [--bits=32|64]" + exit 1 +} + +sqlite_ldarg=-lsqlite3 + +maybe_rpath= +use_maybe_rpath=true + +for arg +do + case $arg in + --disable-*) + arg="--enable-${arg%--disable-}=no" + ;; + esac + case $arg in + --) shift; break ;; + --prefix=*) + opt_prefix=${arg#--prefix=} + if [ -d "$opt_prefix" ]; then + maybe_prefix=$(cd "$opt_prefix"; pwd) + else + maybe_prefix=$opt_prefix + fi + if [ "$maybe_prefix" = "$(pwd)" ]; then + echo >&2 "ERROR: Prefix is current directory. That doesn't work." + exit 1 + fi + ;; + --libdir=*) + opt_libdir=${arg#--libdir=} + ;; + --with-static-sqlite=*) + opt_sqlite_ldarg=${arg#--with-static-sqlite=} + sqlite_ldarg=$opt_sqlite_ldarg + use_maybe_rpath=false + ;; + --without-passwd-fallback) + opt_passwd_fallback='NULL' + ;; + --with-passwd-fallback=*) + opt_passwd_fallback="${arg#--with-passwd-fallback=}" + # Trim a trailing /, remove quotes. So / => "". + opt_passwd_fallback="\"${opt_passwd_fallback%/}\"" + ;; + --enable-static-sqlite) + sqlite_ldarg='$(SQLITE)/$(SQLITE_LIB)/libsqlite3.a' + use_maybe_rpath=false + ;; + --enable-force-async=no | --disable-force-async) + opt_async=false + ;; + --enable-force-async=yes | --enable-force-async) + opt_async=true + ;; + --enable-profiling=no) + opt_profiling=false + ;; + --enable-profiling=yes | --enable-profiling) + opt_profiling=true + ;; + --enable-xattr=no) + opt_xattr=false + ;; + --enable-xattr=yes | --enable-xattr) + opt_xattr=true + ;; + --enable-xattrdb=no) + opt_xattrdb=false + ;; + --enable-xattrdb=yes | --enable-xattrdb) + opt_xattrdb=true + ;; + --enable-memory-db=no) + opt_memory=false + ;; + --enable-memory-db=yes | --enable-memory-db) + opt_memory=true + ;; + --with-sqlite=*) + opt_sqlite=${arg#--with-sqlite=} + # assign new value if unset + maybe_rpath='-Wl,-R$(SQLITE)/$(SQLITE_LIB)' + ;; + --with-sqlite-lib=*) + opt_sqlite_lib=${arg#--with-sqlite-lib=} + # assign new value if unset + maybe_rpath='-Wl,-R$(SQLITE)/$(SQLITE_LIB)' + ;; + --without-rpath) + opt_rpath='' + use_maybe_rpath=false + ;; + --with-rpath=*) + rpath=${arg#--with-rpath=} + opt_rpath=${rpath:+-Wl,-R$rpath} + use_maybe_rpath=false + ;; + --suffix=*) + opt_suffix=${arg#--suffix=} + ;; + --arch=*) + echo >&2 "WARNING: The --arch option is now deprecated. Use --cflags." + opt_arch=${arg#--arch=} + ;; + --cflags=*) + opt_cflags=${arg#--cflags=} + ;; + --bits=*) + opt_bits=${arg#--bits=} + ;; + *) + echo >&2 "warning: Unrecognized option '$arg'" + ;; + esac +done + +case $opt_arch in +'' | x86 | arm ) + ;; +*) echo >&2 "Untested arch $opt_arch." + ;; +esac + +if [ -z "$opt_bits" ]; then + printf >&2 "Bit width unspecified;" + case $(file -L /bin/sh 2>/dev/null) in + *64-bit*) opt_bits=64;; + *32-bit*) opt_bits=32;; + esac + if [ -n "$opt_bits" ]; then + echo >&2 " guessing bit width is $opt_bits, based on /bin/sh." + else + echo >&2 " can't tell, assuming 32." + opt_bits=32 + fi +fi + +case $opt_bits in +64) opt_mark64=64;; +32) opt_mark64=;; +*) echo >&2 "Unknown bit size $opt_bits (only 32 and 64 known)." + ;; +esac + +if [ "${opt_cflags-UNSET}" = "UNSET" ]; then + # Some targets want something like -m64. + eval arch_flags=\$compile_${opt_arch}_${opt_bits} + echo >&2 "WARNING: Guessing architecture CFLAGS '${arch_flags-<unset>}'." + echo >&2 "If you need specific flags, use --cflags." +else + arch_flags=$opt_cflags +fi + +if $use_maybe_rpath && [ -n "$maybe_rpath" ]; then + echo >&2 "Adding default RPATH for sqlite." + opt_rpath="${opt_rpath+${opt_rpath} }${maybe_rpath}" +fi + +if [ -z "$opt_prefix" ]; then + usage +fi + +if [ -z "$opt_libdir" ]; then + opt_libdir=$opt_prefix/lib$opt_mark64 +fi + +# We need to find the libdir relative to the prefix, this is required +# by the code in pseudo-utils.c that handles relocation. +opt_lib=${opt_libdir#$opt_prefix/} +if [ "$opt_lib" = "$opt_libdir" ]; then + echo >&2 "libdir must be relative to prefix." + exit 1 +fi + +if [ -z "$opt_sqlite_lib" ]; then + opt_sqlite_lib=$opt_lib +fi + +if [ ! -f "${opt_sqlite}/include/sqlite3.h" ]; then + echo >&2 "SQLite3 headers not found in at ${opt_sqlite}/include/sqlite3.h. Please check that SQLite3 and SQLite3 headers are installed." + exit 1 +fi + +read t1 t2 SQLITE3_VERSION << EOF + `grep "#define SQLITE_VERSION_NUMBER " ${opt_sqlite}/include/sqlite3.h` +EOF + +echo "SQLite header for version ${SQLITE3_VERSION} found in ${opt_sqlite}." + +if [ "${SQLITE3_VERSION}" -lt "03006000" ]; then + echo >&2 "Pseudo requires SQLite version 3, 3.6.x or later." + exit 1 +fi + +if [ -z "$opt_async" ]; then + opt_async=false +fi + +if $opt_async; then + FORCE_ASYNC="-DPSEUDO_FORCE_ASYNC" +else + FORCE_ASYNC="" +fi + +if getfattr --help >/dev/null 2>&1; then + xattr_runs=true +else + xattr_runs=false +fi + + +if [ -z "$opt_xattr" ]; then + if $opt_xattrdb; then + opt_xattr=true + echo "xattr DB support implies extended attribute support" + else + if $xattr_runs; then + opt_xattr=true + echo "getfattr runs, enabling extended attribute support" + else + opt_xattr=false + echo "getfattr fails, disabling extended attribute support" + fi + fi +fi + +if $opt_xattr || $opt_xattrdb; then + if ! $xattr_runs; then + echo >&2 "WARNING: getfattr doesn't work, but xattr-related features requestd." + fi +fi + +if [ -z "$opt_memory" ]; then + if [ "${SQLITE3_VERSION}" -lt "03007000" ]; then + echo "Disabling in-memory database by default (sqlite too old)." + opt_memory=false + else + echo "Enabling in-memory database by default." + opt_memory=true + fi +fi + +if $opt_memory; then + if [ "${SQLITE3_VERSION}" -lt "03007000" ]; then + cat >&2 <<EOF +WARNING: sqlite prior to 3.7 has been known to perform exceedingly poorly +with the in-memory database option. You asked for it, you get it, but if +you get horrible performance, try turning it off. +EOF + fi + SQLITE_MEMORY="-DUSE_MEMORY_DB" +else + SQLITE_MEMORY="" +fi + +# Suppress the -L if sqlite is in the default path. +if [ "$opt_sqlite" = "/usr" ]; then + default_sqlite="# " +fi + +touch port_deps.mk +touch func_deps.mk + +sed -e ' + s,@PREFIX@,'"$opt_prefix"',g + s,@XATTR@,'"$opt_xattr"',g + s,@XATTRDB@,'"$opt_xattrdb"',g + s,@PROFILING@,'"$opt_profiling"',g + s,@LIBDIR@,'"$opt_libdir"',g + s,@LIB@,'"$opt_lib"',g + s,@SUFFIX@,'"$opt_suffix"',g + s,@SQLITE@,'"$opt_sqlite"',g + s,@ARCH_FLAGS@,'"$arch_flags"',g + s,@DEFAULT_SQLITE@,'"$default_sqlite"',g + s,@SQLITE_LDARG@,'"$sqlite_ldarg"',g + s,@SQLITE_LIB@,'"$opt_sqlite_lib"',g + s,@SQLITE_MEMORY@,'"$SQLITE_MEMORY"',g + s,@FORCE_ASYNC@,'"$FORCE_ASYNC"',g + s,@PASSWD_FALLBACK@,'$opt_passwd_fallback',g + s!@RPATH@!'"$opt_rpath"'!g + s,@MARK64@,'"$opt_mark64"',g + s,@ARCH@,'"$opt_arch"',g + s,@BITS@,'"$opt_bits"',g +' < Makefile.in > Makefile diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..4886897 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,6 @@ +pseudo for Debian + +This package provides a wrapper around pseudo partially replicating the +behaviour of fakeroot (and, in fact, based on fakeroot's launcher script). + + -- Andrew Shadura <andrewsh@debian.org> Sat, 21 Nov 2015 18:44:57 +0100 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..03856e7 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +pseudo (1.7.4-1) unstable; urgency=low + + * Initial release (Closes: #796973). + + -- Andrew Shadura <andrewsh@debian.org> Sun, 22 Nov 2015 21:25:49 +0100 diff --git a/debian/clean b/debian/clean new file mode 100644 index 0000000..585d8cd --- /dev/null +++ b/debian/clean @@ -0,0 +1,2 @@ +a b +*.pyc diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..f06bfdc --- /dev/null +++ b/debian/control @@ -0,0 +1,23 @@ +Source: pseudo +Section: utils +Priority: optional +Maintainer: Andrew Shadura <andrewsh@debian.org> +Build-Depends: debhelper (>=9), attr, libattr1-dev, python, libsqlite3-dev +Standards-Version: 3.9.6 +Homepage: https://www.yoctoproject.org/tools-resources/projects/pseudo + +Package: pseudo +Architecture: any +Multi-Arch: foreign +Depends: ${misc:Depends}, ${shlibs:Depends} +Provides: fakeroot +Description: advanced tool for simulating superuser privileges + The pseudo utility offers a way to run commands in a virtualized "root" + environment, allowing ordinary users to run commands which give the + illusion of creating device nodes, changing file ownership, and otherwise doing + things necessary for creating distribution packages or filesystems. + . + Pseudo has a lot of similarities to fakeroot but is a new implementation + that improves on the problems seen using fakeroot. Pseudo is now + extensively used by Poky as a replacement to fakeroot but can also be + used standalone in many other use cases. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..ae1207b --- /dev/null +++ b/debian/copyright @@ -0,0 +1,47 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: pseudo +Source: <http://www.yoctoproject.org/tools-resources/projects/pseudo> + +Files: * +Copyright: 2008-2014 Wind River Systems, Inc. +License: LGPL-2.1+ + This program is free software; you can redistribute it and/or modify + it under the terms of the Lesser GNU General Public License version 2.1 as + published by the Free Software Foundation. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the Lesser GNU General Public License for more details. + . + On Debian systems, the GNU LGPL 2.1 can be found in + /usr/share/common-licenses/LGPL-2.1. + +Files: debian/fakeroot-pseudo +Copyright: + 1997—2001 Joost Witteveen + 2002—2009 Clint Adams <clint@debian.org> + 2015 Andrew Shadura <andrewsh@debian.org> +License: GPL-3+ + +Files: debian/fakeroot-pseudo.1 +Copyright: + J.H.M. Dassen <jdassen@debian.org> + Joost Witteveen + Clint Adams <clint@debian.org> + Andrew Shadura <andrewsh@debian.org> +License: GPL-3+ + +License: GPL-3+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + On Debian systems, the GNU GPL version 3 can be found in + /usr/share/common-licenses/GPL-3. diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..123ff25 --- /dev/null +++ b/debian/docs @@ -0,0 +1,7 @@ +doc/chroot +doc/database +doc/overview +doc/passwd +doc/pseudo_ipc +doc/utils +README diff --git a/debian/fakeroot-pseudo b/debian/fakeroot-pseudo new file mode 100755 index 0000000..b9fdeac --- /dev/null +++ b/debian/fakeroot-pseudo @@ -0,0 +1,129 @@ +#!/bin/sh + +usage () { +cat - >&2 <<EOF +fakeroot-like wrapper for pseudo, create a fake root environment. + usage: fakeroot [-l|--lib fakerootlib] [-f|--faked fakedbin] + [-i file] [-s file] [-u|--unknown-is-real] + [-b|--fd-base fd] [-h|--help] [-v|--version] + [--] [command] +EOF + exit 1 +} + +stderr () +{ + local i + for i + do + echo >&2 "fakeroot: $i" + done +} + +fatal () +{ + stderr "$@" + exit 1 +} + +# strip /bin/fakeroot to find install prefix +FAKEROOT_PREFIX=/usr +FAKEROOT_BINDIR=/usr/bin + +KEEPSTATEDIR=0 + +GETOPTEST=`getopt --version` +case $GETOPTEST in +getopt*) # GNU getopt + FAKE_TEMP=`getopt -l lib: -l faked: -l unknown-is-real -l fd-base: -l version -l help -- +l:f:i:s:ub:vh "$@"` + ;; +*) # POSIX getopt ? + FAKE_TEMP=`getopt l:f:i:s:ub:vh "$@"` + ;; +esac + +if [ "$?" -ne 0 ] +then + usage +fi + +eval set -- "$FAKE_TEMP" + +while test "X$1" != "X--"; do + case "$1" in + -l|--lib) + shift + ;; + -f|--faked) + shift + ;; + -i) + shift + if test -d "$1" + then + PSEUDO_LOCALSTATEDIR="$(readlink -m "$1")" + else + stderr "local state dir \`$1' doesn't exist." + fi + ;; + -s) + shift + if mkdir -p "$1" + then + PSEUDO_LOCALSTATEDIR="$(readlink -m "$1")" + KEEPSTATEDIR=1 + else + stderr "local state dir \`$1' could not be created." + fi + ;; + -u|--unknown-is-real) + ;; + -b|--fd-base) + shift + ;; + -v|--version) + printf "fakeroot wrapper for " + pseudo -V + exit 0 + ;; + -h|--help) + usage + ;; + esac + shift +done + +shift #get rid of the '--' + +if [ -z "$PSEUDO_LOCALSTATEDIR" ] + then + UID=`id -u` + if [ -d /run/user/$UID -a -w /run/user/$UID ] + then + PSEUDO_LOCALSTATEDIR=/run/user/$UID/pseudo/$$ + mkdir -p "$PSEUDO_LOCALSTATEDIR" + else + PSEUDO_LOCALSTATEDIR=/tmp/pseudo.$UID/$$ + if ! mkdir -p "$PSEUDO_LOCALSTATEDIR" + then + fatal "Could not find a suitable state dir" + fi + fi +fi + +export PSEUDO_PREFIX=/usr +export PSEUDO_LOCALSTATEDIR + +pseudo "$@" +RESULT=$? + +if [ "$KEEPSTATEDIR" -ne 1 ] +then + rm -rf "$PSEUDO_LOCALSTATEDIR" +fi + +exit $RESULT + +# Local Variables: +# mode: shell-script +# End: diff --git a/debian/fakeroot-pseudo.1 b/debian/fakeroot-pseudo.1 new file mode 100644 index 0000000..6289390 --- /dev/null +++ b/debian/fakeroot-pseudo.1 @@ -0,0 +1,163 @@ +.\" Process this file with +.\" groff -man -Tascii foo.1 +.\" +.\" "verbatim" environment (from strace.1) +.de CW +.sp +.nf +.ft CW +.. +.de CE +.ft +.fi +.sp +.. +.\" +.TH fakeroot 1 "22 November 2015" "Debian Project" "Debian manual" +.\" Manpage by J.H.M. Dassen <jdassen@debian.org>, +.\" Clint Adams <clint@debian.org> +.\" and Andrew Shadura <andrewsh@debian.org> +.SH NAME +fakeroot \- run a command in an environment faking root privileges for file +manipulation +.SH SYNOPSIS +.B fakeroot +.B [\-i|\-s +.IB local-state-dir ] +.B [\-h|\-\-help ] +.B [\-v|\-\-version ] +.BI [\-\-] +.BI [command] +.SH DESCRIPTION +.B fakeroot +runs a command in an environment wherein it appears to have root privileges +for file manipulation. This is useful for allowing users to create file system +images, archives and packages (tar, ar, .deb etc.) with files in them +with root permissions/ownership. Without +.B fakeroot +one would need to have root privileges to create the constituent files of +the archives with the correct permissions and ownership, and then pack them +up, or one would have to construct the archives directly, without using the +archiver. +This version of +.B fakeroot +uses pseudo(1) to replace the file manipulation library functions (chmod(2), +stat(2) etc.) by ones that simulate the effect the real library +functions would have had, had the user really been root. + +.SH OPTIONS +.TP +.BI \-l \ arg\fR,\ \fB\-\-lib\ \fIarg +Does nothing, accepted for compatibility only. +.TP +.BI \-\-faked \ arg +Does nothing, accepted for compatibility only. +.TP +.BI [\-\-] \ command +Any command you want to be ran as fakeroot. Use \(oq\-\-\(cq if in the command +you have other options that may confuse fakeroot's option parsing. +.TP +.BI \-s \ local-state-dir +Keep the +.I pseudo +state directory on exit. This directory holds the +.I pseudo +database files and log files. See the +.I pseudo +documentation on the details on how this directory can be reused. +.TP +.BI \-i \ local-state-dir +Load a +.I pseudo +environment previously saved using \-s from the specified directory. +Note that this does not implicitly save the direcotry, use \-s instead for +that behaviour. Using the same file for both \-i and \-s in a single +.BR fakeroot +invocation is safe. +.TP +.BR \-u , +.BR \-\-unknown\-is\-real +Does nothing, accepted for compatibility only. +.TP +.BI \-b \ fd +Does nothing, accepted for compatibility only. +.TP +.BI \-h +Display help. +.TP +.BI \-v +Display version. + +.SH EXAMPLES +Here is an example session with +.BR fakeroot . +Notice that inside the fake root environment file manipulation that +requires root privileges succeeds, but is not really happening. +.CW +$ whoami +joost +$ fakeroot /bin/bash +# whoami +root +# mknod hda3 b 3 1 +# ls \-ld hda3 +brw\-r\-\-r\-\- 1 root root 3, 1 Jul 2 22:58 hda3 +# chown joost:root hda3 +# ls \-ld hda3 +brw\-r\-\-r\-\- 1 joost root 3, 1 Jul 2 22:58 hda3 +# ls \-ld / +drwxr\-xr\-x 20 root root 1024 Jun 17 21:50 / +# chown joost:users / +# chmod a+w / +# ls \-ld / +drwxrwxrwx 20 joost users 1024 Jun 17 21:50 / +# exit +$ ls \-ld / +drwxr\-xr\-x 20 root root 1024 Jun 17 21:50 // +$ ls \-ld hda3 +\-rw\-r\-\-r\-\- 1 joost users 0 Jul 2 22:58 hda3 +.CE +Only the effects that user +.B joost +could do anyway happen for real. + +.B fakeroot +was specifically written to enable users to create Debian GNU/Linux +packages (in the +.B deb(5) +format) without giving them root privileges. +This can be done by commands like +.B dpkg-buildpackage \-rfakeroot +or +.B debuild \-rfakeroot +(actually, \-rfakeroot is default in debuild nowadays, so you don't +need that argument). +.SH SECURITY ASPECTS +.B fakeroot +is a regular, non-setuid program. It does not enhance a user's +privileges, or decrease the system's security. +.SH BUGS +See +.B pseudo(1) +for the details on the bugs of the underlying fakeroot implementation. +.SH COPYING +.B fakeroot +wrapper for +.B pseudo +is distributed under the GNU General Public License version 3.0 or later. +.B pseudo +itself is distributed under the GNU Lesser General Public License version +2.1 or later. +.SH AUTHORS +The original +.B fakeroot +manual page has mostly been written by J.H.M. Dassen +.RI <jdassen@debian.org> +with rather a lot modifications and additions by joost and Clint. +It was later modified by Andrew Shadura for this fakeroot wrapper for +.BR pseudo . +.SH "SEE ALSO" +.BR pseudo (1) +.BR fakeroot (1) +.BR dpkg\-buildpackage (1), +.BR debuild (1) diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..a44783d --- /dev/null +++ b/debian/install @@ -0,0 +1 @@ +debian/fakeroot-pseudo usr/bin diff --git a/debian/manpages b/debian/manpages new file mode 100644 index 0000000..5bb175d --- /dev/null +++ b/debian/manpages @@ -0,0 +1,3 @@ +debian/fakeroot-pseudo.1 +pseudo.1 +pseudolog.1 diff --git a/debian/patches/manpage.patch b/debian/patches/manpage.patch new file mode 100644 index 0000000..f184e65 --- /dev/null +++ b/debian/patches/manpage.patch @@ -0,0 +1,28 @@ +From: Andrew Shadura <andrewsh@debian.org> +Subject: Add NAME section. + +Each manual page should start with a "NAME" section, which lists the +name and a brief description of the page separated by "\-". + +--- a/pseudo.1 ++++ b/pseudo.1 +@@ -16,6 +16,8 @@ + .\" version 2.1 along with this program; if not, write to the Free Software + .\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + .TH pseudo 1 "pseudo - pretending to be root" ++.SH NAME ++pseudo \- run a command in a virtual root environment + .SH SYNOPSIS + .B pseudo + .RB [ \-dflv ] +--- a/pseudolog.1 ++++ b/pseudolog.1 +@@ -16,6 +16,8 @@ + .\" version 2.1 along with this program; if not, write to the Free Software + .\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + .TH pseudolog 1 "pseudo - pretending to be root" ++.SH NAME ++pseudolog \- pseudo log parser + .SH SYNOPSIS + .B pseudolog \-l + .RB [ \-Pv ] diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..973b94b --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +manpage.patch diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 0000000..52e1b26 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +case "$1" in + configure) + # continue below + update-alternatives --install /usr/bin/fakeroot fakeroot \ + /usr/bin/fakeroot-pseudo 60 \ + --slave /usr/share/man/man1/fakeroot.1.gz \ + fakeroot.1.gz /usr/share/man/man1/fakeroot-pseudo.1.gz + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + exit 0 + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 0 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/prerm b/debian/prerm new file mode 100755 index 0000000..43c0ec2 --- /dev/null +++ b/debian/prerm @@ -0,0 +1,23 @@ +#!/bin/sh + +set -e + +case "$1" in + remove|deconfigure) + update-alternatives --remove fakeroot /usr/bin/fakeroot-pseudo + ;; + upgrade) + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 0 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..61fe338 --- /dev/null +++ b/debian/rules @@ -0,0 +1,20 @@ +#!/usr/bin/make -f + +export CFLAGS := $(shell dpkg-buildflags --get CFLAGS) $(shell dpkg-buildflags --get CPPFLAGS) $(shell dpkg-buildflags --get LDFLAGS) + +%: + dh $@ + +override_dh_auto_configure: + ./configure --prefix=/usr --bits=$(shell dpkg-architecture -qDEB_HOST_ARCH_BITS) --libdir=/usr/lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH)/pseudo --without-rpath + +override_dh_auto_install: + dh_auto_install + rm -r debian/*/usr/var + +override_dh_auto_test: + unset LD_PRELOAD; \ + dh_auto_test + +override_dh_installchangelogs: + dh_installchangelogs ChangeLog.txt diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..9e7c0da --- /dev/null +++ b/debian/watch @@ -0,0 +1 @@ +version=3 diff --git a/doc/chroot b/doc/chroot new file mode 100644 index 0000000..a0d1758 --- /dev/null +++ b/doc/chroot @@ -0,0 +1,31 @@ +Pseudo incorporates functionality similar to that of fakechroot. + +The implementation is basically as follows: When incoming arguments +are known to be paths, they are automatically converted to absolute, +fully canonicalized, paths. When the chroot(2) call is used, pseudo +intercepts it and stashes the path to the new root directory internally +in the client. This path is then silently prepended to absolute +paths. The internal tracking of file paths uses absolute paths +including the path to the chroot directory. So, all underlying calls +are invoked with absolute, canonical, paths. + +A few calls are then modified. For instance, when readlink() returns +a link, it strips a leading prefix looking like the chroot directory; +when symlink() creates a link, if the link path is absolute, the chroot +directory is prepended to it. + +This behavior, which may seem surprising, is an attempt to produce +path resolution equivalent to what would normally happen when +canonicalizing a path using real chroot; if you create a link to /tmp +while in a chroot environment, following that link should yield +<chroot>/tmp. Arguably, now that we are intercepting nearly every +incoming path, we should just prepend the chroot path when following +absolute symlinks -- this may get revisited. + +Similarly, when glob() is called, the path to be globbed has the +chroot directory prepended, and then the results have any leading +chroot directory stripped from them. + +This implementation is far from complete or perfect; the intent is +that it provide enough functionality to allow RPM and similar tools +to run file system installs using chroot(). diff --git a/doc/database b/doc/database new file mode 100644 index 0000000..7003aff --- /dev/null +++ b/doc/database @@ -0,0 +1,120 @@ +There are two databases. The log database contains a record of operations +and events. (Operation logging is optional.) The file database contains a +record of known files. In general, the file database is configured with +sqlite options favoring stability, while the log database is configured for +speed, as operation logging tends to outnumber file operations by a large +margin. + +FILES: + id (unique key) + path (varchar, if known) + dev (integer) + ino (integer) + uid (integer) + gid (integer) + mode (integer) + rdev (integer) + deleting (integer) + +There are two indexes on the file database, one by path and one by device +and inode. Earlier versions of pseudo ignored symlinks, but this turned +out to create problems; specifically, if you had a symlink to a directory, +and accessed a file through that, it could create unexpected results. Names +are fully canonicalized by the client, except for functions which would +operate directly on a symlink, in which case the last path component is not +replaced. + +It is not an error to have multiple entries with the same device and inode. +Updates to uid, gid, mode, or rdev are applied to every file with the same +device and inode. Operations by name are handled by looking up the name +to obtain the device and inode, then modifying all matching records. + +If a file shows up with no name (this should VERY rarely happen), it is stored +in the database with the special name 'NAMELESS FILE'. This name can never +be sent by the client (all names are sent as absolute paths). If a laterThe +request comes in with a valid name, the 'NAMELESS FILE' is renamed to it so +it can be unlinked later. + +The "deleting" field is used to track files which are in the midst of +being possibly-deleted. The issue is that if you issue an unlink database +operation only after removing a file, there is a window during which +another program can genuinely create a new file with the same name or +inode, and that request can reach the daemon before the unlink operation +does. To address this, three operations are addded: may-unlink, did-unlink, +and cancel-unlink. The may-unlink operation tags a file for possible +deletion, setting "deleting" to 1. A clash involving a file marked for +deletion is resolved by deleting the existing database entry without +complaint. A did-unlink operation deletes a file ONLY if it is marked +for deletion. A cancel-unlink operation unmarks a file for deletion. + +You can still have a race condition here, but it seems a lot less likely. +The failure would be: + Process A marks file X for deletion. + Process A unlinks file X. + Process B creates file X. + Process B requests a link for X. + The server unlinks the previously marked X. + The server creates a new link for X, not marked for deletion. + Process B marks file X for deletion. + Process A's delete-marked-file request finally shows up. + The server unlinks the newly-marked X. + Process B fails to delete the file. + Process B attempts to cancel the mark-for-deletion. + +This shouldn't be a common occurrence. + +Rename operations use a pair of paths, separated by a null byte; the client +sends the total length of both names (plus the null byte), and the server +knows to split them around the null byte. The impact of a rename on things +contained within a directory is handled in SQL: + UPDATE files SET path = replace(path, oldpath, newpath) WHERE + path = oldpath; + UPDATE files SET path = replace(path, oldpath, newpath) WHERE + (path > oldpath || '/') && (path < oldpath || '0); +That is to say, anything which either starts with "oldpath/" or is exactly +equal to oldpath gets renamed, with oldpath replaced by newpath... The +unusual constructions are to address two key issues. One is that an "OR" +would prevent proper use of the index. The other is that a pattern, +such as "LIKE oldpath || '/%'", would prevent use of the index (at least +in sqlite). The gimmick is that the only things greater than 'a/' and less +than 'a0' are strings which begin with 'a/' and have additional characters +after it. + +LOGS + id (unique key) + stamp (integer, seconds since epoch) + operation (id from operations, can be null) + client (integer identifier) + dev (integer) + ino (integer) + mode (integer) + path (varchar) + result (result id) + severity (severity id) + text (anything else you wanted to say) + tag (identifier for operations) + access (integer) + +The log database contains a primary table (logs). As of this writing it +is not indexed, because indexing is expensive during writes (common, for +the log database) and very few queries are usually run. + +The log database also contains, when created, tables of operations, result +types, and severities. These exist so that queries can be run against +a log database even if these values might have changed in a newer build +of pseudo. The tables of operations and severities are just id->name pairs. +No enforcement of the relation is currently provided. + +The log database "tag" field, added since the initial release of pseudo, +is available for tagging operations. When a client connects to the +pseudo server, it passes the value of the environment variable PSEUDO_TAG; +this tag is then recorded for all log entries pertaining to that client. + +The log database "access" field, added since the initial release of pseudo, +stores values which are bitwise masks of the values 1 (execute), 2 (write), +4 (read), and 8 (append), used to report the modes with which a file was +opened. These values are not completely reliable. A value of 0 is typical +for non-open operations, and a value outside the 0-15 range (usually -1) +indicates that something went wrong trying to identify the mode of a given +open. + diff --git a/doc/overview b/doc/overview new file mode 100644 index 0000000..37d223a --- /dev/null +++ b/doc/overview @@ -0,0 +1,94 @@ +Overview: + +The pseudo program and library combine to provide an environment which +provides the illusion of root permissions with respect to file creation, +ownership, and related functions. + +The underlying mechanism of pseudo is a library inserted using LD_PRELOAD, +which provides replacement symbols for core C library functions. At this +time, the implementation is specific to modern glibc. Support for other +systems is certainly possible, but not currently implemented or immediately +planned. The symbols wrapped are generally those that are documented in +section 2 of the manual -- the ones which are essentially system calls. + +The library works by replacing each real function with a wrapper function +which obtains the addresses of "real" functions (those in the next library +down in the chain, typically glibc) and then calls custom-written wrappers +which alter the behavior of these functions and return results corresponding +to the virtual environment. + +Underlying this is access to a server process, which is automatically +spawned by the library if one is not available. The server process maintains +a UNIX domain socket while it is active, and maintains a database (using +sqlite) of files known to the system. Files are recorded in the database +only if they are created within the virtualized environment or have been +altered by it; files merely read are not added. + +There are four layers of logic for performing or wrapping any function, +although not all functions involve all four layers: + +1. The generic wrapper, which handles details such as thread-synchronization. +This function handles the mutex used to keep multiple threads from trying to +write to the same socket at once, and also disables wrappers when a value +called "antimagic" is set. The antimagic value is set internally by the +pseudo client code, and the check for whether or not to use it is controlled +by the mutex (actually by the mutex owner variable, which is protected by +the mutex.) Without that, read operations in another thread during the +"antimagic" part of an operation would bypass pseudo, yielding erratically +wrong results! Wrappers are where pathnames get canonicalized. +2. The wrapper function itself. This function may translate a single +operation into two or more logical operations. This function has no awareness +of the database, but can send queries to the general client code. +3. The general client code. This code maintains additional data, such as +a mapping of file descriptors to paths. In most cases, this code also +forwards requests to the server code. (If the server is unavailable, the +client can restart it.) +4. The server code. This code is fairly simple; all it does is maintain +the database of file information. Operations consist either of a request +for information (e.g., a stat(2) call) or notification of a change. The +server sends back failure or success notices. + +As a fairly typical example, the progress of a stat(2) call is: + +* The __xstat() wrapper is called. This wrapper checks the version argument + against the _STAT_VER constant in case we some day run into a system where + programs call stat with different versions of struct stat. (Hasn't happened + yet.) +* The __xstat() wrapper calls the __fxstatat() wrapper, which in turn calls + the __fxstatat64() wrapper (this allows us to have only one copy of the + logic shared among all the path-based stat syscalls). +* The __fxstatat64() wrapper calls the underlying __fxstatat64() function, + which has been mapped to the name real___fxstatat64(). (If this fails, + the wrapper function returns immediately.) +* The __fxstatat64() wrapper passes the resulting stat buffer and path to the + client code and asks for a response. +* The client code converts the stat buffer into a pseudo_msg_t message + object, and canonicalizes the path (resolving symlinks and eliminating + extra slashes, as well as references to . and ..). +* The client code now sends the pseudo_msg_t object and converted path to + the server as a message. +* The server receives the message. Since this is a stat() operation (using + a path, not a dev/inode pair, for identification), the server searches its + database for existing entries with the corresponding name. +* If the server finds an object, it updates the contents of the pseudo_msg_t + with the recorded values for uid, gid, mode, and raw device number, and + sends the message back with status SUCCEED. +* The server also performs sanity checks to see whether there may be other + suspiciously-similar entries in the database, in which case it emits + diagnostics. (Usually to pseudo.log.) +* If the server finds no object, it sends the message back with status FAIL. +* The client code returns the message to the wrapper function. +* If the status was SUCCEED, the wrapper function copies the modified + fields back into its stat buffer; otherwise, it does not. +* The wrapper function returns the original exit status from stat. + +Most of the functions wrapped are syscalls. There are a few exceptions, such +as mkstemp, fopen, and freopen. These are wrapped because, in glibc, they +call internal functions which make inline assembly syscalls, rather than +calling the syscall entry points. In each case, the wrapper makes the real +call without intervention, then snoops the results for a file descriptor to +path mapping. (This would be done to opendir/fdopendir/closedir as well, +but the DIR * is opaque and can't be snooped practically. This is why +some versions of 'rm -r' can, at higher diagnostic levels, generate a slew +of warnings about file descriptors being reopened when no close was +observed.) diff --git a/doc/passwd b/doc/passwd new file mode 100644 index 0000000..42d41c6 --- /dev/null +++ b/doc/passwd @@ -0,0 +1,27 @@ +Pseudo provides limited support for faking up password file access. + +Routines such as setpwent(), getpwent(), and so on, are modified to +possibly pick a password file other than /etc/passwd, and likewise +for setgrent() and /etc/group. The logic is as follows: + +* If a chroot directory is set, <chroot>/etc is tried first. +* If PSEUDO_PASSWD is set, PSEUDO_PASSWD/etc is tried next. +* Otherwise, fall back on /etc. + +In each case, failure to find a passwd or group file results in +going on to trying the next case. + +The behavior of lckpwdf()/ulckpwdf() is a special case. In this +case, the same order of directories is tried, but pseudo attempts +to create the files, rather than attempting to open existing files. + +The underlying implementation directs nearly everything to +fgetpwent_r() and fgetgrent_r(), which are extensions available in +glibc. This allows pseudo to avoid having to actually implement yet +another horrible passwd file parser which would inevitably have +bugs that have already been fixed dozens of times in other +implementations. + +Note that both the chroot directory and PSEUDO_PASSWD are assumed +to be the parent directory of etc, not the directory containing the +passwd and group files. diff --git a/doc/perftest b/doc/perftest new file mode 100644 index 0000000..b9169ae --- /dev/null +++ b/doc/perftest @@ -0,0 +1,8 @@ +There's a new "perftest" program. I recommend you run it with a ramdisk. So, +if you've mounted a ramdisk on /tmp/ram: + ./perftest /tmp/ram + +This is a pretty unscientific test, but is designed to exercise some +reasonably common use cases and give ballpark figures as to the effect +of changes to the database indexes, etc. + diff --git a/doc/ports b/doc/ports new file mode 100644 index 0000000..4debb64 --- /dev/null +++ b/doc/ports @@ -0,0 +1,23 @@ +The "ports" system provides functionality for porting pseudo to new targets. +The original motivation was a requirement to support older and newer Linux +systems on which the signature for clone() differed. + +The Darwin port is totally nonfunctional at this point, but it compiles, +and sufficiently careful hand-tuning of DYLD_INSERT_LIBRARIES and a +manually-started server can actually pass a simple test. It will get +worked on in my copious free time. + +The basic design of a port is that it provides a wrapfuncs.in list of +function signatures, guts implementations, and optionally some port-specific +defines or a block of wrapper code for pseudo_wrappers.c. This is used +for cases where the default wrapper would not be appropriate, and may +be combined with the new hand_wrapped=1 flag in wrapfuncs.in. + +A port may specify preports or subports. Preports are ports that are +included and processed *before* the current port -- meaning that functions +in the current port can override them. Subports are processed *after* +the current port -- meaning that they can override functions in the +current port. The preports and subports are specified by scripts, +which echo a list of port names to standard output. (Be sure any +diagnostic messages go to standard error.) + diff --git a/doc/program_flow b/doc/program_flow new file mode 100644 index 0000000..3a399a7 --- /dev/null +++ b/doc/program_flow @@ -0,0 +1,84 @@ +This is a quick attempt at documenting the basic program flow for both the main +pseudo executable and the pseudo wrapper library. The key thing to note is +that there are key init functions. These functions are designed to be +re-invoked if it becomes necessary to reset the system environment. + +libpseudo execution flow: + # on startup # + + pseudo_wrappers.c: (constructor) _libpseudo_init() + pseudo_util.c: pseudo_init_util() + copy environment + setup PSEUDO_DEBUG levels + pseudo_wrappers.c: pseudo_init_wrappers() + setup pseudo_functions + setup pseudo_logfile + pseudo_client.c: pseudo_init_client() + setup PSEUDO_DISABLED + setup pseudo_prefix_dir_fd + setup pseudo_localstate_dir_fd + setup PSEUDO_NOSYMLINKEXP + setup PSEUDO_UIDS + setup PSEUDO_GIDS + setup PSEUDO_CHROOT + setup PSEUDO_PASSWD + + # regular program execution # + exec*() + pseudo_check_wrappers(): + pseudo_reinit_libpseudo if necessary + call wrap_exec*() + pseudo_setupenv() + if PSEUDO_UNLOAD + pseudo_dropenv() + real_exec*() + + fork() + pseudo_check_wrappers(): + pseudo_reinit_libpseudo if necessary + call wrap_fork() + real_fork() + if (child) + pseudo_setupenv() + if !PSEUDO_UNLOAD + pseudo_reinit_libpseudo() + _libpseudo_init() + else + pseudo_dropenv() + + clone() + pseudo_check_wrappers(): + pseudo_reinit_libpseudo if necessary + call wrap_clone() + pseudo_setupenv() + if !PSEUDO_UNLOAD + pseudo_reinit_libpseudo() + _libpseudo_init() + else + pseudo_dropenv() + real_clone() + + ... normal function wrappers ... (templates/wrapfuncs.c): + pseudo_check_wrappers() || !real_* + return enosys + variadic setup (if necessary) + if pseudo_disabled return real_*() + pseudo_sigblock + pseudo_getlock + if antimagic rc = real_*() + else rc = wrap_*() + variadic end (if necessary) + pseudo_droplock + unmask signals + return rc + +pseudo execution flow: + pseudo.c: main() + pseudo_util.c: pseudo_init_util() + <see above> + check LD_PRELOAD + process arguments + setup PSEUDO_OPTS + ... + startup server + diff --git a/doc/pseudo_ipc b/doc/pseudo_ipc new file mode 100644 index 0000000..0892bb7 --- /dev/null +++ b/doc/pseudo_ipc @@ -0,0 +1,78 @@ +MESSAGE PASSING + +typedef struct { + pseudo_msg_type_t type; + op_id_t op; + res_id_t result; + int access; + int client; + dev_t dev; + unsigned long long ino; + uid_t uid; + gid_t gid; + unsigned long long mode; + dev_t rdev; + unsigned int pathlen; + int nlink; + char path[]; +} pseudo_msg_t; + +This structure is used for every communication between the client and the +server. The last field is optional (it's a C99ism called a flexible array +member, allowing a single allocation to hold both the structure and the +variable-length character data at the end). + +All messages contain items up through 'pathlen'. If pathlen is not zero, +an additional pathlen bytes containing path are provided; path is +null-terminated. + +Every message from client should get a response from server. The server +never really sends a path, currently, but maybe it will someday. Note that +all server responses will in general share a single message object, +and future operations may cause that object to be reallocated; the same +goes for messages received by the server. Basically, pseudo_msg_receive +is not thread-safe; this is part of (but not all of) the reason that there's +mutex stuff in the wrappers. (The other part is the "antimagic" being +able to blow things up.) + +type is one of PING, OP, FASTOP, SHUTDOWN, ACK, or NAK. The client +only sends PING, OP, or FASTOP. FASTOP takes no response, otherwise the +server should always send ACK. When run with '-S', the pseudo program +runs as a client, sending a SHUTDOWN message to a server -- but only if +it can find one, it does not start a new one. In this case, the server +could respond with a NAK, in which case it sends a message in which +"path" is a list of space-separated PIDs of currently-living clients, +for the program to print out in an error message. The server will +not shut down while there are living clients. (The request, though, +causes it to shut down immediately when there are no more clients, +rather than waiting for the timeout period.) + +result is the result of a particular operation. It applies only in replies +to OP messages. + +client should be the client's PID on send, and the server's client number for +that client on response. (The response isn't checked, and this is just a +debugging feature.) + +dev/ino/uid/gid/mode/rdev/path are information about the file. They should +all be provided on send if possible, but the server only generally changes +uid/gid/mode/rdev on response, and never sends a path back. Dev and inode +are currently changed by stat-by-path operations, but this may turn out to +be wrong. + +access holds information about the open mode of a file (read, write, append, +etc.), but is not fully implemented. + +A field "xerrno" used to exist; it was never actually implemented. + +nlink is used to forward the number of links. The server DOES NOT modify +this. Rather, nlink is used to provide better diagnostics when checking +paths against inodes. + +32/64 bit: This structure should have the same offsets for every element, +including path, on both 64-bit and 32-bit machines. (Check with 'offsets.c'.) +It is *not* an error if sizeof(pseudo_msg_t) is different; the padding +happens after the path element. (Note: This is contrary to C99, TC1, but is +correct according to the current standard. Anyway, gcc's always done it this +way.) The data written are always pathlen + offsetof(pseudo_msg_t, path), +and that's correct. diff --git a/doc/utils b/doc/utils new file mode 100644 index 0000000..35520d4 --- /dev/null +++ b/doc/utils @@ -0,0 +1,33 @@ +pseudolog + Displays or creates log entries. This offers a quick first + approximation of the sorts of queries one is likely to need to + run. + +pseudo + The pseudo server. Run on the command line, the default behavior + is to set up a pseudo environment (LD_PRELOAD, etc) then run either + the specified command or a shell by default. The -d option specifies + a background daemon, and -f specifies a foreground daemon (which + may display output directly). The launcher function isn't really + 32-bit/64-bit aware, but if you have both types of libraries in + suitably-named directories, it'll do the right thing anyway. + + Path may be in environment as PSEUDO_PREFIX, specified on command + line with -P path, or inferred from the path to $0. (The last + generates a diagnostic.) + + To stop the pseudo server, either wait a while (the default timeout + is 30 seconds, or whatever you specified with "-t" or PSEUDO_OPTS + when starting it) or run "pseudo -S". The server will not exit + while clients are active, but requesting a shutdown sets the timeout + to one second, so it will exit quickly after the last client + disconnects. + +libpseudo.so + The library providing the wrapper functionality, which spawns + the pseudo server automatically if needed. If the environment + variable PSEUDO_ENOSYS_ABORT is set, attempts to call missing + system calls will abort() rather than merely emitting a diagnostic. + +pseudodb + allows browsing and modification of db (not implemented) diff --git a/enums/debug_type.in b/enums/debug_type.in new file mode 100644 index 0000000..950333a --- /dev/null +++ b/enums/debug_type.in @@ -0,0 +1,30 @@ +debug_type: PDBG; INDEXED unsigned char symbolic = '\0', const char * description = NULL, FLAGS +# Note: For convenience/consistency with the old numerc debug levels +# stuff, these are sorted in a very rough approximation of "likelihood +# that the user will care." So, PSEUDO_DEBUG=1 will pick up the +# consistency checks. In general, most numeric debug levels will be +# significantly less chatty than they used to be; there was one +# level 5 message, I think, and nothing else above a 4. Which was a +# problem. +# Note: Descriptions should be under 32 characters to match the formatting +# in pseudo's help message. +consistency, 'n', "consistency checks" +file, 'f', "file creation/deletion" +op, 'o', "operations" +pid, 'P', "show process IDs" +client, 'c', "client side startup/shutdown" +server, 'v', "server side startup/shutdown" +db, 'd', "database interactions" +xattrdb, 'D', "xattr database" +profile, 'R', "profiling" +syscall, 'y', "system calls" +env, 'e', "environment manipulation" +chroot, 'r', "chroot functionality" +path, 'p', "path computations" +sql, 's', "SQL query information" +wrapper, 'w', "wrapper functionality" +ipc, 'i', "client/server interactions" +invoke, 'k', "invocation and launching" +benchmark, 'b', "performance statistics" +verbose, 'V', "extra detail" +xattr, 'x', "extended attributes" diff --git a/enums/msg_type.in b/enums/msg_type.in new file mode 100644 index 0000000..578d571 --- /dev/null +++ b/enums/msg_type.in @@ -0,0 +1,7 @@ +msg_type: PSEUDO_MSG +ping +shutdown +op +ack +nak +fastop diff --git a/enums/op.in b/enums/op.in new file mode 100644 index 0000000..61ee666 --- /dev/null +++ b/enums/op.in @@ -0,0 +1,29 @@ +op: OP; int wait = 0 +chdir, 0 +chmod, 0 +chown, 0 +chroot, 0 +close, 0 +creat, 0 +dup, 0 +fchmod, 0 +fchown, 0 +fstat, 1 +link, 0 +mkdir, 0 +mknod, 1 +open, 0 +rename, 0 +stat, 1 +unlink, 0 +symlink, 0 +exec, 0 +may-unlink, 1 +did-unlink, 0 +cancel-unlink, 0 +get-xattr, 1 +list-xattr, 1 +remove-xattr, 1 +set-xattr, 0 +create-xattr, 1 +replace-xattr, 1 diff --git a/enums/query_field.in b/enums/query_field.in new file mode 100644 index 0000000..5124c5d --- /dev/null +++ b/enums/query_field.in @@ -0,0 +1,28 @@ +query_field: PSQF +# Note: These are later used as bitwise masks into a value, +# currently an unsigned long; if the number of these gets up +# near 32, that may take rethinking. The first thing to +# go would probably be something special to do for FTYPE and +# PERM because they aren't "real" database fields -- both +# of them actually imply MODE. +access +client +dev +fd +ftype +gid +id +inode +mode +op +order +path +perm +program +result +severity +stamp +tag +text +type +uid diff --git a/enums/query_type.in b/enums/query_type.in new file mode 100644 index 0000000..974e24f --- /dev/null +++ b/enums/query_type.in @@ -0,0 +1,9 @@ +query_type: PSQT; const char * sql = "LITTLE BOBBY TABLES" +exact, "=" +less, "<" +greater, ">" +bitand, "&" +notequal, "!=" +like, "LIKE" +notlike, "NOT LIKE" +sqlpat, "LIKE" diff --git a/enums/res.in b/enums/res.in new file mode 100644 index 0000000..435338f --- /dev/null +++ b/enums/res.in @@ -0,0 +1,4 @@ +res: RESULT +succeed +fail +error diff --git a/enums/sev.in b/enums/sev.in new file mode 100644 index 0000000..24c2c6e --- /dev/null +++ b/enums/sev.in @@ -0,0 +1,6 @@ +sev: SEVERITY +debug +info +warn +error +critical diff --git a/guts/COPYRIGHT b/guts/COPYRIGHT new file mode 100644 index 0000000..06188b2 --- /dev/null +++ b/guts/COPYRIGHT @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2008-2014 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ diff --git a/guts/README b/guts/README new file mode 100644 index 0000000..0a1fe5f --- /dev/null +++ b/guts/README @@ -0,0 +1,220 @@ +The files in this directory are partially machine-generated, and are +all covered by the COPYRIGHT file in this directory. + +The set of functions covered here may seem surprising. For instance, +obviously, fopen(3) simply calls the underlying open(2) syscall. But... +There is a problem. In a few places in glibc, the syscalls are inlined +such that there is no actual call to the C function open(2), just a raw +call. So there are a couple of functions (fopen, freopen) which are +wrapped with intent only to detect the possible creation of files. + +Many of these functions are closely related. Some programs may have +calls to openat(), while others have calls to __openat_2(). To reduce +code duplication, a number of functions are implemented purely as calls +to other functions. + +When a *at() function exists, the regular function is implemented +as *at() with AT_FDCWD as the directory fd (see the dummy #define of +this in pseudo_client.h, used for systems which lack these.) On systems +where AT_NOFOLLOW_SYMLINKS is not defined, the underlying *at() functions +don't exist, so we provide a bare implementation which works only when +the fd is AT_FDCWD... + +The creat64 and open64 families are equivalent to the plain versions with +O_LARGEFILE in mode bits. (Again, there's a suitable dummy #define +in pseudo_client.h.) By contrast, the stat64 functions actually do have +some difference -- the structure they manipulate is not the same. + +The following table shows which functions are merely wrappers around +other functions: + + canonicalize_file_name: realpath + chmod: fchmodat + chown: fchownat + creat64: openat + creat: openat + __fxstatat: __fxstatat64 + __fxstat: __fxstat64 + get_current_dir_name: getcwd + getwd: getcwd + __lxstat64: __fxstatat64 + __lxstat: __fxstatat + mkdir: mkdirat + mkfifoat: __xmknodat + mkfifo: mkfifoat + open64: openat + __openat_2: openat + __openat64_2: openat + openat64: openat + open: openat + remove: unlink or rmdir + rename: renameat + symlink: symlinkat + unlink: unlinkat + __xmknod: __xmknodat + __xstat64: __fxstatat64 + __xstat: __fxstatat + +The following functions are full implementations: + + chdir + fchdir + fchmod + fchmodat + fchown + fchownat + __fxstat64 + __fxstatat64 + getcwd + lchown + mkdirat + openat + rmdir + symlinkat + unlinkat + __xmknodat + +The following functions provide only partial implementations, to trap special +cases, to track internal data structures (for instance, close() is tracked so +that the path to a file descriptor can be dropped when the file descriptor +is closed), or to handle functions which may not use the underlying syscall +wrappers: + + close + dup + dup2 + execl* (no guts implementations; see pseudo_wrappers.c) + execv + execve + execvp + fclose + fopen + fopen64 + freopen + freopen64 + mkstemp + mkstemp64 + fcntl + fork + link + +The following functions don't have any direct database interactions, +but are used to simulate the permissions system: + + getegid + getuid + setgid + setreuid + geteuid + setegid + setgroups + setuid + getgid + seteuid + setregid + getresgid + setfsgid + setresgid + getresuid + setfsuid + setresuid + +The following functions are present only to allow filename mangling +for chroot(2) implementation. Most of them have no logic beyond +calling the underlying routine. + + access + acct + chroot + eaccess + euidaccess + fts_open + ftw64 + ftw + glob64 + glob + lutimes + mkdtemp + mktemp + nftw64 + nftw + opendir + pathconf + readlinkat + readlink + realpath + scandir64 + scandir + truncate64 + truncate + utime + utimes + +The following functions are unimplemented. renameat could be done now (it +would have been hard previously due to file name mangling issues), but +since it's never come up, we haven't done it. The tempnam() functions are +fairly hard to get right, and perhaps more imporantly, extremely +dangerous. Since there's no evidence that they're in use anymore, I've +dummied them out: + + renameat + tempnam + tmpnam + +The following functions are partially emulated in order to provide for +emulation of various getpw*() and getgr*() functions. No handling is +provided for putpw*() or putgr*(). Nearly everything is ultimately +implemented in terms of fgetpwent_r() and fgetgrent_r(), which are +GNU extensions corresponding to fgetpwent() and fgetgrent(), allowing +pseudo to read password information from an arbitrary stream; the +setpwent() and setgrent() functions are modified to pick /etc/* from +the pseudo_chroot path, if one is set, or from PSEUDO_PASSWD, if that +is set, or else the system /etc/* files. + + endgrent + endpwent + getgrent + getgrent_r + getgrgid + getgrgid_r + getgrnam + getgrnam_r + getgrouplist + getgroups + getpw + getpwent + getpwent_r + getpwnam + getpwnam_r + getpwuid + getpwuid_r + lckpwdf + setgrent + setgroups + setpwent + ulckpwdf + +The implementation of getgroups() is inauthentic in that it always checks +the group file when called, rather than checking the group file once +"at login" (whatever that means in our context) and returning that saved +status. We don't think this matters. setgroups() just fails; this will +be corrected if we come up with a compelling reason to do so. + +The following functions set errnot to ENOTSUP and return -1; this is +needed because we don't actually track or manage extended attributes, but +a few programs attempt to use *setxattr() to set regular permissions, +and only use a regular chmod if the *setxattr() call returns -1 and +sets errno to ENOTSUP. + + fgetxattr + flistxattr + fremovexattr + fsetxattr + getxattr + lgetxattr + listxattr + llistxattr + lremovexattr + lsetxattr + removexattr + setxattr diff --git a/makedata.c b/makedata.c new file mode 100644 index 0000000..d3f5ed9 --- /dev/null +++ b/makedata.c @@ -0,0 +1,32 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> + +int +main(void) { + int i, j, k, l; + /* templateish form used so it's obvious that it's long enough */ + char name[] = "dir_%d/dir_%d/%d%d.txt"; + for (i = 0; i < 10; ++i) { + snprintf(name, sizeof(name), "dir_%d", i); + mkdir(name, 0755); + for (j = 0; j < 40; ++j) { + snprintf(name, sizeof(name), "dir_%d/dir_%d", i, j); + mkdir(name, 0755); + for (k = 0; k < 10; ++k) { + for (l = 0; l < 10; ++l) { + FILE *fp; + snprintf(name, sizeof(name), + "dir_%d/dir_%d/%d%d.txt", + i, j, k, l); + fp = fopen(name, "w"); + if (fp) { + fprintf(fp, "dummy file.\n"); + fclose(fp); + } + } + } + } + } + return 0; +} diff --git a/maketables b/maketables new file mode 100755 index 0000000..b32312e --- /dev/null +++ b/maketables @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2010, 2013 Wind River Systems, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License version 2.1 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# version 2.1 along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +"""convert tables.in files to enums, tables, and support code. + +Inputs are a type name, prefix, and a list of columns, followed by a list of +names with optional "= value" suffixes, plus optional additional columns. +The names are used to create enums and a table of strings, as well as +to/from lookups between the ids and names. If additional columns are +defined, each column (separated by ", ") is used to create an additional +table of the given name, and a lookup function from ids. Example: + foo: FFF; const char *bar = "GOZINTA" + hello, "yah" + world, "nope" +produces: + typedef enum { + FFF_UNKNOWN = -1, + FFF_MIN = 0, + FFF_NONE = 0, + FFF_HELLO, + FFF_WORLD, + FFF_MAX + } foo_id_t; + extern const char *foo_name(foo_id_t id); + extern foo_id_t foo_id(const char *name); + extern const char *foo_bar(foo_id_t id); + +such that foo_name(1) => "hello" and foo_bar(1) => "yah". If there +is an assigned value for a column description, missing column values +yield that value, otherwise they yield "unknown". + +Values out of range yield "unknown", and unrecognized names yield the +value -1. Note that the "MAX" value is one more than the highest defined +value. (This is for consistency with C array bounds.) +""" + +import glob +import sys +import string +from templatefile import TemplateFile + +class DataType: + """a set of related DataItem objects""" + + def __init__(self, path): + """read the first line of path, then make tuples of the rest""" + source = file(path) + definition = source.readline().rstrip() + self.name, qualifiers = string.split(definition, ': ', 2) + if '; ' in qualifiers: + self.prefix, columns = string.split(qualifiers, '; ') + else: + self.prefix = qualifiers + columns = [] + self.flags = False + if len(columns): + self.columns = [] + columns = string.split(columns, ', ') + for col in columns: + indexed = False + if col.startswith("FLAGS"): + print "Flags: set for %s" % self.name + self.flags = True + continue + if col.startswith("INDEXED "): + col = col[8:] + indexed = True + if "=" in col: + name, default = string.split(col, ' = ') + else: + name, default = col, "" + if " " in name: + words = string.split(name, ' ') + name = words[-1] + del words[-1] + type = ' '.join(words) + else: + type = "char *" + self.columns.append({"indexed":indexed, "type":type, "name":name, "value":default}) + else: + self.columns = [] + self.data = [] + self.comments = [] + index = 1 + for line in source.readlines(): + item = {} + if line.startswith('#'): + self.comments.append(line.rstrip().replace('#', '')) + continue + # first entry on the line is the "real" name/id, following hunks + # are additional columns + cols = string.split(line.rstrip(), ', ') + item["name"] = cols.pop(0) + item["upper"] = item["name"].replace('-', '_').upper() + column_list = [] + for col in self.columns: + if len(cols) > 0: + value = cols.pop(0) + if col["indexed"]: + if not "max" in col: + col["max"] = value + if value > col["max"]: + col["max"] = value + if not "min" in col: + col["min"] = value + if value < col["min"]: + col["min"] = value + column_list.append({"name":col["name"], "value":value}) + else: + column_list.append({"name":col["name"], "value":col["value"]}) + item["cols"] = column_list + item["index"] = index + index = index + 1 + self.data.append(item) + + def __getitem__(self, key): + """Make this object look like a dict for Templates to use""" + attr = getattr(self, key) + + if callable(attr): + return attr() + else: + return attr + + def __repr__(self): + column = 0 + out = "" + out += "type: %s_t" % self.name + out += " (prefix '%s_ENUM')\n" % self.prefix + for col in self.columns: + out += " extra column: %s %s (default %s)\n" % (col["type"], col["name"], col["value"]) + out += " " + for item in self.data: + column = column + 1 + if column > 4 and column % 4 == 1: + out += "\n " + out += "%-19s" % item["name"] +# for col in item["cols"]: +# out += "\t%s(%s)\n" % (col["name"], col["value"]) + return out + + def comment(self): + if len(self.comments): + return '/*' + '\n *'.join(self.comments) + ' */\n' + else: + return '' + + def names(self): + return ',\n\t'.join('"%s"' % x["name"] for x in self.data) + + def enums(self): + return ',\n\t'.join('%s_%s' % (self.prefix, x["upper"]) for x in self.data) + + def flag_enums(self): + if not self.flags: + return "" + enum_lines = [] + enum_lines.append('typedef enum {') + prefix = self.prefix + 'F' + for x in self.data: + enum_lines.append('\t%s_%s = (1 << %s_%s),' % + (prefix, x["upper"], self.prefix, x["upper"])) + enum_lines.append('} pseudo_%s_f;' % self.name) + return '\n'.join(enum_lines) + + def column_names(self): + decl_lines = [] + column = 0 + for col in self.columns: + decl_lines.append("static %s %s_id_to_%s[] = {" % (col["type"], self.name, col["name"])) + decl_lines.append('\t%s,' % col["value"]) + for item in self.data: + decl_lines.append('\t%s,' % item["cols"][column]["value"]) + decl_lines.append('\t0') + decl_lines.append("};") + if col["indexed"]: + decl_lines.append("static int %s_%s_to_id[] = {" % (self.name, col["name"])) + for item in self.data: + decl_lines.append('\t[%s] = %d,' % (item["cols"][column]["value"], item["index"])) + decl_lines.append("};") + column = column + 1 + return '\n'.join(decl_lines) + + def column_funcs(self): + decl_lines = [] + for col in self.columns: + decl_lines.append('extern %s' % col["type"]) + decl_lines.append('pseudo_%s_%s(pseudo_%s_t id) {' % + (self.name, col["name"], self.name)) + decl_lines.append('\tif (id < 0 || id >= %s_MAX)' % (self.prefix)) + decl_lines.append('\t\treturn %s;' % col["value"]) + decl_lines.append('\treturn %s_id_to_%s[id];' % + (self.name, col["name"])) + decl_lines.append('}') + if col["indexed"]: + table_name = '%s_%s_to_id' % (self.name, col["name"]) + decl_lines.append('extern int') + decl_lines.append('pseudo_%s_%s_id(%s val) {' % + (self.name, col["name"], col["type"])) + decl_lines.append('\tif ((val < %s) || (val > %s)) {' % (col["min"], col["max"])) + decl_lines.append('\t\treturn -1;') + decl_lines.append('\t}') + decl_lines.append('\tif (%s[val] != 0) {' % table_name) + decl_lines.append('\t\treturn %s[val];' % table_name) + decl_lines.append('\t}') + decl_lines.append('\treturn -1;') + decl_lines.append('}') + return '\n'.join(decl_lines) + + def column_protos(self): + decl_lines = [] + for col in self.columns: + decl_lines.append('extern %s pseudo_%s_%s(pseudo_%s_t id);' % + (col["type"], self.name, col["name"], self.name)) + if col["indexed"]: + decl_lines.append('extern int pseudo_%s_%s_id(%s val);' % + (self.name, col["name"], col["type"])) + return '\n'.join(decl_lines) + +def main(): + """Read in function defintions, write out files based on templates.""" + datatypes = [] + templates = [] + + # error checking helpfully provided by the exception handler + copyright_file = open('guts/COPYRIGHT') + TemplateFile.copyright = copyright_file.read() + copyright_file.close() + + for path in glob.glob('table_templates/*'): + try: + template_file = TemplateFile(path) + template_file.emit('copyright') + template_file.emit('header') + templates.append(template_file) + except IOError: + print "Invalid or malformed template %s. Aborting." % path + exit(1) + + for filename in sys.argv[1:]: + # read in the datatype + sys.stdout.write("%s: " % filename) + datatype = DataType(filename) + datatypes.append(datatype) + print datatype.__repr__() + print "" + + print "Writing datatypes...", + for datatype in datatypes: + # populate various tables and files with each datatype + for template_file in templates: + template_file.emit('body', datatype) + print "done. Cleaning up." + + for template_file in templates: + # clean up files + template_file.emit('footer') + template_file.close() + +if __name__ == '__main__': + main() diff --git a/makewrappers b/makewrappers new file mode 100755 index 0000000..e9191ed --- /dev/null +++ b/makewrappers @@ -0,0 +1,638 @@ +#!/usr/bin/env python +# +# Copyright (c) 2008-2011,2013 Wind River Systems, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License version 2.1 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# version 2.1 along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +"""convert wrapfuncs.in to wrapper function stubs and tables""" + +import datetime +import glob +import sys +import re +import os.path +import string +import subprocess +from templatefile import TemplateFile + +class ArgumentList: + """A (possibly empty) list of arguments""" + + def __init__(self, text): + "parse a comma-separated argument list (including function prototypes)" + self.args = [] + self.variadic = False + self.variadic_decl = "" + self.variadic_start = "" + self.variadic_end = "" + # (void) is an empty list, not a list of a single argument which is void + if text == "void": + return + + depth = 0 + accum = '' + comma_sep = text.split(', ') + # now, what if there was a comma embedded in an argument? + for arg in comma_sep: + lcount = arg.count('(') + rcount = arg.count(')') + depth = depth + lcount - rcount + if (depth > 0): + accum += arg + ', ' + else: + self.args.append(Argument(accum + arg)) + accum = '' + if depth != 0: + raise Exception("mismatched ()s while parsing '%s'" % text) + if self.args[-1].vararg: + self.variadic = True + self.variadic_arg = self.args[-1] + self.last_fixed_arg = self.args[-2].name + self.variadic_decl = "va_list ap;\n" + self.variadic_start = "va_start(ap, %s);\n" % self.last_fixed_arg + if self.variadic_arg.vararg_wraps: + self.variadic_decl += "\t%s;\n" % \ + self.variadic_arg.vararg_wraps.decl() + self.variadic_start += ("\t%s = va_arg(ap, %s);" + "\n\tva_end(ap);\n") % \ + (self.variadic_arg.name, + self.variadic_arg.type) + else: + # lie blatantly; we don't handle this case + self.variadic = False + + # for a wrap function, the outer foo() wrapper will convert to a va_list, + # but the inner wrap_foo() just passes the va_list through. + def maybe_variadic_start(self): + """Use va_arg() to grab an optional argument if needed.""" + if self.variadic and self.variadic_arg.vararg_wraps: + return self.variadic_start + else: + return "" + + def maybe_variadic_decl(self): + """Declare va_list ap and optional argument, if needed.""" + if self.variadic and self.variadic_arg.vararg_wraps: + return self.variadic_decl + else: + return "" + + def decl(self, comment=False, wrap=False): + """Produce the declaration form of this argument list.""" + if not self.args: + return "void" + + return ', '.join(x.decl(comment=comment, wrap=wrap) for x in self.args) + + def call(self): + """Produce the calling form of this argument list.""" + if not self.args: + return "" + return ', '.join([x.call() for x in self.args]) + + def __repr__(self): + if not self.args: + return "no arguments" + else: + return '::'.join([x.decl() for x in self.args]) + + +class Argument: + """A function argument such as 'char *path' or 'char (*foo)(void)'""" + def __init__(self, text): + """get the type and name of a trivial C declaration""" + self.vararg = False + self.function_pointer = False + self.spacer = '' + + if text == 'void': + raise Exception("Tried to declare a nameless object of type void.") + + if text.startswith('...'): + self.vararg = True + if len(text) > 3: + # we're a wrapper for something else, declared as + # ...{real_decl}, as in the third argument to open(2) + text = text[4:-1] + # stash a copy of these values without the vararg flag, so + # we can declare them prettily later + self.vararg_wraps = Argument(text) + else: + # nothing to do. + self.vararg_wraps = None + self.type, self.name = None, None + return + else: + self.vararg = False + + # try for a function pointer + match = re.match('(.*)\(\*([a-zA-Z0-9$_]*)\)\((.*)\)', text) + if match: + self.function_pointer = True + self.args = match.group(3) + self.type = match.group(1) + self.name = match.group(2).rstrip() + else: + # plain declaration + match = re.match('(.*[ *])\(?\*?([a-zA-Z0-9$_]*)\)?', text) + # there may not be a match, say in the special case + # where an arg is '...' + if match: + self.type, self.name = match.group(1).rstrip(), match.group(2) + else: + self.type, self.name = None, None + + # spacing between type and name, needed if type ends with a character + # which could be part of an identifier + if re.match('[_a-zA-Z0-9]', self.type[-1]): + self.spacer = ' ' + + def decl(self, comment=False, wrap=False): + """Produce the declaration form of this argument.""" + if self.function_pointer: + decl = "%s%s(*%s)(%s)" % \ + (self.type, self.spacer, self.name, self.args) + else: + decl = "%s%s%s" % (self.type, self.spacer, self.name) + + if self.vararg: + if self.vararg_wraps: + if comment: + decl = "... { %s }" % decl + else: + decl = "... /* %s */" % decl + else: + if wrap: + decl = "va_list ap" + else: + decl = "..." + return decl + + def call(self): + """Produce the call form of this argument (usually its name).""" + if self.type == 'void': + return '' + + if self.vararg and not self.vararg_wraps: + return "ap" + + return self.name + + def __str__(self): + return self.decl() + + def __repr__(self): + return self.decl() + +typedata = { + 'char *': { 'format': '%s', 'value': 'rc ? rc : "<nil>"' }, + 'const char *': { 'format': '%s', 'value': 'rc ? rc : "<nil>"' }, + 'DIR *': { 'format': '%p', 'value': '(void *) rc' }, + 'FILE *': { 'format': '%p', 'value': '(void *) rc' }, + 'FTS *': { 'format': '%p', 'value': '(void *) rc' }, + 'gid_t': { 'format': '%ld', 'value': ' (long) rc' }, + 'int': { 'format': '%d', 'value': 'rc' }, + 'long': { 'format': '%ld', 'value': 'rc' }, + 'mode_t': { 'format': '0%lo', 'value': '(long) rc' }, + 'off_t': { 'format': '%lld', 'value': '(long long) rc' }, + 'size_t': { 'format': '%lu', 'value': '(unsigned long) rc' }, + 'ssize_t': { 'format': '%ld', 'value': '(long) rc' }, + 'struct group *': { 'format': '%p', 'value': '(void *) rc' }, + 'struct passwd *': { 'format': '%p', 'value': '(void *) rc' }, + 'uid_t': { 'format': '%ld', 'value': ' (long) rc' }, + 'void *': { 'format': '%p', 'value': 'rc' }, + 'void': { 'format': 'void%s', 'value': '""' }, +} + +class Function: + """A function signature and additional data about how the function works""" + def __init__(self, port, line): + # table of known default values: + default_values = { + 'gid_t': '0', + 'uid_t': '0', + 'int': '-1', + 'long': '-1', + 'mode_t': '0', + 'ssize_t': '-1' + } + + self.dirfd = 'AT_FDCWD' + self.flags = '0' + self.port = port + self.directory = '' + self.version = 'NULL' + # On Darwin, some functions are SECRETLY converted to foo$INODE64 + # when called. So we have to look those up for real_* + self.inode64 = None + self.real_func = None + self.paths_to_munge = [] + self.specific_dirfds = {} + self.hand_wrapped = None + self.async_skip = None + # used for the copyright date when creating stub functions + self.date = datetime.date.today().year + + function, comments = line.split(';') + comment = re.search('/\* *(.*) *\*/', comments) + if comment: + self.comments = comment.group(1) + else: + self.comments = None + + bits = re.match('([^(]*)\((.*)\)', function) + type_and_name = Argument(bits.group(1)) + self.type, self.name = type_and_name.type, type_and_name.name + # convenient to have this declared here so we can use its .decl later + if self.type != 'void': + self.return_code = Argument("%s rc" % self.type) + + # Some args get special treatment: + # * If the arg has a name ending in 'path', we will canonicalize it. + # * If the arg is named 'dirfd' or 'flags', it becomes the default + # values for the dirfd and flags arguments when canonicalizing. + # * If the name ends in dirfd, we do the same fancy stuff. + # * Note that the "comments" field (/* ... */ after the decl) can + # override the dirfd/flags values. + self.args = ArgumentList(bits.group(2)) + for arg in self.args.args: + # ignore varargs, they never get these special treatments + if arg.vararg: + pass + elif arg.name.endswith('dirfd'): + if len(arg.name) > 5: + self.specific_dirfds[arg.name[:-5]] = True + self.dirfd = 'dirfd' + elif arg.name == 'flags': + self.flags = 'flags' + elif arg.name.endswith('path'): + self.paths_to_munge.append(arg.name) + + # pick default values + if self.type == 'void': + self.default_value = '' + elif self.type[-1:] == '*': + self.default_value = 'NULL' + else: + try: + self.default_value = default_values[self.type] + except KeyError: + raise KeyError("Function %s has return type %s," + "for which there is no default value." % + (self.name, self.type)) + + # handle special comments, such as flags=AT_SYMLINK_NOFOLLOW + if self.comments: + modifiers = self.comments.split(', ') + for mod in modifiers: + key, value = mod.split('=') + value = value.rstrip() + setattr(self, key, value) + + def maybe_inode64(self): + if self.inode64 and os.uname()[0] == 'Darwin': + return "$INODE64" + else: + return "" + + def end_maybe_skip(self): + if self.hand_wrapped: + return """/* Hand-written wrapper for this function. */ +#endif +""" + else: + return "" + + def maybe_skip(self): + if self.hand_wrapped: + return """/* Hand-written wrapper for this function. */ +#if 0 +""" + else: + return "" + + def maybe_async_skip(self): + if self.async_skip: + return """/* This function is not called if pseudo is configured --enable-force-async */ +#ifdef PSEUDO_FORCE_ASYNC + if (!pseudo_allow_fsync) { + PROFILE_DONE; + return %s; + } +#endif +""" % self.async_skip + else: + return "" + + def comment(self): + """declare self (in a comment)""" + return self.decl(comment = True) + + def decl(self, comment=False, wrap=True): + """declare self""" + if self.type[-1:] == '*': + spacer = '' + else: + spacer = ' ' + return "%s%s%s(%s)" % \ + (self.type, spacer, self.name, self.args.decl(comment, wrap)) + + def decl_args(self): + """declare argument list""" + return self.args.decl() + + def wrap_args(self): + """declare argument list for wrap_foo() variant""" + return self.args.decl(wrap = True) + + def call_args(self): + """present argument list for a function call""" + return self.args.call() + + def fix_paths(self): + """create/allocate canonical paths""" + fix_paths = [] + for path in self.paths_to_munge: + prefix = path[:-4] + if prefix not in self.specific_dirfds: + prefix = '' + fix_paths.append( + "%s = pseudo_root_path(__func__, __LINE__, %s%s, %s, %s);" % + (path, prefix, self.dirfd, path, self.flags)) + return "\n\t\t".join(fix_paths) + + def real_predecl(self): + if self.real_func: + return self.decl().replace(self.name, self.real_func, 1) + ";" + else: + return "" + + def real_init(self): + if self.real_func: + return self.real_func + else: + return "NULL" + + def rc_return(self): + """return rc (or just return)""" + if self.type == 'void': + return "return;" + else: + return "return rc;" + + def rc_format(self): + """the format string to use for the return value""" + return typedata.get(self.type, { 'format': '[%s]', 'value': '"' + self.type + '"' })['format'] + + def rc_value(self): + """the value to pass for the format string for the return value""" + return typedata.get(self.type, { 'format': '[%s]', 'value': '"' + self.type + '"' })['value'] + + def rc_decl(self): + """declare rc (if needed)""" + if self.type == 'void': + return "" + else: + return "%s = %s;" % (self.return_code.decl(), self.default_value) + + def rc_assign(self): + """assign something to rc (or discard it)""" + if self.type == 'void': + return "(void)" + else: + return "rc =" + + def def_return(self): + """return default value (or just return)""" + if self.type == 'void': + return "return;" + else: + return "return %s;" % self.default_value + + def __getitem__(self, key): + """Make this object look like a dict for Templates to use""" + try: + attr = getattr(self, key) + except AttributeError: + # There's a few attributes that are handled inside the args + # object, so check there too... + attr = getattr(self.args, key) + + if callable(attr): + return attr() + else: + return attr + + def __repr__(self): + pretty = "%(name)s returns %(type)s and takes " % self + pretty += repr(self.args) + if self.comments: + pretty += ' (%s)' % self.comments + return pretty + + def funcdeps(self): + return 'pseudo_wrappers.o: ports/%s/guts/%s.c' % ( self.port, self.name ) + +class Port: + """ +A Port is a set of function declarations and code providing +details specific to a specific host environment, such as Linux. +Ports can override each other, and each port can indicate +additional ports to include. +""" + + def __init__(self, port, sources): + self.name = port + self.subports = [] + self.preports = [] + print port + + if os.path.exists(self.portfile("pseudo_wrappers.c")): + self.wrappers = self.portfile("pseudo_wrappers.c") + else: + self.wrappers = None + + if os.path.exists(self.portfile("portdefs.h")): + self.portdef_file = self.portfile("portdefs.h") + else: + self.portdef_file = None + + if os.path.exists(self.portfile("wrapfuncs.in")): + self.funcs = process_wrapfuncs(port) + else: + self.funcs = {} + + for source in sources: + source.emit('port', self) + + if os.path.exists(self.portfile("preports")): + subport_proc = subprocess.Popen([self.portfile("preports"), self.name], stdout=subprocess.PIPE) + portlist = subport_proc.communicate()[0] + retcode = subport_proc.poll() + if retcode: + raise Exception("preports script failed for port %s" % self.name) + + for preport in string.split(portlist): + next = Port(preport, sources) + self.preports.append(next) + + if os.path.exists(self.portfile("subports")): + subport_proc = subprocess.Popen([self.portfile("subports"), self.name], stdout=subprocess.PIPE) + portlist = subport_proc.communicate()[0] + retcode = subport_proc.poll() + if retcode: + raise Exception("subports script failed for port %s" % self.name) + + for subport in string.split(portlist): + next = Port(subport, sources) + self.subports.append(next) + + def functions(self): + mergedfuncs = {} + for pre in self.preports: + prefuncs = pre.functions() + for name in prefuncs.keys(): + if name in mergedfuncs: + print "Warning: %s from %s overriding %s" % (name, pre.name, mergedfuncs[name].port) + mergedfuncs[name] = prefuncs[name] + for name in self.funcs.keys(): + if name in mergedfuncs: + print "Warning: %s from %s overriding %s" % (name, self.name, mergedfuncs[name].port) + mergedfuncs[name] = self.funcs[name] + for sub in self.subports: + subfuncs = sub.functions() + for name in subfuncs.keys(): + if name in mergedfuncs: + print "Warning: %s from %s overriding %s" % (name, sub.name, mergedfuncs[name].port) + mergedfuncs[name] = subfuncs[name] + return mergedfuncs + + def define(self): + return '#define PSEUDO_PORT_%s 1' % string.upper(self.name).replace('/', '_') + + def portdeps(self): + deps = [] + if self.wrappers: + deps.append(self.wrappers) + if self.portdef_file: + deps.append(self.portdef_file) + if deps: + return 'pseudo_wrappers.o: %s' % ' '.join(deps) + else: + return '# no extra dependencies for %s.' % self.name + + def portdefs(self): + if self.portdef_file: + return '#include "%s"' % self.portdef_file + else: + return '/* no portdefs for %s */' % self.name + + def include(self): + if self.wrappers: + return '#include "%s"' % self.wrappers + else: + return '/* no #include for %s */' % self.name + + def portfile(self, name): + return "ports/%s/%s" % (self.name, name) + + def __getitem__(self, key): + """Make this object look like a dict for Templates to use""" + try: + attr = getattr(self, key) + except AttributeError: + return None + + if callable(attr): + return attr() + else: + return attr + + +def process_wrapfuncs(port): + """Process a wrapfuncs.in file, generating a list of prototypes.""" + filename = "ports/%s/wrapfuncs.in" % port + funcs = {} + directory = os.path.dirname(filename) + sys.stdout.write("%s: " % filename) + funclist = open(filename) + for line in funclist: + line = line.rstrip() + if line.startswith('#') or not line: + continue + try: + func = Function(port, line) + func.directory = directory + funcs[func.name] = func + sys.stdout.write(".") + except Exception, e: + print "Parsing failed:", e + exit(1) + funclist.close() + print "" + return funcs + +def main(argv): + """Read in function definitions, write out files based on templates.""" + funcs = [] + sources = [] + + for arg in argv: + name, value = arg.split('=') + os.environ["port_" + name] = value + + # error checking helpfully provided by the exception handler + copyright_file = open('guts/COPYRIGHT') + TemplateFile.copyright = copyright_file.read() + copyright_file.close() + + for path in glob.glob('templates/*'): + try: + print "Considering template: " + path + source = TemplateFile(path) + if source.name.endswith('.c') or source.name.endswith('.h'): + source.emit('copyright') + source.emit('header') + sources.append(source) + except IOError: + print "Invalid or malformed template %s. Aborting." % path + exit(1) + + try: + port = Port('common', sources) + + except KeyError: + print "Unknown uname -s result: '%s'." % uname_s + print "Known system types are:" + print "%-20s %-10s %s" % ("uname -s", "port name", "description") + for key in host_ports: + print "%-20s %-10s %s" % (key, host_ports[key], + host_descrs[host_ports[key]]) + + # the per-function stuff + print "Writing functions...", + all_funcs = port.functions() + for name in sorted(all_funcs.keys()): + # populate various tables and files with each function + for source in sources: + source.emit('body', all_funcs[name]) + print "done. Cleaning up." + + for source in sources: + # clean up files + source.emit('footer') + source.close() + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/offsets.c b/offsets.c new file mode 100644 index 0000000..5e9cddd --- /dev/null +++ b/offsets.c @@ -0,0 +1,56 @@ +/* + * offsets.c, print offsets in pseudo_ipc structure + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#endif +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stddef.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" + +int +main(void) { + printf("type: %d\n", (int) offsetof(pseudo_msg_t, type)); + printf("op: %d\n", (int) offsetof(pseudo_msg_t, op)); + printf("result: %d\n", (int) offsetof(pseudo_msg_t, result)); + printf("access: %d\n", (int) offsetof(pseudo_msg_t, access)); + printf("client: %d\n", (int) offsetof(pseudo_msg_t, client)); + printf("dev: %d\n", (int) offsetof(pseudo_msg_t, dev)); + printf("ino: %d\n", (int) offsetof(pseudo_msg_t, ino)); + printf("uid: %d\n", (int) offsetof(pseudo_msg_t, uid)); + printf("gid: %d\n", (int) offsetof(pseudo_msg_t, gid)); + printf("mode: %d\n", (int) offsetof(pseudo_msg_t, mode)); + printf("rdev: %d\n", (int) offsetof(pseudo_msg_t, rdev)); + printf("pathlen: %d\n", (int) offsetof(pseudo_msg_t, pathlen)); + printf("nlink: %d\n", (int) offsetof(pseudo_msg_t, nlink)); + printf("deleting: %d\n", (int) offsetof(pseudo_msg_t, deleting)); + printf("path: %d\n", (int) offsetof(pseudo_msg_t, path)); + printf("size: %d\n", (int) sizeof(pseudo_msg_t)); + return 0; +} + diff --git a/perftest b/perftest new file mode 100755 index 0000000..97dced1 --- /dev/null +++ b/perftest @@ -0,0 +1,57 @@ +#!/bin/sh +# do a quick performance test of pseudo +opt_f=false +flag_f= + +while getopts "f" o +do + case $o in + f) opt_f=true + flag_f=-f + ;; + \?) die "Usage: perftest [-f] [directory]";; + esac; +done +shift `expr $OPTIND - 1` + +die() { + printf "%s\n" "$*" >&2 + exit 1 +} + +doit() ( + cd $dir + printf "%s\n" "Making test data..." + time ./makedata + printf "%s\n" "Timing tar command." + time sh -c 'tar cf - dir_[0-9] | tar -C new -xf -' + printf "%s\n" "Timing find command." + time find new -perm 0100 -exec true {} + + printf "%s\n" "Timing rm." + time rm -rf dir_[0-9] new +) + +[ -x bin/pseudo ] || die "You need a bin/pseudo to test." +case $# in +0) dir="perftest.d";; +1) [ -d "$1" ] || die "Specify an existing directory to test in. '%s' is not a directory." "$1" + dir="$1/perftest.d" + ;; +*) die "Usage: perftest [directory]" + ;; +esac + +if $opt_f || [ `id -u` = x0 ]; then + printf "Running test in %s.\n" "$dir" + doit + printf "Done.\n" +else + [ -d $dir ] && die "Directory '$dir' already exists, delete it if you're done." + mkdir $dir + mkdir -p $dir/new + cc -o $dir/makedata makedata.c + printf "%s\n" "Running performance test (total time at end)" + time bin/pseudo ./perftest -f ${dir%perftest.d} + rm -rf $dir +fi + diff --git a/ports/common/guts/execl.c b/ports/common/guts/execl.c new file mode 100644 index 0000000..be86b49 --- /dev/null +++ b/ports/common/guts/execl.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int execl(const char *file, const char *arg, va_list ap) + * int rc = -1; + */ + + /* NOTE THAT THIS IS NEVER USED! + * We implement all execl() in terms of execv() + * so this call is not used. + */ + + rc = real_execl(file, arg, ap); + +/* return rc; + * } + */ diff --git a/ports/common/guts/execle.c b/ports/common/guts/execle.c new file mode 100644 index 0000000..a3bc3ca --- /dev/null +++ b/ports/common/guts/execle.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int execle(const char *file, const char *arg, va_list ap) + * int rc = -1; + */ + + /* NOTE THAT THIS IS NEVER USED! + * We implement all execl() in terms of execv() + * so this call is not used. + */ + + rc = real_execle(file, arg, ap); + +/* return rc; + * } + */ diff --git a/ports/common/guts/execlp.c b/ports/common/guts/execlp.c new file mode 100644 index 0000000..3cf2889 --- /dev/null +++ b/ports/common/guts/execlp.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int execlp(const char *file, const char *arg, va_list ap) + * int rc = -1; + */ + + /* NOTE THAT THIS IS NEVER USED! + * We implement all execl() in terms of execv() + * so this call is not used. + */ + + rc = real_execlp(file, arg, ap); + +/* return rc; + * } + */ diff --git a/ports/common/guts/execv.c b/ports/common/guts/execv.c new file mode 100644 index 0000000..ba1ce65 --- /dev/null +++ b/ports/common/guts/execv.c @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_execv(const char *file, char *const *argv) { + * int rc = -1; + */ + /* note: we don't canonicalize this, because we are intentionally + * NOT redirecting execs into the chroot environment. If you try + * to execute /bin/sh, you get the actual /bin/sh, not + * <CHROOT>/bin/sh. This allows use of basic utilities. This + * design will likely be revisited. + */ + if (antimagic == 0) { + const char *path_guess = pseudo_exec_path(file, 0); + pseudo_client_op(OP_EXEC, PSA_EXEC, -1, -1, path_guess, 0); + } + + pseudo_setupenv(); + if (pseudo_has_unload(NULL)) + pseudo_dropenv(); + + /* if exec() fails, we may end up taking signals unexpectedly... + * not much we can do about that. + */ + sigprocmask(SIG_SETMASK, &pseudo_saved_sigmask, NULL); + rc = real_execv(file, argv); + +/* return rc; + * } + */ diff --git a/ports/common/guts/execve.c b/ports/common/guts/execve.c new file mode 100644 index 0000000..24cc177 --- /dev/null +++ b/ports/common/guts/execve.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_execve(const char *file, char *const *argv, char *const *envp) { + * int rc = -1; + */ + char * const *new_environ; + /* note: we don't canonicalize this, because we are intentionally + * NOT redirecting execs into the chroot environment. If you try + * to execute /bin/sh, you get the actual /bin/sh, not + * <CHROOT>/bin/sh. This allows use of basic utilities. This + * design will likely be revisited. + */ + if (antimagic == 0) { + const char *path_guess = pseudo_exec_path(file, 0); + pseudo_client_op(OP_EXEC, PSA_EXEC, -1, -1, path_guess, 0); + } + + new_environ = pseudo_setupenvp(envp); + if (pseudo_has_unload(new_environ)) + new_environ = pseudo_dropenvp(new_environ); + + /* if exec() fails, we may end up taking signals unexpectedly... + * not much we can do about that. + */ + sigprocmask(SIG_SETMASK, &pseudo_saved_sigmask, NULL); + rc = real_execve(file, argv, new_environ); + +/* return rc; + * } + */ diff --git a/ports/common/guts/execvp.c b/ports/common/guts/execvp.c new file mode 100644 index 0000000..e6bf09f --- /dev/null +++ b/ports/common/guts/execvp.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_execvp(const char *file, char *const *argv) { + * int rc = -1; + */ + + /* note: we don't canonicalize this, because we are intentionally + * NOT redirecting execs into the chroot environment. If you try + * to execute /bin/sh, you get the actual /bin/sh, not + * <CHROOT>/bin/sh. This allows use of basic utilities. This + * design will likely be revisited. + */ + if (antimagic == 0) { + const char *path_guess = pseudo_exec_path(file, 1); + pseudo_client_op(OP_EXEC, PSA_EXEC, -1, -1, path_guess, 0); + } + + pseudo_setupenv(); + if (pseudo_has_unload(NULL)) + pseudo_dropenv(); + + /* if exec() fails, we may end up taking signals unexpectedly... + * not much we can do about that. + */ + sigprocmask(SIG_SETMASK, &pseudo_saved_sigmask, NULL); + rc = real_execvp(file, argv); + +/* return rc; + * } + */ diff --git a/ports/common/guts/fork.c b/ports/common/guts/fork.c new file mode 100644 index 0000000..bebe3b0 --- /dev/null +++ b/ports/common/guts/fork.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_fork(void) { + * int rc = -1; + */ + rc = real_fork(); + /* special case: we may want to enable or disable + * pseudo in the child process + */ + if (rc == 0) { + pseudo_setupenv(); + if (!pseudo_has_unload(NULL)) { + pseudo_reinit_libpseudo(); + } else { + pseudo_dropenv(); + } + } +/* return rc; + * } + */ diff --git a/ports/common/pseudo_wrappers.c b/ports/common/pseudo_wrappers.c new file mode 100644 index 0000000..81be635 --- /dev/null +++ b/ports/common/pseudo_wrappers.c @@ -0,0 +1,408 @@ +/* these aren't used, but the wrapper table isn't happy unless they + * exist + */ +static int +wrap_execl(const char *file, const char *arg, va_list ap) { + (void) file; + (void) arg; + (void) ap; + return 0; +} + +static int +wrap_execle(const char *file, const char *arg, va_list ap) { + (void) file; + (void) arg; + (void) ap; + return 0; +} + +static int +wrap_execlp(const char *file, const char *arg, va_list ap) { + (void) file; + (void) arg; + (void) ap; + return 0; +} + +static char ** +execl_to_v(va_list ap, const char *argv0, char *const **envp) { + size_t i = 0; + size_t alloc_size = 256; + + char **argv = malloc((sizeof *argv) * alloc_size); + + if (!argv) { + pseudo_debug(PDBGF_CLIENT, "execl failed: couldn't allocate memory for %lu arguments\n", + (unsigned long) alloc_size); + return NULL; + } + argv[i++] = (char *) argv0; + + while (argv[i-1]) { + argv[i++] = va_arg(ap, char *const); + if (i > alloc_size - 1) { + alloc_size = alloc_size + 256; + argv = realloc(argv, (sizeof *argv) * alloc_size); + if (!argv) { + pseudo_debug(PDBGF_CLIENT, "execl failed: couldn't allocate memory for %lu arguments\n", + (unsigned long) alloc_size); + return NULL; + } + } + } + if (envp) { + *envp = va_arg(ap, char **); + } + return argv; +} + +/* The following wrappers require Special Handling */ + +int +execl(const char *file, const char *arg, ...) { + sigset_t saved; + va_list ap; + char **argv; + + int rc = -1; + + PROFILE_START; + + if (!pseudo_check_wrappers()) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("execl"); + PROFILE_DONE; + return rc; + } + + va_start(ap, arg); + argv = execl_to_v(ap, arg, 0); + va_end(ap); + if (!argv) { + errno = ENOMEM; + PROFILE_DONE; + return -1; + } + + pseudo_debug(PDBGF_WRAPPER, "called: execl\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + PROFILE_DONE; + return -1; + } + + int save_errno; + + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + rc = wrap_execv(file, argv); + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: execl\n"); + errno = save_errno; + free(argv); + PROFILE_DONE; + return rc; +} + +int +execlp(const char *file, const char *arg, ...) { + sigset_t saved; + va_list ap; + char **argv; + + int rc = -1; + PROFILE_START; + + if (!pseudo_check_wrappers()) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("execlp"); + PROFILE_DONE; + return rc; + } + + va_start(ap, arg); + argv = execl_to_v(ap, arg, 0); + va_end(ap); + if (!argv) { + errno = ENOMEM; + PROFILE_DONE; + return -1; + } + + pseudo_debug(PDBGF_WRAPPER, "called: execlp\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + PROFILE_DONE; + return -1; + } + + int save_errno; + + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + rc = wrap_execvp(file, argv); + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: execlp\n"); + errno = save_errno; + free(argv); + PROFILE_DONE; + return rc; +} + +int +execle(const char *file, const char *arg, ...) { + sigset_t saved; + va_list ap; + char **argv; + char **envp; + + int rc = -1; + PROFILE_START; + + if (!pseudo_check_wrappers()) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("execle"); + PROFILE_DONE; + return rc; + } + + va_start(ap, arg); + argv = execl_to_v(ap, arg, (char *const **)&envp); + va_end(ap); + if (!argv) { + errno = ENOMEM; + PROFILE_DONE; + return -1; + } + + pseudo_debug(PDBGF_WRAPPER, "called: execle\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + PROFILE_DONE; + return -1; + } + + int save_errno; + + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + rc = wrap_execve(file, argv, envp); + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: execle\n"); + errno = save_errno; + free(argv); + PROFILE_DONE; + return rc; +} + +int +execv(const char *file, char *const *argv) { + sigset_t saved; + + int rc = -1; + + PROFILE_START; + + if (!pseudo_check_wrappers() || !real_execv) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("execv"); + PROFILE_DONE; + return rc; + } + + pseudo_debug(PDBGF_WRAPPER, "called: execv\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + PROFILE_DONE; + return -1; + } + + int save_errno; + + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + rc = wrap_execv(file, argv); + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: execv\n"); + errno = save_errno; + PROFILE_DONE; + return rc; +} + +int +execve(const char *file, char *const *argv, char *const *envp) { + sigset_t saved; + + int rc = -1; + PROFILE_START; + + if (!pseudo_check_wrappers() || !real_execve) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("execve"); + PROFILE_DONE; + return rc; + } + + pseudo_debug(PDBGF_WRAPPER, "called: execve\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + PROFILE_DONE; + return -1; + } + + int save_errno; + + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + rc = wrap_execve(file, argv, envp); + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: execve\n"); + errno = save_errno; + PROFILE_DONE; + return rc; +} + +int +execvp(const char *file, char *const *argv) { + sigset_t saved; + + int rc = -1; + PROFILE_START; + + if (!pseudo_check_wrappers() || !real_execvp) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("execvp"); + PROFILE_DONE; + return rc; + } + + pseudo_debug(PDBGF_WRAPPER, "called: execvp\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + PROFILE_DONE; + return -1; + } + + int save_errno; + + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + rc = wrap_execvp(file, argv); + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: execvp\n"); + errno = save_errno; + PROFILE_DONE; + return rc; +} + +/* no profiling in fork because it wouldn't work anyway + * half the time + */ +int +fork(void) { + sigset_t saved; + + int rc = -1; + + if (!pseudo_check_wrappers() || !real_fork) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("fork"); + return rc; + } + + pseudo_debug(PDBGF_WRAPPER, "called: fork\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + return -1; + } + + int save_errno; + + rc = wrap_fork(); + + save_errno = errno; + + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: fork\n"); + errno = save_errno; + return rc; +} + +int +vfork(void) { + /* we don't provide support for the distinct semantics + * of vfork() + */ + return fork(); +} + +static int +wrap_execv(const char *file, char *const *argv) { + int rc = -1; + +#include "guts/execv.c" + + return rc; +} + +static int +wrap_execve(const char *file, char *const *argv, char *const *envp) { + int rc = -1; + +#include "guts/execve.c" + + return rc; +} + +static int +wrap_execvp(const char *file, char *const *argv) { + int rc = -1; + +#include "guts/execvp.c" + + return rc; +} + +static int +wrap_fork(void) { + int rc = -1; + +#include "guts/fork.c" + + return rc; +} + diff --git a/ports/common/subports b/ports/common/subports new file mode 100755 index 0000000..e2aac56 --- /dev/null +++ b/ports/common/subports @@ -0,0 +1,8 @@ +#!/bin/sh +case $(uname -s) in +Linux) echo "linux";; +Darwin) echo "darwin";; +*) echo >&2 "Unknown result from uname -s: %(uname -s). Aborting." + exit 1 + ;; +esac diff --git a/ports/common/wrapfuncs.in b/ports/common/wrapfuncs.in new file mode 100644 index 0000000..17440f9 --- /dev/null +++ b/ports/common/wrapfuncs.in @@ -0,0 +1,7 @@ +int execlp(const char *file, const char *arg, ...); /* hand_wrapped=1 */ +int execl(const char *file, const char *arg, ...); /* hand_wrapped=1 */ +int execle(const char *file, const char *arg, ...); /* hand_wrapped=1 */ +int execv(const char *file, char *const *argv); /* hand_wrapped=1 */ +int execve(const char *file, char *const *argv, char *const *envp); /* hand_wrapped=1 */ +int execvp(const char *file, char *const *argv); /* hand_wrapped=1 */ +int fork(void); /* hand_wrapped=1 */ diff --git a/ports/darwin/guts/COPYRIGHT b/ports/darwin/guts/COPYRIGHT new file mode 100644 index 0000000..c96e1b1 --- /dev/null +++ b/ports/darwin/guts/COPYRIGHT @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ diff --git a/ports/darwin/guts/fcntl.c b/ports/darwin/guts/fcntl.c new file mode 100644 index 0000000..c0b142b --- /dev/null +++ b/ports/darwin/guts/fcntl.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2011, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fcntl(int fd, int cmd, ... { struct flock *lock }) + * int rc = -1; + */ + int save_errno; + long long flag = 0; + + va_start(ap, cmd); + flag = va_arg(ap, long long); + va_end(ap); + rc = real_fcntl(fd, cmd, flag); + + switch (cmd) { + case F_DUPFD: +#ifdef F_DUPFD_CLOEXEC + /* it doesn't exist now, but if I take this out they'll add it + * just to mess with me. + */ + case F_DUPFD_CLOEXEC: +#endif + /* actually do something */ + save_errno = errno; + if (rc != -1) { + pseudo_debug(PDBGF_OP, "fcntl_dup: %d->%d\n", fd, rc); + pseudo_client_op(OP_DUP, 0, fd, rc, 0, 0); + } + errno = save_errno; + break; + default: + /* nothing to do, we hope */ + break; + } + + save_errno = errno; + pseudo_debug(PDBGF_OP, "fcntl(fd %d, cmd %d, %llx) => %d (%s)\n", + fd, cmd, flag, rc, strerror(errno)); + errno = save_errno; + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/fgetgrent_r.c b/ports/darwin/guts/fgetgrent_r.c new file mode 100644 index 0000000..e760cdd --- /dev/null +++ b/ports/darwin/guts/fgetgrent_r.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fgetgrent_r(FILE *fp, struct group*gbuf, char *buf, size_t buflen, struct group **gbufp) + * int rc = -1; + */ + + rc = real_fgetgrent_r(fp, gbuf, buf, buflen, gbufp); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/fgetpwent_r.c b/ports/darwin/guts/fgetpwent_r.c new file mode 100644 index 0000000..cfea5b8 --- /dev/null +++ b/ports/darwin/guts/fgetpwent_r.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fgetpwent_r(FILE *fp, struct passwd *pbuf, char *buf, size_t buflen, struct passwd **pbufp) + * int rc = -1; + */ + + rc = real_fgetpwent_r(fp, pbuf, buf, buflen, pbufp); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/fgetxattr.c b/ports/darwin/guts/fgetxattr.c new file mode 100644 index 0000000..dbb3681 --- /dev/null +++ b/ports/darwin/guts/fgetxattr.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size, u_int32_t position, int options) + * ssize_t rc = -1; + */ + + rc = shared_getxattr(NULL, filedes, name, value, size, position, options); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/flistxattr.c b/ports/darwin/guts/flistxattr.c new file mode 100644 index 0000000..bfaa4e9 --- /dev/null +++ b/ports/darwin/guts/flistxattr.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t flistxattr(int filedes, char *list, size_t size, int options) + * ssize_t rc = -1; + */ + + rc = shared_listxattr(NULL, filedes, list, size, options); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/fremovexattr.c b/ports/darwin/guts/fremovexattr.c new file mode 100644 index 0000000..4edc38c --- /dev/null +++ b/ports/darwin/guts/fremovexattr.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fremovexattr(int filedes, const char *name, int options) + * int rc = -1; + */ + + rc = shared_removexattr(NULL, filedes, name, options); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/fsetxattr.c b/ports/darwin/guts/fsetxattr.c new file mode 100644 index 0000000..d707595 --- /dev/null +++ b/ports/darwin/guts/fsetxattr.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fsetxattr(int filedes, const char *name, const void *value, size_t size, u_int32_t position, int options) + * int rc = -1; + */ + + rc = shared_setxattr(NULL, filedes, name, value, size, position, options); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/fstat.c b/ports/darwin/guts/fstat.c new file mode 100644 index 0000000..7695147 --- /dev/null +++ b/ports/darwin/guts/fstat.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fstat(int fd, struct stat *buf) + * int rc = -1; + */ + pseudo_msg_t *msg; + + rc = real_fstat(fd, buf); + + if (rc == -1) { + return rc; + } + + /* query database + * note that symlink canonicalizing is now automatic, so we + * don't need to check for a symlink on this end + */ + msg = pseudo_client_op(OP_FSTAT, 0, fd, -1, 0, buf); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(buf, msg); + } + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/getgrent_r.c b/ports/darwin/guts/getgrent_r.c new file mode 100644 index 0000000..9d5db5a --- /dev/null +++ b/ports/darwin/guts/getgrent_r.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) + * int rc = -1; + */ + + rc = real_getgrent_r(gbuf, buf, buflen, gbufp); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/getgrouplist.c b/ports/darwin/guts/getgrouplist.c new file mode 100644 index 0000000..85fccc9 --- /dev/null +++ b/ports/darwin/guts/getgrouplist.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getgrouplist(const char *name, int basegid, int *groups, int *ngroups) { + * int rc = -1; + */ + + int found = 0; + int found_group = 0; + char buf[PSEUDO_PWD_MAX]; + struct group grp, *gbuf = &grp; + + setgrent(); + while ((rc = wrap_getgrent_r(gbuf, buf, PSEUDO_PWD_MAX, &gbuf)) == 0) { + int i = 0; + for (i = 0; gbuf->gr_mem[i]; ++i) { + if (!strcmp(gbuf->gr_mem[i], name)) { + if (found < *ngroups) + groups[found] = gbuf->gr_gid; + ++found; + if ((int) gbuf->gr_gid == basegid) + found_group = 1; + } + } + } + endgrent(); + if (!found_group) { + if (found < *ngroups) + groups[found] = basegid; + ++found; + } + if (found >= *ngroups) + rc = -1; + else + rc = found; + *ngroups = found; + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/getgroups.c b/ports/darwin/guts/getgroups.c new file mode 100644 index 0000000..3cbeb76 --- /dev/null +++ b/ports/darwin/guts/getgroups.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getgroups(int size, gid_t *list) { + * int rc = -1; + */ + struct passwd *p = wrap_getpwuid(wrap_getuid()); + int oldsize = size; + + if (p) { + rc = wrap_getgrouplist(p->pw_name, wrap_getgid(), (int *) list, &size); + if (oldsize == 0 || size <= oldsize) + rc = size; + } else { + errno = ENOENT; + } + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/getpwent_r.c b/ports/darwin/guts/getpwent_r.c new file mode 100644 index 0000000..3de41b9 --- /dev/null +++ b/ports/darwin/guts/getpwent_r.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) + * int rc = -1; + */ + + rc = real_getpwent_r(pwbuf, buf, buflen, pwbufp); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/getxattr.c b/ports/darwin/guts/getxattr.c new file mode 100644 index 0000000..ecef9cf --- /dev/null +++ b/ports/darwin/guts/getxattr.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options) + * ssize_t rc = -1; + */ + + rc = shared_getxattr(path, -1, name, value, size, position, options); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/listxattr.c b/ports/darwin/guts/listxattr.c new file mode 100644 index 0000000..5a8a7a8 --- /dev/null +++ b/ports/darwin/guts/listxattr.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t listxattr(const char *path, char *list, size_t size, int options) + * ssize_t rc = -1; + */ + + rc = shared_listxattr(path, -1, list, size, options); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/lstat.c b/ports/darwin/guts/lstat.c new file mode 100644 index 0000000..01e0f30 --- /dev/null +++ b/ports/darwin/guts/lstat.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int lstat(const char *path, struct stat *buf) + * int rc = -1; + */ + + pseudo_msg_t *msg; + + rc = real_lstat(path, buf); + if (rc == -1) { + return rc; + } + + /* query database + * note that symlink canonicalizing is now automatic, so we + * don't need to check for a symlink on this end + */ + msg = pseudo_client_op(OP_STAT, 0, -1, -1, path, buf); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(buf, msg); + } + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/open.c b/ports/darwin/guts/open.c new file mode 100644 index 0000000..f34b0d3 --- /dev/null +++ b/ports/darwin/guts/open.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2011-2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int open(const char *path, int flags, ... { int mode }) + * int rc = -1; + */ + + struct stat buf = { }; + int existed = 1; + int save_errno; + + /* mask out mode bits appropriately */ + mode = mode & ~pseudo_umask; +#ifdef PSEUDO_FORCE_ASYNCH + flags &= ~O_SYNC; +#endif + + /* if a creation has been requested, check whether file exists */ + if (flags & O_CREAT) { + save_errno = errno; + rc = real_stat(path, &buf); + existed = (rc != -1); + if (!existed) + pseudo_debug(PDBGF_FILE, "open_creat: %s -> 0%o\n", path, mode); + errno = save_errno; + } + + /* because we are not actually root, secretly mask in 0600 to the + * underlying mode. The ", 0" is because the only time mode matters + * is if a file is going to be created, in which case it's + * not a directory. + */ + rc = real_open(path, flags, PSEUDO_FS_MODE(mode, 0)); + save_errno = errno; + + if (rc != -1) { + int stat_rc; + stat_rc = real_stat(path, &buf); + + if (stat_rc != -1) { + buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode); + if (!existed) { + real_fchmod(rc, PSEUDO_FS_MODE(mode, 0)); + pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf); + } + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, -1, path, &buf); + } else { + pseudo_debug(PDBGF_CONSISTENCY, "open (fd %d, path %s, flags %d) succeeded, but stat failed (%s).\n", + rc, path, flags, strerror(errno)); + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, -1, path, 0); + } + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/removexattr.c b/ports/darwin/guts/removexattr.c new file mode 100644 index 0000000..c125b1a --- /dev/null +++ b/ports/darwin/guts/removexattr.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int removexattr(const char *path, const char *name, int options) + * int rc = -1; + */ + + rc = shared_removexattr(path, -1, name, options); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/scandir.c b/ports/darwin/guts/scandir.c new file mode 100644 index 0000000..6492b1b --- /dev/null +++ b/ports/darwin/guts/scandir.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_scandir(const char *path, struct dirent ***namelist, int (*filter)(struct dirent *), int (*compar)(const void *, const void *)) { + * int rc = -1; + */ + + rc = real_scandir(path, namelist, filter, compar); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/setxattr.c b/ports/darwin/guts/setxattr.c new file mode 100644 index 0000000..10ffba4 --- /dev/null +++ b/ports/darwin/guts/setxattr.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int setxattr(const char *path, const char *name, const void *value, size_t size, u_int32_t position, int options) + * int rc = -1; + */ + + rc = shared_setxattr(path, -1, name, value, size, position, options); + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/stat.c b/ports/darwin/guts/stat.c new file mode 100644 index 0000000..8a0742c --- /dev/null +++ b/ports/darwin/guts/stat.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int stat(const char *path, struct stat *buf) + * int rc = -1; + */ + + pseudo_msg_t *msg; + int save_errno; + + rc = real_stat(path, buf); + if (rc == -1) { + return rc; + } + save_errno = errno; + + /* query database + * note that symlink canonicalizing is now automatic, so we + * don't need to check for a symlink on this end + */ + msg = pseudo_client_op(OP_STAT, 0, -1, AT_FDCWD, path, buf); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(buf, msg); + } + + errno = save_errno; + +/* return rc; + * } + */ diff --git a/ports/darwin/guts/sync_file_range.c b/ports/darwin/guts/sync_file_range.c new file mode 100644 index 0000000..e0a31a4 --- /dev/null +++ b/ports/darwin/guts/sync_file_range.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int sync_file_range(int fd, off_t offset, off_t nbytes, unsigned int flags) + * int rc = -1; + */ + + rc = real_sync_file_range(fd, offset, nbytes, flags); + +/* return rc; + * } + */ diff --git a/ports/darwin/portdefs.h b/ports/darwin/portdefs.h new file mode 100644 index 0000000..900d98e --- /dev/null +++ b/ports/darwin/portdefs.h @@ -0,0 +1,13 @@ +#define PRELINK_LIBRARIES "DYLD_INSERT_LIBRARIES" +#define PRELINK_PATH "DYLD_LIBRARY_PATH" +#define PSEUDO_STATBUF_64 0 +#define PSEUDO_STATBUF struct stat +#define PSEUDO_LINKPATH_SEPARATOR ":" +/* hackery to allow sneaky things to be done with getgrent() */ +extern int pseudo_host_etc_passwd_fd; +extern int pseudo_host_etc_group_fd; +extern FILE *pseudo_host_etc_passwd_file; +extern FILE *pseudo_host_etc_group_file; +/* Darwin ALWAYS follows symlinks for link(2) */ +#undef PSEUDO_LINK_SYMLINK_BEHAVIOR +#define PSEUDO_LINK_SYMLINK_BEHAVIOR AT_SYMLINK_FOLLOW diff --git a/ports/darwin/preports b/ports/darwin/preports new file mode 100755 index 0000000..a996c69 --- /dev/null +++ b/ports/darwin/preports @@ -0,0 +1,2 @@ +#!/bin/sh +echo "unix" "uids_generic" diff --git a/ports/darwin/pseudo_wrappers.c b/ports/darwin/pseudo_wrappers.c new file mode 100644 index 0000000..e33533e --- /dev/null +++ b/ports/darwin/pseudo_wrappers.c @@ -0,0 +1,606 @@ +/* + * pseudo_wrappers.c, darwin pseudo wrappers + * + * Copyright (c) 2008-2011 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* we need XATTR_NOFOLLOW in scope */ +#include <sys/xattr.h> + +/* shared functionality for the xattr code */ +/* Each of these functions is expecting to get an optional name, and + * a populated statbuf to use for sending messages to the server. + */ + +/* to avoid namespace pollution and such, we now duplicate the + * basic functionality of a POSIX ACL list, as used by libacl or + * the kernel. Documentation was obtained from the headers of libacl + * and from a page or two of _The Linux Programming Interface_, by + * Michael Kerrisk. + */ + +typedef struct { + uint16_t tag; + uint16_t perm; + uint32_t id; +} acl_entry; + +typedef struct { + uint32_t version; + acl_entry entries[]; +} acl_header; + +enum acl_tags { + ACL_UNDEFINED = 0x0, + ACL_USER_OBJ = 0x1, + ACL_USER = 0x2, + ACL_GROUP_OBJ = 0x4, + ACL_GROUP = 0x8, + ACL_MASK = 0x10, + ACL_OTHER = 0x20, +}; + +static const int endian_test = 1; +static const char *endian_tester = (char *) &endian_test; + +static inline int +le16(int x16) { + if (*endian_tester) { + return x16; + } else { + return ((x16 & 0xff) << 8) | ((x16 & 0xff00) >> 8); + } +} + +static inline int +le32(int x32) { + if (*endian_tester) { + return x32; + } else { + return ((x32 & 0xff) << 24) | ((x32 & 0xff00) << 8) | + ((x32 & 0xff0000) >> 8) | ((x32 & 0xff000000) >> 24); + } +} + +/* set mode to match the contents of header. Return non-zero on error. + * On a zero return, mode is a valid posix mode, and *extra is set to + * 1 if any of the entries are not reflected by that mode. On a non-zero + * return, no promises are made about *extra or *mode. + */ +static int +posix_permissions(const acl_header *header, int entries, int *extra, int *mode) { + int acl_seen = 0; + if (le32(header->version) != 2) { + pseudo_diag("Fatal: ACL support no available for header version %d.\n", + le32(header->version)); + return 1; + } + *mode = 0; + *extra = 0; + for (int i = 0; i < entries; ++i) { + const acl_entry *e = &header->entries[i]; + int tag = le16(e->tag); + int perm = le16(e->perm); + acl_seen |= tag; + switch (tag) { + case ACL_USER_OBJ: + *mode = *mode | (perm << 6); + break; + case ACL_GROUP_OBJ: + *mode = *mode | (perm << 3); + break; + case ACL_OTHER: + *mode = *mode | perm; + break; + case ACL_USER: + case ACL_GROUP: + case ACL_MASK: + *extra = *extra + 1; + break; + default: + pseudo_debug(PDBGF_XATTR, "Unknown tag in ACL: 0x%x.\n", + tag); + return 1; + } + } + return 0; +} + +#define RC_AND_BUF \ + int rc; \ + PSEUDO_STATBUF buf; \ + if (path) { \ + rc = base_lstat(path, &buf); \ + } else { \ + rc = base_fstat(fd, &buf); \ + } \ + if (rc == -1) { \ + return rc; \ + } + +/* Note: no profiling implementation yet. */ +static ssize_t shared_getxattr(const char *path, int fd, const char *name, void *value, size_t size, u_int32_t position, int options) { + RC_AND_BUF + + if (!strncmp(name, "com.apple.", 10)) { + if (fd != -1) { + return real_fgetxattr(fd, name, value, size, position, options); + } else { + return real_getxattr(path, name, value, size, position, options); + } + } + + pseudo_debug(PDBGF_XATTR, "getxattr(%s [fd %d], %s)\n", + path ? path : "<no path>", fd, name); + pseudo_msg_t *result = pseudo_client_op(OP_GET_XATTR, 0, fd, -1, path, &buf, name); + if (result->result != RESULT_SUCCEED) { + errno = ENOATTR; + return -1; + } + + if (value) { + pseudo_debug(PDBGF_XATTR, "returned attributes: '%s' (%d bytes)\n", + result->path, result->pathlen); + if (size >= result->pathlen) { + memcpy(value, result->path, result->pathlen); + } else { + memcpy(value, result->path, size); + errno = ERANGE; + } + } + return result->pathlen; +} + +static int shared_setxattr(const char *path, int fd, const char *name, const void *value, size_t size, u_int32_t position, int options) { + RC_AND_BUF + pseudo_op_t op; + + pseudo_debug(PDBGF_XATTR, "setxattr(%s [fd %d], %s => '%.*s')\n", + path ? path : "<no path>", fd, name, (int) size, (char *) value); + + if (!strncmp(name, "com.apple.", 10)) { + if (fd != -1) { + return real_fsetxattr(fd, name, value, size, position, options); + } else { + return real_setxattr(path, name, value, size, position, options); + } + } + + /* this may be a plain chmod */ + if (!strcmp(name, "system.posix_acl_access")) { + int extra; + int mode; + int entries = (size - sizeof(acl_header)) / sizeof(acl_entry); + if (!posix_permissions(value, entries, &extra, &mode)) { + pseudo_debug(PDBGF_XATTR, "posix_acl_access translated to mode %04o. Remaining attribute(s): %d.\n", + mode, extra); + buf.st_mode = mode; + /* we want to actually issue a corresponding chmod, + * as well, or else the file ends up 0600 on the + * host. Using the slightly-less-efficient wrap_chmod + * avoids possible misalignment. + */ + if (path) { + wrap_chmod(path, mode); + } else { + wrap_fchmod(fd, mode); + } + /* we are sneaky, and do not actually record this using + * extended attributes. */ + if (!extra) { + return 0; + } + } + } + + if (options & XATTR_CREATE) { + op = OP_CREATE_XATTR; + } else if (options & XATTR_REPLACE) { + op = OP_REPLACE_XATTR; + } else { + op = OP_SET_XATTR; + } + + pseudo_msg_t *result = pseudo_client_op(op, 0, fd, -1, path, &buf, name, value, size); + + /* we automatically assume success */ + if (op == OP_SET_XATTR) { + return 0; + } + + /* CREATE/REPLACE operations can report failure */ + if (!result || result->result == RESULT_FAIL) { + return -1; + } + + return 0; +} + +static ssize_t shared_listxattr(const char *path, int fd, char *list, size_t size, int options) { + RC_AND_BUF + char extra_name_buf[4096]; + ssize_t real_attr_len; + ssize_t used = 0; + if (fd != -1) { + real_attr_len = real_flistxattr(fd, extra_name_buf, sizeof(extra_name_buf), options); + } else { + real_attr_len = real_listxattr(path, extra_name_buf, sizeof(extra_name_buf), options); + } + pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of FS xattr names, starting '%.*s'\n", + (int) real_attr_len, (int) real_attr_len, extra_name_buf); + + /* we don't care why there aren't any */ + if (real_attr_len < 1) { + real_attr_len = 0; + } + + pseudo_msg_t *result = pseudo_client_op(OP_LIST_XATTR, 0, fd, -1, path, &buf); + + if (result->result != RESULT_SUCCEED && real_attr_len < 1) { + pseudo_debug(PDBGF_XATTR, "listxattr: no success.\n"); + errno = ENOATTR; + return -1; + } + + if (list) { + pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of names, starting '%.*s'\n", + (int) result->pathlen, (int) result->pathlen, result->path); + if (size >= result->pathlen) { + memcpy(list, result->path, result->pathlen); + used = result->pathlen; + } else { + memcpy(list, result->path, size); + used = size; + errno = ERANGE; + } + if (real_attr_len > 0) { + if ((ssize_t) size >= used + real_attr_len) { + memcpy(list + used, extra_name_buf, real_attr_len); + used += real_attr_len; + } else { + memcpy(list + used, extra_name_buf, size - used); + used = size; + errno = ERANGE; + } + + } + } else { + used = real_attr_len + result->pathlen; + } + return used; +} + +static int shared_removexattr(const char *path, int fd, const char *name, int options) { + RC_AND_BUF + + if (!strncmp(name, "com.apple.", 10)) { + if (fd != -1) { + return real_fremovexattr(fd, name, options); + } else { + return real_removexattr(path, name, options); + } + } + + pseudo_msg_t *result = pseudo_client_op(OP_REMOVE_XATTR, 0, fd, -1, path, &buf, name); + + if (result->result != RESULT_SUCCEED) { + /* docs say ENOATTR, but I don't have one */ + errno = ENOENT; + return -1; + } + return 0; +} + + +/* there's no fgetgrent_r or fgetpwent_r in Darwin */ + +#define PLENTY_LONG 2048 +/* the original uid/gid code for Linux was written in terms of the + * fget*ent_r() functions... which Darwin doesn't have. But wait! They're + * actually pretty easy to implement. + */ +int +pseudo_fgetgrent_r(FILE *fp, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { + char linebuf[PLENTY_LONG] = { 0 }; + char *s, *t, *u; + size_t max_members; + char **members; + size_t member = 0; + long started_at = -1; + gid_t gid; + int error = ENOENT; + size_t len; + + /* any early exit should set *gbufp to NULL */ + if (gbufp) + *gbufp = NULL; + + if (!gbuf || !fp || !buf) + goto error_out; + + if (fp == pseudo_host_etc_group_file) { + struct group *g; + pseudo_antimagic(); + g = getgrent(); + pseudo_magic(); + if (g) { + char *s = linebuf; + s += snprintf(linebuf, PLENTY_LONG, + "%s:%s:%ld:", + g->gr_name, + g->gr_passwd, + (long) g->gr_gid); + if (g->gr_mem) { + int i; + for (i = 0; g->gr_mem[i]; ++i) { + s += snprintf(s, + PLENTY_LONG - (s - linebuf), + "%s,", + g->gr_mem[i]); + } + if (s[-1] == ',') + --s; + } + strcpy(s, "\n"); + } else { + goto error_out; + } + } else { + started_at = ftell(fp); + if (started_at == -1) { + goto error_out; + } + s = fgets(linebuf, PLENTY_LONG, fp); + if (!s) { + goto error_out; + } + } + /* fgets will have stored a '\0' if there was no error; if there + * was an error, though, linebuf was initialized to all zeroes so + * the string is null-terminated anyway... + */ + len = strlen(linebuf); + if (len > buflen) { + error = ERANGE; + goto error_out; + } + memcpy(buf, linebuf, len); + /* round up to 8, hope for the best? */ + len = len + 8 + (((unsigned long long) (buf + len)) % 8); + members = (char **) (buf + len); + if (len >= buflen) { + error = ERANGE; + goto error_out; + } + /* this is how many pointers we have room for... */ + max_members = (buflen - len) / sizeof(*members); + + t = buf; + /* yes, I can assume that Darwin has strsep() */ + s = strsep(&t, ":"); + if (!s) { + goto error_out; + } + gbuf->gr_name = s; + s = strsep(&t, ":"); + if (!s) { + goto error_out; + } + gbuf->gr_passwd = s; + s = strsep(&t, ":"); + if (!s) { + goto error_out; + } + gid = (gid_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + gbuf->gr_gid = gid; + + /* now, s points to a comma-separated list of members, which we + * want to stash pointers to in 'members'. + */ + s = strsep(&t, ":"); + t = s; + while ((s = strsep(&t, ",")) != NULL) { + if (*s) { + if (member + 1 > max_members) { + errno = ERANGE; + goto error_out; + } + members[member++] = s; + } + } + if (member + 1 > max_members) { + errno = ERANGE; + goto error_out; + } + members[member++] = NULL; + *gbufp = gbuf; + return 0; + +error_out: + if (started_at != -1) + fseek(fp, started_at, SEEK_SET); + return error; + return -1; +} + +int +pseudo_fgetpwent_r(FILE *fp, struct passwd *pbuf, char *buf, size_t buflen, struct passwd **pbufp) { + char linebuf[PLENTY_LONG] = { 0 }; + char *s, *t, *u; + long started_at = -1; + __darwin_time_t timestamp; + uid_t uid; + gid_t gid; + int error = ENOENT; + size_t len; + + /* any early exit should set *gbufp to NULL */ + if (pbufp) + *pbufp = NULL; + + if (!pbuf || !fp || !buf) + goto error_out; + + if (fp == pseudo_host_etc_passwd_file) { + struct passwd *p; + + pseudo_antimagic(); + p = getpwent(); + pseudo_magic(); + if (p) { + snprintf(linebuf, PLENTY_LONG, + "%s:%s:%ld:%ld:%s:%ld:%ld:%s:%s:%s\n", + p->pw_name, + p->pw_passwd, + (long) p->pw_uid, + (long) p->pw_gid, + p->pw_class, + (long) p->pw_change, + (long) p->pw_expire, + p->pw_gecos, + p->pw_dir, + p->pw_shell); + } else { + goto error_out; + } + } else { + started_at = ftell(fp); + if (started_at == -1) { + goto error_out; + } + s = fgets(linebuf, PLENTY_LONG, fp); + if (!s) { + goto error_out; + } + } + /* fgets will have stored a '\0' if there was no error; if there + * was an error, though, linebuf was initialized to all zeroes so + * the string is null-terminated anyway... + */ + len = strlen(linebuf); + if (len > buflen) { + error = ERANGE; + goto error_out; + } + if (linebuf[len - 1] == '\n') { + linebuf[len - 1] = '\0'; + --len; + } + memcpy(buf, linebuf, len); + + t = buf; + /* yes, I can assume that Darwin has strsep() */ + s = strsep(&t, ":"); + if (!s) { + goto error_out; + } + pbuf->pw_name = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_passwd = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + uid = (uid_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + pbuf->pw_uid = uid; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + gid = (gid_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + pbuf->pw_gid = gid; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_class = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + timestamp = (__darwin_time_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + pbuf->pw_change = timestamp; + + timestamp = (__darwin_time_t) strtol(s, &u, 10); + /* should be a null byte, otherwise we didn't get a valid number */ + if (*u) + goto error_out; + pbuf->pw_expire = timestamp; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_gecos = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_dir = s; + + s = strsep(&t, ":"); + if (!s) + goto error_out; + pbuf->pw_shell = s; + + *pbufp = pbuf; + return 0; + +error_out: + if (started_at != -1) + fseek(fp, started_at, SEEK_SET); + return error; + return -1; +} + +int +pseudo_getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) { + /* note that we don't wrap fgetpwent_r, since there's no path + * references in it. + */ + if (!pseudo_pwd) { + errno = ENOENT; + return -1; + } + return pseudo_fgetpwent_r(pseudo_pwd, pwbuf, buf, buflen, pwbufp); +} + +int +pseudo_getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { + /* note that we don't wrap fgetgrent_r, since there's no path + * references in it. + */ + if (!pseudo_grp) { + errno = ENOENT; + return -1; + } + return pseudo_fgetgrent_r(pseudo_grp, gbuf, buf, buflen, gbufp); +} + diff --git a/ports/darwin/subports b/ports/darwin/subports new file mode 100755 index 0000000..57bdd6d --- /dev/null +++ b/ports/darwin/subports @@ -0,0 +1,3 @@ +#!/bin/sh +# no subports at this time +exit 0 diff --git a/ports/darwin/wrapfuncs.in b/ports/darwin/wrapfuncs.in new file mode 100644 index 0000000..306ad66 --- /dev/null +++ b/ports/darwin/wrapfuncs.in @@ -0,0 +1,27 @@ +# On Darwin, mode_t promotes to int, so you have to use int for va_arg +int open(const char *path, int flags, ...{int mode}); /* flags=0 */ +int stat(const char *path, struct stat *buf); /* inode64=1 */ +int lstat(const char *path, struct stat *buf); /* flags=AT_SYMLINK_NOFOLLOW, inode64=1 */ +int fstat(int fd, struct stat *buf); /* inode64=1 */ +int fcntl(int fd, int cmd, ...{struct flock *lock}); +# just so we know the inums of symlinks +# for emulation of passwd utilities +int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups); +# local color UIDs +int getgrouplist(const char *name, int basegid, int *groups, int *ngroups); +int scandir(const char *path, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)()); +int getgroups(int size, gid_t *list); +int fgetgrent_r(FILE *fp, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp); /* real_func=pseudo_fgetgrent_r */ +int fgetpwent_r(FILE *fp, struct passwd *pbuf, char *buf, size_t buflen, struct passwd **pbufp); /* real_func=pseudo_fgetpwent_r */ +int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp); /* real_func=pseudo_getpwent_r */ +int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp); /* real_func=pseudo_getgrent_r */ +int sync_file_range(int fd, off_t offset, off_t nbytes, unsigned int flags); /* async_skip=0 */ + +ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options); /* flags=(options&XATTR_NOFOLLOW?AT_SYMLINK_NOFOLLOW:0) */ +ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size, u_int32_t position, int options); +int setxattr(const char *path, const char *name, const void *value, size_t size, u_int32_t position, int options); /* flags=0 */ +int fsetxattr(int filedes, const char *name, const void *value, size_t size, u_int32_t position, int options); +ssize_t listxattr(const char *path, char *list, size_t size, int options); /* flags=0 */ +ssize_t flistxattr(int filedes, char *list, size_t size, int options); +int removexattr(const char *path, const char *name, int options); /* flags=0 */ +int fremovexattr(int filedes, const char *name, int options); diff --git a/ports/linux/guts/COPYRIGHT b/ports/linux/guts/COPYRIGHT new file mode 100644 index 0000000..c96e1b1 --- /dev/null +++ b/ports/linux/guts/COPYRIGHT @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ diff --git a/ports/linux/guts/__fxstat.c b/ports/linux/guts/__fxstat.c new file mode 100644 index 0000000..db9716b --- /dev/null +++ b/ports/linux/guts/__fxstat.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int + * wrap___fxstat(int ver, int fd, struct stat *buf) { + * int rc = -1; + */ + + struct stat64 buf64; + /* populate buffer with complete data */ + real___fxstat(ver, fd, buf); + /* obtain fake data */ + rc = wrap___fxstat64(ver, fd, &buf64); + /* overwrite */ + pseudo_stat32_from64(buf, &buf64); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/__fxstat64.c b/ports/linux/guts/__fxstat64.c new file mode 100644 index 0000000..8601904 --- /dev/null +++ b/ports/linux/guts/__fxstat64.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int + * wrap___fxstat64(int ver, int fd, struct stat64 *buf) { + * int rc = -1; + */ + pseudo_msg_t *msg; + int save_errno; + + rc = real___fxstat64(ver, fd, buf); + save_errno = errno; + if (rc == -1) { + return rc; + } + if (ver != _STAT_VER) { + pseudo_debug(PDBGF_CLIENT, "version mismatch: got stat version %d, only supporting %d\n", ver, _STAT_VER); + errno = save_errno; + return rc; + } + msg = pseudo_client_op(OP_FSTAT, 0, fd, -1, 0, buf); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(buf, msg); + } + + errno = save_errno; +/* return rc; + * } + */ diff --git a/ports/linux/guts/__fxstatat.c b/ports/linux/guts/__fxstatat.c new file mode 100644 index 0000000..94c5ff6 --- /dev/null +++ b/ports/linux/guts/__fxstatat.c @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags) { + * int rc = -1; + */ + + struct stat64 buf64; + /* populate buffer with complete data */ +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + if (flags & AT_SYMLINK_NOFOLLOW) { + rc = real___lxstat(ver, path, buf); + } else { + rc = real___xstat(ver, path, buf); + } +#else + real___fxstatat(ver, dirfd, path, buf, flags); +#endif + /* obtain fake data */ + rc = wrap___fxstatat64(ver, dirfd, path, &buf64, flags); + /* overwrite */ + pseudo_stat32_from64(buf, &buf64); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/__fxstatat64.c b/ports/linux/guts/__fxstatat64.c new file mode 100644 index 0000000..62fc3f1 --- /dev/null +++ b/ports/linux/guts/__fxstatat64.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags) { + * int rc = -1; + */ + pseudo_msg_t *msg; + int save_errno; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } +#endif + if (flags & AT_SYMLINK_NOFOLLOW) { +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = real___lxstat64(ver, path, buf); +#else + rc = real___fxstatat64(ver, dirfd, path, buf, flags); +#endif + if (rc == -1) { + return rc; + } + } else { +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = real___xstat64(ver, path, buf); +#else + rc = real___fxstatat64(ver, dirfd, path, buf, flags); +#endif + if (rc == -1) { + return rc; + } + } + save_errno = errno; + + if (ver != _STAT_VER) { + pseudo_debug(PDBGF_CLIENT, "version mismatch: got stat version %d, only supporting %d\n", ver, _STAT_VER); + errno = save_errno; + return rc; + } + + /* query database + * note that symlink canonicalizing is now automatic, so we + * don't need to check for a symlink on this end + */ + msg = pseudo_client_op(OP_STAT, 0, -1, dirfd, path, buf); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(buf, msg); + } + + errno = save_errno; + +/* return rc; + * } + */ diff --git a/ports/linux/guts/__lxstat.c b/ports/linux/guts/__lxstat.c new file mode 100644 index 0000000..32b0301 --- /dev/null +++ b/ports/linux/guts/__lxstat.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___lxstat(int ver, const char *path, struct stat *buf) { + * int rc = -1; + */ + + rc = wrap___fxstatat(ver, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW); + +/* + * } + */ diff --git a/ports/linux/guts/__lxstat64.c b/ports/linux/guts/__lxstat64.c new file mode 100644 index 0000000..ac1f782 --- /dev/null +++ b/ports/linux/guts/__lxstat64.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___lxstat64(int ver, const char *path, struct stat64 *buf) { + * int rc = -1; + */ + + rc = wrap___fxstatat64(ver, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW); + +/* + * } + */ diff --git a/ports/linux/guts/__openat64_2.c b/ports/linux/guts/__openat64_2.c new file mode 100644 index 0000000..e970df7 --- /dev/null +++ b/ports/linux/guts/__openat64_2.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010,2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___openat64_2(int dirfd, const char *path, int flags) { + * int rc = -1; + */ + + rc = wrap_openat(dirfd, path, flags | O_LARGEFILE, 0); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/__openat_2.c b/ports/linux/guts/__openat_2.c new file mode 100644 index 0000000..33ed620 --- /dev/null +++ b/ports/linux/guts/__openat_2.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___openat_2(int dirfd, const char *path, int flags) { + * int rc = -1; + */ + + rc = wrap_openat(dirfd, path, flags, 0); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/__xmknod.c b/ports/linux/guts/__xmknod.c new file mode 100644 index 0000000..fa31b66 --- /dev/null +++ b/ports/linux/guts/__xmknod.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___xmknod(int ver, const char *path, mode_t mode, dev_t *dev) { + * int rc = -1; + */ + + rc = wrap___xmknodat(ver, AT_FDCWD, path, mode, dev); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/__xmknodat.c b/ports/linux/guts/__xmknodat.c new file mode 100644 index 0000000..296918a --- /dev/null +++ b/ports/linux/guts/__xmknodat.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___xmknodat(int ver, int dirfd, const char *path, mode_t mode, dev_t *dev) { + * int rc = -1; + */ + pseudo_msg_t *msg; + struct stat64 buf; + + /* mask out mode bits appropriately */ + mode = mode & ~pseudo_umask; + + /* we don't use underlying call, so _ver is irrelevant to us */ + (void) ver; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + rc = real___xstat64(_STAT_VER, path, &buf); +#else + rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, AT_SYMLINK_NOFOLLOW); +#endif + if (rc != -1) { + /* if we can stat the file, you can't mknod it */ + errno = EEXIST; + return -1; + } + if (!dev) { + errno = EINVAL; + return -1; + } +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = real_open(path, O_CREAT | O_WRONLY | O_EXCL, + PSEUDO_FS_MODE(mode, 0)); +#else + rc = real_openat(dirfd, path, O_CREAT | O_WRONLY | O_EXCL, + PSEUDO_FS_MODE(mode, 0)); +#endif + if (rc == -1) { + return -1; + } + real_fchmod(rc, PSEUDO_FS_MODE(mode, 0)); + real___fxstat64(_STAT_VER, rc, &buf); + /* mknod does not really open the file. We don't have + * to use wrap_close because we've never exposed this file + * descriptor to the client code. + */ + real_close(rc); + + /* mask in the mode type bits again */ + buf.st_mode = (PSEUDO_DB_MODE(buf.st_mode, mode) & 07777) | + (mode & ~07777); + buf.st_rdev = *dev; + msg = pseudo_client_op(OP_MKNOD, 0, -1, dirfd, path, &buf); + if (msg && msg->result != RESULT_SUCCEED) { + errno = EPERM; + rc = -1; + } else { + /* just pretend we worked */ + rc = 0; + } + if (rc == -1) { + int save_errno = errno; +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + real_unlink(path); +#else + real_unlinkat(dirfd, path, AT_SYMLINK_NOFOLLOW); +#endif + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/linux/guts/__xstat.c b/ports/linux/guts/__xstat.c new file mode 100644 index 0000000..ec10abb --- /dev/null +++ b/ports/linux/guts/__xstat.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___xstat(int ver, const char *path, struct stat *buf) { + * int rc = -1; + */ + + rc = wrap___fxstatat(ver, AT_FDCWD, path, buf, 0); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/__xstat64.c b/ports/linux/guts/__xstat64.c new file mode 100644 index 0000000..ed62e7e --- /dev/null +++ b/ports/linux/guts/__xstat64.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap___xstat64(int ver, const char *path, struct stat64 *buf) { + * int rc = -1; + */ + rc = wrap___fxstatat64(ver, AT_FDCWD, path, buf, 0); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/canonicalize_file_name.c b/ports/linux/guts/canonicalize_file_name.c new file mode 100644 index 0000000..9a04f33 --- /dev/null +++ b/ports/linux/guts/canonicalize_file_name.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_canonicalize_file_name(const char *filename) { + * char * rc = NULL; + */ + + rc = wrap_realpath(filename, NULL); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/creat64.c b/ports/linux/guts/creat64.c new file mode 100644 index 0000000..2d2fc27 --- /dev/null +++ b/ports/linux/guts/creat64.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_creat64(const char *path, ...mode_t mode) { + * int rc = -1; + */ + + rc = wrap_openat(AT_FDCWD, path, O_CREAT|O_WRONLY|O_TRUNC|O_LARGEFILE, mode); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/eaccess.c b/ports/linux/guts/eaccess.c new file mode 100644 index 0000000..e2119cc --- /dev/null +++ b/ports/linux/guts/eaccess.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_eaccess(const char *path, int mode) { + * int rc = -1; + */ + + rc = wrap_access(path, mode); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/euidaccess.c b/ports/linux/guts/euidaccess.c new file mode 100644 index 0000000..85433a8 --- /dev/null +++ b/ports/linux/guts/euidaccess.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_euidaccess(const char *path, int mode) { + * int rc = -1; + */ + + rc = wrap_access(path, mode); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/fcntl.c b/ports/linux/guts/fcntl.c new file mode 100644 index 0000000..639fd24 --- /dev/null +++ b/ports/linux/guts/fcntl.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_fcntl(int fd, int cmd, ...struct flock *lock) { + * int rc = -1; + */ + long arg; + int save_errno; + + /* we don't know whether we need lock or arg; grab both, which + * should be safe enough on Linuxy systems. */ + va_start(ap, cmd); + arg = va_arg(ap, long); + va_end(ap); + + switch (cmd) { + case F_DUPFD: +#ifdef F_DUPFD_CLOEXEC + case F_DUPFD_CLOEXEC: +#endif + /* actually do something */ + rc = real_fcntl(fd, cmd, arg); + save_errno = errno; + if (rc != -1) { + pseudo_debug(PDBGF_OP, "fcntl_dup: %d->%d\n", fd, rc); + pseudo_client_op(OP_DUP, 0, fd, rc, 0, 0); + } + errno = save_errno; + break; + /* no argument: */ + case F_GETFD: + case F_GETFL: + case F_GETOWN: + case F_GETSIG: + case F_GETLEASE: + rc = real_fcntl(fd, cmd); + break; + /* long argument */ + case F_SETFD: + case F_SETFL: + case F_SETOWN: + case F_SETSIG: + case F_SETLEASE: + case F_NOTIFY: + rc = real_fcntl(fd, cmd, arg); + break; + /* struct flock * argument */ + case F_GETLK: + case F_SETLK: + case F_SETLKW: + rc = real_fcntl(fd, cmd, lock); + break; +#if defined(F_GETLK64) && (F_GETLK64 != F_GETLK) + /* the cast is safe, all struct pointers must smell the same */ + case F_GETLK64: + case F_SETLK64: + case F_SETLKW64: + rc = real_fcntl(fd, cmd, (struct flock64 *) lock); + break; +#endif + default: + pseudo_diag("unknown fcntl argument %d, assuming long argument.\n", + cmd); + rc = real_fcntl(fd, cmd, arg); + break; + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/fopen64.c b/ports/linux/guts/fopen64.c new file mode 100644 index 0000000..b243345 --- /dev/null +++ b/ports/linux/guts/fopen64.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static FILE * + * wrap_fopen64(const char *path, const char *mode) { + * FILE * rc = 0; + */ + struct stat64 buf; + int save_errno; + + int existed = (real___xstat64(_STAT_VER, path, &buf) != -1); + + rc = real_fopen64(path, mode); + save_errno = errno; + + if (rc) { + int fd = fileno(rc); + + pseudo_debug(PDBGF_FILE, "fopen64 '%s': fd %d <FILE %p>\n", path, fd, (void *) rc); + if (real___fxstat64(_STAT_VER, fd, &buf) != -1) { + if (!existed) { + real_fchmod(fd, PSEUDO_FS_MODE(0666 & ~pseudo_umask, 0)); + pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf); + } + pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, &buf); + } else { + pseudo_debug(PDBGF_CONSISTENCY, "fopen64 (fd %d) succeeded, but fstat failed (%s).\n", + fd, strerror(errno)); + pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, 0); + } + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/linux/guts/freopen64.c b/ports/linux/guts/freopen64.c new file mode 100644 index 0000000..4bad533 --- /dev/null +++ b/ports/linux/guts/freopen64.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static FILE * + * wrap_freopen64(const char *path, const char *mode, FILE *stream) { + * FILE * rc = NULL; + */ + struct stat64 buf; + int save_errno; + int existed = (real___xstat64(_STAT_VER, path, &buf) != -1); + + rc = real_freopen64(path, mode, stream); + save_errno = errno; + + if (rc) { + int fd = fileno(rc); + + pseudo_debug(PDBGF_FILE, "freopen64 '%s': fd %d\n", path, fd); + if (real___fxstat64(_STAT_VER, fd, &buf) != -1) { + if (!existed) { + real_fchmod(fd, PSEUDO_FS_MODE(0666 & ~pseudo_umask, 0)); + pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf); + } + pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, &buf); + } else { + pseudo_debug(PDBGF_FILE, "fopen (fd %d) succeeded, but stat failed (%s).\n", + fd, strerror(errno)); + pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, 0); + } + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/linux/guts/fstat.c b/ports/linux/guts/fstat.c new file mode 100644 index 0000000..2cf2787 --- /dev/null +++ b/ports/linux/guts/fstat.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fstat(int fd, struct stat *buf) + * int rc = -1; + */ + + rc = wrap___fxstat(_STAT_VER, fd, buf); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/fstat64.c b/ports/linux/guts/fstat64.c new file mode 100644 index 0000000..4a759f7 --- /dev/null +++ b/ports/linux/guts/fstat64.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fstat64(int fd, struct stat *buf) + * int rc = -1; + */ + + rc = wrap___fxstat64(_STAT_VER, fd, buf); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/ftw64.c b/ports/linux/guts/ftw64.c new file mode 100644 index 0000000..a375fbf --- /dev/null +++ b/ports/linux/guts/ftw64.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd) { + * int rc = -1; + */ + + rc = real_ftw64(path, fn, nopenfd); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/get_current_dir_name.c b/ports/linux/guts/get_current_dir_name.c new file mode 100644 index 0000000..79f82f9 --- /dev/null +++ b/ports/linux/guts/get_current_dir_name.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_get_current_dir_name(void) { + * char * rc = NULL; + */ + + /* this relies on a Linux extension, but we dutifully + * emulated that extension. + */ + rc = wrap_getcwd(NULL, 0); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/getgrent_r.c b/ports/linux/guts/getgrent_r.c new file mode 100644 index 0000000..b04373d --- /dev/null +++ b/ports/linux/guts/getgrent_r.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010-2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { + * int rc = -1; + */ + + /* note that we don't wrap fgetgrent_r, since there's no path + * references in it. + */ + if (!pseudo_grp) { + errno = ENOENT; + return -1; + } + rc = fgetgrent_r(pseudo_grp, gbuf, buf, buflen, gbufp); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/getgrouplist.c b/ports/linux/guts/getgrouplist.c new file mode 100644 index 0000000..3489ec9 --- /dev/null +++ b/ports/linux/guts/getgrouplist.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups) { + * int rc = -1; + */ + + int found = 0; + int found_group = 0; + char buf[PSEUDO_PWD_MAX]; + struct group grp, *gbuf = &grp; + + setgrent(); + while ((rc = wrap_getgrent_r(gbuf, buf, PSEUDO_PWD_MAX, &gbuf)) == 0) { + int i = 0; + for (i = 0; gbuf->gr_mem[i]; ++i) { + if (!strcmp(gbuf->gr_mem[i], user)) { + if (found < *ngroups) + groups[found] = gbuf->gr_gid; + ++found; + if (gbuf->gr_gid == group) + found_group = 1; + } + } + } + endgrent(); + if (!found_group) { + if (found < *ngroups) + groups[found] = group; + ++found; + } + if (found >= *ngroups) + rc = -1; + else + rc = found; + *ngroups = found; + +/* return rc; + * } + */ diff --git a/ports/linux/guts/getgroups.c b/ports/linux/guts/getgroups.c new file mode 100644 index 0000000..afb9662 --- /dev/null +++ b/ports/linux/guts/getgroups.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getgroups(int size, gid_t *list) { + * int rc = -1; + */ + struct passwd *p = wrap_getpwuid(wrap_getuid()); + int oldsize = size; + + if (p) { + rc = wrap_getgrouplist(p->pw_name, wrap_getgid(), list, &size); + if (oldsize == 0 || size <= oldsize) + rc = size; + } else { + errno = ENOENT; + } + +/* return rc; + * } + */ diff --git a/ports/linux/guts/getpw.c b/ports/linux/guts/getpw.c new file mode 100644 index 0000000..62b44da --- /dev/null +++ b/ports/linux/guts/getpw.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getpw(uid_t uid, char *buf) { + * int rc = -1; + */ + static struct passwd pwd; + static char pwbuf[PSEUDO_PWD_MAX]; + struct passwd *pwp; + + pseudo_diag("warning: unsafe getpw() called. hoping buf has at least %d chars.\n", + PSEUDO_PWD_MAX); + rc = wrap_getpwuid_r(uid, &pwd, pwbuf, PSEUDO_PWD_MAX, &pwp); + /* different error return conventions */ + if (rc != 0) { + errno = rc; + rc = -1; + } else { + snprintf(buf, PSEUDO_PWD_MAX, "%s:%s:%d:%d:%s:%s:%s", + pwd.pw_name, + pwd.pw_passwd, + pwd.pw_uid, + pwd.pw_gid, + pwd.pw_gecos, + pwd.pw_dir, + pwd.pw_shell); + } + +/* return rc; + * } + */ diff --git a/ports/linux/guts/getpwent_r.c b/ports/linux/guts/getpwent_r.c new file mode 100644 index 0000000..4fd9cc0 --- /dev/null +++ b/ports/linux/guts/getpwent_r.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010-2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) { + * int rc = -1; + */ + + /* note that we don't wrap fgetpwent_r, since there's no path + * references in it. + */ + if (!pseudo_pwd) { + errno = ENOENT; + return -1; + } + rc = fgetpwent_r(pseudo_pwd, pwbuf, buf, buflen, pwbufp); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/getresgid.c b/ports/linux/guts/getresgid.c new file mode 100644 index 0000000..13551a4 --- /dev/null +++ b/ports/linux/guts/getresgid.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) { + * int rc = -1; + */ + if (rgid) + *rgid = pseudo_rgid; + if (egid) + *egid = pseudo_egid; + if (sgid) + *sgid = pseudo_sgid; + if (rgid && egid && sgid) { + rc = 0; + } else { + rc = -1; + errno = EFAULT; + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/getresuid.c b/ports/linux/guts/getresuid.c new file mode 100644 index 0000000..2e47520 --- /dev/null +++ b/ports/linux/guts/getresuid.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) { + * int rc = -1; + */ + if (ruid) + *ruid = pseudo_ruid; + if (euid) + *euid = pseudo_euid; + if (suid) + *suid = pseudo_suid; + if (ruid && euid && suid) { + rc = 0; + } else { + rc = -1; + errno = EFAULT; + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/glob64.c b/ports/linux/guts/glob64.c new file mode 100644 index 0000000..ccac6e4 --- /dev/null +++ b/ports/linux/guts/glob64.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_glob64(const char *pattern, int flags, int (*errfunc)(const char *, int), glob64_t *pglob) { + * int rc = -1; + */ + char *rpattern = NULL; + int alloced = 0; + + /* note: no canonicalization */ + if (pattern && (*pattern == '/') && pseudo_chroot_len) { + size_t len = strlen(pattern) + pseudo_chroot_len + 2; + rpattern = malloc(len); + if (!rpattern) { + errno = ENOMEM; + return GLOB_NOSPACE; + } + snprintf(rpattern, len, "%s/%s", pseudo_chroot, pattern); + alloced = 1; + } + + rc = real_glob64(alloced ? rpattern : pattern, flags, errfunc, pglob); + + free(rpattern); + + if (rc == 0) { + unsigned int i; + for (i = 0; i < pglob->gl_pathc; ++i) { + pseudo_dechroot(pglob->gl_pathv[i], (size_t) -1); + } + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/lchown.c b/ports/linux/guts/lchown.c new file mode 100644 index 0000000..4eb1202 --- /dev/null +++ b/ports/linux/guts/lchown.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_lchown(const char *path, uid_t owner, gid_t group) { + */ + + rc = wrap_fchownat(AT_FDCWD, path, owner, group, AT_SYMLINK_NOFOLLOW); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/lckpwdf.c b/ports/linux/guts/lckpwdf.c new file mode 100644 index 0000000..b452ec0 --- /dev/null +++ b/ports/linux/guts/lckpwdf.c @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_lckpwdf(void) { + * int rc = -1; + */ + rc = pseudo_pwd_lck_open(); + if (rc != -1) { + struct flock lck = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 1 + }; + /* I don't really care whether this works. */ + fcntl(rc, F_SETFD, FD_CLOEXEC); + /* Now lock it. */ + alarm(15); /* magic number from man page */ + rc = fcntl(rc, F_SETLKW, &lck); + alarm(0); + if (rc == -1) { + int save_errno = errno; + pseudo_pwd_lck_close(); + errno = save_errno; + } + } + +/* return rc; + * } + */ diff --git a/ports/linux/guts/lstat.c b/ports/linux/guts/lstat.c new file mode 100644 index 0000000..19c202f --- /dev/null +++ b/ports/linux/guts/lstat.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int lstat(const char *path, struct stat *buf) + * int rc = -1; + */ + + rc = wrap___fxstatat(_STAT_VER, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/lstat64.c b/ports/linux/guts/lstat64.c new file mode 100644 index 0000000..94eb60f --- /dev/null +++ b/ports/linux/guts/lstat64.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int lstat64(const char *path, struct stat *buf) + * int rc = -1; + */ + + rc = wrap___fxstatat64(_STAT_VER, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/mkstemp64.c b/ports/linux/guts/mkstemp64.c new file mode 100644 index 0000000..48be612 --- /dev/null +++ b/ports/linux/guts/mkstemp64.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_mkstemp64(char *template) { + * int rc = -1; + */ + struct stat64 buf; + int save_errno; + size_t len; + char *tmp_template; + + if (!template) { + errno = EFAULT; + return 0; + } + + len = strlen(template); + tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW); + + if (!tmp_template) { + errno = ENOENT; + return -1; + } + + rc = real_mkstemp64(tmp_template); + + if (rc != -1) { + save_errno = errno; + + if (real___fxstat64(_STAT_VER, rc, &buf) != -1) { + real_fchmod(rc, PSEUDO_FS_MODE(0600, 0)); + pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf); + pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, &buf); + } else { + pseudo_debug(PDBGF_CONSISTENCY, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n", + rc, strerror(errno)); + pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, 0); + } + errno = save_errno; + } + /* mkstemp only changes the XXXXXX at the end. */ + memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6); +/* return rc; + * } + */ diff --git a/ports/linux/guts/nftw64.c b/ports/linux/guts/nftw64.c new file mode 100644 index 0000000..82571cd --- /dev/null +++ b/ports/linux/guts/nftw64.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag) { + * int rc = -1; + */ + + rc = real_nftw64(path, fn, nopenfd, flag); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/open.c b/ports/linux/guts/open.c new file mode 100644 index 0000000..0a0596c --- /dev/null +++ b/ports/linux/guts/open.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_open(const char *path, int flags, ...mode_t mode) { + * int rc = -1; + */ + + return wrap_openat(AT_FDCWD, path, flags, mode); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/open64.c b/ports/linux/guts/open64.c new file mode 100644 index 0000000..8028ede --- /dev/null +++ b/ports/linux/guts/open64.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010,2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_open64(const char *path, int flags, ...mode_t mode) { + * int rc = -1; + */ + + rc = wrap_openat(AT_FDCWD, path, flags | O_LARGEFILE, mode); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/openat.c b/ports/linux/guts/openat.c new file mode 100644 index 0000000..eb7c0b5 --- /dev/null +++ b/ports/linux/guts/openat.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2008-2010, 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_openat(int dirfd, const char *path, int flags, ...mode_t mode) { + * int rc = -1; + */ + struct stat64 buf; + int existed = 1; + int save_errno; + + /* mask out mode bits appropriately */ + mode = mode & ~pseudo_umask; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } +#endif + +#ifdef PSEUDO_FORCE_ASYNCH + /* Yes, I'm aware that every Linux system I've seen has + * DSYNC and RSYNC being the same value as SYNC. + */ + + flags &= ~(O_SYNC +#ifdef O_DIRECT + | O_DIRECT +#endif +#ifdef O_DSYNC + | O_DSYNC +#endif +#ifdef O_RSYNC + | O_RSYNC +#endif + ); +#endif + + /* if a creation has been requested, check whether file exists */ + if (flags & O_CREAT) { + save_errno = errno; +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = real___xstat64(_STAT_VER, path, &buf); +#else + rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, 0); +#endif + existed = (rc != -1); + if (!existed) + pseudo_debug(PDBGF_FILE, "openat_creat: %s -> 0%o\n", path, mode); + errno = save_errno; + } + + /* because we are not actually root, secretly mask in 0600 to the + * underlying mode. The ", 0" is because the only time mode matters + * is if a file is going to be created, in which case it's + * not a directory. + */ +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = real_open(path, flags, PSEUDO_FS_MODE(mode, 0)); +#else + rc = real_openat(dirfd, path, flags, PSEUDO_FS_MODE(mode, 0)); +#endif + save_errno = errno; + + if (rc != -1) { + int stat_rc; +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + stat_rc = real___xstat64(_STAT_VER, path, &buf); +#else + stat_rc = real___fxstatat64(_STAT_VER, dirfd, path, &buf, 0); +#endif + + if (stat_rc != -1) { + buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode); + if (!existed) { + real_fchmod(rc, PSEUDO_FS_MODE(mode, 0)); + pseudo_client_op(OP_CREAT, 0, -1, dirfd, path, &buf); + } + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, &buf); + } else { + pseudo_debug(PDBGF_FILE, "openat (fd %d, path %d/%s, flags %d) succeeded, but stat failed (%s).\n", + rc, dirfd, path, flags, strerror(errno)); + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, 0); + } + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/linux/guts/openat64.c b/ports/linux/guts/openat64.c new file mode 100644 index 0000000..8dedcbf --- /dev/null +++ b/ports/linux/guts/openat64.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010,2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_openat64(int dirfd, const char *path, int flags, ...mode_t mode) { + * int rc = -1; + */ + + rc = wrap_openat(dirfd, path, flags | O_LARGEFILE, mode); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/scandir.c b/ports/linux/guts/scandir.c new file mode 100644 index 0000000..afcebaf --- /dev/null +++ b/ports/linux/guts/scandir.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_scandir(const char *path, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const void *, const void *)) { + * int rc = -1; + */ + + rc = real_scandir(path, namelist, filter, compar); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/scandir64.c b/ports/linux/guts/scandir64.c new file mode 100644 index 0000000..1317b73 --- /dev/null +++ b/ports/linux/guts/scandir64.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_scandir64(const char *path, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)(const void *, const void *)) { + * int rc = -1; + */ + + rc = real_scandir64(path, namelist, filter, compar); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/setfsgid.c b/ports/linux/guts/setfsgid.c new file mode 100644 index 0000000..0e5a10b --- /dev/null +++ b/ports/linux/guts/setfsgid.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setfsgid(gid_t fsgid) { + * int rc = -1; + */ + if (pseudo_euid == 0 || + pseudo_egid == fsgid || pseudo_rgid == fsgid || pseudo_sgid == fsgid) { + pseudo_fgid = fsgid; + rc = 0; + } else { + rc = -1; + errno = EPERM; + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/setfsuid.c b/ports/linux/guts/setfsuid.c new file mode 100644 index 0000000..e52b65e --- /dev/null +++ b/ports/linux/guts/setfsuid.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setfsuid(uid_t fsuid) { + * int rc = -1; + */ + if (pseudo_euid == 0 || + pseudo_euid == fsuid || pseudo_ruid == fsuid || pseudo_suid == fsuid) { + pseudo_fuid = fsuid; + rc = 0; + } else { + rc = -1; + errno = EPERM; + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/setgroups.c b/ports/linux/guts/setgroups.c new file mode 100644 index 0000000..31b2b57 --- /dev/null +++ b/ports/linux/guts/setgroups.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setgroups(size_t size, const gid_t *list) { + * int rc = -1; + */ + + /* let gcc know we're ignoring these */ + (void) size; + (void) list; + /* you always have all group privileges. we're like magic! */ + rc = 0; + +/* return rc; + * } + */ diff --git a/ports/linux/guts/setresgid.c b/ports/linux/guts/setresgid.c new file mode 100644 index 0000000..2a26405 --- /dev/null +++ b/ports/linux/guts/setresgid.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setresgid(gid_t rgid, gid_t egid, gid_t sgid) { + * int rc = -1; + */ + rc = 0; + if (pseudo_euid != 0 && rgid != (gid_t) -1 && + rgid != pseudo_egid && rgid != pseudo_rgid && rgid != pseudo_sgid) { + rc = -1; + errno = EPERM; + } + if (pseudo_euid != 0 && egid != (gid_t) -1 && + egid != pseudo_egid && egid != pseudo_rgid && egid != pseudo_sgid) { + rc = -1; + errno = EPERM; + } + if (pseudo_euid != 0 && sgid != (gid_t) -1 && + sgid != pseudo_egid && sgid != pseudo_rgid && sgid != pseudo_sgid) { + rc = -1; + errno = EPERM; + } + if (rc != -1) { + if (rgid != (gid_t) -1) + pseudo_rgid = rgid; + if (egid != (gid_t) -1) + pseudo_egid = egid; + if (sgid != (gid_t) -1) + pseudo_sgid = sgid; + pseudo_fgid = pseudo_egid; + pseudo_client_touchuid(); + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/setresuid.c b/ports/linux/guts/setresuid.c new file mode 100644 index 0000000..a0a367f --- /dev/null +++ b/ports/linux/guts/setresuid.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setresuid(uid_t ruid, uid_t euid, uid_t suid) { + * int rc = -1; + */ + rc = 0; + if (pseudo_euid != 0 && ruid != (uid_t) -1 && + ruid != pseudo_euid && ruid != pseudo_ruid && ruid != pseudo_suid) { + rc = -1; + errno = EPERM; + } + if (pseudo_euid != 0 && euid != (uid_t) -1 && + euid != pseudo_euid && euid != pseudo_ruid && euid != pseudo_suid) { + rc = -1; + errno = EPERM; + } + if (pseudo_euid != 0 && suid != (uid_t) -1 && + suid != pseudo_euid && suid != pseudo_ruid && suid != pseudo_suid) { + rc = -1; + errno = EPERM; + } + if (rc != -1) { + if (ruid != (uid_t) -1) + pseudo_ruid = ruid; + if (euid != (uid_t) -1) + pseudo_euid = euid; + if (suid != (uid_t) -1) + pseudo_suid = suid; + pseudo_fuid = pseudo_euid; + pseudo_client_touchuid(); + } +/* return rc; + * } + */ diff --git a/ports/linux/guts/stat.c b/ports/linux/guts/stat.c new file mode 100644 index 0000000..1fe800e --- /dev/null +++ b/ports/linux/guts/stat.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int stat(const char *path, struct stat *buf) + * int rc = -1; + */ + + rc = wrap___fxstatat(_STAT_VER, AT_FDCWD, path, buf, 0); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/stat64.c b/ports/linux/guts/stat64.c new file mode 100644 index 0000000..53dd156 --- /dev/null +++ b/ports/linux/guts/stat64.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int stat64(const char *path, struct stat *buf) + * int rc = -1; + */ + + rc = wrap___fxstatat64(_STAT_VER, AT_FDCWD, path, buf, 0); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/truncate64.c b/ports/linux/guts/truncate64.c new file mode 100644 index 0000000..a798984 --- /dev/null +++ b/ports/linux/guts/truncate64.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_truncate64(const char *path, off64_t length) { + * int rc = -1; + */ + + rc = real_truncate64(path, length); + +/* return rc; + * } + */ diff --git a/ports/linux/guts/ulckpwdf.c b/ports/linux/guts/ulckpwdf.c new file mode 100644 index 0000000..ed6a671 --- /dev/null +++ b/ports/linux/guts/ulckpwdf.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_ulckpwdf(void) { + * int rc = -1; + */ + + /* lock is cleared automatically on close */ + rc = pseudo_pwd_lck_close(); + +/* return rc; + * } + */ diff --git a/ports/linux/newclone/guts/clone.c b/ports/linux/newclone/guts/clone.c new file mode 100644 index 0000000..ee6fc09 --- /dev/null +++ b/ports/linux/newclone/guts/clone.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2008-2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * clone(...) { + * .... + */ + /* because clone() doesn't actually continue in this function, we + * can't check the return and fix up environment variables in the + * child. Instead, we have to create an intermediate function, + * wrap_clone_child, which does this fix up. + */ + + struct clone_args * myargs = malloc(sizeof(struct clone_args)); + + myargs->fn = fn; + myargs->flags = flags; + myargs->arg = arg; + + /* call the real syscall */ + rc = (*real_clone)(wrap_clone_child, child_stack, flags, myargs, pid, tls, ctid); + + /* If we're not sharing memory, we need to free myargs in the parent */ + if (!(flags & CLONE_VM)) + free(myargs); + +/* ... + * return rc; + * } + */ diff --git a/ports/linux/newclone/pseudo_wrappers.c b/ports/linux/newclone/pseudo_wrappers.c new file mode 100644 index 0000000..1fc6c59 --- /dev/null +++ b/ports/linux/newclone/pseudo_wrappers.c @@ -0,0 +1,92 @@ +static int +wrap_clone(int (*fn)(void *), void *child_stack, int flags, void *arg, va_list +ap) { + /* unused */ + (void) fn; + (void) child_stack; + (void) flags; + (void) arg; + (void) ap; + return 0; +} + +struct clone_args { + int (*fn)(void *); + int flags; + void *arg; +}; + +int wrap_clone_child(void *args) { + struct clone_args *clargs = args; + + int (*fn)(void *) = clargs->fn; + int flags = clargs->flags; + void *arg = clargs->arg; + + /* We always free in the client */ + free(clargs); + + if (!(flags & CLONE_VM)) { + pseudo_setupenv(); + if (!pseudo_has_unload(NULL)) { + pseudo_reinit_libpseudo(); + } else { + pseudo_dropenv(); + } + } + + return fn(arg); +} + +int +clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) { + sigset_t saved; + va_list ap; + pid_t *pid; + struct user_desc *tls; + pid_t *ctid; + + int rc = -1; + + if (!pseudo_check_wrappers() || !real_clone) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("clone"); + return rc; + } + + va_start(ap, arg); + pid = va_arg(ap, pid_t *); + tls = va_arg(ap, struct user_desc *); + ctid = va_arg(ap, pid_t *); + va_end(ap); + + pseudo_debug(PDBGF_WRAPPER, "called: clone\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + return -1; + } + + int save_errno; + int save_disabled = pseudo_disabled; + +#include "guts/clone.c" + + if (save_disabled != pseudo_disabled) { + if (pseudo_disabled) { + pseudo_disabled = 0; + pseudo_magic(); + } else { + pseudo_disabled = 1; + pseudo_antimagic(); + } + } + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: clone\n"); + errno = save_errno; + return rc; +} diff --git a/ports/linux/newclone/wrapfuncs.in b/ports/linux/newclone/wrapfuncs.in new file mode 100644 index 0000000..8849310 --- /dev/null +++ b/ports/linux/newclone/wrapfuncs.in @@ -0,0 +1 @@ +int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...); /* hand_wrapped=1 */ diff --git a/ports/linux/noxattr/guts/fgetxattr.c b/ports/linux/noxattr/guts/fgetxattr.c new file mode 100644 index 0000000..9d33643 --- /dev/null +++ b/ports/linux/noxattr/guts/fgetxattr.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size) + * ssize_t rc = -1; + */ + + /* suppress warnings */ + (void) filedes; + (void) name; + (void) value; + (void) size; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/flistxattr.c b/ports/linux/noxattr/guts/flistxattr.c new file mode 100644 index 0000000..77db021 --- /dev/null +++ b/ports/linux/noxattr/guts/flistxattr.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t flistxattr(int filedes, char *list, size_t size) + * ssize_t rc = -1; + */ + + /* suppress warnings */ + (void) filedes; + (void) list; + (void) size; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/fremovexattr.c b/ports/linux/noxattr/guts/fremovexattr.c new file mode 100644 index 0000000..529a9de --- /dev/null +++ b/ports/linux/noxattr/guts/fremovexattr.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fremovexattr(int filedes, const char *name) + * int rc = -1; + */ + + /* suppress warnings */ + (void) filedes; + (void) name; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/fsetxattr.c b/ports/linux/noxattr/guts/fsetxattr.c new file mode 100644 index 0000000..3c56ddd --- /dev/null +++ b/ports/linux/noxattr/guts/fsetxattr.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fsetxattr(int filedes, const char *name, const void *value, size_t size, int flags) + * int rc = -1; + */ + + /* suppress warnings */ + (void) filedes; + (void) name; + (void) value; + (void) size; + (void) flags; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/getxattr.c b/ports/linux/noxattr/guts/getxattr.c new file mode 100644 index 0000000..fe8912d --- /dev/null +++ b/ports/linux/noxattr/guts/getxattr.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t getxattr(const char *pathname, const char *name, void *value, size_t size) + * ssize_t rc = -1; + */ + + /* suppress warnings */ + (void) pathname; + (void) name; + (void) value; + (void) size; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/lgetxattr.c b/ports/linux/noxattr/guts/lgetxattr.c new file mode 100644 index 0000000..404211f --- /dev/null +++ b/ports/linux/noxattr/guts/lgetxattr.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t lgetxattr(const char *pathname, const char *name, void *value, size_t size) + * ssize_t rc = -1; + */ + + /* suppress warnings */ + (void) pathname; + (void) name; + (void) value; + (void) size; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/listxattr.c b/ports/linux/noxattr/guts/listxattr.c new file mode 100644 index 0000000..1b0b5e7 --- /dev/null +++ b/ports/linux/noxattr/guts/listxattr.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t listxattr(const char *pathname, char *list, size_t size) + * ssize_t rc = -1; + */ + + /* suppress warnings */ + (void) pathname; + (void) list; + (void) size; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/llistxattr.c b/ports/linux/noxattr/guts/llistxattr.c new file mode 100644 index 0000000..a33f970 --- /dev/null +++ b/ports/linux/noxattr/guts/llistxattr.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t llistxattr(const char *pathname, char *list, size_t size) + * ssize_t rc = -1; + */ + + /* suppress warnings */ + (void) pathname; + (void) list; + (void) size; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/lremovexattr.c b/ports/linux/noxattr/guts/lremovexattr.c new file mode 100644 index 0000000..38429da --- /dev/null +++ b/ports/linux/noxattr/guts/lremovexattr.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int lremovexattr(const char *pathname, const char *name) + * int rc = -1; + */ + + /* suppress warnings */ + (void) pathname; + (void) name; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/lsetxattr.c b/ports/linux/noxattr/guts/lsetxattr.c new file mode 100644 index 0000000..140ae8d --- /dev/null +++ b/ports/linux/noxattr/guts/lsetxattr.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int lsetxattr(const char *pathname, const char *name, const void *value, size_t size, int flags) + * int rc = -1; + */ + + /* suppress warnings */ + (void) pathname; + (void) name; + (void) value; + (void) size; + (void) flags; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/removexattr.c b/ports/linux/noxattr/guts/removexattr.c new file mode 100644 index 0000000..cd7f486 --- /dev/null +++ b/ports/linux/noxattr/guts/removexattr.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int removexattr(const char *pathname, const char *name) + * int rc = -1; + */ + + /* suppress warnings */ + (void) pathname; + (void) name; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/guts/setxattr.c b/ports/linux/noxattr/guts/setxattr.c new file mode 100644 index 0000000..de2de98 --- /dev/null +++ b/ports/linux/noxattr/guts/setxattr.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int setxattr(const char *pathname, const char *name, const void *value, size_t size, int flags) + * int rc = -1; + */ + + /* suppress warnings */ + (void) pathname; + (void) name; + (void) value; + (void) size; + (void) flags; + errno = ENOTSUP; + +/* return rc; + * } + */ diff --git a/ports/linux/noxattr/wrapfuncs.in b/ports/linux/noxattr/wrapfuncs.in new file mode 100644 index 0000000..de22ae1 --- /dev/null +++ b/ports/linux/noxattr/wrapfuncs.in @@ -0,0 +1,14 @@ +# we use "pathname" to avoid canonicalizing paths, because these functions are +# unimplemented +ssize_t getxattr(const char *pathname, const char *name, void *value, size_t size); +ssize_t lgetxattr(const char *pathname, const char *name, void *value, size_t size); +ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size); +ssize_t listxattr(const char *pathname, char *list, size_t size); +ssize_t llistxattr(const char *pathname, char *list, size_t size); +ssize_t flistxattr(int filedes, char *list, size_t size); +int setxattr(const char *pathname, const char *name, const void *value, size_t size, int flags); +int lsetxattr(const char *pathname, const char *name, const void *value, size_t size, int flags); +int fsetxattr(int filedes, const char *name, const void *value, size_t size, int flags); +int removexattr(const char *pathname, const char *name); +int lremovexattr(const char *pathname, const char *name); +int fremovexattr(int filedes, const char *name); diff --git a/ports/linux/oldclone/guts/clone.c b/ports/linux/oldclone/guts/clone.c new file mode 100644 index 0000000..c6771e5 --- /dev/null +++ b/ports/linux/oldclone/guts/clone.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2008-2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * clone(...) { + * .... + */ + /* because clone() doesn't actually continue in this function, we + * can't check the return and fix up environment variables in the + * child. Instead, we have to create an intermediate function, + * wrap_clone_child, which does this fix up. + */ + + struct clone_args * myargs = malloc(sizeof(struct clone_args)); + + myargs->fn = fn; + myargs->flags = flags; + myargs->arg = arg; + + /* call the real syscall */ + rc = (*real_clone)(wrap_clone_child, child_stack, flags, myargs); + + /* If we're not sharing memory, we need to free myargs in the parent */ + if (!(flags & CLONE_VM)) + free(myargs); + +/* ... + * return rc; + * } + */ diff --git a/ports/linux/oldclone/pseudo_wrappers.c b/ports/linux/oldclone/pseudo_wrappers.c new file mode 100644 index 0000000..1720dfb --- /dev/null +++ b/ports/linux/oldclone/pseudo_wrappers.c @@ -0,0 +1,76 @@ +static int +wrap_clone(int (*fn)(void *), void *child_stack, int flags, void *arg) { + /* unused */ + return 0; +} + +struct clone_args { + int (*fn)(void *); + int flags; + void *arg; +}; + +int wrap_clone_child(void *args) { + struct clone_args *clargs = args; + + int (*fn)(void *) = clargs->fn; + int flags = clargs->flags; + void *arg = clargs->arg; + + /* We always free in the client */ + free(clargs); + + if (!(flags & CLONE_VM)) { + pseudo_setupenv(); + if (!pseudo_has_unload(NULL)) { + pseudo_reinit_libpseudo(); + } else { + pseudo_dropenv(); + } + } + + return fn(arg); +} + +int +clone(int (*fn)(void *), void *child_stack, int flags, void *arg) { + sigset_t saved; + + int rc = -1; + + if (!pseudo_check_wrappers() || !real_clone) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("clone"); + return rc; + } + + pseudo_debug(PDBGF_WRAPPER, "called: clone\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + return -1; + } + + int save_errno; + int save_disabled = pseudo_disabled; + +#include "guts/clone.c" + + if (save_disabled != pseudo_disabled) { + if (pseudo_disabled) { + pseudo_disabled = 0; + pseudo_magic(); + } else { + pseudo_disabled = 1; + pseudo_antimagic(); + } + } + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "completed: clone\n"); + errno = save_errno; + return rc; +} diff --git a/ports/linux/oldclone/wrapfuncs.in b/ports/linux/oldclone/wrapfuncs.in new file mode 100644 index 0000000..c915376 --- /dev/null +++ b/ports/linux/oldclone/wrapfuncs.in @@ -0,0 +1 @@ +int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); /* hand_wrapped=1 */ diff --git a/ports/linux/portdefs.h b/ports/linux/portdefs.h new file mode 100644 index 0000000..f0a0e40 --- /dev/null +++ b/ports/linux/portdefs.h @@ -0,0 +1,27 @@ +#define PRELINK_LIBRARIES "LD_PRELOAD" +#define PRELINK_PATH "LD_LIBRARY_PATH" +#define PSEUDO_STATBUF_64 1 +#define PSEUDO_STATBUF struct stat64 +#define PSEUDO_LINKPATH_SEPARATOR " " +/* Linux NEVER follows symlinks for link(2)... except on old kernels + * I don't care about. + */ +#undef PSEUDO_LINK_SYMLINK_BEHAVIOR +/* Note: 0, here, really means AT_SYMLINK_NOFOLLOW, but specifying that + * causes errors; you have to leave it empty or specify AT_SYMLINK_FOLLOW. + */ +#define PSEUDO_LINK_SYMLINK_BEHAVIOR 0 + +/* There were symbol changes that can cause the linker to request + * newer versions of glibc, which causes problems occasionally on + * older hosts if pseudo is built against a newer glibc and then + * run with an older one. Sometimes we can just avoid the symbols, + * but memcpy's pretty hard to get away from. + */ +#define GLIBC_COMPAT_SYMBOL(sym, ver) __asm(".symver " #sym "," #sym "@GLIBC_" #ver) + +#ifdef __amd64__ +GLIBC_COMPAT_SYMBOL(memcpy,2.2.5); +#elif defined(__i386__) +GLIBC_COMPAT_SYMBOL(memcpy,2.0); +#endif diff --git a/ports/linux/preports b/ports/linux/preports new file mode 100755 index 0000000..a996c69 --- /dev/null +++ b/ports/linux/preports @@ -0,0 +1,2 @@ +#!/bin/sh +echo "unix" "uids_generic" diff --git a/ports/linux/pseudo_wrappers.c b/ports/linux/pseudo_wrappers.c new file mode 100644 index 0000000..26b29b0 --- /dev/null +++ b/ports/linux/pseudo_wrappers.c @@ -0,0 +1,33 @@ +/* the unix port wants to know that real_stat() and + * friends exist. So they do. And because the Linux + * port really uses stat64 for those... + */ +int +pseudo_stat(const char *path, struct stat *buf) { + return real___xstat(_STAT_VER, path, buf); +} + +int +pseudo_lstat(const char *path, struct stat *buf) { + return real___lxstat(_STAT_VER, path, buf); +} + +int +pseudo_fstat(int fd, struct stat *buf) { + return real___fxstat(_STAT_VER, fd, buf); +} + +int +pseudo_stat64(const char *path, struct stat64 *buf) { + return real___xstat64(_STAT_VER, path, buf); +} + +int +pseudo_lstat64(const char *path, struct stat64 *buf) { + return real___lxstat64(_STAT_VER, path, buf); +} + +int +pseudo_fstat64(int fd, struct stat64 *buf) { + return real___fxstat64(_STAT_VER, fd, buf); +} diff --git a/ports/linux/subports b/ports/linux/subports new file mode 100755 index 0000000..507794e --- /dev/null +++ b/ports/linux/subports @@ -0,0 +1,43 @@ +#!/bin/sh +found=false +printf >&2 "Checking for old/new clone mechanics... " +cat > dummy.c <<EOF +#include <sched.h> +int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) { return 0; } +EOF +if ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then + rm -f dummy.c dummy.o + echo >&2 "New clone." + echo "linux/newclone" + found=true +fi +cat > dummy.c <<EOF +#include <sched.h> +int clone(int (*fn)(void *), void *child_stack, int flags, void *arg) { return 0; } +EOF +if ! $found && ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then + rm -f dummy.c dummy.o + echo >&2 "Old clone." + echo "linux/oldclone" + found=true +fi +rm -f dummy.c dummy.o +if ! $found; then + echo >&2 "Can't tell, omitting clone(2) support." +fi + +if $port_xattr; then + cat > dummy.c <<EOF +#include <sys/types.h> +#include <attr/xattr.h> +int i; +EOF + if ! ${CC} -c -o dummy.o dummy.c >/dev/null 2>&1; then + echo >&2 "Warning: Can't compile trivial program using <attr/xattr.h>". + echo >&2 " xattr support will require that header." + fi + echo "linux/xattr" +else + echo "linux/noxattr" +fi +rm -f dummy.c dummy.o diff --git a/ports/linux/wrapfuncs.in b/ports/linux/wrapfuncs.in new file mode 100644 index 0000000..3b8955a --- /dev/null +++ b/ports/linux/wrapfuncs.in @@ -0,0 +1,53 @@ +int open(const char *path, int flags, ...{mode_t mode}); /* flags=0 */ +char *get_current_dir_name(void); +int __xstat(int ver, const char *path, struct stat *buf); +int __lxstat(int ver, const char *path, struct stat *buf); /* flags=AT_SYMLINK_NOFOLLOW */ +int __fxstat(int ver, int fd, struct stat *buf); +int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW */ +int __fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags); +int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); +int __openat_2(int dirfd, const char *path, int flags); +int __xmknod(int ver, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW */ +int __xmknodat(int ver, int dirfd, const char *path, mode_t mode, dev_t *dev); /* flags=AT_SYMLINK_NOFOLLOW */ +int fcntl(int fd, int cmd, ...{struct flock *lock}); +# just so we know the inums of symlinks +char *canonicalize_file_name(const char *filename); +int eaccess(const char *path, int mode); +int open64(const char *path, int flags, ...{mode_t mode}); /* flags=0 */ +int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=0 */ +int __openat64_2(int dirfd, const char *path, int flags); /* flags=0 */ +int creat64(const char *path, mode_t mode); +int stat(const char *path, struct stat *buf); /* real_func=pseudo_stat */ +int lstat(const char *path, struct stat *buf); /* real_func=pseudo_lstat, flags=AT_SYMLINK_NOFOLLOW */ +int fstat(int fd, struct stat *buf); /* real_func=pseudo_fstat */ +int stat64(const char *path, struct stat64 *buf); /* real_func=pseudo_stat64 */ +int lstat64(const char *path, struct stat64 *buf); /* real_func=pseudo_lstat64, flags=AT_SYMLINK_NOFOLLOW */ +int fstat64(int fd, struct stat64 *buf); /* real_func=pseudo_fstat64 */ +int __xstat64(int ver, const char *path, struct stat64 *buf); +int __lxstat64(int ver, const char *path, struct stat64 *buf); /* flags=AT_SYMLINK_NOFOLLOW */ +int __fxstat64(int ver, int fd, struct stat64 *buf); +int __fxstatat64(int ver, int dirfd, const char *path, struct stat64 *buf, int flags); +FILE *fopen64(const char *path, const char *mode); +int nftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int, struct FTW *), int nopenfd, int flag); +FILE *freopen64(const char *path, const char *mode, FILE *stream); +int ftw64(const char *path, int (*fn)(const char *, const struct stat64 *, int), int nopenfd); +int glob64(const char *pattern, int flags, int (*errfunc)(const char *, int), glob64_t *pglob); +int scandir64(const char *path, struct dirent64 ***namelist, int (*filter)(const struct dirent64 *), int (*compar)()); +int truncate64(const char *path, off64_t length); +int mkstemp64(char *template); /* flags=AT_SYMLINK_NOFOLLOW */ +int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups); +int setgroups(size_t size, const gid_t *list); +int setfsgid(gid_t fsgid); +int setfsuid(uid_t fsuid); +int setresgid(gid_t rgid, gid_t egid, gid_t sgid); +int setresuid(uid_t ruid, uid_t euid, uid_t suid); +int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid); +int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid); +int scandir(const char *path, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)()); +int getgroups(int size, gid_t *list); +int lckpwdf(void); +int ulckpwdf(void); +int euidaccess(const char *path, int mode); +int getpw(uid_t uid, char *buf); +int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp); +int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp); diff --git a/ports/linux/xattr/guts/fgetxattr.c b/ports/linux/xattr/guts/fgetxattr.c new file mode 100644 index 0000000..ae8c3a3 --- /dev/null +++ b/ports/linux/xattr/guts/fgetxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size) + * ssize_t rc = -1; + */ + rc = shared_getxattr(NULL, filedes, name, value, size); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/flistxattr.c b/ports/linux/xattr/guts/flistxattr.c new file mode 100644 index 0000000..cdd9454 --- /dev/null +++ b/ports/linux/xattr/guts/flistxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t flistxattr(int filedes, char *list, size_t size) + * ssize_t rc = -1; + */ + rc = shared_listxattr(NULL, filedes, list, size); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/fremovexattr.c b/ports/linux/xattr/guts/fremovexattr.c new file mode 100644 index 0000000..a029d2c --- /dev/null +++ b/ports/linux/xattr/guts/fremovexattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fremovexattr(int filedes, const char *name) + * int rc = -1; + */ + rc = shared_removexattr(NULL, filedes, name); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/fsetxattr.c b/ports/linux/xattr/guts/fsetxattr.c new file mode 100644 index 0000000..376cf08 --- /dev/null +++ b/ports/linux/xattr/guts/fsetxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fsetxattr(int filedes, const char *name, const void *value, size_t size, int xflags) + * int rc = -1; + */ + rc = shared_setxattr(NULL, filedes, name, value, size, xflags); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/getxattr.c b/ports/linux/xattr/guts/getxattr.c new file mode 100644 index 0000000..7bd2bf5 --- /dev/null +++ b/ports/linux/xattr/guts/getxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t getxattr(const char *path, const char *name, void *value, size_t size) + * ssize_t rc = -1; + */ + rc = shared_getxattr(path, -1, name, value, size); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/lgetxattr.c b/ports/linux/xattr/guts/lgetxattr.c new file mode 100644 index 0000000..675d3da --- /dev/null +++ b/ports/linux/xattr/guts/lgetxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size) + * ssize_t rc = -1; + */ + rc = shared_getxattr(path, -1, name, value, size); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/listxattr.c b/ports/linux/xattr/guts/listxattr.c new file mode 100644 index 0000000..0decf71 --- /dev/null +++ b/ports/linux/xattr/guts/listxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t listxattr(const char *path, char *list, size_t size) + * ssize_t rc = -1; + */ + rc = shared_listxattr(path, -1, list, size); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/llistxattr.c b/ports/linux/xattr/guts/llistxattr.c new file mode 100644 index 0000000..9934256 --- /dev/null +++ b/ports/linux/xattr/guts/llistxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ssize_t llistxattr(const char *path, char *list, size_t size) + * ssize_t rc = -1; + */ + rc = shared_listxattr(path, -1, list, size); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/lremovexattr.c b/ports/linux/xattr/guts/lremovexattr.c new file mode 100644 index 0000000..1f39788 --- /dev/null +++ b/ports/linux/xattr/guts/lremovexattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int lremovexattr(const char *path, const char *name) + * int rc = -1; + */ + rc = shared_removexattr(path, -1, name); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/lsetxattr.c b/ports/linux/xattr/guts/lsetxattr.c new file mode 100644 index 0000000..9fe35bc --- /dev/null +++ b/ports/linux/xattr/guts/lsetxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int lsetxattr(const char *path, const char *name, const void *value, size_t size, int xflags) + * int rc = -1; + */ + rc = shared_setxattr(path, -1, name, value, size, xflags); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/removexattr.c b/ports/linux/xattr/guts/removexattr.c new file mode 100644 index 0000000..0d4d8e3 --- /dev/null +++ b/ports/linux/xattr/guts/removexattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int removexattr(const char *path, const char *name) + * int rc = -1; + */ + rc = shared_removexattr(path, -1, name); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/guts/setxattr.c b/ports/linux/xattr/guts/setxattr.c new file mode 100644 index 0000000..bace0d8 --- /dev/null +++ b/ports/linux/xattr/guts/setxattr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int setxattr(const char *path, const char *name, const void *value, size_t size, int xflags) + * int rc = -1; + */ + rc = shared_setxattr(path, -1, name, value, size, xflags); + +/* return rc; + * } + */ diff --git a/ports/linux/xattr/portdefs.h b/ports/linux/xattr/portdefs.h new file mode 100644 index 0000000..367ca60 --- /dev/null +++ b/ports/linux/xattr/portdefs.h @@ -0,0 +1,2 @@ +#include <attr/xattr.h> +#include <stdint.h> diff --git a/ports/linux/xattr/pseudo_wrappers.c b/ports/linux/xattr/pseudo_wrappers.c new file mode 100644 index 0000000..46bc053 --- /dev/null +++ b/ports/linux/xattr/pseudo_wrappers.c @@ -0,0 +1,233 @@ +/* shared functionality for the xattr code */ +/* Each of these functions is expecting to get an optional name, and + * a populated statbuf to use for sending messages to the server. + */ + +/* to avoid namespace pollution and such, we now duplicate the + * basic functionality of a POSIX ACL list, as used by libacl or + * the kernel. Documentation was obtained from the headers of libacl + * and from a page or two of _The Linux Programming Interface_, by + * Michael Kerrisk. + */ + +typedef struct { + uint16_t tag; + uint16_t perm; + uint32_t id; +} acl_entry; + +typedef struct { + uint32_t version; + acl_entry entries[]; +} acl_header; + +enum acl_tags { + ACL_UNDEFINED = 0x0, + ACL_USER_OBJ = 0x1, + ACL_USER = 0x2, + ACL_GROUP_OBJ = 0x4, + ACL_GROUP = 0x8, + ACL_MASK = 0x10, + ACL_OTHER = 0x20, +}; + +static const int endian_test = 1; +static const char *endian_tester = (char *) &endian_test; + +static inline int +le16(int x16) { + if (*endian_tester) { + return x16; + } else { + return ((x16 & 0xff) << 8) | ((x16 & 0xff00) >> 8); + } +} + +static inline int +le32(int x32) { + if (*endian_tester) { + return x32; + } else { + return ((x32 & 0xff) << 24) | ((x32 & 0xff00) << 8) | + ((x32 & 0xff0000) >> 8) | ((x32 & 0xff000000) >> 24); + } +} + +/* set mode to match the contents of header. Return non-zero on error. + * On a zero return, mode is a valid posix mode, and *extra is set to + * 1 if any of the entries are not reflected by that mode. On a non-zero + * return, no promises are made about *extra or *mode. + */ +static int +posix_permissions(const acl_header *header, int entries, int *extra, int *mode) { + int acl_seen = 0; + if (le32(header->version) != 2) { + pseudo_diag("Fatal: ACL support no available for header version %d.\n", + le32(header->version)); + return 1; + } + *mode = 0; + *extra = 0; + for (int i = 0; i < entries; ++i) { + const acl_entry *e = &header->entries[i]; + int tag = le16(e->tag); + int perm = le16(e->perm); + acl_seen |= tag; + switch (tag) { + case ACL_USER_OBJ: + *mode = *mode | (perm << 6); + break; + case ACL_GROUP_OBJ: + *mode = *mode | (perm << 3); + break; + case ACL_OTHER: + *mode = *mode | perm; + break; + case ACL_USER: + case ACL_GROUP: + case ACL_MASK: + *extra = *extra + 1; + break; + default: + pseudo_debug(PDBGF_XATTR, "Unknown tag in ACL: 0x%x.\n", + tag); + return 1; + } + } + return 0; +} + +#define RC_AND_BUF \ + int rc; \ + PSEUDO_STATBUF buf; \ + if (path) { \ + rc = base_lstat(path, &buf); \ + } else { \ + rc = base_fstat(fd, &buf); \ + } \ + if (rc == -1) { \ + return rc; \ + } + +static ssize_t shared_getxattr(const char *path, int fd, const char *name, void *value, size_t size) { + RC_AND_BUF + + pseudo_debug(PDBGF_XATTR, "getxattr(%s [fd %d], %s)\n", + path ? path : "<no path>", fd, name); + pseudo_msg_t *result = pseudo_client_op(OP_GET_XATTR, 0, fd, -1, path, &buf, name); + if (result->result != RESULT_SUCCEED) { + errno = ENOATTR; + return -1; + } + + if (value) { + pseudo_debug(PDBGF_XATTR, "returned attributes: '%s' (%d bytes)\n", + result->path, result->pathlen); + if (size >= result->pathlen) { + memcpy(value, result->path, result->pathlen); + } else { + memcpy(value, result->path, size); + errno = ERANGE; + } + } + return result->pathlen; +} + +static int shared_setxattr(const char *path, int fd, const char *name, const void *value, size_t size, int flags) { + RC_AND_BUF + pseudo_op_t op; + + pseudo_debug(PDBGF_XATTR, "setxattr(%s [fd %d], %s => '%.*s')\n", + path ? path : "<no path>", fd, name, (int) size, (char *) value); + + /* this may be a plain chmod */ + if (!strcmp(name, "system.posix_acl_access")) { + int extra; + int mode; + int entries = (size - sizeof(acl_header)) / sizeof(acl_entry); + if (!posix_permissions(value, entries, &extra, &mode)) { + pseudo_debug(PDBGF_XATTR, "posix_acl_access translated to mode %04o. Remaining attribute(s): %d.\n", + mode, extra); + buf.st_mode = mode; + /* we want to actually issue a corresponding chmod, + * as well, or else the file ends up 0600 on the + * host. Using the slightly-less-efficient wrap_chmod + * avoids possible misalignment. + */ + if (path) { + wrap_chmod(path, mode); + } else { + wrap_fchmod(fd, mode); + } + /* we are sneaky, and do not actually record this using + * extended attributes. */ + if (!extra) { + return 0; + } + } + } + if (!strcmp(name, "user.pseudo_data")) { + pseudo_debug(PDBGF_XATTR | PDBGF_XATTRDB, "user.pseudo_data xattribute does not get to go in database.\n"); + return -1; + } + + switch (flags) { + case XATTR_CREATE: + op = OP_CREATE_XATTR; + break; + case XATTR_REPLACE: + op = OP_REPLACE_XATTR; + break; + default: + op = OP_SET_XATTR; + break; + } + + pseudo_msg_t *result = pseudo_client_op(op, 0, fd, -1, path, &buf, name, value, size); + + /* we automatically assume success */ + if (op == OP_SET_XATTR) { + return 0; + } + + /* CREATE/REPLACE operations can report failure */ + if (!result || result->result == RESULT_FAIL) { + return -1; + } + + return 0; +} + +static ssize_t shared_listxattr(const char *path, int fd, char *list, size_t size) { + RC_AND_BUF + pseudo_msg_t *result = pseudo_client_op(OP_LIST_XATTR, 0, fd, -1, path, &buf); + if (result->result != RESULT_SUCCEED) { + pseudo_debug(PDBGF_XATTR, "listxattr: no success.\n"); + errno = ENOATTR; + return -1; + } + if (list) { + pseudo_debug(PDBGF_XATTR, "listxattr: %d bytes of names, starting '%.*s'\n", + (int) result->pathlen, (int) result->pathlen, result->path); + if (size >= result->pathlen) { + memcpy(list, result->path, result->pathlen); + } else { + memcpy(list, result->path, size); + errno = ERANGE; + } + } + return result->pathlen; +} + +static int shared_removexattr(const char *path, int fd, const char *name) { + RC_AND_BUF + pseudo_msg_t *result = pseudo_client_op(OP_REMOVE_XATTR, 0, fd, -1, path, &buf, name); + + if (result->result != RESULT_SUCCEED) { + /* docs say ENOATTR, but I don't have one */ + errno = ENOENT; + return -1; + } + return 0; +} + diff --git a/ports/linux/xattr/wrapfuncs.in b/ports/linux/xattr/wrapfuncs.in new file mode 100644 index 0000000..c37f78a --- /dev/null +++ b/ports/linux/xattr/wrapfuncs.in @@ -0,0 +1,12 @@ +ssize_t getxattr(const char *path, const char *name, void *value, size_t size); /* flags=0 */ +ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size); /* flags=AT_SYMLINK_NOFOLLOW */ +ssize_t fgetxattr(int filedes, const char *name, void *value, size_t size); +int setxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=0 */ +int lsetxattr(const char *path, const char *name, const void *value, size_t size, int xflags); /* flags=AT_SYMLINK_NOFOLLOW */ +int fsetxattr(int filedes, const char *name, const void *value, size_t size, int xflags); +ssize_t listxattr(const char *path, char *list, size_t size); /* flags=0 */ +ssize_t llistxattr(const char *path, char *list, size_t size); /* flags=AT_SYMLINK_NOFOLLOW */ +ssize_t flistxattr(int filedes, char *list, size_t size); +int removexattr(const char *path, const char *name); /* flags=0 */ +int lremovexattr(const char *path, const char *name); /* flags=AT_SYMLINK_NOFOLLOW */ +int fremovexattr(int filedes, const char *name); diff --git a/ports/uids_generic/guts/COPYRIGHT b/ports/uids_generic/guts/COPYRIGHT new file mode 100644 index 0000000..c96e1b1 --- /dev/null +++ b/ports/uids_generic/guts/COPYRIGHT @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ diff --git a/ports/uids_generic/guts/endgrent.c b/ports/uids_generic/guts/endgrent.c new file mode 100644 index 0000000..843cad0 --- /dev/null +++ b/ports/uids_generic/guts/endgrent.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static void + * wrap_endgrent(void) { + * + */ + + pseudo_grp_close(); + +/* return; + * } + */ diff --git a/ports/uids_generic/guts/endpwent.c b/ports/uids_generic/guts/endpwent.c new file mode 100644 index 0000000..f76cf10 --- /dev/null +++ b/ports/uids_generic/guts/endpwent.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static void + * wrap_endpwent(void) { + * + */ + + pseudo_pwd_close(); + +/* return; + * } + */ diff --git a/ports/uids_generic/guts/getegid.c b/ports/uids_generic/guts/getegid.c new file mode 100644 index 0000000..7c14f48 --- /dev/null +++ b/ports/uids_generic/guts/getegid.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static gid_t + * wrap_getegid(void) { + * gid_t rc = 0; + */ + + rc = pseudo_egid; + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/geteuid.c b/ports/uids_generic/guts/geteuid.c new file mode 100644 index 0000000..1745e13 --- /dev/null +++ b/ports/uids_generic/guts/geteuid.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static uid_t + * wrap_geteuid(void) { + * uid_t rc = 0; + */ + + rc = pseudo_euid; + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getgid.c b/ports/uids_generic/guts/getgid.c new file mode 100644 index 0000000..ca8bad3 --- /dev/null +++ b/ports/uids_generic/guts/getgid.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static gid_t + * wrap_getgid(void) { + * gid_t rc = 0; + */ + + rc = pseudo_rgid; + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getgrent.c b/ports/uids_generic/guts/getgrent.c new file mode 100644 index 0000000..e8e07f5 --- /dev/null +++ b/ports/uids_generic/guts/getgrent.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static struct group * + * wrap_getgrent(void) { + * struct group * rc = NULL; + */ + static struct group grp; + static char grbuf[PSEUDO_PWD_MAX]; + int r_rc; + + r_rc = wrap_getgrent_r(&grp, grbuf, PSEUDO_PWD_MAX, &rc); + /* different error return conventions */ + if (r_rc != 0) { + errno = r_rc; + } + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getgrgid.c b/ports/uids_generic/guts/getgrgid.c new file mode 100644 index 0000000..c1824e7 --- /dev/null +++ b/ports/uids_generic/guts/getgrgid.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static struct group * + * wrap_getgrgid(gid_t gid) { + * struct group * rc = NULL; + */ + static struct group grp; + static char grbuf[PSEUDO_PWD_MAX]; + int r_rc; + + r_rc = wrap_getgrgid_r(gid, &grp, grbuf, PSEUDO_PWD_MAX, &rc); + /* different error return conventions */ + if (r_rc != 0) { + errno = r_rc; + } + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getgrgid_r.c b/ports/uids_generic/guts/getgrgid_r.c new file mode 100644 index 0000000..b043995 --- /dev/null +++ b/ports/uids_generic/guts/getgrgid_r.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getgrgid_r(gid_t gid, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { + * int rc = -1; + */ + + setgrent(); + while ((rc = wrap_getgrent_r(gbuf, buf, buflen, gbufp)) == 0) { + /* 0 means no error occurred, and *gbufp == gbuf */ + if (gbuf->gr_gid == gid) { + pseudo_debug(PDBGF_CLIENT, "found group gid %d, name %s\n", + gbuf->gr_gid, gbuf->gr_name); + endgrent(); + return rc; + } + } + endgrent(); + /* we never found a match; rc is 0 if there was no error, or + * non-zero if an error occurred. Either way, set the + * pwbufp pointer to NULL to indicate that we didn't find + * something, and leave rc alone. + */ + *gbufp = NULL; + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getgrnam.c b/ports/uids_generic/guts/getgrnam.c new file mode 100644 index 0000000..0e26444 --- /dev/null +++ b/ports/uids_generic/guts/getgrnam.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static struct group * + * wrap_getgrnam(const char *name) { + * struct group * rc = NULL; + */ + + static struct group grp; + static char grbuf[PSEUDO_PWD_MAX]; + int r_rc; + + r_rc = wrap_getgrnam_r(name, &grp, grbuf, PSEUDO_PWD_MAX, &rc); + /* different error return conventions */ + if (r_rc != 0) { + errno = r_rc; + } + + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getgrnam_r.c b/ports/uids_generic/guts/getgrnam_r.c new file mode 100644 index 0000000..39de641 --- /dev/null +++ b/ports/uids_generic/guts/getgrnam_r.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getgrnam_r(const char *name, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { + * int rc = -1; + */ + + setgrent(); + while ((rc = wrap_getgrent_r(gbuf, buf, buflen, gbufp)) == 0) { + /* 0 means no error occurred, and *gbufp == gbuf */ + if (gbuf->gr_name && !strcmp(gbuf->gr_name, name)) { + endgrent(); + return rc; + } + } + endgrent(); + /* we never found a match; rc is 0 if there was no error, or + * non-zero if an error occurred. Either way, set the + * pwbufp pointer to NULL to indicate that we didn't find + * something, and leave rc alone. + */ + *gbufp = NULL; + + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getpwent.c b/ports/uids_generic/guts/getpwent.c new file mode 100644 index 0000000..3b1f837 --- /dev/null +++ b/ports/uids_generic/guts/getpwent.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static struct passwd * + * wrap_getpwent(void) { + * struct passwd * rc = NULL; + */ + static struct passwd pwd; + static char pwbuf[PSEUDO_PWD_MAX]; + int r_rc; + + r_rc = wrap_getpwent_r(&pwd, pwbuf, PSEUDO_PWD_MAX, &rc); + /* different error return conventions */ + if (r_rc != 0) { + errno = r_rc; + } + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getpwnam.c b/ports/uids_generic/guts/getpwnam.c new file mode 100644 index 0000000..024b3d8 --- /dev/null +++ b/ports/uids_generic/guts/getpwnam.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static struct passwd * + * wrap_getpwnam(const char *name) { + * struct passwd * rc = NULL; + */ + static struct passwd pwd; + static char pwbuf[PSEUDO_PWD_MAX]; + int r_rc; + + r_rc = wrap_getpwnam_r(name, &pwd, pwbuf, PSEUDO_PWD_MAX, &rc); + /* different error return conventions */ + if (r_rc != 0) { + errno = r_rc; + } + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getpwnam_r.c b/ports/uids_generic/guts/getpwnam_r.c new file mode 100644 index 0000000..5d7a4ea --- /dev/null +++ b/ports/uids_generic/guts/getpwnam_r.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) { + * int rc = -1; + */ + + setpwent(); + while ((rc = wrap_getpwent_r(pwbuf, buf, buflen, pwbufp)) == 0) { + /* 0 means no error occurred, and *pwbufp == pwbuf */ + if (pwbuf->pw_name && !strcmp(pwbuf->pw_name, name)) { + endpwent(); + return rc; + } + } + endpwent(); + /* we never found a match; rc is 0 if there was no error, or + * non-zero if an error occurred. Either way, set the + * pwbufp pointer to NULL to indicate that we didn't find + * something, and leave rc alone. + */ + *pwbufp = NULL; + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getpwuid.c b/ports/uids_generic/guts/getpwuid.c new file mode 100644 index 0000000..11142de --- /dev/null +++ b/ports/uids_generic/guts/getpwuid.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static struct passwd * + * wrap_getpwuid(uid_t uid) { + * struct passwd * rc = NULL; + */ + static struct passwd pwd; + static char pwbuf[PSEUDO_PWD_MAX]; + int r_rc; + + r_rc = wrap_getpwuid_r(uid, &pwd, pwbuf, PSEUDO_PWD_MAX, &rc); + /* different error return conventions */ + if (r_rc != 0) { + errno = r_rc; + } + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getpwuid_r.c b/ports/uids_generic/guts/getpwuid_r.c new file mode 100644 index 0000000..06b920e --- /dev/null +++ b/ports/uids_generic/guts/getpwuid_r.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_getpwuid_r(uid_t uid, struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) { + * int rc = -1; + */ + + setpwent(); + while ((rc = wrap_getpwent_r(pwbuf, buf, buflen, pwbufp)) == 0) { + /* 0 means no error occurred, and *pwbufp == pwbuf */ + if (pwbuf->pw_uid == uid) { + endpwent(); + return rc; + } + } + endpwent(); + /* we never found a match; rc is 0 if there was no error, or + * non-zero if an error occurred. Either way, set the + * pwbufp pointer to NULL to indicate that we didn't find + * something, and leave rc alone. + */ + *pwbufp = NULL; + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/getuid.c b/ports/uids_generic/guts/getuid.c new file mode 100644 index 0000000..e783cc8 --- /dev/null +++ b/ports/uids_generic/guts/getuid.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static uid_t + * wrap_getuid(void) { + * uid_t rc = 0; + */ + + rc = pseudo_ruid; + +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/setegid.c b/ports/uids_generic/guts/setegid.c new file mode 100644 index 0000000..ff777a0 --- /dev/null +++ b/ports/uids_generic/guts/setegid.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setegid(gid_t egid) { + * int rc = -1; + */ + if (pseudo_euid == 0 || egid == pseudo_egid || egid == pseudo_rgid || egid == pseudo_sgid) { + pseudo_egid = egid; + pseudo_fgid = egid; + pseudo_client_touchgid(); + rc = 0; + } else { + rc = -1; + errno = EPERM; + } +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/seteuid.c b/ports/uids_generic/guts/seteuid.c new file mode 100644 index 0000000..430768f --- /dev/null +++ b/ports/uids_generic/guts/seteuid.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_seteuid(uid_t euid) { + * int rc = -1; + */ + if (pseudo_euid == 0 || euid == pseudo_euid || euid == pseudo_ruid || euid == pseudo_suid) { + pseudo_euid = euid; + pseudo_fuid = euid; + pseudo_client_touchuid(); + rc = 0; + } else { + rc = -1; + errno = EPERM; + } +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/setgid.c b/ports/uids_generic/guts/setgid.c new file mode 100644 index 0000000..b94db1a --- /dev/null +++ b/ports/uids_generic/guts/setgid.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setgid(gid_t gid) { + * int rc = -1; + */ + if (pseudo_euid == 0) { + pseudo_rgid = gid; + pseudo_egid = gid; + pseudo_sgid = gid; + pseudo_fgid = gid; + pseudo_client_touchgid(); + rc = 0; + } else if (pseudo_egid == gid || pseudo_sgid == gid || pseudo_rgid == gid) { + pseudo_egid = gid; + pseudo_fgid = gid; + pseudo_client_touchgid(); + rc = 0; + } else { + rc = -1; + errno = EPERM; + } +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/setgrent.c b/ports/uids_generic/guts/setgrent.c new file mode 100644 index 0000000..75aab61 --- /dev/null +++ b/ports/uids_generic/guts/setgrent.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static void + * wrap_setgrent(void) { + * + */ + + pseudo_grp_open(); + +/* return; + * } + */ diff --git a/ports/uids_generic/guts/setpwent.c b/ports/uids_generic/guts/setpwent.c new file mode 100644 index 0000000..fb35e07 --- /dev/null +++ b/ports/uids_generic/guts/setpwent.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static void + * wrap_setpwent(void) { + * + */ + + pseudo_pwd_open(); + +/* return; + * } + */ diff --git a/ports/uids_generic/guts/setregid.c b/ports/uids_generic/guts/setregid.c new file mode 100644 index 0000000..78b2037 --- /dev/null +++ b/ports/uids_generic/guts/setregid.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setregid(gid_t rgid, gid_t egid) { + * int rc = -1; + */ + rc = 0; + if (pseudo_euid != 0 && rgid != (gid_t) -1 && + rgid != pseudo_egid && rgid != pseudo_rgid && rgid != pseudo_sgid) { + rc = -1; + errno = EPERM; + } + if (pseudo_euid != 0 && egid != (gid_t) -1 && + egid != pseudo_egid && egid != pseudo_rgid && egid != pseudo_sgid) { + rc = -1; + errno = EPERM; + } + if (rc != -1) { + if (rgid != (gid_t) -1) + pseudo_rgid = rgid; + if (egid != (gid_t) -1) + pseudo_egid = egid; + pseudo_fgid = pseudo_egid; + pseudo_client_touchuid(); + } +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/setreuid.c b/ports/uids_generic/guts/setreuid.c new file mode 100644 index 0000000..3ff82ab --- /dev/null +++ b/ports/uids_generic/guts/setreuid.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setreuid(uid_t ruid, uid_t euid) { + * int rc = -1; + */ + rc = 0; + if (pseudo_euid != 0 && ruid != (uid_t) -1 && + ruid != pseudo_euid && ruid != pseudo_ruid && ruid != pseudo_suid) { + rc = -1; + errno = EPERM; + } + if (pseudo_euid != 0 && euid != (uid_t) -1 && + euid != pseudo_euid && euid != pseudo_ruid && euid != pseudo_suid) { + rc = -1; + errno = EPERM; + } + if (rc != -1) { + if (ruid != (uid_t) -1) + pseudo_ruid = ruid; + if (euid != (uid_t) -1) + pseudo_euid = euid; + pseudo_fuid = pseudo_euid; + pseudo_client_touchuid(); + } +/* return rc; + * } + */ diff --git a/ports/uids_generic/guts/setuid.c b/ports/uids_generic/guts/setuid.c new file mode 100644 index 0000000..6bfdf6c --- /dev/null +++ b/ports/uids_generic/guts/setuid.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_setuid(uid_t uid) { + * int rc = -1; + */ + if (pseudo_euid == 0) { + pseudo_ruid = uid; + pseudo_euid = uid; + pseudo_suid = uid; + pseudo_fuid = uid; + pseudo_client_touchuid(); + rc = 0; + } else if (pseudo_euid == uid || pseudo_suid == uid || pseudo_ruid == uid) { + pseudo_euid = uid; + pseudo_fuid = uid; + pseudo_client_touchuid(); + rc = 0; + } else { + rc = -1; + errno = EPERM; + } +/* return rc; + * } + */ diff --git a/ports/uids_generic/wrapfuncs.in b/ports/uids_generic/wrapfuncs.in new file mode 100644 index 0000000..38aa558 --- /dev/null +++ b/ports/uids_generic/wrapfuncs.in @@ -0,0 +1,25 @@ +# I bet you never knew there were this many of these! +gid_t getegid(void); +gid_t getgid(void); +int getgrgid_r(gid_t gid, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp); +int getgrnam_r(const char *name, struct group *gbuf, char *buf, size_t buflen, struct group **gbufp); +int getpwnam_r(const char *name, struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp); +int getpwuid_r(uid_t uid, struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp); +int setegid(gid_t egid); +int seteuid(uid_t euid); +int setgid(gid_t gid); +int setregid(gid_t rgid, gid_t egid); +int setreuid(uid_t ruid, uid_t euid); +int setuid(uid_t uid); +struct group *getgrent(void); +struct group *getgrgid(gid_t gid); +struct group *getgrnam(const char *name); +struct passwd *getpwent(void); +struct passwd *getpwnam(const char *name); +struct passwd *getpwuid(uid_t uid); +uid_t geteuid(void); +uid_t getuid(void); +void endgrent(void); +void endpwent(void); +void setgrent(void); +void setpwent(void); diff --git a/ports/unix/guts/COPYRIGHT b/ports/unix/guts/COPYRIGHT new file mode 100644 index 0000000..c96e1b1 --- /dev/null +++ b/ports/unix/guts/COPYRIGHT @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ diff --git a/ports/unix/guts/access.c b/ports/unix/guts/access.c new file mode 100644 index 0000000..0093a3b --- /dev/null +++ b/ports/unix/guts/access.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_access(const char *path, int mode) { + * int rc = -1; + */ + PSEUDO_STATBUF buf; + + /* note: no attempt to handle the case where user isn't + * root. + */ + rc = base_stat(path, &buf); + if (rc == -1) + return rc; + + if (mode & X_OK) { + if (buf.st_mode & 0111) { + return 0; + } else { + errno = EPERM; + return -1; + } + } else { + return 0; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/acct.c b/ports/unix/guts/acct.c new file mode 100644 index 0000000..b8dca5d --- /dev/null +++ b/ports/unix/guts/acct.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_acct(const char *path) { + * int rc = -1; + */ + + rc = real_acct(path); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/chdir.c b/ports/unix/guts/chdir.c new file mode 100644 index 0000000..9e30348 --- /dev/null +++ b/ports/unix/guts/chdir.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_chdir(const char *path) { + * int rc = -1; + */ + pseudo_debug(PDBGF_CLIENT, "chdir: '%s'\n", + path ? path : "<nil>"); + + if (!path) { + errno = EFAULT; + return -1; + } + rc = real_chdir(path); + + if (rc != -1) { + pseudo_client_op(OP_CHDIR, 0, -1, -1, path, 0); + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/chmod.c b/ports/unix/guts/chmod.c new file mode 100644 index 0000000..a157335 --- /dev/null +++ b/ports/unix/guts/chmod.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_chmod(const char *path, mode_t mode) { + * int rc = -1; + */ + + rc = wrap_fchmodat(AT_FDCWD, path, mode, 0); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/chown.c b/ports/unix/guts/chown.c new file mode 100644 index 0000000..4fcbdda --- /dev/null +++ b/ports/unix/guts/chown.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_chown(const char *path, uid_t owner, gid_t group) { + * int rc = -1; + */ + + rc = wrap_fchownat(AT_FDCWD, path, owner, group, 0); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/chroot.c b/ports/unix/guts/chroot.c new file mode 100644 index 0000000..ac24955 --- /dev/null +++ b/ports/unix/guts/chroot.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_chroot(const char *path) { + * int rc = -1; + */ + pseudo_debug(PDBGF_CLIENT | PDBGF_CHROOT, "chroot: %s\n", path); + if (!pseudo_client_op(OP_CHROOT, 0, -1, -1, path, 0)) { + pseudo_debug(PDBGF_OP | PDBGF_CHROOT, "chroot failed: %s\n", strerror(errno)); + rc = -1; + } else { + rc = 0; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/close.c b/ports/unix/guts/close.c new file mode 100644 index 0000000..09c73e6 --- /dev/null +++ b/ports/unix/guts/close.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_close(int fd) { + * int rc = -1; + */ + /* this cleans up an internal table, and shouldn't even + * make it to the server. + */ + pseudo_client_op(OP_CLOSE, 0, fd, -1, 0, 0); + rc = real_close(fd); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/closedir.c b/ports/unix/guts/closedir.c new file mode 100644 index 0000000..1085361 --- /dev/null +++ b/ports/unix/guts/closedir.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_closedir(DIR *dirp) { + * int rc = -1; + */ + if (!dirp) { + errno = EFAULT; + return -1; + } + + int fd = dirfd(dirp); + pseudo_client_op(OP_CLOSE, 0, fd, -1, 0, 0); + rc = real_closedir(dirp); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/creat.c b/ports/unix/guts/creat.c new file mode 100644 index 0000000..8593cd4 --- /dev/null +++ b/ports/unix/guts/creat.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_creat(const char *path, mode_t mode) { + * int rc = -1; + */ + + rc = wrap_open(path, O_CREAT|O_WRONLY|O_TRUNC, mode); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/dup.c b/ports/unix/guts/dup.c new file mode 100644 index 0000000..f03c2ad --- /dev/null +++ b/ports/unix/guts/dup.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_dup(int fd) { + * int rc = -1; + */ + int save_errno; + + rc = real_dup(fd); + save_errno = errno; + pseudo_debug(PDBGF_CLIENT, "dup: %d->%d\n", fd, rc); + pseudo_client_op(OP_DUP, 0, fd, rc, 0, 0); + + errno = save_errno; +/* return rc; + * } + */ diff --git a/ports/unix/guts/dup2.c b/ports/unix/guts/dup2.c new file mode 100644 index 0000000..cd335ac --- /dev/null +++ b/ports/unix/guts/dup2.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_dup2(int oldfd, int newfd) { + * int rc = -1; + */ + int save_errno; + + /* close existing one first - this also causes the socket to the + * server to get moved around if someone tries to overwrite it. */ + pseudo_debug(PDBGF_CLIENT, "dup2: %d->%d\n", oldfd, newfd); + pseudo_client_op(OP_CLOSE, 0, newfd, -1, 0, 0); + rc = real_dup2(oldfd, newfd); + save_errno = errno; + pseudo_client_op(OP_DUP, 0, oldfd, newfd, 0, 0); + errno = save_errno; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fchdir.c b/ports/unix/guts/fchdir.c new file mode 100644 index 0000000..ba77ebf --- /dev/null +++ b/ports/unix/guts/fchdir.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_fchdir(int dirfd) { + * int rc = -1; + */ + + rc = real_fchdir(dirfd); + + if (rc != -1) { + pseudo_client_op(OP_CHDIR, 0, -1, dirfd, 0, 0); + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fchmod.c b/ports/unix/guts/fchmod.c new file mode 100644 index 0000000..e2301e3 --- /dev/null +++ b/ports/unix/guts/fchmod.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_fchmod(int fd, mode_t mode) { + * int rc = -1; + */ + PSEUDO_STATBUF buf; + int save_errno = errno; + + if (base_fstat(fd, &buf) == -1) { + /* can't stat it, can't chmod it */ + return -1; + } + buf.st_mode = (buf.st_mode & ~07777) | (mode & 07777); + pseudo_client_op(OP_FCHMOD, 0, fd, -1, 0, &buf); + real_fchmod(fd, PSEUDO_FS_MODE(mode, S_ISDIR(buf.st_mode))); + /* just pretend we worked */ + errno = save_errno; + rc = 0; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fchmodat.c b/ports/unix/guts/fchmodat.c new file mode 100644 index 0000000..0506794 --- /dev/null +++ b/ports/unix/guts/fchmodat.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_fchmodat(int dirfd, const char *path, mode_t mode, int flags) { + * int rc = -1; + */ + PSEUDO_STATBUF buf; + int save_errno = errno; + + if (flags & AT_SYMLINK_NOFOLLOW) { + errno = ENOTSUP; + return -1; + } +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + rc = base_stat(path, &buf); +#else + rc = base_fstatat(dirfd, path, &buf, flags); +#endif + if (rc == -1) { + return rc; + } + if (S_ISLNK(buf.st_mode)) { + /* we don't really support chmod of a symlink */ + errno = ENOSYS; + return -1; + } + save_errno = errno; + +#if 0 + pseudo_msg_t *msg; + /* purely for debugging purposes: check whether file + * is already in database. We don't need the resulting + * information for anything. This is currently ifdefed + * out because it's only useful when trying to track where + * files are coming from. + */ + msg = pseudo_client_op(OP_STAT, 0, -1, -1, path, &buf); + if (!msg || msg->result != RESULT_SUCCEED) { + pseudo_debug(PDBGF_FILE, "chmodat to 0%o on %d/%s, ino %llu, new file.\n", + mode, dirfd, path, (unsigned long long) buf.st_ino); + + } +#endif + + /* user bits added so "root" can always access files. */ +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + /* note: if path was a symlink, and AT_SYMLINK_NOFOLLOW was + * specified, we already bailed previously. */ + real_chmod(path, PSEUDO_FS_MODE(mode, S_ISDIR(buf.st_mode))); +#else + rc = real_fchmodat(dirfd, path, PSEUDO_FS_MODE(mode, S_ISDIR(buf.st_mode)), flags); +#endif + /* we otherwise ignore failures from underlying fchmod, because pseudo + * may believe you are permitted to change modes that the filesystem + * doesn't. Note that we also don't need to know whether the + * file might be a (pseudo) block device or some such; pseudo + * will only modify permission bits based on an OP_CHMOD, and does + * not care about device/file type mismatches, only directory/file + * or symlink/file. + */ + + buf.st_mode = (buf.st_mode & ~07777) | (mode & 07777); + pseudo_client_op(OP_CHMOD, 0, -1, dirfd, path, &buf); + /* don't change errno from what it was originally */ + errno = save_errno; + rc = 0; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fchown.c b/ports/unix/guts/fchown.c new file mode 100644 index 0000000..89cabe2 --- /dev/null +++ b/ports/unix/guts/fchown.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_fchown(int fd, uid_t owner, gid_t group) { + * int rc = -1; + */ + pseudo_msg_t *msg; + PSEUDO_STATBUF buf; + int save_errno = errno; + + if (base_fstat(fd, &buf) == -1) { + save_errno = errno; + pseudo_debug(PDBGF_CONSISTENCY, "fchown failing because fstat failed: %s\n", + strerror(errno)); + errno = save_errno; + return -1; + } + if (owner == (uid_t) -1 || group == (gid_t) -1) { + msg = pseudo_client_op(OP_STAT, 0, fd, -1, NULL, &buf); + /* copy in any existing values... */ + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(&buf, msg); + } else { + pseudo_debug(PDBGF_FILE, "fchown fd %d, ino %llu, unknown file.\n", + fd, (unsigned long long) buf.st_ino); + } + } + /* now override with arguments */ + if (owner != (uid_t) -1) { + buf.st_uid = owner; + } + if (group != (gid_t) -1) { + buf.st_gid = group; + } + pseudo_debug(PDBGF_OP, "fchown, fd %d: %d:%d -> %d:%d\n", + fd, owner, group, buf.st_uid, buf.st_gid); + pseudo_client_op(OP_FCHOWN, 0, fd, -1, 0, &buf); + /* pretend we worked, errno should be unchanged */ + errno = save_errno; + rc = 0; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fchownat.c b/ports/unix/guts/fchownat.c new file mode 100644 index 0000000..2888087 --- /dev/null +++ b/ports/unix/guts/fchownat.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_fchownat(int dirfd, const char *path, uid_t owner, gid_t group, int flags) { + * int rc = -1; + */ + pseudo_msg_t *msg; + PSEUDO_STATBUF buf; + int save_errno = errno; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + if (flags & AT_SYMLINK_NOFOLLOW) { + rc = base_lstat(path, &buf); + } else { + rc = base_stat(path, &buf); + } +#else + rc = base_fstatat(dirfd, path, &buf, flags); +#endif + if (rc == -1) { + return rc; + } + save_errno = errno; + + if (owner == (uid_t) -1 || group == (gid_t) -1) { + msg = pseudo_client_op(OP_STAT, 0, -1, -1, path, &buf); + /* copy in any existing values... */ + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(&buf, msg); + } else { + pseudo_debug(PDBGF_FILE, "chownat to %d:%d on %d/%s, ino %llu, new file.\n", + owner, group, dirfd, path, + (unsigned long long) buf.st_ino); + } + } + /* now override with arguments */ + if (owner != (uid_t) -1) { + buf.st_uid = owner; + } + if (group != (gid_t) -1) { + buf.st_gid = group; + } + pseudo_client_op(OP_CHOWN, 0, -1, dirfd, path, &buf); + /* just pretend we worked */ + errno = save_errno; + rc = 0; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fclose.c b/ports/unix/guts/fclose.c new file mode 100644 index 0000000..4469f5b --- /dev/null +++ b/ports/unix/guts/fclose.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_fclose(FILE *fp) { + * int rc = -1; + */ + + if (!fp) { + errno = EFAULT; + return -1; + } + int fd = fileno(fp); + pseudo_client_op(OP_CLOSE, 0, fd, -1, 0, 0); + rc = real_fclose(fp); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fdatasync.c b/ports/unix/guts/fdatasync.c new file mode 100644 index 0000000..4aa77a8 --- /dev/null +++ b/ports/unix/guts/fdatasync.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fdatasync(int fd) + * int rc = -1; + */ + + /* note: wrapper will never call this if PSEUDO_FORCE_ASYNC + * is defined. + */ + rc = real_fdatasync(fd); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fopen.c b/ports/unix/guts/fopen.c new file mode 100644 index 0000000..87c7d78 --- /dev/null +++ b/ports/unix/guts/fopen.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008-2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static FILE * + * wrap_fopen(const char *path, const char *mode) { + * FILE * rc = 0; + */ + PSEUDO_STATBUF buf; + int save_errno; + int existed = (base_stat(path, &buf) != -1); + + rc = real_fopen(path, mode); + save_errno = errno; + + if (rc) { + int fd = fileno(rc); + + pseudo_debug(PDBGF_OP, "fopen '%s': fd %d <FILE %p>\n", path, fd, (void *) rc); + if (base_fstat(fd, &buf) != -1) { + if (!existed) { + real_fchmod(fd, PSEUDO_FS_MODE(0666 & ~pseudo_umask, 0)); + pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf); + } + pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, &buf); + } else { + pseudo_debug(PDBGF_CONSISTENCY, "fopen (fd %d) succeeded, but fstat failed (%s).\n", + fd, strerror(errno)); + pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, 0); + } + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/freopen.c b/ports/unix/guts/freopen.c new file mode 100644 index 0000000..e706d9f --- /dev/null +++ b/ports/unix/guts/freopen.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static FILE * + * wrap_freopen(const char *path, const char *mode, FILE *stream) { + * FILE * rc = NULL; + */ + PSEUDO_STATBUF buf; + int save_errno; + int existed = (base_stat(path, &buf) != -1); + + rc = real_freopen(path, mode, stream); + save_errno = errno; + + if (rc) { + int fd = fileno(rc); + + pseudo_debug(PDBGF_OP, "freopen '%s': fd %d\n", path, fd); + if (base_fstat(fd, &buf) != -1) { + if (!existed) { + real_fchmod(fd, PSEUDO_FS_MODE(0666 & ~pseudo_umask, 0)); + pseudo_client_op(OP_CREAT, 0, -1, -1, path, &buf); + } + pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, &buf); + } else { + pseudo_debug(PDBGF_OP, "fopen (fd %d) succeeded, but stat failed (%s).\n", + fd, strerror(errno)); + pseudo_client_op(OP_OPEN, pseudo_access_fopen(mode), fd, -1, path, 0); + } + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fsync.c b/ports/unix/guts/fsync.c new file mode 100644 index 0000000..6c87a56 --- /dev/null +++ b/ports/unix/guts/fsync.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int fsync(int fd) + * int rc = -1; + */ + + /* note: wrapper will never call this if PSEUDO_FORCE_ASYNC + * is defined. + */ + rc = real_fsync(fd); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fts_open.c b/ports/unix/guts/fts_open.c new file mode 100644 index 0000000..964314e --- /dev/null +++ b/ports/unix/guts/fts_open.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static FTS * + * wrap_fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)) { + * FTS * rc = NULL; + */ + char **rpath_argv; + int args = 0; + int errored = 0; + int i; + + if (!path_argv) { + errno = EFAULT; + return NULL; + } + /* count args */ + for (i = 0; path_argv[i]; ++i) { + ++args; + } + rpath_argv = malloc((args + 1) * sizeof(*rpath_argv)); + if (!rpath_argv) { + errno = ENOMEM; + return NULL; + } + + for (i = 0; i < args; ++i) { + rpath_argv[i] = PSEUDO_ROOT_PATH(AT_FDCWD, path_argv[i], AT_SYMLINK_NOFOLLOW); + if (!rpath_argv[i]) + errored = 1; + else + rpath_argv[i] = strdup(rpath_argv[i]); + } + + if (errored) { + errno = ENOMEM; + rc = NULL; + } else { + rc = real_fts_open(path_argv, options, compar); + } + for (i = 0; i < args; ++i) + free(rpath_argv[i]); + free(rpath_argv); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/ftw.c b/ports/unix/guts/ftw.c new file mode 100644 index 0000000..0861194 --- /dev/null +++ b/ports/unix/guts/ftw.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_ftw(const char *path, int (*fn)(const char *, const struct stat *, int), int nopenfd) { + * int rc = -1; + */ + + rc = real_ftw(path, fn, nopenfd); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/getcwd.c b/ports/unix/guts/getcwd.c new file mode 100644 index 0000000..2915a18 --- /dev/null +++ b/ports/unix/guts/getcwd.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_getcwd(char *buf, size_t size) { + * char * rc = NULL; + */ + pseudo_debug(PDBGF_CLIENT, "wrap_getcwd: %p, %lu\n", + (void *) buf, (unsigned long) size); + if (!pseudo_cwd) { + pseudo_diag("Asked for CWD, but don't have it!\n"); + errno = EACCES; + return NULL; + } + /* emulate Linux semantics in case of non-Linux systems. */ + if (!buf) { + /* if we don't have a cwd, something's very wrong... */ + if (!size) { + size = pseudo_cwd_len + 1; + if (pseudo_chroot_len && size >= pseudo_chroot_len && + !memcmp(pseudo_cwd, pseudo_chroot, pseudo_chroot_len)) { + size -= pseudo_chroot_len; + /* if cwd is precisely the same as chroot, we + * actually want a /, not an empty string + */ + if (size < 2) + size = 2; + } + } + if (size) { + buf = malloc(size); + } else { + pseudo_diag("can't figure out CWD: length %ld + 1 - %ld => %ld\n", + (unsigned long) pseudo_cwd_len, + (unsigned long) pseudo_chroot_len, + (unsigned long) size); + } + if (!buf) { + pseudo_diag("couldn't allocate requested CWD buffer - need %ld byes\n", + (unsigned long) size); + errno = ENOMEM; + return NULL; + } + } + if (pseudo_cwd_len - (pseudo_cwd_rel - pseudo_cwd) >= size) { + pseudo_debug(PDBGF_CLIENT, "only %ld bytes available, need %ld (%ld + 1 - %ld)\n", + (unsigned long) size, + (unsigned long) pseudo_cwd_len + 1 - pseudo_chroot_len, + (unsigned long) pseudo_cwd_len, + (unsigned long) pseudo_chroot_len); + errno = ERANGE; + return NULL; + } + rc = buf; + pseudo_debug(PDBGF_CLIENT, "getcwd: copying %d (%d + 1 - %d) characters from <%s>.\n", + (int) ((pseudo_cwd_len + 1) - pseudo_chroot_len), + (int) pseudo_cwd_len, (int) pseudo_chroot_len, + pseudo_cwd_rel); + memcpy(buf, pseudo_cwd_rel, (pseudo_cwd_len + 1) - (pseudo_cwd_rel - pseudo_cwd)); + if (!*buf) { + strcpy(buf, "/"); + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/getwd.c b/ports/unix/guts/getwd.c new file mode 100644 index 0000000..b1bcf90 --- /dev/null +++ b/ports/unix/guts/getwd.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_getwd(char *buf) { + * char * rc = NULL; + */ + + pseudo_debug(PDBGF_CLIENT, "getwd (getcwd)\n"); + rc = wrap_getcwd(buf, pseudo_path_max()); + /* because it would violate everything we have ever known about + * UNIX for these functions to have the same errno semantics, + * that's why. + */ + if (rc == NULL && errno == ERANGE ) + errno = ENAMETOOLONG; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/glob.c b/ports/unix/guts/glob.c new file mode 100644 index 0000000..0012179 --- /dev/null +++ b/ports/unix/guts/glob.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) { + * int rc = -1; + */ + char *rpattern = NULL; + int alloced = 0; + + /* note: no canonicalization */ + if (pattern && (*pattern == '/') && pseudo_chroot_len) { + size_t len = strlen(pattern) + pseudo_chroot_len + 2; + rpattern = malloc(len); + if (!rpattern) { + errno = ENOMEM; + return GLOB_NOSPACE; + } + snprintf(rpattern, len, "%s/%s", pseudo_chroot, pattern); + alloced = 1; + } + + rc = real_glob(alloced ? rpattern : pattern, flags, errfunc, pglob); + + free(rpattern); + + if (rc == 0) { + unsigned int i; + for (i = 0; i < pglob->gl_pathc; ++i) { + pseudo_dechroot(pglob->gl_pathv[i], (size_t) -1); + } + } +/* return rc; + * } + */ diff --git a/ports/unix/guts/lchown.c b/ports/unix/guts/lchown.c new file mode 100644 index 0000000..60727d0 --- /dev/null +++ b/ports/unix/guts/lchown.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2008,2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int lchown(const char *path, uid_t owner, gid_t group) + * int rc = -1; + */ + + rc = wrap_chown(path, owner, group); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/link.c b/ports/unix/guts/link.c new file mode 100644 index 0000000..3b340ee --- /dev/null +++ b/ports/unix/guts/link.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2008-2010, 2012, 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_link(const char *oldname, const char *newname) { + * int rc = -1; + */ + /* since 2.6.18 or so, linkat supports AT_SYMLINK_FOLLOW, which + * provides the behavior link() has on most non-Linux systems, + * but the default is not to follow symlinks. Better yet, it + * does NOT support AT_SYMLINK_NOFOLLOW! So define this in + * your port's portdefs.h or hope the default works for you. + */ + rc = wrap_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, + PSEUDO_LINK_SYMLINK_BEHAVIOR); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/linkat.c b/ports/unix/guts/linkat.c new file mode 100644 index 0000000..ec27e47 --- /dev/null +++ b/ports/unix/guts/linkat.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2012, 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int linkat(int olddirfd, const char *oldname, int newdirfd, const char *newname, int flags) + * int rc = -1; + */ + + int rc2, rflags, save_errno; + pseudo_msg_t *msg; + char *oldpath = NULL, *newpath = NULL; + PSEUDO_STATBUF buf; + + /* This is gratuitously complicated. On Linux 2.6.18 and later, + * flags may contain AT_SYMLINK_FOLLOW, which implies following + * symlinks; otherwise, linkat() will *not* follow symlinks. FreeBSD + * appears to use the same semantics. + * + * So on Darwin, always pass AT_SYMLINK_FOLLOW, because the + * alternative doesn't work. And never pass AT_SYMLINK_NOFOLLOW + * because that's not a valid flag to linkat(). + * + * So we need a flag for path resolution which is AT_SYMLINK_NOFOLLOW + * unless AT_SYMLINK_FOLLOW was specified, in which case it's 0. + */ + + rflags = (flags & AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } +#endif + oldpath = pseudo_root_path(__func__, __LINE__, olddirfd, oldname, rflags); + newpath = pseudo_root_path(__func__, __LINE__, newdirfd, newname, AT_SYMLINK_NOFOLLOW); + rc = real_link(oldpath, newpath); + save_errno = errno; + if (rc == -1) { + errno = save_errno; + return rc; + } + + /* if we got this far, the link succeeded, and oldpath and newpath + * are the newly-allocated canonical paths. If OS, filesystem, or + * the flags value prevent hard linking to symlinks, the resolved + * path should be the target's path anyway, so lstat is safe here. + */ + /* find the target: */ + rc2 = base_lstat(oldpath, &buf); + if (rc2 == -1) { + pseudo_diag("Fatal: Tried to stat '%s' after linking it, but failed: %s.\n", + oldpath, strerror(errno)); + errno = ENOENT; + return rc; + } + msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, &buf); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(&buf, msg); + } + /* Long story short: I am pretty sure we still want OP_LINK even + * if the thing linked is a symlink. + */ + pseudo_client_op(OP_LINK, 0, -1, -1, newpath, &buf); + + errno = save_errno; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/lutimes.c b/ports/unix/guts/lutimes.c new file mode 100644 index 0000000..cdadbbd --- /dev/null +++ b/ports/unix/guts/lutimes.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_lutimes(const char *path, const struct timeval *tv) { + * int rc = -1; + */ + + rc = real_lutimes(path, tv); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/mkdir.c b/ports/unix/guts/mkdir.c new file mode 100644 index 0000000..9f116e2 --- /dev/null +++ b/ports/unix/guts/mkdir.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_mkdir(const char *path, mode_t mode) { + * int rc = -1; + */ + + rc = wrap_mkdirat(AT_FDCWD, path, mode); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/mkdirat.c b/ports/unix/guts/mkdirat.c new file mode 100644 index 0000000..ef2e3a1 --- /dev/null +++ b/ports/unix/guts/mkdirat.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2008-2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_mkdirat(int dirfd, const char *path, mode_t mode) { + * int rc = -1; + */ + /* mask out mode bits appropriately */ + mode = mode & ~pseudo_umask; +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + + rc = real_mkdir(path, PSEUDO_FS_MODE(mode, 1)); +#else + rc = real_mkdirat(dirfd, path, PSEUDO_FS_MODE(mode, 1)); +#endif + if (rc != -1) { + PSEUDO_STATBUF buf; + int stat_rc; + int save_errno = errno; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + stat_rc = base_lstat(path, &buf); +#else + stat_rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW); +#endif + if (stat_rc != -1) { + buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode); + pseudo_client_op(OP_MKDIR, 0, -1, dirfd, path, &buf); +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + real_fchmod(path, PSEUDO_FS_MODE(mode, 1)); +#else + real_fchmodat(dirfd, path, PSEUDO_FS_MODE(mode, 1), 0); +#endif + } else { + pseudo_debug(PDBGF_OP, "mkdir of %s succeeded, but stat failed: %s\n", + path, strerror(errno)); + } + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/mkdtemp.c b/ports/unix/guts/mkdtemp.c new file mode 100644 index 0000000..5337661 --- /dev/null +++ b/ports/unix/guts/mkdtemp.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_mkdtemp(char *template) { + * char * rc = NULL; + */ + PSEUDO_STATBUF buf; + int save_errno; + size_t len; + char *tmp_template; + + if (!template) { + errno = EFAULT; + return NULL; + } + + len = strlen(template); + tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW); + + if (!tmp_template) { + errno = ENOENT; + return NULL; + } + + rc = real_mkdtemp(tmp_template); + + if (rc != NULL) { + save_errno = errno; + + if (base_stat(rc, &buf) != -1) { + pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf); + } else { + pseudo_debug(PDBGF_CONSISTENCY, "mkdtemp (path %s) succeeded, but fstat failed (%s).\n", + rc, strerror(errno)); + } + errno = save_errno; + } + /* mkdtemp only changes the XXXXXX at the end. */ + memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6); + rc = template; +/* return rc; + * } + */ diff --git a/ports/unix/guts/mkfifo.c b/ports/unix/guts/mkfifo.c new file mode 100644 index 0000000..32f79fb --- /dev/null +++ b/ports/unix/guts/mkfifo.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_mkfifo(const char *path, mode_t mode) { + * int rc = -1; + */ + + rc = wrap_mkfifoat(AT_FDCWD, path, mode); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/mkfifoat.c b/ports/unix/guts/mkfifoat.c new file mode 100644 index 0000000..6947e70 --- /dev/null +++ b/ports/unix/guts/mkfifoat.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2015 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_mkfifoat(int dirfd, const char *path, mode_t mode) { + * int rc = -1; + */ + + pseudo_msg_t *msg; + PSEUDO_STATBUF buf; + int save_errno = errno; + + /* mask out mode bits appropriately */ + mode = mode & ~pseudo_umask; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + rc = base_stat(path, &buf); +#else + rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW); +#endif + if (rc != -1) { + /* if we can stat the file, you can't mkfifo it */ + errno = EEXIST; + return -1; + } +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = real_mkfifo(path, PSEUDO_FS_MODE(mode, 0)); + if (rc == -1) { + return -1; + } + save_errno = errno; + rc = base_stat(path, &buf); + real_chmod(path, PSEUDO_FS_MODE(mode, 0)); +#else + rc = real_mkfifoat(dirfd, path, PSEUDO_FS_MODE(mode, 0)); + if (rc == -1) { + return -1; + } + save_errno = errno; + rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW); + real_fchmodat(dirfd, path, PSEUDO_FS_MODE(mode, 0), 0); +#endif + /* if the stat failed, we are going to give up and nuke + * any file we may have created, and hope for the best. + */ + if (rc == 0) { + buf.st_mode = PSEUDO_DB_MODE(buf.st_mode, mode); + /* mkfifo/mknod are the same op, in that they create a file + * with a non-file type. + */ + msg = pseudo_client_op(OP_MKNOD, 0, -1, dirfd, path, &buf); + if (msg && msg->result != RESULT_SUCCEED) { + errno = EPERM; + rc = -1; + } else { + /* just pretend we worked */ + errno = save_errno; + rc = 0; + } + } + if (rc == -1) { + save_errno = errno; +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + real_unlink(path); +#else + real_unlinkat(dirfd, path, AT_SYMLINK_NOFOLLOW); +#endif + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/mknod.c b/ports/unix/guts/mknod.c new file mode 100644 index 0000000..eeca65d --- /dev/null +++ b/ports/unix/guts/mknod.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2011,2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int mknod(const char *path, mode_t mode, dev_t dev) + * int rc = -1; + */ + + rc = wrap_mknodat(AT_FDCWD, path, mode, dev); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/mknodat.c b/ports/unix/guts/mknodat.c new file mode 100644 index 0000000..76e4dd9 --- /dev/null +++ b/ports/unix/guts/mknodat.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2011 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev) + * int rc = -1; + */ + + pseudo_msg_t *msg; + PSEUDO_STATBUF buf; + int save_errno = errno; + + /* mask out mode bits appropriately */ + mode = mode & ~pseudo_umask; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + rc = base_stat(path, &buf); +#else + rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW); +#endif + if (rc != -1) { + /* if we can stat the file, you can't mknod it */ + errno = EEXIST; + return -1; + } +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = real_open(path, O_CREAT | O_WRONLY | O_EXCL, + PSEUDO_FS_MODE(mode, 0)); +#else + rc = real_openat(dirfd, path, O_CREAT | O_WRONLY | O_EXCL, + PSEUDO_FS_MODE(mode, 0)); +#endif + if (rc == -1) { + return -1; + } + real_fchmod(rc, PSEUDO_FS_MODE(mode, 0)); + base_fstat(rc, &buf); + /* mknod does not really open the file. We don't have + * to use wrap_close because we've never exposed this file + * descriptor to the client code. + */ + real_close(rc); + + /* mask in the mode type bits again */ + buf.st_mode = (PSEUDO_DB_MODE(buf.st_mode, mode) & 07777) | + (mode & ~07777); + buf.st_rdev = dev; + msg = pseudo_client_op(OP_MKNOD, 0, -1, dirfd, path, &buf); + if (msg && msg->result != RESULT_SUCCEED) { + errno = EPERM; + rc = -1; + } else { + /* just pretend we worked */ + errno = save_errno; + rc = 0; + } + if (rc == -1) { + save_errno = errno; +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + real_unlink(path); +#else + real_unlinkat(dirfd, path, AT_SYMLINK_NOFOLLOW); +#endif + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/mkstemp.c b/ports/unix/guts/mkstemp.c new file mode 100644 index 0000000..1e2b026 --- /dev/null +++ b/ports/unix/guts/mkstemp.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2008-2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_mkstemp(char *template) { + * int rc = -1; + */ + PSEUDO_STATBUF buf; + int save_errno; + size_t len; + char *tmp_template; + + if (!template) { + errno = EFAULT; + return 0; + } + + len = strlen(template); + tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW); + + if (!tmp_template) { + errno = ENOENT; + return -1; + } + + rc = real_mkstemp(tmp_template); + + if (rc != -1) { + save_errno = errno; + + if (base_fstat(rc, &buf) != -1) { + real_fchmod(rc, PSEUDO_FS_MODE(0600, 0)); + pseudo_client_op(OP_CREAT, 0, -1, -1, tmp_template, &buf); + pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, &buf); + } else { + pseudo_debug(PDBGF_CONSISTENCY, "mkstemp (fd %d) succeeded, but fstat failed (%s).\n", + rc, strerror(errno)); + pseudo_client_op(OP_OPEN, PSA_READ | PSA_WRITE, rc, -1, tmp_template, 0); + } + errno = save_errno; + } + /* mkstemp only changes the XXXXXX at the end. */ + memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6); +/* return rc; + * } + */ diff --git a/ports/unix/guts/mktemp.c b/ports/unix/guts/mktemp.c new file mode 100644 index 0000000..a39d1b7 --- /dev/null +++ b/ports/unix/guts/mktemp.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_mktemp(char *template) { + * char * rc = NULL; + */ + size_t len; + char *tmp_template; + + if (!template) { + errno = EFAULT; + return NULL; + } + + len = strlen(template); + tmp_template = PSEUDO_ROOT_PATH(AT_FDCWD, template, AT_SYMLINK_NOFOLLOW); + + if (!tmp_template) { + errno = ENOENT; + return NULL; + } + + rc = real_mktemp(tmp_template); + + /* mktemp only changes the XXXXXX at the end, and never created + * a file -- note the race condition implied here. + */ + memcpy(template + len - 6, tmp_template + strlen(tmp_template) - 6, 6); + rc = template; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/msync.c b/ports/unix/guts/msync.c new file mode 100644 index 0000000..fbc5e26 --- /dev/null +++ b/ports/unix/guts/msync.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int msync(void *addr, size_t length, int flags) + * int rc = -1; + */ + + /* note: wrapper will never call this if PSEUDO_FORCE_ASYNC + * is defined. + */ + rc = real_msync(addr, length, flags); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/nftw.c b/ports/unix/guts/nftw.c new file mode 100644 index 0000000..73daec8 --- /dev/null +++ b/ports/unix/guts/nftw.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *), int nopenfd, int flag) { + * int rc = -1; + */ + + rc = real_nftw(path, fn, nopenfd, flag); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/opendir.c b/ports/unix/guts/opendir.c new file mode 100644 index 0000000..c8a78f8 --- /dev/null +++ b/ports/unix/guts/opendir.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static DIR * + * wrap_opendir(const char *path) { + * DIR * rc = NULL; + */ + PSEUDO_STATBUF buf; + int save_errno; + + rc = real_opendir(path); + if (rc) { + int fd; + save_errno = errno; + fd = dirfd(rc); + if (base_fstat(fd, &buf) == -1) { + pseudo_debug(PDBGF_CONSISTENCY, "diropen (fd %d) succeeded, but fstat failed (%s).\n", + fd, strerror(errno)); + pseudo_client_op(OP_OPEN, PSA_READ, fd, -1, path, 0); + } else { + pseudo_client_op(OP_OPEN, PSA_READ, fd, -1, path, &buf); + } + + + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/pathconf.c b/ports/unix/guts/pathconf.c new file mode 100644 index 0000000..c6caa34 --- /dev/null +++ b/ports/unix/guts/pathconf.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static long + * wrap_pathconf(const char *path, int name) { + * long rc = -1; + */ + + rc = real_pathconf(path, name); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/popen.c b/ports/unix/guts/popen.c new file mode 100644 index 0000000..5d44c0e --- /dev/null +++ b/ports/unix/guts/popen.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * FILE *popen(const char *command, const char *mode) + * FILE *rc = NULL; + */ + /* on at least some systems, popen() calls fork and exec + * in ways that avoid our usual enforcement of the environment. + */ + pseudo_setupenv(); + if (pseudo_has_unload(NULL)) + pseudo_dropenv(); + + rc = real_popen(command, mode); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/readlink.c b/ports/unix/guts/readlink.c new file mode 100644 index 0000000..18d9dc7 --- /dev/null +++ b/ports/unix/guts/readlink.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static ssize_t + * wrap_readlink(const char *path, char *buf, size_t bufsiz) { + * ssize_t rc = -1; + */ + + rc = wrap_readlinkat(AT_FDCWD, path, buf, bufsiz); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/readlinkat.c b/ports/unix/guts/readlinkat.c new file mode 100644 index 0000000..5282e2b --- /dev/null +++ b/ports/unix/guts/readlinkat.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static ssize_t + * wrap_readlinkat(int dirfd, const char *path, char *buf, size_t bufsiz) { + * ssize_t rc = -1; + */ +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + rc = real_readlink(path, buf, bufsiz); +#else + rc = real_readlinkat(dirfd, path, buf, bufsiz); +#endif + + if (rc > 0) { + rc = pseudo_dechroot(buf, rc); + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c new file mode 100644 index 0000000..a59808d --- /dev/null +++ b/ports/unix/guts/realpath.c @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_realpath(const char *name, char *resolved_name) { + * char * rc = NULL; + */ + char *rname = PSEUDO_ROOT_PATH(AT_FDCWD, name, 0); + ssize_t len; + if (!rname) { + errno = ENAMETOOLONG; + return NULL; + } + if ((len = strlen(rname)) >= pseudo_sys_path_max()) { + errno = ENAMETOOLONG; + return NULL; + } + if (resolved_name) { + memcpy(resolved_name, rname, len + 1); + rc = resolved_name; + } else { + rc = strdup(rname); + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/remove.c b/ports/unix/guts/remove.c new file mode 100644 index 0000000..4e2cecb --- /dev/null +++ b/ports/unix/guts/remove.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2008-2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_remove(const char *path) { + * int rc = -1; + */ + PSEUDO_STATBUF buf; + if (base_lstat(path, &buf) == -1) { + errno = ENOENT; + return -1; + } + if (S_ISDIR(buf.st_mode)) { + rc = wrap_rmdir(path); + } else { + rc = wrap_unlink(path); + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/rename.c b/ports/unix/guts/rename.c new file mode 100644 index 0000000..b8ee8b0 --- /dev/null +++ b/ports/unix/guts/rename.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2008-2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_rename(const char *oldpath, const char *newpath) { + * int rc = -1; + */ + pseudo_msg_t *msg; + PSEUDO_STATBUF oldbuf, newbuf; + int oldrc, newrc; + int save_errno; + int old_db_entry = 0; + int may_unlinked = 0; + + pseudo_debug(PDBGF_OP, "rename: %s->%s\n", + oldpath ? oldpath : "<nil>", + newpath ? newpath : "<nil>"); + + if (!oldpath || !newpath) { + errno = EFAULT; + return -1; + } + + save_errno = errno; + + newrc = base_lstat(newpath, &newbuf); + oldrc = base_lstat(oldpath, &oldbuf); + + errno = save_errno; + + /* newpath must be removed. */ + /* as with unlink, we have to mark that the file may get deleted */ + msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, -1, newpath, newrc ? NULL : &newbuf); + if (msg && msg->result == RESULT_SUCCEED) + may_unlinked = 1; + msg = pseudo_client_op(OP_STAT, 0, -1, -1, oldpath, oldrc ? NULL : &oldbuf); + if (msg && msg->result == RESULT_SUCCEED) + old_db_entry = 1; + rc = real_rename(oldpath, newpath); + save_errno = errno; + if (may_unlinked) { + if (rc == -1) { + /* since we failed, that wasn't really unlinked -- put + * it back. + */ + pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, newpath, &newbuf); + } else { + /* confirm that the file was removed */ + pseudo_client_op(OP_DID_UNLINK, 0, -1, -1, newpath, &newbuf); + } + } + if (rc == -1) { + /* and we're done. */ + errno = save_errno; + return rc; + } + save_errno = errno; + /* nothing to do for a "rename" of a link to itself */ + if (newrc != -1 && oldrc != -1 && + newbuf.st_dev == oldbuf.st_dev && + newbuf.st_ino == oldbuf.st_ino) { + return rc; + } + + /* rename(3) is not mv(1). rename(file, dir) fails; you must provide + * the corrected path yourself. You can rename over a directory only + * if the source is a directory. Symlinks are simply removed. + * + * If we got here, the real rename call succeeded. That means newpath + * has been unlinked and oldpath has been linked to it. + * + * There are a ton of special cases to error check. I don't check + * for any of them, because in every such case, the underlying rename + * failed, and there is nothing to do. + * The only tricky part is that, because we used to ignore symlinks, + * we may have to rename or remove directory trees even though in + * theory rename can never destroy a directory tree. + */ + if (!old_db_entry) { + /* create an entry under the old name, which will then be + * renamed; this way, children would get renamed too, if there + * were any. + */ + if (newrc == 0) { + if (newbuf.st_dev != oldbuf.st_dev) { + oldbuf.st_dev = newbuf.st_dev; + oldbuf.st_ino = newbuf.st_ino; + } + } + pseudo_debug(PDBGF_FILE, "creating new '%s' [%llu] to rename\n", + oldpath, (unsigned long long) oldbuf.st_ino); + pseudo_client_op(OP_LINK, 0, -1, -1, oldpath, &oldbuf); + } + pseudo_client_op(OP_RENAME, 0, -1, -1, newpath, &oldbuf, oldpath); + + errno = save_errno; +/* return rc; + * } + */ diff --git a/ports/unix/guts/renameat.c b/ports/unix/guts/renameat.c new file mode 100644 index 0000000..ade0509 --- /dev/null +++ b/ports/unix/guts/renameat.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2008-2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + * int rc = -1; + */ + pseudo_msg_t *msg; + PSEUDO_STATBUF oldbuf, newbuf; + int oldrc, newrc; + int save_errno; + int old_db_entry = 0; + + pseudo_debug(PDBGF_FILE, "renameat: %d,%s->%d,%s\n", + olddirfd, oldpath ? oldpath : "<nil>", + newdirfd, newpath ? newpath : "<nil>"); + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } +#endif + + if (!oldpath || !newpath) { + errno = EFAULT; + return -1; + } + + save_errno = errno; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + newrc = base_lstat(newpath, &newbuf); + oldrc = base_lstat(oldpath, &oldbuf); +#else + oldrc = base_fstatat(olddirfd, oldpath, &oldbuf, AT_SYMLINK_NOFOLLOW); + newrc = base_fstatat(newdirfd, newpath, &newbuf, AT_SYMLINK_NOFOLLOW); +#endif + + errno = save_errno; + + /* newpath must be removed. */ + /* as with unlink, we have to mark that the file may get deleted */ + msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, newdirfd, newpath, newrc ? NULL : &newbuf); + if (msg && msg->result == RESULT_SUCCEED) + old_db_entry = 1; + rc = real_renameat(olddirfd, oldpath, newdirfd, newpath); + save_errno = errno; + if (old_db_entry) { + if (rc == -1) { + /* since we failed, that wasn't really unlinked -- put + * it back. + */ + pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, newdirfd, newpath, &newbuf); + } else { + /* confirm that the file was removed */ + pseudo_client_op(OP_DID_UNLINK, 0, -1, newdirfd, newpath, &newbuf); + } + } + if (rc == -1) { + /* and we're done. */ + errno = save_errno; + return rc; + } + save_errno = errno; + /* nothing to do for a "rename" of a link to itself */ + if (newrc != -1 && oldrc != -1 && + newbuf.st_dev == oldbuf.st_dev && + newbuf.st_ino == oldbuf.st_ino) { + return rc; + } + + /* rename(3) is not mv(1). rename(file, dir) fails; you must provide + * the corrected path yourself. You can rename over a directory only + * if the source is a directory. Symlinks are simply removed. + * + * If we got here, the real rename call succeeded. That means newpath + * has been unlinked and oldpath has been linked to it. + * + * There are a ton of special cases to error check. I don't check + * for any of them, because in every such case, the underlying rename + * failed, and there is nothing to do. + * The only tricky part is that, because we used to ignore symlinks, + * we may have to rename or remove directory trees even though in + * theory rename can never destroy a directory tree. + */ + if (!old_db_entry) { + /* create an entry under the old name, which will then be + * renamed; this way, children would get renamed too, if there + * were any. + */ + if (newrc == 0) { + if (newbuf.st_dev != oldbuf.st_dev) { + oldbuf.st_dev = newbuf.st_dev; + oldbuf.st_ino = newbuf.st_ino; + } + } + pseudo_debug(PDBGF_OP, "creating new '%s' [%llu] to rename\n", + oldpath, (unsigned long long) oldbuf.st_ino); + pseudo_client_op(OP_LINK, 0, -1, olddirfd, oldpath, &oldbuf); + } + /* special case: use 'fd' for olddirfd, because + * we know it has no other meaning for RENAME + */ + pseudo_client_op(OP_RENAME, 0, olddirfd, newdirfd, newpath, &oldbuf, oldpath); + + errno = save_errno; +/* return rc; + * } + */ diff --git a/ports/unix/guts/rmdir.c b/ports/unix/guts/rmdir.c new file mode 100644 index 0000000..ebc522a --- /dev/null +++ b/ports/unix/guts/rmdir.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008-2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_rmdir(const char *path) { + * int rc = -1; + */ + pseudo_msg_t *msg; + PSEUDO_STATBUF buf; + int save_errno; + int old_db_entry = 0; + + rc = base_lstat(path, &buf); + if (rc == -1) { + return rc; + } + msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, -1, path, &buf); + if (msg && msg->result == RESULT_SUCCEED) + old_db_entry = 1; + rc = real_rmdir(path); + if (old_db_entry) { + if (rc == -1) { + save_errno = errno; + pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, path, &buf); + errno = save_errno; + } else { + pseudo_client_op(OP_DID_UNLINK, 0, -1, -1, path, &buf); + } + } else { + pseudo_debug(PDBGF_FILE, "rmdir on <%s>, not in database, no effect.\n", path); + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/symlink.c b/ports/unix/guts/symlink.c new file mode 100644 index 0000000..487c135 --- /dev/null +++ b/ports/unix/guts/symlink.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_symlink(const char *oldname, const char *newpath) { + * int rc = -1; + */ + + rc = wrap_symlinkat(oldname, AT_FDCWD, newpath); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/symlinkat.c b/ports/unix/guts/symlinkat.c new file mode 100644 index 0000000..1346db1 --- /dev/null +++ b/ports/unix/guts/symlinkat.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2008-2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_symlinkat(const char *oldname, int dirfd, const char *newpath) { + * int rc = -1; + */ + PSEUDO_STATBUF buf; + char *roldname = 0; + + if (oldname[0] == '/' && pseudo_chroot_len && !pseudo_nosymlinkexp) { + size_t len = pseudo_chroot_len + strlen(oldname) + 1; + roldname = malloc(len); + snprintf(roldname, len, "%s%s", pseudo_chroot, oldname); + } + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + rc = real_symlink(roldname ? roldname : oldname, newpath); +#else + rc = real_symlinkat(roldname ? roldname : oldname, dirfd, newpath); +#endif + + if (rc == -1) { + free(roldname); + return rc; + } +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = base_lstat(newpath, &buf); +#else + rc = base_fstatat(dirfd, newpath, &buf, AT_SYMLINK_NOFOLLOW); +#endif + if (rc == -1) { + int save_errno = errno; + pseudo_diag("symlinkat: couldn't stat '%s' even though symlink creation succeeded (%s).\n", + newpath, strerror(errno)); + errno = save_errno; + free(roldname); + return rc; + } + /* just record the entry */ + pseudo_client_op(OP_SYMLINK, 0, -1, dirfd, newpath, &buf); + + free(roldname); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/sync.c b/ports/unix/guts/sync.c new file mode 100644 index 0000000..c5d9554 --- /dev/null +++ b/ports/unix/guts/sync.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * void sync(void) + * + */ + + /* note: wrapper will never call this if PSEUDO_FORCE_ASYNC + * is defined. + */ + (void) real_sync(); + +/* return; + * } + */ diff --git a/ports/unix/guts/sync_file_range.c b/ports/unix/guts/sync_file_range.c new file mode 100644 index 0000000..03cfc6c --- /dev/null +++ b/ports/unix/guts/sync_file_range.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags) + * int rc = -1; + */ + + rc = real_sync_file_range(fd, offset, nbytes, flags); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/syncfs.c b/ports/unix/guts/syncfs.c new file mode 100644 index 0000000..2c9a685 --- /dev/null +++ b/ports/unix/guts/syncfs.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2013 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int syncfs(int fd) + * int rc = -1; + */ + + rc = real_syncfs(fd); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/system.c b/ports/unix/guts/system.c new file mode 100644 index 0000000..6351592 --- /dev/null +++ b/ports/unix/guts/system.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2011, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * int system(const char *command) + * int rc = -1; + */ + if (!command) + return 1; + + pseudo_setupenv(); + if (pseudo_has_unload(NULL)) + pseudo_dropenv(); + + rc = real_system(command); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/tempnam.c b/ports/unix/guts/tempnam.c new file mode 100644 index 0000000..9b0257f --- /dev/null +++ b/ports/unix/guts/tempnam.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_tempnam(const char *template, const char *pfx) { + * char * rc = NULL; + */ + /* let gcc know we ignored these on purpose */ + (void) template; + (void) pfx; + pseudo_diag("tempnam() is so ludicrously insecure as to defy implementation."); + errno = ENOMEM; + rc = NULL; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/tmpnam.c b/ports/unix/guts/tmpnam.c new file mode 100644 index 0000000..3fece57 --- /dev/null +++ b/ports/unix/guts/tmpnam.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static char * + * wrap_tmpnam(char *s) { + * char * rc = NULL; + */ + + /* let gcc know we're ignoring this */ + (void) s; + pseudo_diag("tmpnam() is so ludicrously insecure as to defy implementation."); + errno = ENOMEM; + rc = NULL; + +/* return rc; + * } + */ diff --git a/ports/unix/guts/truncate.c b/ports/unix/guts/truncate.c new file mode 100644 index 0000000..6a19a50 --- /dev/null +++ b/ports/unix/guts/truncate.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_truncate(const char *path, off_t length) { + * int rc = -1; + */ + + rc = real_truncate(path, length); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/umask.c b/ports/unix/guts/umask.c new file mode 100644 index 0000000..6b060d3 --- /dev/null +++ b/ports/unix/guts/umask.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2014 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * mode_t umask(mode_t mask) + * mode_t rc = 0; + */ + + pseudo_umask = mask; + rc = real_umask(mask); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/unlink.c b/ports/unix/guts/unlink.c new file mode 100644 index 0000000..d8a5d01 --- /dev/null +++ b/ports/unix/guts/unlink.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2008-2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_unlink(const char *path) { + * int rc = -1; + */ + + rc = wrap_unlinkat(AT_FDCWD, path, 0); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/unlinkat.c b/ports/unix/guts/unlinkat.c new file mode 100644 index 0000000..e723a01 --- /dev/null +++ b/ports/unix/guts/unlinkat.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2008-2010, 2012 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_unlinkat(int dirfd, const char *path, int rflags) { + * int rc = -1; + */ + pseudo_msg_t *msg; + int save_errno; + PSEUDO_STATBUF buf; + int old_db_entry = 0; + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + if (dirfd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + if (rflags) { + /* the only supported flag is AT_REMOVEDIR. We'd never call + * with that flag unless the real AT functions exist, so + * something must have gone horribly wrong.... + */ + pseudo_diag("wrap_unlinkat called with flags (0x%x), path '%s'\n", + rflags, path ? path : "<nil>"); + errno = ENOSYS; + return -1; + } +#endif + +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = base_lstat(path, &buf); +#else + rc = base_fstatat(dirfd, path, &buf, AT_SYMLINK_NOFOLLOW); +#endif + if (rc == -1) { + return rc; + } + msg = pseudo_client_op(OP_MAY_UNLINK, 0, -1, dirfd, path, &buf); + if (msg && msg->result == RESULT_SUCCEED) + old_db_entry = 1; +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + rc = real_unlink(path); +#else + rc = real_unlinkat(dirfd, path, rflags); +#endif + if (old_db_entry) { + if (rc == -1) { + save_errno = errno; + pseudo_client_op(OP_CANCEL_UNLINK, 0, -1, -1, path, &buf); + errno = save_errno; + } else { + pseudo_client_op(OP_DID_UNLINK, 0, -1, -1, path, &buf); + } + } else { + pseudo_debug(PDBGF_FILE, "unlink on <%s>, not in database, no effect.\n", path); + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/utime.c b/ports/unix/guts/utime.c new file mode 100644 index 0000000..ff65237 --- /dev/null +++ b/ports/unix/guts/utime.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_utime(const char *path, const struct utimbuf *buf) { + * int rc = -1; + */ + rc = real_utime(path, buf); + +/* return rc; + * } + */ diff --git a/ports/unix/guts/utimes.c b/ports/unix/guts/utimes.c new file mode 100644 index 0000000..69ad949 --- /dev/null +++ b/ports/unix/guts/utimes.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2010 Wind River Systems; see + * guts/COPYRIGHT for information. + * + * static int + * wrap_utimes(const char *path, const struct timeval *times) { + * int rc = -1; + */ + rc = real_utimes(path, times); + +/* return rc; + * } + */ diff --git a/ports/unix/pseudo_wrappers.c b/ports/unix/pseudo_wrappers.c new file mode 100644 index 0000000..b825d56 --- /dev/null +++ b/ports/unix/pseudo_wrappers.c @@ -0,0 +1,50 @@ +FILE * +popen(const char *command, const char *mode) { + sigset_t saved; + + FILE *rc = NULL; + + if (!pseudo_check_wrappers() || !real_popen) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("popen"); + return rc; + } + + pseudo_debug(PDBGF_WRAPPER, "called: popen\n"); + pseudo_sigblock(&saved); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + return NULL; + } + + int save_errno; + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + rc = wrap_popen(command, mode); + + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); +#if 0 +/* This can cause hangs on some recentish systems which use locale + * stuff for strerror... + */ + pseudo_debug(PDBGF_WRAPPER, "completed: popen (maybe: %s)\n", strerror(save_errno)); +#endif + pseudo_debug(PDBGF_WRAPPER, "completed: popen (errno: %d)\n", save_errno); + errno = save_errno; + return rc; +} + +static FILE * +wrap_popen(const char *command, const char *mode) { + FILE *rc = NULL; + + + +#include "guts/popen.c" + + return rc; +} + diff --git a/ports/unix/wrapfuncs.in b/ports/unix/wrapfuncs.in new file mode 100644 index 0000000..5f30ae6 --- /dev/null +++ b/ports/unix/wrapfuncs.in @@ -0,0 +1,70 @@ +int creat(const char *path, mode_t mode); +char *getcwd(char *buf, size_t size); +char *getwd(char *buf); +int close(int fd); +int fchmod(int fd, mode_t mode); +int fchown(int fd, uid_t owner, gid_t group); +int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW */ +int dup2(int oldfd, int newfd); +int dup(int fd); +int chdir(const char *path); +int fchdir(int dirfd); +int access(const char *path, int mode); +FTS *fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)); /* inode64=1 */ +int ftw(const char *path, int (*fn)(const char *, const struct stat *, int), int nopenfd); +int nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *), int nopenfd, int flag); +int glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob); +int lutimes(const char *path, const struct timeval *tv); /* flags=AT_SYMLINK_NOFOLLOW */ +char *mkdtemp(char *template); +char *mktemp(char *template); +long pathconf(const char *path, int name); +char *realpath(const char *name, char *resolved_name); /* version="GLIBC_2.3" */ +int remove(const char *path); /* flags=AT_SYMLINK_NOFOLLOW */ +DIR *opendir(const char *path); +int closedir(DIR *dirp); +char *tempnam(const char *template, const char *pfx); +char *tmpnam(char *s); +int truncate(const char *path, off_t length); +int utime(const char *path, const struct utimbuf *buf); +int utimes(const char *path, const struct timeval *times); +# needed because libc stdio does horrible things with inline asm syscalls +FILE *fopen(const char *path, const char *mode); +int fclose(FILE *fp); +FILE *freopen(const char *path, const char *mode, FILE *stream); +int chroot(const char *path); +int acct(const char *path); +int chmod(const char *path, mode_t mode); +int chown(const char *path, uid_t owner, gid_t group); +int fchmodat(int dirfd, const char *path, mode_t mode, int flags); +int fchownat(int dirfd, const char *path, uid_t owner, gid_t group, int flags); +int link(const char *oldname, const char *newname); /* flags=AT_SYMLINK_NOFOLLOW */ +int linkat(int olddirfd, const char *oldname, int newdirfd, const char *newname, int flags); +int mkdir(const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */ +int mkdirat(int dirfd, const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */ +int mkfifo(const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */ +int mkfifoat(int dirfd, const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW */ +int mknod(const char *path, mode_t mode, dev_t dev); /* flags=AT_SYMLINK_NOFOLLOW */ +int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev); /* flags=AT_SYMLINK_NOFOLLOW */ +int mkstemp(char *template); /* flags=AT_SYMLINK_NOFOLLOW */ +int rename(const char *oldpath, const char *newpath); /* flags=AT_SYMLINK_NOFOLLOW */ +int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); /* flags=AT_SYMLINK_NOFOLLOW */ +int rmdir(const char *path); /* flags=AT_SYMLINK_NOFOLLOW */ +int symlink(const char *oldname, const char *newpath); /* flags=AT_SYMLINK_NOFOLLOW */ +int symlinkat(const char *oldname, int dirfd, const char *newpath); /* flags=AT_SYMLINK_NOFOLLOW */ +int unlink(const char *path); /* flags=AT_SYMLINK_NOFOLLOW */ +int unlinkat(int dirfd, const char *path, int rflags); /* flags=AT_SYMLINK_NOFOLLOW */ +# primarily for use with chroot() +ssize_t readlink(const char *path, char *buf, size_t bufsiz); /* flags=AT_SYMLINK_NOFOLLOW */ +ssize_t readlinkat(int dirfd, const char *path, char *buf, size_t bufsiz); /* flags=AT_SYMLINK_NOFOLLOW */ +int system(const char *command); +FILE *popen(const char *command, const char *mode); /* hand_wrapped=1 */ +# Based on experiments by Richard Purdie: Allow pseudo to eliminate +# sync-type operations globally, mostly relevant for performance reasons +# during filesystem assembly. +int fsync(int fd); /* async_skip=0 */ +int fdatasync(int fd); /* async_skip=0 */ +void sync(void); /* async_skip= */ +int syncfs(int fd); /* async_skip=0 */ +int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags); /* async_skip=0 */ +int msync(void *addr, size_t length, int flags); /* async_skip=0 */ +mode_t umask(mode_t mask); diff --git a/pseudo.1 b/pseudo.1 new file mode 100644 index 0000000..63a6782 --- /dev/null +++ b/pseudo.1 @@ -0,0 +1,672 @@ +.\" +.\" pseudo(1) man page +.\" +.\" Copyright (c) 2010 Wind River Systems, Inc. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the Lesser GNU General Public License version 2.1 as +.\" published by the Free Software Foundation. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.\" See the Lesser GNU General Public License for more details. +.\" +.\" You should have received a copy of the Lesser GNU General Public License +.\" version 2.1 along with this program; if not, write to the Free Software +.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +.TH pseudo 1 "pseudo - pretending to be root" +.SH NAME +pseudo \- run a command in a virtual root environment +.SH SYNOPSIS +.B pseudo +.RB [ \-dflv ] +[ +.B \-x +.I flags +] +[ +.B \-P +.I prefix +] +[ +.B \-rR +.I root +] +[ +.B \-t +.I timeout +] +.RI [ command ] +.PP +.B pseudo \-h +.PP +.B pseudo +.RB [ \-dflv ] +[ +.B \-x +.I flags +] +[ +.B \-P +.I prefix +] +.RB [ \-BC ] +.BR \-i\ path +.PP +.B pseudo +.RB [ \-dflv ] +[ +.B \-x +.I flags +] +[ +.B \-P +.I prefix +] +.RB [ \-BC ] +.BR \-m\ from\ \-M\ to +.PP +.B pseudo +.RB [ \-dflv ] +[ +.B \-x +.I flags +] +[ +.B \-P +.I prefix +] +.B \-S +.PP +.B pseudo +.RB [ \-dflv ] +[ +.B \-x +.I flags +] +[ +.B \-P +.I prefix +] +.B \-V +.SH DESCRIPTION +The +.I pseudo +utility provides a virtual root environment, hereafter referred to as the +.IR pseudo\ environment , +allowing the creation of file system images and packages by users +without root privileges. The pseudo environment is implemented by pushing +a special library +.RI ( libpseudo.so ) +into the +.B LD_PRELOAD +environment variable. This library intercepts a large number of common +filesystem operations and some user-id related operations, and returns +values that look as though the operations had been performed by a root +user. This is in turn managed by a daemon program which keeps a list +of virtualized file ownership and permissions; this daemon program itself +is +.IR pseudo . + +The +.I pseudo +program itself can also be used as a program launcher. The launcher +is used to automatically configure a working environment, then execute +processes within that environment. Alternatively, you can bypass this +by setting up certain environment variables (see the +.B ENVIRONMENT +section below). The +.I pseudo +client library +.RI ( libpseudo.so ) +can then start the server automatically. + +The +.I pseudo +command can be invoked in one of several possible modes: + +.TP 8 +.B \-B +The +.B \-B +option causes +.I pseudo +to scan its database, as with the +.B \-C +option, but instead of reporting mismatches, +.I pseudo +attempts to repair them. Specifically, device and inode number mismatches +are corrected, and symlink or directory mismatches result in deletion of +database entries. +.TP 8 +.B \-C +The +.B \-C +option causes +.I pseudo +to scan its database, comparing against the filesystem, and reporting likely +errors. This may be unreliable when the server is actively running. +.TP 8 +.B \-h +The +.B \-h +option causes +.I pseudo +to print a usage message and exit. +.TP 8 +.B \-i +The +.B \-i +option causes +.I pseudo +to attempt to correct device number mismatches by +checking inodes; if +.I path +has the same inode number as recorded in the database, but a different +device number, all instances of the device number recorded in the database +are updated to the device number in the live filesystem for +.IR path . +This is intended to handle the mismatches that can occur when remounting +an NFS filesystem. The +.B \-i +option implies the +.B \-C +option. You can also specify the +.B \-B +option to request that the database be rebuilt. +.TP 8 +.B \-m +The +.B \-m +and +.B \-M +options cause +.I pseudo +to rename files, replacing the string +.I from +with the string +.I to. +The +.B \-m +option pair implies the +.B \-C +option. You can also specify the +.B \-B +option to request that the database be rebuilt. +.TP 8 +.B \-V +The +.B \-V +option causes +.I pseudo +to print configuration information and exit immediately. +.TP 8 +.B \-S +The +.B \-S +option causes +.I pseudo +to try to find an existing server, and if it finds one, instructs that +server to shut down as soon as all clients are detached from it. Note +that the server will not shut down while clients are connected to it; +in this case, +.I pseudo +will print a list of the remaining client PIDs. +.TP 8 +.B \-d +The +.B \-d +option causes pseudo to immediately detach and run in the background +as a daemon. This is rarely useful except for debugging. +.PP +Finally, invoked without any of these options, +.I pseudo +sets up an emulated root environment, then invokes +.I command +if it was provided, otherwise a shell (using the +.B SHELL +environment variable if it is set, or +.I /bin/sh +otherwise). + +The following options modify the behavior of +.IR pseudo : + +.TP 8 +.BI \-d\ (daemonize) +Run as a daemon; +.I pseudo +detaches from the calling environment and runs as a daemon. The command +returns successfully if this appears to have succeeded, otherwise it +produces an error message and returns a failure status. + +.TP 8 +.BI \-f\ (foreground) +Run in the foreground; +.I pseudo +runs as a server, and does not try to start other commands. This mode +is useful for debugging. + +.TP 8 +.BI \-l\ (log) +Enable logging. The +.I pseudo +daemon will log every filesystem transaction in the log database. + +.TP 8 +.BI \-r\ root +.TP 8 +.BI \-R\ root +Set the +.B PSEUDO_CHROOT +environment variable, running as though the program had called +.I chroot(2) +on the specified path. With +.BR \-r , +this implies changing the working directory to the specified directory; +with +.BR \-R , +it does not. + +.TP 8 +.B \-t timeout +Set the timeout of the +.I pseudo +daemon, in seconds. The default is currently 30 seconds. After this +long with no attached clients, the +.I pseudo +daemon shuts down automatically. The server never shuts down while it +has attached clients. Note that this does not prevent continued use; +new clients can restart the daemon if they need it. + +.TP 8 +.BI \-v\ (verbose) +Increase the verbosity of the +.I pseudo +daemon, and the client library for any programs started by this +invocation of +.IR pseudo . +This is equivalent to the numeric form of the +.B PSEUDO_DEBUG +environment variable; multiple +.B \-v +options increase the debugging level. + +.TP 8 +.BI \-x\ (debug) +Set specific deugging flags (the +.I pseudo +utility's help message lists them). This is equivalent to the string +form of the +.B PSEUDO_DEBUG +environment variable. + +.SH EXAMPLES +The two most common usages of +.I pseudo +are using it to run specific commands, and setting up an environment manually +for running various other commands. + +For the first case, the usage is reasonably simple: + +.sp +$ +.I /path/to/pseudo +.br +# +.I commands which require root privileges + +You may have to use the +.BI \-P prefix +option to tell +.I pseudo +where to look for its database and server. If you specify a full path, +.I pseudo +assumes that +.B PSEUDO_PREFIX +should be the path to the directory containing the +.I pseudo +program, or to the +.I /bin +directory containing the +.I pseudo +program. + +The other way to use +.I pseudo +is by setting up an environment. This is suitable for use in +.I Makefiles +or similar environments, where you want to run a series of commands in +the +.I pseudo +environment, but not to keep invoking the +.I pseudo +command. To do this, set up the +.BR PSEUDO_PREFIX ,\ LD_PRELOAD ,\ and\ LD_LIBRARY_PATH +environment variables, then run programs normally. You do not need to +separately invoke the +.I pseudo +daemon; the client library starts it as needed. + +If you have moved a directory which +.I pseudo +was tracking, you may be able to get the database reattached using the +.B \-m +option. A typical usage might be: + +.sp +$ +.I /path/to/pseudo +.B \-B \-m +.I oldpath +.B \-M +.I newpath +.br + +This requests that +.I pseudo +replace the string +.I oldpath +with the string +.I newpath +at the beginnings of filenames, then regenerate the database, correcting any +device/inode numbers. + +.SH DIAGNOSTICS +Depending on invocation, diagnostic messages usually go either to standard +error or to the file +.B PSEUDO_PREFIX +.IR /var/pseudo/pseudo.log . +By default, +.I pseudo +daemon messages go into the log file, but messages generated by the client +code go to standard error. These can be changed using the +.B PSEUDO_DEBUG_FILE +environment variable, documented in +.BR ENVIRONMENT . +At the default logging level, only critical +messages are displayed. If you have raised the logging level (using the +.I \-v +option or the +.B PSEUDO_DEBUG +environment variable), additional messages are displayed. Levels higher +than 2 are very unlikely to be useful outside of +.I pseudo +development. + +Diagnostic messages seen by default are those which are believed to indicate +either a serious internal flaw in +.I pseudo +or a completely unexpected failure from the underlying operating system. In +normal use, you should see no diagnostic messages. + +.SH ENVIRONMENT +The most significant environment variables for +.I pseudo +are +.B LD_PRELOAD +and +.BR LD_LIBRARY_PATH . +However, these variables have no special meaning to +.IR pseudo ; +rather, they are used in the standard way to manipulate the dynamic linker +into loading the +.I libpseudo +library so that it can intercept calls into the underlying C library. + +The following environment variables are used directly by +.IR pseudo : + +.TP 8 +.B PSEUDO_BINDIR +This directory holds the path to the +.I pseudo +binary; by default, it is the +.I bin +directory under +.B PSEUDO_PREFIX. +.TP 8 +.B PSEUDO_CHROOT +This variable holds the current emulated +.I chroot(2) +path. Paths that are relative to this are treated as though they were +instead relative to the filesystem root. +.TP 8 +.B PSEUDO_DEBUG +This variable holds either a numeric "debug level" for +.I pseudo +to run at, or a set of specific debugging flags, generally letters. +Use +.B pseudo -h +to see the available flags. In general, this is useful only for debugging +.I pseudo +itself. +.TP 8 +.B PSEUDO_DEBUG_FILE +The name of a file to use for debugging messages from the pseudo client; +the default is to log to standard error. If the string contains a single +.BR %s , +that string is replaced with the short program name, and if it contains +a single +.BR %d , +that string is replaced with the process ID. Other format specifiers +(other than '%%') are not allowed. By default, the +.I pseudo +server logs to the file +.I pseudo.log +in the +.I var/pseudo +directory, while clients log to standard error. +.TP 8 +.B PSEUDO_DISABLED +If this variable is set to a value that doesn't look like f, F, n, N, s, S, or +a numeric zero, the +.I pseudo +client library does not modify the behavior of called functions, though it +continues to intercept them and block signals while processing them. This +variable is reevaluated on every call to +.IR fork(2) ,\ clone(2) +or related functions. If the value starts with a lowercase or uppercase +.I s +, the pseudo client disables all server spawning and communications, but still +operates locally. This means that no filesystem mode or permissions changes +are actually recorded or reported, but functions like +.I chown() +will still report success, even though nothing happens. This function is +intended for debugging of issues which are complicated by the server's +involvement. +.TP 8 +.B PSEUDO_ALLOW_FSYNC +If this variable is set, pseudo will allow +.I fsync() +and related system calls, even it was configured with the +.I --enable-force-async +option. Otherwise, that option results in all such calls being +discarded silently, even when +.B PSEUDO_DISABLED +is set. The value specified doesn't matter. +.TP 8 +.B PSEUDO_ENOSYS_ABORT +If this variable is set, the +.I pseudo +client library calls +.I abort() +rather than setting +.I errno +to +.B ENOSYS +in the event of a call to a missing underlying function. This variable has +no function outside of debugging +.I pseudo +itself. +.TP 8 +.B PSEUDO_LIBDIR +This directory holds the path to the +.I pseudo +shared libraries; by default, it is the +.I lib +directory under +.BR PSEUDO_PREFIX . +(On 64-bit hosts, +.I lib64 +is also used.) +.TP 8 +.B PSEUDO_LOCALSTATEDIR +This directory holds the +.I pseudo +database files and log files; by default, it is the +.I var/pseudo +directory under +.BR PSEUDO_PREFIX . +.TP 8 +.B PSEUDO_NOSYMLINKEXP +By default, when chrooted, +.I pseudo +prepends the chroot directory to +the paths used for absolute symlinks; this behavior ensures that +opening symlinks produces expected results in most cases. In some +cases you may want to suppress this. If this variable is unset, or +set to any value other than 0, +.I pseudo +expands symlink paths like this. If this variable is set to 0, +the behavior is disabled. +.TP 8 +.BR PSEUDO_OPTS +This variable holds options to be passed to any new +.I pseudo +servers started. Typically, when +.I pseudo +is used as a launcher, this will be set automatically; however, you +can also use it to pass options when using +.B LD_PRELOAD +to manually run things in the +.I pseudo +environment. +.TP 8 +.B PSEUDO_PASSWD +This variable holds the path to a directory containing password and +group files to use for emulation of various password and group routines. +It should be the path to a directory containing the +.I etc +directory containing files named +.I passwd +and +.IR group . +When +.I pseudo +is emulating a +.I chroot +environment, the chroot directory is used by preference. The +parallelism between these cases is why this variable points at +the parent directory of +.I etc +rather than the directory containing the files. If there is no +.I chroot +environment, and this variable is also unset, +.I pseudo +falls back to a directory specified at configure time, with the +default being the root directory. This is controlled by the +.B PSEUDO_PASSWD_FALLBACK +definition. +.TP 8 +.B PSEUDO_PREFIX +If set, the variable +.B PSEUDO_PREFIX +is used to determine the path to use to find the +.I pseudo +server, in +.BR PSEUDO_PREFIX /bin, +and the +.I pseudo +data files, in +.BR PSEUDO_PREFIX /var/pseudo. +This variable is automatically set by the +.I pseudo +program when it is used as a launcher. +.TP 8 +.B PSEUDO_PROFILE_PATH +If +.I pseudo +was configured with profiling enabled, specifies a path in which to +write client profiling information for use with the +.I pseudo_profile +utility (not built by default). +.TP 8 +.B PSEUDO_TAG +If this variable is set in a client's environment, its value is +communicated to the server at the beginning of each client session, +and recorded in the log database if any logging occurs related to a +specific client. Note that different clients may have different tags +associated with them; the tag value is per-client, not per-server. +.TP 8 +.BR PSEUDO_UIDS ,\ PSEUDO_GIDS +These variables are used internally to pass information about the current +emulated user and group identity from one process to another. +.TP 8 +.B PSEUDO_UNLOAD +This variable is reevaluated on every call to +.IR fork(2) ,\ exec(3) +or related functions. If the variable exists +.RI libpseudo.so +will be removed from +.B LD_PRELOAD +and +.B PSEUDO_DISABLED +behavior will also be triggered. For processes +that simply +.IR fork(2), +the behavior will be the same as if +.B PSEUDO_DISABLED +was set. For new processes, after a call to +.IR exec(3)\ or\ system(3) +pseudo will not be loaded in the new process. +.TP 8 +.B SHELL +If set, this will be used when +.I pseudo +is invoked without either a command or one of the options which directs +it to do something other than run a command. Otherwise, +.I pseudo +defaults to +.I /bin/sh . +.B +.SH BUGS +The +.I pseudo +database is not particularly robust in the face of whole directory trees +being moved, or changes in the underlying device and inode numbers. It +has a reasonable chance of recovering if only the path or the device numbers +have changed, but it is not particularly designed to address this. A future +release is expected to have improved resilience in these cases. + +The filesystem on which +.I pseudo +keeps its database and files must at a minimum support UNIX domain sockets +and reasonable file locking semantics. Note that +.I pseudo +relies on +.I flock(2) +locking semantics; a lock has to persist into a child process. This should +probably eventually be fixed. + +The +.I pseudo +client library is probably thread-safe, but has not been adequately tested +or debugged in that context. + +Filesystem performance is noticably worse under +.I pseudo +than it is otherwise. This is probably because nearly every operation +(other than reads and writes) involves at least one round-trip network +communication with the server, and probably some kind of database +activity. + +.SH SEE ALSO +fakeroot(1), ld.so(8), pseudolog(1), sqlite3(1) +.SH FURTHER READING +Documentation of the internals of +.I pseudo +may be found in the +.I doc +subdirectory of the pseudo source tree. diff --git a/pseudo.c b/pseudo.c new file mode 100644 index 0000000..5715af2 --- /dev/null +++ b/pseudo.c @@ -0,0 +1,1224 @@ +/* + * pseudo.c, main pseudo utility program + * + * Copyright (c) 2008-2013 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <stdlib.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <time.h> +#include <limits.h> + +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/fcntl.h> +#include <sys/file.h> +#include <sys/wait.h> +#include <sys/xattr.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_client.h" +#include "pseudo_server.h" +#include "pseudo_db.h" + +int opt_B = 0; +int opt_C = 0; +int opt_d = 0; +int opt_f = 0; +char *opt_i = NULL; +int opt_l = 0; +char *opt_m = NULL; +char *opt_M = NULL; +long opt_p = 0; +char *opt_r = NULL; +int opt_S = 0; + +static int pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len); +static int pseudo_db_check(int fix); + +void +usage(int status) { + FILE *f = status ? stderr : stdout; + fputs("Usage: pseudo [-dflv] [-x flags] [-P prefix] [-rR root] [-t timeout] [command]\n", f); + fputs(" pseudo -h\n", f); + fputs(" pseudo [-dflv] [-x flags] [-P prefix] [-BC] -i path\n", f); + fputs(" pseudo [-dflv] [-x flags] [-P prefix] [-BC] -m from -M to\n", f); + fputs(" pseudo [-dflv] [-x flags] [-P prefix] -C\n", f); + fputs(" pseudo [-dflv] [-x flags] [-P prefix] -S\n", f); + fputs(" pseudo [-dflv] [-x flags] [-P prefix] -V\n", f); + fputs("Debugging flags:\n", f); + for (int i = 1; i < PDBG_MAX; i += 2) { + unsigned char symbolics[2]; + const char *descriptions[2]; + symbolics[0] = pseudo_debug_type_symbolic(i); + symbolics[1] = pseudo_debug_type_symbolic(i + 1); + descriptions[0] = pseudo_debug_type_description(i); + descriptions[1] = pseudo_debug_type_description(i + 1); + if (symbolics[1]) { + fprintf(f, " %c %-32s %c %-32s\n", + symbolics[0], descriptions[0], + symbolics[1], descriptions[1]); + } else { + fprintf(f, " %c %-32s\n", + symbolics[0], descriptions[0]); + } + } + exit(status); +} + +/* helper function to make a directory, just like mkdir -p. + * Can't use system() because the child shell would end up trying + * to do the same thing... + */ +static void +mkdir_p(char *path) { + size_t len = strlen(path); + size_t i; + + for (i = 1; i < len; ++i) { + /* try to create all the directories in path, ignoring + * failures + */ + if (path[i] == '/') { + path[i] = '\0'; + (void) mkdir(path, 0755); + path[i] = '/'; + } + } + (void) mkdir(path, 0755); +} + +/* main server process */ +int +main(int argc, char *argv[]) { + int o; + char *s; + int lockfd, newfd; + char *ld_env = getenv(PRELINK_LIBRARIES); + int rc = 0; + char opts[pseudo_path_max()], *optptr = opts; + char *lockname; + char *lockpath; + + opts[0] = '\0'; + + pseudo_init_util(); + + if (ld_env && strstr(ld_env, "libpseudo")) { + pseudo_debug(PDBGF_SERVER, "can't run daemon with libpseudo in %s\n", PRELINK_LIBRARIES); + s = pseudo_get_value("PSEUDO_UNLOAD"); + if (s) { + pseudo_diag("pseudo: I can't seem to make %s go away. Sorry.\n", PRELINK_LIBRARIES); + pseudo_diag("pseudo: %s: %s\n", PRELINK_LIBRARIES, ld_env); + exit(EXIT_FAILURE); + } + free(s); + pseudo_set_value("PSEUDO_UNLOAD", "YES"); + pseudo_setupenv(); + pseudo_dropenv(); /* Drop PRELINK_LIBRARIES */ + + execv(argv[0], argv); + exit(EXIT_FAILURE); + } + + /* Be sure to clean PSEUDO_UNLOAD so if we're asked to run any + * programs pseudo will be active in the process... + * (note: pseudo_set_value doesn't muck w/ the environment, thus + * the need for the unsetenv, which is safe because "pseudo" + * is the executable in this case!) + */ + pseudo_set_value("PSEUDO_UNLOAD", NULL); + unsetenv("PSEUDO_UNLOAD"); + + /* we need cwd to canonicalize paths */ + pseudo_client_getcwd(); + + /* warning: GNU getopt permutes arguments, which is just plain + * wrong. The + suppresses this annoying behavior, but may not + * be compatible with sane option libraries. + */ + while ((o = getopt(argc, argv, "+BCdfhi:lm:M:p:P:r:R:St:vVx:")) != -1) { + switch (o) { + case 'B': /* rebuild database */ + opt_B = 1; + opt_C = 1; + break; + case 'C': /* check database */ + opt_C = 1; + break; + case 'd': /* run as daemon */ + opt_d = 1; + break; + case 'f': /* run foregrounded */ + opt_f = 1; + break; + case 'h': /* help */ + usage(0); + break; + case 'i': /* renumber devices, assuming stable inodes */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0); + if (!s) { + pseudo_diag("Can't resolve path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + opt_i = strdup(s); + break; + case 'l': /* log */ + optptr += snprintf(optptr, pseudo_path_max() - (optptr - opts), + "%s-l", optptr > opts ? " " : ""); + opt_l = 1; + break; + case 'm': /* move from... (see also 'M') */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0); + if (!s) { + pseudo_diag("Can't resolve move-from path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + opt_m = strdup(s); + break; + case 'M': /* move to... (see also 'm') */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, 0); + if (!s) { + pseudo_diag("Can't resolve move-to path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + opt_M = strdup(s); + break; + case 'p': /* passwd file path */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW); + if (!s) { + pseudo_diag("Can't resolve passwd path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + pseudo_set_value("PSEUDO_PASSWD", s); + break; + case 'P': /* prefix */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW); + if (!s) { + pseudo_diag("Can't resolve prefix path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + pseudo_set_value("PSEUDO_PREFIX", s); + break; + case 'r': /* chroot to... (fallthrough) */ + case 'R': /* pseudo root path */ + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW); + if (!s) { + pseudo_diag("Can't resolve root path '%s'\n", optarg); + usage(EXIT_FAILURE); + } + pseudo_set_value("PSEUDO_CHROOT", s); + if (o == 'r') + opt_r = strdup(s); + break; + case 'S': /* stop */ + opt_S = 1; + break; + case 't': /* timeout */ + pseudo_server_timeout = strtol(optarg, &s, 10); + if (*s && !isspace(*s)) { + pseudo_diag("Timeout must be an integer value.\n"); + usage(EXIT_FAILURE); + } + optptr += snprintf(optptr, pseudo_path_max() - (optptr - opts), + "%s-t %d", optptr > opts ? " " : "", + pseudo_server_timeout); + break; + case 'v': /* verbosity */ + pseudo_debug_verbose(); + break; + case 'V': /* version info */ + printf("pseudo version %s\n", pseudo_version ? pseudo_version : "<undefined>"); + printf("pseudo configuration:\n prefix: %s\n", + PSEUDO_PREFIX); + printf("Set PSEUDO_PREFIX to run with a different prefix.\n"); + exit(0); + break; + case 'x': /* debug flags */ + pseudo_debug_set(optarg); + break; + case '?': + default: + pseudo_diag("unknown/invalid argument (option '%c').\n", optopt); + usage(EXIT_FAILURE); + break; + } + } + pseudo_debug_flags_finalize(); + /* Options are processed, preserve them... */ + pseudo_set_value("PSEUDO_OPTS", opts); + + if (!pseudo_get_prefix(argv[0])) { + pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n"); + exit(EXIT_FAILURE); + } + + /* move database */ + if (opt_m || opt_M) { + struct stat buf; + pseudo_msg_t *msg; + int rc; + if (!(opt_m && opt_M)) { + pseudo_diag("You cannot move the database without specifying from and to.\n"); + exit(EXIT_FAILURE); + } + if (stat(opt_M, &buf) < 0) { + pseudo_diag("stat of '%s' failed: %s\n", + opt_M, strerror(errno)); + pseudo_diag("The directory the database is being moved to must exist.\n"); + exit(EXIT_FAILURE); + } + msg = pseudo_msg_new(0, opt_M); + if (!msg) { + pseudo_diag("Can't allocate message structure.\n"); + exit(EXIT_FAILURE); + } + rc = pdb_rename_file(opt_m, msg); + free(msg); + if (rc < 0) { + pseudo_diag("Warning: Database move may have failed.\n"); + pseudo_diag("To try to restore, you can reverse the move.\n"); + pseudo_diag("To commit to this anyway, run pseudo -C to check the database.\n"); + exit(EXIT_FAILURE); + } + pseudo_diag("Rename looked okay, running database sanity check.\n"); + opt_C = 1; + } + + if (opt_i) { + int rc; + struct stat buf; + pseudo_msg_t *msg; + if (stat(opt_i, &buf) < 0) { + pseudo_diag("stat of '%s' failed: %s\n", + opt_i, strerror(errno)); + pseudo_diag("The file used to renumber the database must exist.\n"); + exit(EXIT_FAILURE); + } + msg = pseudo_msg_new(0, opt_i); + if (!msg) { + pseudo_diag("Couldn't allocate data structure for path.\n"); + exit(EXIT_FAILURE); + } + if (pdb_find_file_path(msg, NULL)) { + pseudo_diag("Couldn't find a database entry for '%s'.\n", opt_i); + exit(EXIT_FAILURE); + } + if (buf.st_ino != msg->ino) { + pseudo_diag("The database inode entry for '%s' doesn't match; you must use -b.\n", + opt_i); + exit(EXIT_FAILURE); + } + rc = pdb_renumber_all(msg->dev, buf.st_dev); + free(msg); + if (rc < 0) { + pseudo_diag("Warning: Database renumber failed.\n"); + exit(EXIT_FAILURE); + } + pseudo_diag("Renumber looked okay, running database sanity check.\n"); + opt_C = 1; + } + + if (opt_C) { + /* if opt_B is set, try to fix database */ + return pseudo_db_check(opt_B); + } + + if (opt_S) { + return pseudo_client_shutdown(); + } + + if (opt_d && opt_f) { + pseudo_diag("You cannot run a foregrounded daemon.\n"); + exit(EXIT_FAILURE); + } + + if (opt_f || opt_d) { + if (argc > optind) { + pseudo_diag("pseudo: running program implies spawning background daemon.\n"); + exit(EXIT_FAILURE); + } + } else { + char fullpath[pseudo_path_max()]; + char *path; + + if (opt_r) { + if (chdir(opt_r) == -1) { + pseudo_diag("failed to chdir to '%s': %s\n", + opt_r, strerror(errno)); + exit(EXIT_FAILURE); + } + } + if (argc > optind) { + pseudo_debug(PDBGF_INVOKE, "running command: %s\n", + argv[optind]); + argc -= optind; + argv += optind; + } else { + static char *newargv[2]; + argv = newargv; + pseudo_debug(PDBGF_INVOKE, "running shell.\n"); + argv[0] = getenv("SHELL"); + if (!argv[0]) + argv[0] = "/bin/sh"; + argv[1] = NULL; + } + + if (strchr(argv[0], '/')) { + snprintf(fullpath, pseudo_path_max(), "%s", argv[0]); + } else { + int found = 0; + if ((path = getenv("PATH")) == NULL) + path = "/bin:/usr/bin"; + while (*path) { + struct stat buf; + int len = strcspn(path, ":"); + snprintf(fullpath, pseudo_path_max(), "%.*s/%s", + len, path, argv[0]); + path += len; + if (*path == ':') + ++path; + if (!stat(fullpath, &buf)) { + if (buf.st_mode & 0111) { + found = 1; + break; + } + } + } + if (!found) { + pseudo_diag("Can't find '%s' in $PATH.\n", + argv[0]); + exit(EXIT_FAILURE); + } + } + pseudo_setupenv(); + + rc = fork(); + if (rc) { + waitpid(rc, &rc, 0); + /* try to hint that we don't think we still need + * the server. + */ + pseudo_client_shutdown(); + return WEXITSTATUS(rc); + } else { + rc = execv(fullpath, argv); + if (rc == -1) { + pseudo_diag("pseudo: can't run %s: %s\n", + argv[0], strerror(errno)); + } + exit(EXIT_FAILURE); + } + } + /* if we got here, we are not running a command, and we are not in + * a pseudo environment. + */ + pseudo_new_pid(); + + pseudo_debug(PDBGF_SERVER, "opening lock.\n"); + lockpath = pseudo_localstatedir_path(NULL); + if (!lockpath) { + pseudo_diag("Couldn't allocate a file path.\n"); + exit(EXIT_FAILURE); + } + mkdir_p(lockpath); + lockname = pseudo_localstatedir_path(PSEUDO_LOCKFILE); + if (!lockname) { + pseudo_diag("Couldn't allocate a file path.\n"); + exit(EXIT_FAILURE); + } + lockfd = open(lockname, O_RDWR | O_CREAT, 0644); + if (lockfd < 0) { + pseudo_diag("Can't open or create lockfile %s: %s\n", + lockname, strerror(errno)); + exit(EXIT_FAILURE); + } + free(lockname); + + if (lockfd <= 2) { + newfd = fcntl(lockfd, F_DUPFD, 3); + if (newfd < 0) { + pseudo_diag("Can't move lockfile to safe descriptor: %s\n", + strerror(errno)); + } else { + close(lockfd); + lockfd = newfd; + } + } + + pseudo_debug(PDBGF_SERVER, "acquiring lock.\n"); + if (flock(lockfd, LOCK_EX | LOCK_NB) < 0) { + if (errno == EACCES || errno == EAGAIN) { + pseudo_debug(PDBGF_SERVER, "Existing server has lock. Exiting.\n"); + } else { + pseudo_diag("pseudo: Error obtaining lock: %s\n", strerror(errno)); + } + exit(0); + } else { + pseudo_debug(PDBGF_SERVER, "Acquired lock.\n"); + } + pseudo_debug(PDBGF_SERVER, "serving (%s)\n", opt_d ? "daemon" : "foreground"); + return pseudo_server_start(opt_d); +} + +/* + * actually process operations. + * This first evaluates the message, figures out what's in the DB, does some + * sanity checks, then implements the fairly small DB changes required. + */ +int +pseudo_op(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len) { + pseudo_msg_t msg_header; + pseudo_msg_t by_path = { .op = 0 }, by_ino = { .op = 0 }; + long long row = -1; + pseudo_msg_t db_header; + char *path_by_ino = 0; + char *oldpath = 0; + size_t oldpathlen = 0; + int found_path = 0, found_ino = 0; + int prefer_ino = 0; + int xattr_flags = 0; + int trailing_slash = 0; + + if (!msg) + return 1; + + msg->result = RESULT_SUCCEED; + + /* debugging message. Primary key first. */ + switch (msg->op) { + case OP_FCHOWN: /* FALLTHROUGH */ + case OP_FCHMOD: /* FALLTHROUGH */ + case OP_FSTAT: + prefer_ino = 1; + pseudo_debug(PDBGF_OP, "%s %llu [%s]: ", pseudo_op_name(msg->op), + (unsigned long long) msg->ino, + msg->pathlen ? msg->path : "no path"); + break; + default: + pseudo_debug(PDBGF_OP, "%s %s [%llu]: ", pseudo_op_name(msg->op), + msg->pathlen ? msg->path : "no path", + (unsigned long long) msg->ino); + break; + } + + /* Process rename path separation, there are two paths old / new + * stuff into a rename, break them apart (null seperated) + */ + + if (msg->pathlen) { + size_t initial_len; + switch (msg->op) { + case OP_RENAME: + case OP_CREATE_XATTR: + case OP_GET_XATTR: + case OP_LIST_XATTR: + case OP_REPLACE_XATTR: + case OP_SET_XATTR: + /* In a rename there are two paths, null separated in msg->path */ + initial_len = strlen(msg->path); + oldpath = msg->path + initial_len + 1; + /* for rename, the path name would be null-terminated, + * but for *xattr, we don't want the null. */ + oldpathlen = msg->pathlen - (oldpath - msg->path) - 1; + pseudo_debug(PDBGF_OP | PDBGF_FILE | PDBGF_XATTR, "%s: path '%s', oldpath '%s' [%d/%d]\n", + pseudo_op_name(msg->op), msg->path, oldpath, (int) oldpathlen, (int) msg->pathlen); + /* For a rename op, we want to strip any trailing + * slashes. For xattr, "oldpath" is the raw data + * to be stored. */ + if (oldpathlen > 0 && msg->op == OP_RENAME) { + if (oldpath[oldpathlen - 1] == '/') { + oldpath[--oldpathlen] = '\0'; + } + } + /* if we got an oldpath, but a 0-length initial + * path, we don't want to act as though we had + * a non-empty initial path. + */ + + msg->pathlen = initial_len; + break; + default: + break; + } + } + + /* stash original header, in case we need it later */ + msg_header = *msg; + by_ino = msg_header; + + /* trailing slashes are kept in paths because they affect + * path resolution, but we don't want them in the database + * because they're optional. For now, any error-checking on + * this server-side is purely advisory, but the client should + * bail with ENOTDIR a lot earlier in many cases, before the + * server even sees anything. + */ + if (msg->pathlen) { + if (msg->path[msg->pathlen - 1] == '/') { + msg->path[--msg->pathlen] = '\0'; + trailing_slash = 1; + } + } + + /* There should usually be a path. Even for f* ops, the client + * tries to provide a path from its table of known fd paths. + */ + /* Lookup the full path, with inode and dev if available */ + if (msg->pathlen && msg->dev && msg->ino) { + if (!pdb_find_file_exact(msg, &row)) { + /* restore header contents */ + by_path = *msg; + by_ino = *msg; + *msg = msg_header; + found_path = 1; + found_ino = 1; + /* note: we have to avoid freeing this later */ + path_by_ino = msg->path; + if (msg->op == OP_LINK) { + pseudo_debug(PDBGF_FILE, "[matches existing link]"); + } + } + } + + if (!found_path && !found_ino) { + if (msg->pathlen) { + /* for now, don't canonicalize paths anymore */ + /* used to do it here, but now doing it in client */ + if (!pdb_find_file_path(msg, &row)) { + by_path = *msg; + found_path = 1; + } else { + if (msg->op != OP_RENAME && msg->op != OP_LINK) { + pseudo_debug(PDBGF_FILE, "(new?) "); + } + } + } + /* search on original inode -- in case of mismatch */ + if (msg->dev && msg->ino) { + if (!pdb_find_file_dev(&by_ino, &row, &path_by_ino)) { + found_ino = 1; + } + } + } + + pseudo_debug(PDBGF_OP, "incoming: '%s'%s [%llu]%s\n", + msg->pathlen ? msg->path : "no path", + found_path ? "+" : "-", + (unsigned long long) msg_header.ino, + found_ino ? "+" : "-"); + + /* the sanity checks are inappropriate for DID_UNLINK, since it's + * completely legitimate to have a new database entry for the + * same inode. + */ + if (found_path && msg->op != OP_DID_UNLINK) { + /* This is a bad sign. We should never have a different entry + * for the inode... But an inode of 0 from an EXEC is normal, + * we don't track those. + */ + if (by_path.ino != msg_header.ino && msg_header.ino != 0) { + switch (msg->op) { + case OP_EXEC: + break; + default: + /* if the path is in the database with a + * different inode, but we were expecting + * it to get deleted, mark the old one + * as deleted. + */ + if (by_path.deleting != 0) { + pseudo_debug(PDBGF_FILE, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n", + msg->path); + pdb_did_unlink_file(msg->path, by_path.deleting); + } else { + pseudo_diag("inode mismatch: '%s' ino %llu in db, %llu in request.\n", + msg->path, + (unsigned long long) by_path.ino, + (unsigned long long) msg_header.ino); + } + } + } + /* If the database entry disagrees on S_ISDIR, it's just + * plain wrong. We remove the database entry, because it + * is absolutely certain to be wrong. This means found_path + * is now 0, because there is no entry in db... + * + * This used to unlink everything with the inode from + * the message -- but what if the entry in the database + * had a different inode? We should nuke THAT inode, + * and everything agreeing with it, which will also catch + * the bogus entry that we noticed. + */ + if (S_ISDIR(by_path.mode) != S_ISDIR(msg_header.mode)) { + pseudo_diag("dir mismatch: '%s' [%llu] db mode 0%o, header mode 0%o (unlinking db)\n", + msg->path, (unsigned long long) by_path.ino, + (int) by_path.mode, (int) msg_header.mode); + /* unlink everything with this inode */ + pdb_unlink_file_dev(&by_path); + found_path = 0; + } else if (S_ISLNK(by_path.mode) != S_ISLNK(msg_header.mode)) { + pseudo_diag("symlink mismatch: '%s' [%llu] db mode 0%o, header mode 0%o (unlinking db)\n", + msg->path, (unsigned long long) by_path.ino, + (int) by_path.mode, (int) msg_header.mode); + /* unlink everything with this inode */ + pdb_unlink_file_dev(&by_path); + found_path = 0; + } + if (trailing_slash && !S_ISDIR(by_path.mode)) { + pseudo_diag("dir quasi-mismatch: '%s' [%llu] db mode 0%o, incoming path had trailing slash. Not unlinking.\n", + msg->path, (unsigned long long) by_path.ino, + (int) by_path.mode); + } + } + + /* for OP_DID_UNLINK, the reason this op exists is that the same + * inode might have been reclaimed. Don't sanity-check it, and + * especially don't delete the database contents! + */ + if (found_ino && msg->op != OP_DID_UNLINK) { + /* Not always an absolute failure case. + * If a file descriptor shows up unexpectedly and gets + * fchown()d, you could have an entry giving the inode and + * data, but not path. So, we add the path to the entry. + * Any other changes from the incoming message will be applied + * at leisure. + */ + if (msg->pathlen && !path_by_ino) { + pseudo_debug(PDBGF_FILE, "db path missing: ino %llu, request '%s'.\n", + (unsigned long long) msg_header.ino, msg->path); + pdb_update_file_path(msg); + } else if (!msg->pathlen && path_by_ino) { + /* harmless */ + pseudo_debug(PDBGF_FILE, "req path missing: ino %llu, db '%s'.\n", + (unsigned long long) msg_header.ino, path_by_ino); + } else if (msg->pathlen && path_by_ino) { + /* this suggests a database error, except in LINK + * cases. In those cases, it is normal for a + * mismatch to occur. :) (SYMLINK shouldn't, + * because the symlink gets its own inode number.) + * + * RENAME can get false positives on this, when + * link count is greater than one. So we skip this + * test for OP_LINK (always) and OP_RENAME (for link + * count greater than one). For RENAME, the test + * should be against the old name, though! + */ + int mismatch = 0; + switch (msg->op) { + case OP_LINK: + case OP_EXEC: + break; + case OP_RENAME: + if (msg->nlink == 1 && strcmp(oldpath, path_by_ino)) { + mismatch = 1; + } + break; + default: + if (strcmp(msg->path, path_by_ino)) { + mismatch = 1; + } + break; + } + if (mismatch) { + /* a mismatch, but we were planning to delete + * the file, so it must have gotten deleted + * already. + */ + if (by_ino.deleting != 0) { + pseudo_debug(PDBGF_FILE, "inode mismatch for '%s' -- old one was marked for deletion, deleting.\n", + msg->path); + pdb_did_unlink_file(path_by_ino, by_ino.deleting); + } else { + pseudo_diag("path mismatch [%d link%s]: ino %llu db '%s' req '%s'.\n", + msg->nlink, + msg->nlink == 1 ? "" : "s", + (unsigned long long) msg_header.ino, + path_by_ino ? path_by_ino : "no path", + msg->path); + } + } + } else { + /* I don't think I've ever seen this one. */ + pseudo_debug(PDBGF_FILE, "warning: ino %llu in db (mode 0%o, owner %d), no path known.\n", + (unsigned long long) msg_header.ino, + (int) by_ino.mode, (int) by_ino.uid); + } + /* Again, in the case of a directory mismatch, nuke the DB + * entry. There is no way it can be right. + */ + if (S_ISDIR(by_ino.mode) != S_ISDIR(msg_header.mode)) { + pseudo_diag("dir err : %llu ['%s'] (db '%s') db mode 0%o, header mode 0%o (unlinking db)\n", + (unsigned long long) msg_header.ino, + msg->pathlen ? msg->path : "no path", + path_by_ino ? path_by_ino : "no path", + (int) by_ino.mode, (int) msg_header.mode); + pdb_unlink_file_dev(msg); + found_ino = 0; + } else if (S_ISLNK(by_ino.mode) != S_ISLNK(msg_header.mode)) { + /* In the current implementation, only msg_header.mode + * can ever be a symlink; the test is generic as + * insurance against forgetting to fix it in a future + * update. */ + pseudo_diag("symlink err : %llu ['%s'] (db '%s') db mode 0%o, header mode 0%o (unlinking db)\n", + (unsigned long long) msg_header.ino, + msg->pathlen ? msg->path : "no path", + path_by_ino ? path_by_ino : "no path", + (int) by_ino.mode, (int) msg_header.mode); + pdb_unlink_file_dev(msg); + found_ino = 0; + } + } + + /* In the case of a stat() call, if a mismatch existed, we prefer + * by-inode for fstat, by-path for stat. Nothing else actually uses + * this... + */ + if (found_ino && (prefer_ino || !found_path)) { + db_header = by_ino; + } else if (found_path) { + db_header = by_path; + } + + switch (msg->op) { + case OP_CHDIR: /* FALLTHROUGH */ + case OP_CLOSE: + /* these messages are handled entirely on the client side, + * as of this writing, but might be logged by accident: */ + pseudo_diag("error: op %s sent to server.\n", pseudo_op_name(msg->op)); + break; + case OP_EXEC: /* FALLTHROUGH */ + case OP_OPEN: + /* nothing to do -- just sent in case we're logging */ + break; + case OP_CREAT: + /* implies a new file -- not a link, which would be OP_LINK */ + if (found_ino) { + /* CREAT should never be sent if the file existed. + * So, any existing entry is an error. Nuke it. + */ + pseudo_diag("creat for '%s' replaces existing %llu ['%s'].\n", + msg->pathlen ? msg->path : "no path", + (unsigned long long) by_ino.ino, + path_by_ino ? path_by_ino : "no path"); + pdb_unlink_file_dev(&by_ino); + } + if (!found_path) { + pseudo_debug(PDBGF_DB, "linking %s for OP_CREAT\n", + msg->pathlen ? msg->path : "no path"); + pdb_link_file(msg, NULL); + } else { + /* again, an error, but leaving it alone for now. */ + pseudo_diag("creat ignored for existing file '%s'.\n", + msg->pathlen ? msg->path : "no path"); + } + break; + case OP_CHMOD: /* FALLTHROUGH */ + case OP_FCHMOD: + pseudo_debug(PDBGF_OP, "mode 0%o ", (int) msg->mode); + /* if the inode is known, update it */ + if (found_ino) { + /* obtain the existing data, merge with mode */ + *msg = by_ino; + msg->mode = (msg_header.mode & 07777) | + (msg->mode & ~07777); + pdb_update_file(msg); + } else if (found_path) { + /* obtain the existing data, merge with mode */ + *msg = by_path; + msg->mode = (msg_header.mode & 07777) | + (by_path.mode & ~07777); + pdb_update_file(msg); + } else { + /* just in case find_file_path screwed up the msg */ + msg->mode = msg_header.mode; + } + /* if we've never seen the file at all before, link it. + * If we have it in the db by inode, but not by name, + * it got fixed during the sanity checks. + */ + if (!found_path && !found_ino) { + pseudo_debug(PDBGF_FILE, "(new) "); + pseudo_debug(PDBGF_DB, "linking %s for OP_[F]CHMOD\n", + msg->pathlen ? msg->path : "no path"); + pdb_link_file(msg, NULL); + } + break; + case OP_CHOWN: /* FALLTHROUGH */ + case OP_FCHOWN: + pseudo_debug(PDBGF_OP, "owner %d:%d ", (int) msg_header.uid, (int) msg_header.gid); + /* if the inode is known, update it */ + if (found_ino) { + /* obtain the existing data, merge with mode */ + *msg = by_ino; + msg->uid = msg_header.uid; + msg->gid = msg_header.gid; + pdb_update_file(msg); + } else if (found_path) { + /* obtain the existing data, merge with mode */ + *msg = by_path; + msg->uid = msg_header.uid; + msg->gid = msg_header.gid; + pdb_update_file(msg); + } else { + /* just in case find_file_path screwed up the msg */ + msg->uid = msg_header.uid; + msg->gid = msg_header.gid; + } + /* if we've never seen the file at all before, link it. + * If we have it in the db by inode, but not by name, + * it got fixed during the sanity checks. + */ + if (!found_path && !found_ino) { + pseudo_debug(PDBGF_FILE, "(new) "); + pseudo_debug(PDBGF_DB, "linking %s for OP_[F]CHOWN\n", + msg->pathlen ? msg->path : "no path"); + pdb_link_file(msg, NULL); + } + break; + case OP_STAT: /* FALLTHROUGH */ + case OP_FSTAT: + /* db_header will be whichever one looked best, in the rare + * case where there might be a clash. + */ + if (found_ino || found_path) { + #ifdef PSEUDO_XATTRDB + if (db_header.uid == (uid_t) -1 && + db_header.gid == (gid_t) -1) { + /* special case: this row was created + * to allow xattr lookups, and it's not + * actually valid data. + */ + msg->result = RESULT_FAIL; + } else + #endif + { + *msg = db_header; + } + } else { + msg->result = RESULT_FAIL; + } + pseudo_debug(PDBGF_OP | PDBGF_VERBOSE, "%s, ino %llu (old mode 0%o): mode 0%o\n", + pseudo_op_name(msg->op), (unsigned long long) msg->ino, + (int) msg_header.mode, (int) msg->mode); + break; + case OP_LINK: /* FALLTHROUGH */ + case OP_SYMLINK: + /* a successful link (client only notifies us for those) + * implies that the new path did not previously exist, and + * the old path did. We get the stat buffer and the new path. + * So, we unlink it, then link it. Neither unlink nor link + * touches the message, which was initialized from the + * underlying file data in the client. + */ + if (found_path) { + pseudo_debug(PDBGF_OP | PDBGF_FILE, "replace %slink: path %s, old ino %llu, mode 0%o, new ino %llu, mode 0%o\n", + msg->op == OP_SYMLINK ? "sym" : "", + msg->path, (unsigned long long) msg->ino, + (int) msg->mode, + (unsigned long long) msg_header.ino, + (int) msg_header.mode); + pdb_unlink_file(msg); + } else { + pseudo_debug(PDBGF_OP | PDBGF_FILE, "new %slink: path %s, ino %llu, mode 0%o\n", + msg->op == OP_SYMLINK ? "sym" : "", + msg->path, + (unsigned long long) msg_header.ino, + (int) msg_header.mode); + } + if (found_ino) { + if (msg->op == OP_SYMLINK) { + pseudo_debug(PDBGF_OP | PDBGF_FILE, "symlink: ignoring existing file %llu ['%s']\n", + (unsigned long long) by_ino.ino, + path_by_ino ? path_by_ino : "no path"); + } else { + *msg = by_ino; + pseudo_debug(PDBGF_OP | PDBGF_FILE, "link: copying data from existing file %llu ['%s']\n", + (unsigned long long) by_ino.ino, + path_by_ino ? path_by_ino : "no path"); + } + } else { + *msg = msg_header; + } + pseudo_debug(PDBGF_DB, "linking %s for %s\n", + msg->pathlen ? msg->path : "no path", + pseudo_op_name(msg->op)); + pdb_link_file(msg, NULL); + break; + case OP_RENAME: + /* a rename implies renaming an existing entry... and every + * database entry rooted in it, if it's a directory. + */ + pdb_rename_file(oldpath, msg); + pdb_update_inode(msg); + break; + case OP_MAY_UNLINK: + if (pdb_may_unlink_file(msg, msg->client)) { + /* harmless, but client wants to know so it knows + * whether to follow up... */ + msg->result = RESULT_FAIL; + } + break; + case OP_DID_UNLINK: + pdb_did_unlink_file(msg->path, msg->client); + break; + case OP_CANCEL_UNLINK: + pdb_cancel_unlink_file(msg); + break; + case OP_UNLINK: + /* this removes any entries with the given path from the + * database. No response is needed. + * DO NOT try to fail if the entry is already gone -- if the + * server's response didn't make it, the client would resend. + */ + pdb_unlink_file(msg); + pdb_unlink_contents(msg); + /* If we are seeing an unlink for something with only one + * link, we should delete all records for that inode, even + * ones through different paths. This handles the case + * where something is removed through the wrong path, but + * only if it didn't have multiple hard links. + * + * This should cease to be needed once symlinks are tracked. + */ + if (msg_header.nlink == 1 && found_ino) { + pseudo_debug(PDBGF_FILE | PDBGF_OP, "link count 1, unlinking anything with ino %llu.\n", + (unsigned long long) msg->ino); + pdb_unlink_file_dev(msg); + } + msg->result = RESULT_NONE; + break; + case OP_MKDIR: /* FALLTHROUGH */ + case OP_MKNOD: + pseudo_debug(PDBGF_OP, "mode 0%o", (int) msg->mode); + /* for us to get called, the client has to have succeeded in + * a creation (of a regular file, for mknod) -- meaning this + * file DID NOT exist before the call. Fix database: + */ + if (found_path) { + pseudo_diag("mkdir/mknod: '%s' [%llu] already existed (mode 0%o), unlinking\n", + msg->path, (unsigned long long) by_path.ino, + (int) by_path.mode); + pdb_unlink_file(msg); + } + if (found_ino) { + pdb_unlink_file_dev(&by_ino); + } + *msg = msg_header; + pseudo_debug(PDBGF_DB, "linking %s for %s\n", + msg->pathlen ? msg->path : "no path", + pseudo_op_name(msg->op)); + pdb_link_file(msg, NULL); + break; + case OP_GET_XATTR: + if (pdb_get_xattr(row, &oldpath, &oldpathlen)) { + msg->result = RESULT_FAIL; + } else { + *response_path = oldpath; + *response_len = oldpathlen; + pseudo_debug(PDBGF_XATTR, "get results: '%.*s' (%d bytes)\n", + (int) *response_len, *response_path, (int) *response_len); + } + break; + case OP_LIST_XATTR: + if (pdb_list_xattr(row, &oldpath, &oldpathlen)) { + msg->result = RESULT_FAIL; + } else { + pseudo_debug(PDBGF_XATTR, "got %d bytes of xattrs to list: %.*s\n", (int) oldpathlen, (int) oldpathlen, oldpath); + *response_path = oldpath; + *response_len = oldpathlen; + } + break; + case OP_CREATE_XATTR: + case OP_REPLACE_XATTR: /* fallthrough */ + if (msg->op == OP_CREATE_XATTR) { + xattr_flags = XATTR_CREATE; + } + if (msg->op == OP_REPLACE_XATTR) { + xattr_flags = XATTR_REPLACE; + } + case OP_SET_XATTR: + /* we need a row entry to store xattr info */ + if (row == -1) { +#ifdef PSEUDO_XATTRDB + /* mark the entry as Not Reliable for purposes + * of stat-type calls, since changes wouldn't + * get reported to us. + */ + msg->uid = (uid_t) -1; + msg->gid = (gid_t) -1; +#endif + pseudo_debug(PDBGF_DB, "linking %s (uid -1 if xattr) for %s\n", + msg->pathlen ? msg->path : "no path", + pseudo_op_name(msg->op)); + pdb_link_file(msg, &row); + } + if (pdb_set_xattr(row, oldpath, oldpathlen, xattr_flags)) { + msg->result = RESULT_FAIL; + } + break; + case OP_REMOVE_XATTR: + pdb_remove_xattr(row, oldpath, oldpathlen); + break; + default: + pseudo_diag("unknown op from client %d, op %d [%s]\n", + msg->client, msg->op, + msg->pathlen ? msg->path : "no path"); + break; + } + + /* in the case of an exact match, we just used the pointer + * rather than allocating space. + */ + if (path_by_ino != msg->path) { + free(path_by_ino); + } + pseudo_debug(PDBGF_OP, "completed %s.\n", pseudo_op_name(msg->op)); + if (opt_l) + pdb_log_msg(SEVERITY_INFO, msg, program, tag, NULL); + return 0; +} + +/* SHUTDOWN does not get this far, it's handled in pseudo_server.c */ +int +pseudo_server_response(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len) { + switch (msg->type) { + case PSEUDO_MSG_PING: + /* mad hackery: if we aren't logging, the client gets told + * not to send open/exec notifications, which have no other + * purpose. + */ + msg->result = opt_l ? RESULT_SUCCEED : RESULT_FAIL; + if (opt_l) + pdb_log_msg(SEVERITY_INFO, msg, program, tag, NULL); + return 0; + break; + case PSEUDO_MSG_OP: + case PSEUDO_MSG_FASTOP: + return pseudo_op(msg, program, tag, response_path, response_len); + break; + case PSEUDO_MSG_ACK: /* FALLTHROUGH */ + case PSEUDO_MSG_NAK: /* FALLTHROUGH */ + default: + pdb_log_msg(SEVERITY_WARN, msg, program, tag, "invalid message"); + return 1; + } +} + +int +pseudo_db_check(int fix) { + struct stat buf; + pseudo_msg_t *m; + pdb_file_list l; + int errors = 0; + int delete_some = 0; + /* magic cookie used to show who's deleting the files */ + int magic_cookie = (int) getpid(); + int rc = 0; + + l = pdb_files(); + if (!l) { + pseudo_diag("Couldn't start file list, can't scan.\n"); + return EXIT_FAILURE; + } + while ((m = pdb_file(l)) != NULL) { + pseudo_debug(PDBGF_DB, "m: %p (%d: %s)\n", + (void *) m, + m ? (int) m->pathlen : -1, + m ? m->path : "<n/a>"); + if (m->pathlen > 0) { + int fixup_needed = 0; + pseudo_debug(PDBGF_DB, "Checking <%s>\n", m->path); + if (lstat(m->path, &buf)) { + errors = EXIT_FAILURE; + pseudo_diag("can't stat <%s>\n", m->path); + continue; + } + /* can't check for device type mismatches, uid/gid, or + * permissions, because those are the very things we + * can't really set. + */ + if (buf.st_ino != m->ino) { + pseudo_debug(PDBGF_DB, "ino mismatch <%s>: ino %llu, db %llu\n", + m->path, + (unsigned long long) buf.st_ino, + (unsigned long long) m->ino); + m->ino = buf.st_ino; + fixup_needed = 1; + } + if (buf.st_dev != m->dev) { + pseudo_debug(PDBGF_DB, "dev mismatch <%s>: dev %llu, db %llu\n", + m->path, + (unsigned long long) buf.st_dev, + (unsigned long long) m->dev); + m->dev = buf.st_dev; + fixup_needed = 1; + } + if (S_ISLNK(buf.st_mode) != S_ISLNK(m->mode)) { + pseudo_debug(PDBGF_DB, "symlink mismatch <%s>: file %d, db %d\n", + m->path, + S_ISLNK(buf.st_mode), + S_ISLNK(m->mode)); + fixup_needed = 2; + } + if (S_ISDIR(buf.st_mode) != S_ISDIR(m->mode)) { + pseudo_debug(PDBGF_DB, "symlink mismatch <%s>: file %d, db %d\n", + m->path, + S_ISDIR(buf.st_mode), + S_ISDIR(m->mode)); + fixup_needed = 2; + } + if (fixup_needed) { + /* in fixup mode, either delete (mismatches) or + * correct (dev/ino). + */ + if (fix) { + if (fixup_needed == 1) { + rc = pdb_update_inode(m); + } else if (fixup_needed == 2) { + /* mark for deletion */ + delete_some = 1; + rc = pdb_may_unlink_file(m, magic_cookie); + } + if (rc) { + pseudo_diag("error updating file %s\n", + m->path); + errors = EXIT_FAILURE; + } + } else { + errors = EXIT_FAILURE; + } + } + } + } + pdb_files_done(l); + /* and now delete files marked for deletion */ + if (delete_some) { + rc = pdb_did_unlink_files(magic_cookie); + if (rc) { + pseudo_diag("error nuking mismatched files.\n"); + pseudo_diag("database may not be fixed.\n"); + errors = EXIT_FAILURE; + } + } + return errors; +} diff --git a/pseudo.h b/pseudo.h new file mode 100644 index 0000000..81db201 --- /dev/null +++ b/pseudo.h @@ -0,0 +1,165 @@ +/* + * pseudo.h, shared definitions and structures for pseudo + * + * Copyright (c) 2008-2010, 2013 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <stdlib.h> +#include <fcntl.h> + +/* List of magic initialization functions... */ +extern void pseudo_init_wrappers(void); +extern void pseudo_init_util(void); +extern void pseudo_init_client(void); + +void pseudo_dump_env(char **envp); +int pseudo_set_value(const char *key, const char *value); +char *pseudo_get_value(const char *key); +int pseudo_has_unload(char * const *envp); + +#include "pseudo_tables.h" + +extern void pseudo_debug_verbose(void); +extern void pseudo_debug_terse(void); +extern void pseudo_debug_set(char *); +extern void pseudo_debug_clear(char *); +extern void pseudo_debug_flags_finalize(void); +extern unsigned long pseudo_util_debug_flags; +extern int pseudo_util_debug_fd; +extern int pseudo_disabled; +extern int pseudo_allow_fsync; +extern int pseudo_diag(char *, ...) __attribute__ ((format (printf, 1, 2))); +#ifndef NDEBUG +#define pseudo_debug(x, ...) do { \ + if ((x) & PDBGF_VERBOSE) { \ + if ((pseudo_util_debug_flags & PDBGF_VERBOSE) && (pseudo_util_debug_flags & ((x) & ~PDBGF_VERBOSE))) { pseudo_diag(__VA_ARGS__); } \ + } else { \ + if (pseudo_util_debug_flags & (x)) { pseudo_diag(__VA_ARGS__); } \ + } \ +} while (0) +#define pseudo_debug_call(x, fn, ...) do { \ + if ((x) & PDBGF_VERBOSE) { \ + if ((pseudo_util_debug_flags & PDBGF_VERBOSE) && (pseudo_util_debug_flags & ((x) & ~PDBGF_VERBOSE))) { fn(__VA_ARGS__); } \ + } else { \ + if (pseudo_util_debug_flags & (x)) { fn(__VA_ARGS__); } \ + } \ +} while (0) +#else +/* this used to be a static inline function, but that meant that arguments + * were still evaluated for side effects. We don't want that. The ... + * is a C99ism, also supported by GNU C. + */ +#define pseudo_debug(...) 0 +#define pseudo_debug_call(...) 0 +#endif +extern void pseudo_dump_data(char *, const void *, size_t); +void pseudo_new_pid(void); +/* pseudo_fix_path resolves symlinks up to this depth */ +#define PSEUDO_MAX_LINK_RECURSION 16 +extern char *pseudo_fix_path(const char *, const char *, size_t, size_t, size_t *, int); +extern void pseudo_dropenv(void); +extern char **pseudo_dropenvp(char * const *); +extern void pseudo_setupenv(void); +extern char **pseudo_setupenvp(char * const *); +extern char *pseudo_prefix_path(char *); +extern char *pseudo_bindir_path(char *); +extern char *pseudo_libdir_path(char *); +extern char *pseudo_localstatedir_path(char *); +extern char *pseudo_get_prefix(char *); +extern char *pseudo_get_bindir(void); +extern char *pseudo_get_libdir(void); +extern char *pseudo_get_localstatedir(void); +extern int pseudo_logfile(char *defname); +extern ssize_t pseudo_sys_path_max(void); +extern ssize_t pseudo_path_max(void); +#define PSEUDO_PWD_MAX 4096 +extern int pseudo_etc_file(const char *filename, char *realname, int flags, const char **search_dirs, int dircount); +extern void pseudo_stat32_from64(struct stat *, const struct stat64 *); +extern void pseudo_stat64_from32(struct stat64 *, const struct stat *); + +extern char *pseudo_version; + +#ifndef PSEUDO_BINDIR + #define PSEUDO_BINDIR "bin" +#endif + +#ifndef PSEUDO_LIBDIR + #define PSEUDO_LIBDIR "lib" +#endif + +#define STARTSWITH(x, y) (!memcmp((x), (y), sizeof(y) - 1)) + +#ifndef PSEUDO_LOCALSTATEDIR + #define PSEUDO_LOCALSTATEDIR "var/pseudo" +#endif + +#define PSEUDO_LOCKFILE "pseudo.lock" +#define PSEUDO_LOGFILE "pseudo.log" +#define PSEUDO_PIDFILE "pseudo.pid" +#define PSEUDO_SOCKET "pseudo.socket" + +/* some systems might not have *at(). We like to define operations in + * terms of each other, and for instance, open(...) is the same as + * openat(AT_FDCWD, ...). If no AT_FDCWD is provided, any value that can't + * be a valid file descriptor will do. Using -2 because -1 could be + * mistaken for a failed syscall return. AT_SYMLINK_NOFOLLOW has to be + * non-zero; AT_SYMLINK_FOLLOW has to be non-zero and different. Finally, + * if this happened, we set our own flag we can use to indicate that dummy + * implementations of the _at functions are needed. + */ +#ifndef AT_FDCWD +#define AT_FDCWD -2 +#define AT_SYMLINK_NOFOLLOW 1 +#define AT_SYMLINK_FOLLOW 2 +#define PSEUDO_NO_REAL_AT_FUNCTIONS +#endif + +/* Likewise, someone might not have O_LARGEFILE (the flag equivalent to + * using open64()). Since open64() is the same as O_LARGEFILE in flags, + * we implement it that way... If the system has no O_LARGEFILE, we'll + * just call open() with nothing special. + */ +#ifndef O_LARGEFILE +#define O_LARGEFILE 0 +#endif + +/* Does link(2) let you create hard links to symlinks? Of course not. Who + * would ever do that? Well, Linux did, and possibly as a result, linkat() + * does by default too; if you are on a host with the historical Unix + * behavior of following symlinks to find the link target, you will want + * to set this to AT_SYMLINK_FOLLOW. Darwin does. + */ +#define PSEUDO_LINK_SYMLINK_BEHAVIOR 0 + +/* given n, pick a multiple of block enough bigger than n + * to give us some breathing room. + */ +static inline size_t +round_up(size_t n, size_t block) { + return block * (((n + block / 4) / block) + 1); +} + +#ifdef PSEUDO_PROFILING +typedef struct { + int processes; + long long total_ops; + long long messages; + struct timeval op_time; + struct timeval ipc_time; + struct timeval wrapper_time; +} pseudo_profile_t; +#endif +#include "pseudo_ports.h" diff --git a/pseudo_client.c b/pseudo_client.c new file mode 100644 index 0000000..4eb91df --- /dev/null +++ b/pseudo_client.c @@ -0,0 +1,1982 @@ +/* + * pseudo_client.c, pseudo client library code + * + * Copyright (c) 2008-2013 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#define _GNU_SOURCE + +#include <stdio.h> +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <unistd.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> + +#ifdef PSEUDO_XATTRDB +#include <sys/xattr.h> +#endif + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_client.h" + +/* GNU extension */ +#if PSEUDO_PORT_LINUX +extern char *program_invocation_name; +#else +static char *program_invocation_name = "unknown"; +#endif + +static char *base_path(int dirfd, const char *path, int leave_last); + +static int connect_fd = -1; +static int server_pid = 0; +int pseudo_prefix_dir_fd = -1; +int pseudo_localstate_dir_fd = -1; +int pseudo_pwd_fd = -1; +int pseudo_pwd_lck_fd = -1; +char *pseudo_pwd_lck_name = NULL; +FILE *pseudo_pwd = NULL; +int pseudo_grp_fd = -1; +FILE *pseudo_grp = NULL; +char *pseudo_cwd = NULL; +size_t pseudo_cwd_len; +char *pseudo_chroot = NULL; +char *pseudo_passwd = NULL; +size_t pseudo_chroot_len = 0; +char *pseudo_cwd_rel = NULL; +/* used for PSEUDO_DISABLED */ +int pseudo_disabled = 0; +int pseudo_allow_fsync = 0; +static int pseudo_local_only = 0; +static int pseudo_client_logging = 1; + +int pseudo_umask = 022; + +static char **fd_paths = NULL; +static int nfds = 0; +static const char **passwd_paths = NULL; +static int npasswd_paths = 0; +#ifdef PSEUDO_PROFILING +int pseudo_profile_fd = -1; +static int profile_interval = 1; +static pseudo_profile_t profile_data; +struct timeval *pseudo_wrapper_time = &profile_data.wrapper_time; +#endif +static int pseudo_inited = 0; + +static int sent_messages = 0; + +int pseudo_nosymlinkexp = 0; + +/* note: these are int, not uid_t/gid_t, so I can use 'em with scanf */ +uid_t pseudo_ruid; +uid_t pseudo_euid; +uid_t pseudo_suid; +uid_t pseudo_fuid; +gid_t pseudo_rgid; +gid_t pseudo_egid; +gid_t pseudo_sgid; +gid_t pseudo_fgid; + +#define PSEUDO_ETC_FILE(filename, realname, flags) pseudo_etc_file(filename, realname, flags, passwd_paths, npasswd_paths) + +/* helper function to make a directory, just like mkdir -p. + * Can't use system() because the child shell would end up trying + * to do the same thing... + */ +static void +mkdir_p(char *path) { + size_t len = strlen(path); + size_t i; + + for (i = 1; i < len; ++i) { + /* try to create all the directories in path, ignoring + * failures + */ + if (path[i] == '/') { + path[i] = '\0'; + (void) mkdir(path, 0755); + path[i] = '/'; + } + } + (void) mkdir(path, 0755); +} + +/* Populating an array of unknown size is one of my least favorite + * things. The idea here is to ensure that the logic flow is the same + * both when counting expected items, and when populating them. + */ +static void +build_passwd_paths(void) +{ + int np = 0; + int pass = 0; + + /* should never happen... */ + if (passwd_paths) { + free(passwd_paths); + passwd_paths = 0; + npasswd_paths = 0; + } + +#define SHOW_PATH pseudo_debug(PDBGF_CHROOT | PDBGF_VERBOSE, "passwd_paths[%d]: '%s'\n", np, (passwd_paths[np])) +#define ADD_PATH(p) do { if (passwd_paths) { passwd_paths[np] = (p); SHOW_PATH; } ++np; } while(0) +#define NUL_BYTE(p) do { if (passwd_paths) { *(p)++ = '\0'; } else { ++(p); } } while(0) + + do { + if (pseudo_chroot) { + ADD_PATH(pseudo_chroot); + } + if (pseudo_passwd) { + char *s = pseudo_passwd; + while (s) { + char *t = strchr(s, ':'); + if (t) { + NUL_BYTE(t); + } + ADD_PATH(s); + s = t; + } + } + if (PSEUDO_PASSWD_FALLBACK) { + ADD_PATH(PSEUDO_PASSWD_FALLBACK); + } + + /* allocation and/or return */ + if (passwd_paths) { + if (np != npasswd_paths) { + pseudo_diag("internal error: path allocation was inconsistent.\n"); + } else { + /* yes, we allocated one extra for a trailing + * null pointer. + */ + passwd_paths[np] = NULL; + } + return; + } else { + passwd_paths = malloc((np + 1) * sizeof(*passwd_paths)); + npasswd_paths = np; + if (!passwd_paths) { + pseudo_diag("couldn't allocate storage for password paths.\n"); + exit(1); + } + np = 0; + } + } while (++pass < 2); + /* in theory the second pass already returned, but. */ + pseudo_diag("should totally not have gotten here.\n"); + + return; +} + +#ifdef PSEUDO_XATTRDB +/* We really want to avoid calling the wrappers for these inside the + * implementation. pseudo_wrappers will reinitialize these after it's + * gotten the real_* found. + */ +ssize_t (*pseudo_real_lgetxattr)(const char *, const char *, void *, size_t) = lgetxattr; +ssize_t (*pseudo_real_fgetxattr)(int, const char *, void *, size_t) = fgetxattr; +int (*pseudo_real_lsetxattr)(const char *, const char *, const void *, size_t, int) = lsetxattr; +int (*pseudo_real_fsetxattr)(int, const char *, const void *, size_t, int) = fsetxattr; +/* Executive summary: We use an extended attribute, + * user.pseudo_data, to store exactly the data we would otherwise + * have stored in the database. Which is to say, uid, gid, mode, rdev. + * + * If we don't find a value, save an empty one with a lower version + * number to indicate that we don't have data to reduce round trips. + */ +typedef struct { + int version; + uid_t uid; + gid_t gid; + mode_t mode; + dev_t rdev; +} pseudo_db_data_t; + +static pseudo_msg_t xattrdb_data; + +pseudo_msg_t * +pseudo_xattrdb_save(int fd, const char *path, const struct stat64 *buf) { + int rc = -1; + if (!path && fd < 0) + return NULL; + if (!buf) + return NULL; + pseudo_db_data_t pseudo_db_data = { + .version = 1, + .uid = buf->st_uid, + .gid = buf->st_gid, + .mode = buf->st_mode, + .rdev = buf->st_rdev + }; + if (path) { + rc = pseudo_real_lsetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0); + } else if (fd >= 0) { + rc = pseudo_real_fsetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0); + } + pseudo_debug(PDBGF_XATTRDB, "tried to save data for %s/%d: uid %d, mode %o, rc %d.\n", + path ? path : "<nil>", fd, (int) pseudo_db_data.uid, (int) pseudo_db_data.mode, rc); + /* none of the other fields are checked on save, and the value + * is currently only really used by mknod. + */ + if (rc == 0) { + xattrdb_data.result = RESULT_SUCCEED; + return &xattrdb_data; + } + return NULL; +} + +pseudo_msg_t * +pseudo_xattrdb_load(int fd, const char *path, const struct stat64 *buf) { + int rc = -1, retryrc = -1; + if (!path && fd < 0) + return NULL; + /* don't try to getxattr on a thing unless we think it is + * likely to work. + */ + if (buf) { + if (!S_ISDIR(buf->st_mode) && !S_ISREG(buf->st_mode)) { + return NULL; + } + } + pseudo_db_data_t pseudo_db_data; + if (path) { + rc = pseudo_real_lgetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data)); + if (rc == -1) { + pseudo_db_data = (pseudo_db_data_t) { .version = 0 }; + retryrc = pseudo_real_lsetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0); + } + } else if (fd >= 0) { + rc = pseudo_real_fgetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data)); + if (rc == -1) { + pseudo_db_data = (pseudo_db_data_t) { .version = 0 }; + retryrc = pseudo_real_fsetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0); + } + } + pseudo_debug(PDBGF_XATTRDB, "tried to load data for %s[%d]: rc %d, version %d.\n", + path ? path : "<nil>", fd, rc, pseudo_db_data.version); + if (rc == -1 && retryrc == 0) { + /* there's no data, but there could have been; treat + * this as an empty database result. + */ + pseudo_debug(PDBGF_XATTRDB, "wrote version 0 for %s[%d]\n", + path ? path : "<nil>", fd); + xattrdb_data.result = RESULT_FAIL; + return &xattrdb_data; + } else if (rc == -1) { + /* we can't create an extended attribute, so we may have + * used the database. + */ + return NULL; + } + /* Version 0 = just recording that we looked and found + * nothing. + * Version 1 = actually implemented. + */ + switch (pseudo_db_data.version) { + case 0: + default: + xattrdb_data.result = RESULT_FAIL; + break; + case 1: + xattrdb_data.uid = pseudo_db_data.uid; + xattrdb_data.gid = pseudo_db_data.gid; + xattrdb_data.mode = pseudo_db_data.mode; + xattrdb_data.rdev = pseudo_db_data.rdev; + xattrdb_data.result = RESULT_SUCCEED; + break; + } + return &xattrdb_data; +} +#endif + +#ifdef PSEUDO_PROFILING +static int pseudo_profile_pid = -2; + +static void +pseudo_profile_start(void) { + /* We use -1 as a starting value, and -2 as a value + * indicating not to try to open it. + */ + int existing_data = 0; + int pid = getpid(); + if (pseudo_profile_pid > 0 && pseudo_profile_pid != pid) { + /* looks like there's been a fork. We intentionally + * abandon any existing stats, since the parent might + * want to write them, and we want to combine with + * any previous stats for this pid. + */ + close(pseudo_profile_fd); + pseudo_profile_fd = -1; + } + if (pseudo_profile_fd == -1) { + int fd = -2; + char *profile_path = pseudo_get_value("PSEUDO_PROFILE_PATH"); + pseudo_profile_pid = pid; + if (profile_path) { + fd = open(profile_path, O_RDWR | O_CREAT, 0600); + if (fd >= 0) { + fd = pseudo_fd(fd, MOVE_FD); + } + if (fd < 0) { + fd = -2; + } else { + if (pid > 0) { + pseudo_profile_t data; + off_t rc; + + rc = lseek(fd, pid * sizeof(data), SEEK_SET); + if (rc == (off_t) (pid * sizeof(data))) { + rc = read(fd, &profile_data, sizeof(profile_data)); + /* cumulative with other values in same file */ + if (rc == sizeof(profile_data)) { + pseudo_debug(PDBGF_PROFILE, "pid %d found existing profiling data.\n", pid); + existing_data = 1; + ++profile_data.processes; + } else { + pseudo_debug(PDBGF_PROFILE, "read failed for pid %d: %d, %d\n", pid, (int) rc, errno); + } + profile_interval = 1; + } + } + } + } + pseudo_profile_fd = fd; + } else { + pseudo_debug(PDBGF_PROFILE, "_start called with existing fd? (pid %d)", (int) pseudo_profile_pid); + existing_data = 1; + ++profile_data.processes; + profile_data.total_ops = 0; + profile_data.messages = 0; + profile_data.wrapper_time = (struct timeval) { .tv_sec = 0 }; + profile_data.op_time = (struct timeval) { .tv_sec = 0 }; + profile_data.ipc_time = (struct timeval) { .tv_sec = 0 }; + } + if (!existing_data) { + pseudo_debug(PDBGF_PROFILE, "pid %d found no existing profiling data.\n", pid); + profile_data = (pseudo_profile_t) { + .processes = 1, + .total_ops = 0, + .messages = 0, + .wrapper_time = (struct timeval) { .tv_sec = 0 }, + .op_time = (struct timeval) { .tv_sec = 0 }, + .ipc_time = (struct timeval) { .tv_sec = 0 }, + }; + } +} + +static inline void +fix_tv(struct timeval *tv) { + if (tv->tv_usec > 1000000) { + tv->tv_sec += tv->tv_usec / 1000000; + tv->tv_usec %= 1000000; + } else if (tv->tv_usec < 0) { + /* C99 and later guarantee truncate-towards-zero. + * We want -1 through -1000000 usec to produce + * -1 seconds, etcetera. Note that sec is + * negative, so yes, we want to add to tv_sec and + * subtract from tv_usec. + */ + int sec = (tv->tv_usec - 999999) / 1000000; + tv->tv_sec += sec; + tv->tv_usec -= 1000000 * sec; + } +} + +static int profile_reported = 0; +static void +pseudo_profile_report(void) { + if (pseudo_profile_fd < 0) { + return; + } + fix_tv(&profile_data.wrapper_time); + fix_tv(&profile_data.op_time); + fix_tv(&profile_data.ipc_time); + if (pseudo_profile_pid >= 0) { + int rc1, rc2; + rc1 = lseek(pseudo_profile_fd, pseudo_profile_pid * sizeof(profile_data), SEEK_SET); + rc2 = write(pseudo_profile_fd, &profile_data, sizeof(profile_data)); + if (!profile_reported) { + pseudo_debug(PDBGF_PROFILE, "pid %d (%s) saving profiling info: %d, %d.\n", + pseudo_profile_pid, + program_invocation_name ? program_invocation_name : "unknown", + rc1, rc2); + profile_reported = 1; + } + } +} +#endif + +void +pseudo_init_client(void) { + char *env; + + pseudo_antimagic(); + pseudo_new_pid(); + if (connect_fd != -1) { + close(connect_fd); + connect_fd = -1; + } +#ifdef PSEUDO_PROFILING + if (pseudo_profile_fd > -1) { + close(pseudo_profile_fd); + } + pseudo_profile_fd = -1; +#endif + + /* in child processes, PSEUDO_DISABLED may have become set to + * some truthy value, in which case we'd disable pseudo, + * or it may have gone away, in which case we'd enable + * pseudo (and cause it to reinit the defaults). + */ + env = getenv("PSEUDO_DISABLED"); + if (!env) { + env = pseudo_get_value("PSEUDO_DISABLED"); + } + if (env) { + int actually_disabled = 1; + switch (*env) { + case '0': + case 'f': + case 'F': + case 'n': + case 'N': + actually_disabled = 0; + break; + case 's': + case 'S': + actually_disabled = 0; + pseudo_local_only = 1; + break; + } + if (actually_disabled) { + if (!pseudo_disabled) { + pseudo_antimagic(); + pseudo_disabled = 1; + } + pseudo_set_value("PSEUDO_DISABLED", "1"); + } else { + if (pseudo_disabled) { + pseudo_magic(); + pseudo_disabled = 0; + pseudo_inited = 0; /* Re-read the initial values! */ + } + pseudo_set_value("PSEUDO_DISABLED", "0"); + } + } else { + pseudo_set_value("PSEUDO_DISABLED", "0"); + } + + /* ALLOW_FSYNC is here because some crazy hosts will otherwise + * report incorrect values for st_size/st_blocks. I can sort of + * understand st_blocks, but bogus values for st_size? Not cool, + * dudes, not cool. + */ + env = getenv("PSEUDO_ALLOW_FSYNC"); + if (!env) { + env = pseudo_get_value("PSEUDO_ALLOW_FSYNC"); + } else { + pseudo_set_value("PSEUDO_ALLOW_FSYNC", env); + } + if (env) { + pseudo_allow_fsync = 1; + } else { + pseudo_allow_fsync = 0; + } + + /* in child processes, PSEUDO_UNLOAD may become set to + * some truthy value, in which case we're being asked to + * remove pseudo from the LD_PRELOAD. We need to make sure + * this value gets loaded into the internal variables. + * + * If we've been told to unload, but are still available + * we need to act as if unconditionally disabled. + */ + env = getenv("PSEUDO_UNLOAD"); + if (env) { + pseudo_set_value("PSEUDO_UNLOAD", env); + pseudo_disabled = 1; + } + + /* Setup global items needed for pseudo to function... */ + if (!pseudo_inited) { + /* Ensure that all of the values are reset */ + server_pid = 0; + pseudo_prefix_dir_fd = -1; + pseudo_localstate_dir_fd = -1; + pseudo_pwd_fd = -1; + pseudo_pwd_lck_fd = -1; + pseudo_pwd_lck_name = NULL; + pseudo_pwd = NULL; + pseudo_grp_fd = -1; + pseudo_grp = NULL; + pseudo_cwd = NULL; + pseudo_cwd_len = 0; + pseudo_chroot = NULL; + pseudo_passwd = NULL; + pseudo_chroot_len = 0; + pseudo_cwd_rel = NULL; + pseudo_nosymlinkexp = 0; + } + + if (!pseudo_disabled && !pseudo_inited) { + char *pseudo_path = 0; + + pseudo_umask = umask(022); + umask(pseudo_umask); + + pseudo_path = pseudo_prefix_path(NULL); + if (pseudo_prefix_dir_fd == -1) { + if (pseudo_path) { + pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY); + /* directory is missing? */ + if (pseudo_prefix_dir_fd == -1 && errno == ENOENT) { + pseudo_debug(PDBGF_CLIENT, "prefix directory '%s' doesn't exist, trying to create\n", pseudo_path); + mkdir_p(pseudo_path); + pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY); + } + pseudo_prefix_dir_fd = pseudo_fd(pseudo_prefix_dir_fd, MOVE_FD); + } else { + pseudo_diag("No prefix available to to find server.\n"); + exit(1); + } + if (pseudo_prefix_dir_fd == -1) { + pseudo_diag("Can't open prefix path '%s' for server: %s\n", + pseudo_path, + strerror(errno)); + exit(1); + } + } + free(pseudo_path); + pseudo_path = pseudo_localstatedir_path(NULL); + if (pseudo_localstate_dir_fd == -1) { + if (pseudo_path) { + pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY); + /* directory is missing? */ + if (pseudo_localstate_dir_fd == -1 && errno == ENOENT) { + pseudo_debug(PDBGF_CLIENT, "local state directory '%s' doesn't exist, trying to create\n", pseudo_path); + mkdir_p(pseudo_path); + pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY); + } + pseudo_localstate_dir_fd = pseudo_fd(pseudo_localstate_dir_fd, MOVE_FD); + } else { + pseudo_diag("No local state directory available for server/file interactions.\n"); + exit(1); + } + if (pseudo_localstate_dir_fd == -1) { + pseudo_diag("Can't open local state path '%s': %s\n", + pseudo_path, + strerror(errno)); + exit(1); + } + } + free(pseudo_path); + + env = pseudo_get_value("PSEUDO_NOSYMLINKEXP"); + if (env) { + char *endptr; + /* if the environment variable is not an empty string, + * parse it; "0" means turn NOSYMLINKEXP off, "1" means + * turn it on (disabling the feature). An empty string + * or something we can't parse means to set the flag; this + * is a safe default because if you didn't want the flag + * set, you normally wouldn't set the environment variable + * at all. + */ + if (*env) { + pseudo_nosymlinkexp = strtol(env, &endptr, 10); + if (*endptr) + pseudo_nosymlinkexp = 1; + } else { + pseudo_nosymlinkexp = 1; + } + } else { + pseudo_nosymlinkexp = 0; + } + free(env); + env = pseudo_get_value("PSEUDO_UIDS"); + if (env) + sscanf(env, "%d,%d,%d,%d", + &pseudo_ruid, &pseudo_euid, + &pseudo_suid, &pseudo_fuid); + free(env); + + env = pseudo_get_value("PSEUDO_GIDS"); + if (env) + sscanf(env, "%d,%d,%d,%d", + &pseudo_rgid, &pseudo_egid, + &pseudo_sgid, &pseudo_fuid); + free(env); + + env = pseudo_get_value("PSEUDO_CHROOT"); + if (env) { + pseudo_chroot = strdup(env); + if (pseudo_chroot) { + pseudo_chroot_len = strlen(pseudo_chroot); + } else { + pseudo_diag("Can't store chroot path '%s'\n", env); + } + } + free(env); + + env = pseudo_get_value("PSEUDO_PASSWD"); + if (env) { + /* note: this means that pseudo_passwd is a + * string we're allowed to modify... */ + pseudo_passwd = strdup(env); + } + free(env); + build_passwd_paths(); + + pseudo_inited = 1; + } + if (!pseudo_disabled) { + pseudo_client_getcwd(); +#ifdef PSEUDO_PROFILING + pseudo_profile_start(); +#endif + } + + pseudo_magic(); +} + +static void +pseudo_file_close(int *fd, FILE **fp) { + if (!fp || !fd) { + pseudo_diag("pseudo_file_close: needs valid pointers.\n"); + return; + } + pseudo_antimagic(); + if (*fp) { +#if PSEUDO_PORT_DARWIN + if (*fp == pseudo_host_etc_passwd_file) { + endpwent(); + } else if (*fp != pseudo_host_etc_group_file) { + endgrent(); + } else { + fclose(*fp); + } +#else + fclose(*fp); +#endif + *fd = -1; + *fp = 0; + } +#if PSEUDO_PORT_DARWIN + if (*fd == pseudo_host_etc_passwd_fd || + *fd == pseudo_host_etc_group_fd) { + *fd = -1; + } +#endif + /* this should be impossible */ + if (*fd >= 0) { + close(*fd); + *fd = -1; + } + pseudo_magic(); +} + +static FILE * +pseudo_file_open(char *name, int *fd, FILE **fp) { + if (!fp || !fd || !name) { + pseudo_diag("pseudo_file_open: needs valid pointers.\n"); + return NULL; + } + pseudo_file_close(fd, fp); + pseudo_antimagic(); + *fd = PSEUDO_ETC_FILE(name, NULL, O_RDONLY); +#if PSEUDO_PORT_DARWIN + if (*fd == pseudo_host_etc_passwd_fd) { + *fp = pseudo_host_etc_passwd_file; + setpwent(); + } else if (*fd == pseudo_host_etc_group_fd) { + *fp = pseudo_host_etc_group_file; + setgrent(); + } +#endif + if (*fd >= 0) { + *fd = pseudo_fd(*fd, MOVE_FD); + *fp = fdopen(*fd, "r"); + if (!*fp) { + close(*fd); + *fd = -1; + } + } + pseudo_magic(); + return *fp; +} + +/* there is no spec I know of requiring us to defend this fd + * against being closed by the user. + */ +int +pseudo_pwd_lck_open(void) { + pseudo_pwd_lck_close(); + if (!pseudo_pwd_lck_name) { + pseudo_pwd_lck_name = malloc(pseudo_path_max()); + if (!pseudo_pwd_lck_name) { + pseudo_diag("couldn't allocate space for passwd lockfile path.\n"); + return -1; + } + } + pseudo_antimagic(); + pseudo_pwd_lck_fd = PSEUDO_ETC_FILE(".pwd.lock", + pseudo_pwd_lck_name, O_RDWR | O_CREAT); + pseudo_magic(); + return pseudo_pwd_lck_fd; +} + +int +pseudo_pwd_lck_close(void) { + if (pseudo_pwd_lck_fd != -1) { + pseudo_antimagic(); + close(pseudo_pwd_lck_fd); + if (pseudo_pwd_lck_name) { + unlink(pseudo_pwd_lck_name); + free(pseudo_pwd_lck_name); + pseudo_pwd_lck_name = 0; + } + pseudo_magic(); + pseudo_pwd_lck_fd = -1; + return 0; + } else { + return -1; + } +} + +FILE * +pseudo_pwd_open(void) { + return pseudo_file_open("passwd", &pseudo_pwd_fd, &pseudo_pwd); +} + +void +pseudo_pwd_close(void) { + pseudo_file_close(&pseudo_pwd_fd, &pseudo_pwd); +} + +FILE * +pseudo_grp_open(void) { + return pseudo_file_open("group", &pseudo_grp_fd, &pseudo_grp); +} + +void +pseudo_grp_close(void) { + pseudo_file_close(&pseudo_grp_fd, &pseudo_grp); +} + +void +pseudo_client_touchuid(void) { + static char uidbuf[256]; + snprintf(uidbuf, 256, "%d,%d,%d,%d", + pseudo_ruid, pseudo_euid, pseudo_suid, pseudo_fuid); + pseudo_set_value("PSEUDO_UIDS", uidbuf); +} + +void +pseudo_client_touchgid(void) { + static char gidbuf[256]; + snprintf(gidbuf, 256, "%d,%d,%d,%d", + pseudo_rgid, pseudo_egid, pseudo_sgid, pseudo_fgid); + pseudo_set_value("PSEUDO_GIDS", gidbuf); +} + +int +pseudo_client_chroot(const char *path) { + /* free old value */ + free(pseudo_chroot); + + pseudo_debug(PDBGF_CLIENT | PDBGF_CHROOT, "client chroot: %s\n", path); + if (!strcmp(path, "/")) { + pseudo_chroot_len = 0; + pseudo_chroot = 0; + pseudo_set_value("PSEUDO_CHROOT", NULL); + return 0; + } + /* allocate new value */ + pseudo_chroot_len = strlen(path); + pseudo_chroot = malloc(pseudo_chroot_len + 1); + if (!pseudo_chroot) { + pseudo_diag("Couldn't allocate chroot directory buffer.\n"); + pseudo_chroot_len = 0; + errno = ENOMEM; + return -1; + } + memcpy(pseudo_chroot, path, pseudo_chroot_len + 1); + pseudo_set_value("PSEUDO_CHROOT", pseudo_chroot); + return 0; +} + +char * +pseudo_root_path(const char *func, int line, int dirfd, const char *path, int leave_last) { + char *rc; + pseudo_antimagic(); + rc = base_path(dirfd, path, leave_last); + pseudo_magic(); + if (!rc) { + pseudo_diag("couldn't allocate absolute path for '%s'.\n", + path); + } + pseudo_debug(PDBGF_CHROOT, "root_path [%s, %d]: '%s' from '%s'\n", + func, line, + rc ? rc : "<nil>", + path ? path : "<nil>"); + return rc; +} + +int +pseudo_client_getcwd(void) { + char *cwd; + cwd = malloc(pseudo_path_max()); + if (!cwd) { + pseudo_diag("Can't allocate CWD buffer!\n"); + return -1; + } + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "getcwd: trying to find cwd.\n"); + if (getcwd(cwd, pseudo_path_max())) { + /* cwd now holds a canonical path to current directory */ + free(pseudo_cwd); + pseudo_cwd = cwd; + pseudo_cwd_len = strlen(pseudo_cwd); + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "getcwd okay: [%s] %d bytes\n", pseudo_cwd, (int) pseudo_cwd_len); + if (pseudo_chroot_len && + pseudo_cwd_len >= pseudo_chroot_len && + !memcmp(pseudo_cwd, pseudo_chroot, pseudo_chroot_len) && + (pseudo_cwd[pseudo_chroot_len] == '\0' || + pseudo_cwd[pseudo_chroot_len] == '/')) { + pseudo_cwd_rel = pseudo_cwd + pseudo_chroot_len; + } else { + pseudo_cwd_rel = pseudo_cwd; + } + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "abscwd: <%s>\n", pseudo_cwd); + if (pseudo_cwd_rel != pseudo_cwd) { + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "relcwd: <%s>\n", pseudo_cwd_rel); + } + return 0; + } else { + pseudo_diag("Can't get CWD: %s\n", strerror(errno)); + return -1; + } +} + +static char * +fd_path(int fd) { + if (fd >= 0 && fd < nfds) { + return fd_paths[fd]; + } + if (fd == AT_FDCWD) { + return pseudo_cwd; + } + return 0; +} + +static void +pseudo_client_path(int fd, const char *path) { + if (fd < 0) + return; + + if (fd >= nfds) { + int i; + pseudo_debug(PDBGF_CLIENT, "expanding fds from %d to %d\n", + nfds, fd + 1); + fd_paths = realloc(fd_paths, (fd + 1) * sizeof(char *)); + for (i = nfds; i < fd + 1; ++i) + fd_paths[i] = 0; + nfds = fd + 1; + } else { + if (fd_paths[fd]) { + pseudo_debug(PDBGF_CLIENT, "reopening fd %d [%s] -- didn't see close\n", + fd, fd_paths[fd]); + } + /* yes, it is safe to free null pointers. yay for C89 */ + free(fd_paths[fd]); + fd_paths[fd] = 0; + } + if (path) { + fd_paths[fd] = strdup(path); + } +} + +static void +pseudo_client_close(int fd) { + if (fd < 0 || fd >= nfds) + return; + + free(fd_paths[fd]); + fd_paths[fd] = 0; +} + +/* spawn server */ +static int +client_spawn_server(void) { + int status; + FILE *fp; + char * pseudo_pidfile; + + if ((server_pid = fork()) != 0) { + if (server_pid == -1) { + pseudo_diag("couldn't fork server: %s\n", strerror(errno)); + return 1; + } + pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "spawned server, pid %d\n", server_pid); + /* wait for the child process to terminate, indicating server + * is ready + */ + waitpid(server_pid, &status, 0); + server_pid = -2; + pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE); + fp = fopen(pseudo_pidfile, "r"); + if (fp) { + if (fscanf(fp, "%d", &server_pid) != 1) { + pseudo_debug(PDBGF_CLIENT, "Opened server PID file, but didn't get a pid.\n"); + } + fclose(fp); + } else { + pseudo_debug(PDBGF_CLIENT, "no pid file (%s): %s\n", + pseudo_pidfile, strerror(errno)); + } + pseudo_debug(PDBGF_CLIENT, "read new pid file: %d\n", server_pid); + free(pseudo_pidfile); + /* at this point, we should have a new server_pid */ + return 0; + } else { + char *base_args[] = { NULL, NULL, NULL }; + char **argv; + char *option_string = pseudo_get_value("PSEUDO_OPTS"); + int args; + int fd; + + pseudo_new_pid(); + base_args[0] = pseudo_bindir_path("pseudo"); + base_args[1] = "-d"; + if (option_string) { + char *s; + int arg; + + /* count arguments in PSEUDO_OPTS, starting at 2 + * for pseudo/-d/NULL, plus one for the option string. + * The number of additional arguments may be less + * than the number of spaces, but can't be more. + */ + args = 4; + for (s = option_string; *s; ++s) + if (*s == ' ') + ++args; + + argv = malloc(args * sizeof(char *)); + argv[0] = base_args[0]; + argv[1] = base_args[1]; + arg = 2; + while ((s = strsep(&option_string, " ")) != NULL) { + if (*s) { + argv[arg++] = strdup(s); + } + } + argv[arg] = 0; + } else { + argv = base_args; + } + + /* close any higher-numbered fds which might be open, + * such as sockets. We don't have to worry about 0 and 1; + * the server closes them already, and more importantly, + * they can't have been opened or closed without us already + * having spawned a server... The issue is just socket() + * calls which could result in fds being left open, and those + * can't overwrite fds 0-2 unless we closed them... + * + * No, really. It works. + */ + for (fd = 3; fd < 1024; ++fd) { + if (fd != pseudo_util_debug_fd) + close(fd); + } + /* and now, execute the server */ + + pseudo_set_value("PSEUDO_UNLOAD", "YES"); + pseudo_setupenv(); + pseudo_dropenv(); /* drop PRELINK_LIBRARIES */ + + pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER | PDBGF_INVOKE, "calling execv on %s\n", argv[0]); + + execv(argv[0], argv); + pseudo_diag("critical failure: exec of pseudo daemon failed: %s\n", strerror(errno)); + exit(1); + } +} + +static int +client_ping(void) { + pseudo_msg_t ping; + pseudo_msg_t *ack; + char tagbuf[pseudo_path_max()]; + char *tag = pseudo_get_value("PSEUDO_TAG"); + + memset(&ping, 0, sizeof(ping)); + + ping.type = PSEUDO_MSG_PING; + ping.op = OP_NONE; + + ping.pathlen = snprintf(tagbuf, sizeof(tagbuf), "%s%c%s", + program_invocation_name ? program_invocation_name : "<unknown>", + 0, + tag ? tag : ""); + free(tag); + ping.client = getpid(); + ping.result = 0; + errno = 0; + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending ping\n"); + if (pseudo_msg_send(connect_fd, &ping, ping.pathlen, tagbuf)) { + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "error pinging server: %s\n", strerror(errno)); + return 1; + } + ack = pseudo_msg_receive(connect_fd); + if (!ack) { + pseudo_debug(PDBGF_CLIENT, "no ping response from server: %s\n", strerror(errno)); + /* and that's not good, so... */ + server_pid = 0; + return 1; + } + if (ack->type != PSEUDO_MSG_ACK) { + pseudo_debug(PDBGF_CLIENT, "invalid ping response from server: expected ack, got %d\n", ack->type); + /* and that's not good, so... */ + server_pid = 0; + return 1; + } else { + /* The server tells us whether or not to log things. */ + if (ack->result == RESULT_SUCCEED) { + pseudo_client_logging = 1; + } else { + pseudo_client_logging = 0; + } + } + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "ping ok\n"); + return 0; +} + +static void +void_client_ping(void) { + client_ping(); +} + +int +pseudo_fd(int fd, int how) { + int newfd; + + if (fd < 0) + return(-1); + + /* If already above PSEUDO_MIN_FD, no need to move */ + if ((how == MOVE_FD) && (fd >= PSEUDO_MIN_FD)) { + newfd = fd; + } else { + newfd = fcntl(fd, F_DUPFD, PSEUDO_MIN_FD); + + if (how == MOVE_FD) + close(fd); + } + + /* Set close on exec, even if we didn't move it. */ + if ((newfd >= 0) && (fcntl(newfd, F_SETFD, FD_CLOEXEC) < 0)) + pseudo_diag("Can't set close on exec flag: %s\n", + strerror(errno)); + + return(newfd); +} + +static int +client_connect(void) { + /* we have a server pid, is it responsive? */ + struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET }; + int cwd_fd; + +#if PSEUDO_PORT_DARWIN + sun.sun_len = strlen(PSEUDO_SOCKET) + 1; +#endif + + connect_fd = socket(PF_UNIX, SOCK_STREAM, 0); + connect_fd = pseudo_fd(connect_fd, MOVE_FD); + if (connect_fd == -1) { + pseudo_diag("Can't create socket: %s (%s)\n", sun.sun_path, strerror(errno)); + return 1; + } + + pseudo_debug(PDBGF_CLIENT, "connecting socket...\n"); + cwd_fd = open(".", O_RDONLY); + if (cwd_fd == -1) { + pseudo_diag("Couldn't stash directory before opening socket: %s", + strerror(errno)); + close(connect_fd); + connect_fd = -1; + return 1; + } + if (fchdir(pseudo_localstate_dir_fd) == -1) { + pseudo_diag("Couldn't chdir to server directory [%d]: %s\n", + pseudo_localstate_dir_fd, strerror(errno)); + close(connect_fd); + close(cwd_fd); + connect_fd = -1; + return 1; + } + if (connect(connect_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) { + pseudo_debug(PDBGF_CLIENT, "Can't connect socket to pseudo.socket: (%s)\n", strerror(errno)); + close(connect_fd); + if (fchdir(cwd_fd) == -1) { + pseudo_diag("return to previous directory failed: %s\n", + strerror(errno)); + } + close(cwd_fd); + connect_fd = -1; + return 1; + } + if (fchdir(cwd_fd) == -1) { + pseudo_diag("return to previous directory failed: %s\n", + strerror(errno)); + } + close(cwd_fd); + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "connected socket.\n"); + return 0; +} + +static int +pseudo_client_setup(void) { + char * pseudo_pidfile; + FILE *fp; + server_pid = 0; + + /* avoid descriptor leak, I hope */ + if (connect_fd >= 0) { + close(connect_fd); + connect_fd = -1; + } + + pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE); + fp = fopen(pseudo_pidfile, "r"); + if (fp) { + if (fscanf(fp, "%d", &server_pid) != 1) { + pseudo_debug(PDBGF_CLIENT, "Opened server PID file, but didn't get a pid.\n"); + } + fclose(fp); + } + if (server_pid) { + if (kill(server_pid, 0) == -1) { + pseudo_debug(PDBGF_CLIENT, "couldn't find server at pid %d: %s\n", + server_pid, strerror(errno)); + server_pid = 0; + } + } + if (!server_pid) { + if (client_spawn_server()) { + return 1; + } + } + if (!client_connect() && !client_ping()) { + return 0; + } + pseudo_debug(PDBGF_CLIENT, "server seems to be gone, trying to restart\n"); + if (client_spawn_server()) { + pseudo_debug(PDBGF_CLIENT, "failed to spawn server, giving up.\n"); + return 1; + } else { + pseudo_debug(PDBGF_CLIENT, "restarted, new pid %d\n", server_pid); + if (!client_connect() && !client_ping()) { + return 0; + } + } + pseudo_debug(PDBGF_CLIENT, "couldn't get a server, giving up.\n"); + return 1; +} + +static pseudo_msg_t * +pseudo_client_request(pseudo_msg_t *msg, size_t len, const char *path) { + pseudo_msg_t *response = 0; + int tries = 0; + int rc; + + if (!msg) + return 0; + + do { + do { + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending a message: ino %llu\n", + (unsigned long long) msg->ino); + if (connect_fd < 0) { + pseudo_debug(PDBGF_CLIENT, "trying to get server\n"); + if (pseudo_client_setup()) { + return 0; + } + } + rc = pseudo_msg_send(connect_fd, msg, len, path); + if (rc != 0) { + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "msg_send: %d%s\n", + rc, + rc == -1 ? " (sigpipe)" : + " (short write)"); + pseudo_client_setup(); + ++tries; + if (tries > 3) { + pseudo_debug(PDBGF_CLIENT, "Can't get server going again.\n"); + return 0; + } + } + } while (rc != 0); + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sent!\n"); + if (msg->type != PSEUDO_MSG_FASTOP) { + response = pseudo_msg_receive(connect_fd); + if (!response) { + ++tries; + if (tries > 3) { + pseudo_debug(PDBGF_CLIENT, "Can't get responses.\n"); + return 0; + } + } + } else { + return 0; + } + } while (response == 0); + if (response->type != PSEUDO_MSG_ACK) { + pseudo_debug(PDBGF_CLIENT, "got non-ack response %d\n", response->type); + return 0; + } else { + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "got response type %d\n", response->type); + } + return response; +} + +int +pseudo_client_shutdown(void) { + pseudo_msg_t msg; + pseudo_msg_t *ack; + char *pseudo_path; + + pseudo_debug(PDBGF_INVOKE, "attempting to shut down server.\n"); + pseudo_path = pseudo_prefix_path(NULL); + if (pseudo_prefix_dir_fd == -1) { + if (pseudo_path) { + pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY); + /* directory is missing? */ + if (pseudo_prefix_dir_fd == -1 && errno == ENOENT) { + pseudo_debug(PDBGF_CLIENT, "prefix directory doesn't exist, trying to create\n"); + mkdir_p(pseudo_path); + pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY); + } + pseudo_prefix_dir_fd = pseudo_fd(pseudo_prefix_dir_fd, COPY_FD); + free(pseudo_path); + } else { + pseudo_diag("No prefix available to to find server.\n"); + exit(1); + } + if (pseudo_prefix_dir_fd == -1) { + pseudo_diag("Can't open prefix path (%s) for server. (%s)\n", + pseudo_prefix_path(NULL), + strerror(errno)); + exit(1); + } + } + pseudo_path = pseudo_localstatedir_path(NULL); + mkdir_p(pseudo_path); + if (pseudo_localstate_dir_fd == -1) { + if (pseudo_path) { + pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY); + /* directory is missing? */ + if (pseudo_localstate_dir_fd == -1 && errno == ENOENT) { + pseudo_debug(PDBGF_CLIENT, "local state dir doesn't exist, trying to create\n"); + mkdir_p(pseudo_path); + pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY); + } + pseudo_localstate_dir_fd = pseudo_fd(pseudo_localstate_dir_fd, COPY_FD); + free(pseudo_path); + } else { + pseudo_diag("No prefix available to to find server.\n"); + exit(1); + } + if (pseudo_localstate_dir_fd == -1) { + pseudo_diag("Can't open local state path (%s) for server. (%s)\n", + pseudo_localstatedir_path(NULL), + strerror(errno)); + exit(1); + } + } + if (client_connect()) { + pseudo_debug(PDBGF_INVOKE, "Pseudo server seems to be already offline.\n"); + return 0; + } + memset(&msg, 0, sizeof(pseudo_msg_t)); + msg.type = PSEUDO_MSG_SHUTDOWN; + msg.op = OP_NONE; + msg.client = getpid(); + pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "sending shutdown request\n"); + if (pseudo_msg_send(connect_fd, &msg, 0, NULL)) { + pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "error requesting shutdown: %s\n", strerror(errno)); + return 1; + } + ack = pseudo_msg_receive(connect_fd); + if (!ack) { + pseudo_diag("server did not respond to shutdown query.\n"); + return 1; + } + if (ack->type == PSEUDO_MSG_ACK) { + return 0; + } + pseudo_diag("Server refused shutdown. Remaining client fds: %d\n", ack->fd); + pseudo_diag("Client pids: %s\n", ack->path); + pseudo_diag("Server will shut down after all clients exit.\n"); + return 0; +} + +static char * +base_path(int dirfd, const char *path, int leave_last) { + char *basepath = 0; + size_t baselen = 0; + size_t minlen = 0; + char *newpath; + + if (!path) + return NULL; + if (!*path) + return ""; + + if (path[0] != '/') { + if (dirfd != -1 && dirfd != AT_FDCWD) { + if (dirfd >= 0) { + basepath = fd_path(dirfd); + } + if (basepath) { + baselen = strlen(basepath); + } else { + pseudo_diag("got *at() syscall for unknown directory, fd %d\n", dirfd); + } + } else { + basepath = pseudo_cwd; + baselen = pseudo_cwd_len; + } + if (!basepath) { + pseudo_diag("unknown base path for fd %d, path %s\n", dirfd, path); + return 0; + } + /* if there's a chroot path, and it's the start of basepath, + * flag it for pseudo_fix_path + */ + if (pseudo_chroot_len && baselen >= pseudo_chroot_len && + !memcmp(basepath, pseudo_chroot, pseudo_chroot_len) && + (basepath[pseudo_chroot_len] == '\0' || basepath[pseudo_chroot_len] == '/')) { + + minlen = pseudo_chroot_len; + } + } else if (pseudo_chroot_len) { + /* "absolute" is really relative to chroot path */ + basepath = pseudo_chroot; + baselen = pseudo_chroot_len; + minlen = pseudo_chroot_len; + } + + newpath = pseudo_fix_path(basepath, path, minlen, baselen, NULL, leave_last); + pseudo_debug(PDBGF_PATH, "base_path: %s</>%s\n", + basepath ? basepath : "<nil>", + path ? path : "<nil>"); + return newpath; +} + +pseudo_msg_t * +pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...) { + pseudo_msg_t *result = 0; + pseudo_msg_t msg = { .type = PSEUDO_MSG_OP }; + size_t pathlen = -1; + int do_request = 0; + char *path_extra_1 = 0; + size_t path_extra_1len = 0; + char *path_extra_2 = 0; + size_t path_extra_2len = 0; + static char *alloced_path = 0; + static size_t alloced_len = 0; + int strip_slash; + +#ifdef PSEUDO_PROFILING + struct timeval tv1_op, tv2_op; + + gettimeofday(&tv1_op, NULL); + ++profile_data.total_ops; +#endif + /* disable wrappers */ + pseudo_antimagic(); + + /* note: I am not entirely sure this should happen even if no + * messages have actually been sent. */ + if (!sent_messages) { + sent_messages = 1; + atexit(void_client_ping); + } + + /* if path isn't available, try to find one? */ + if (!path && fd >= 0 && fd <= nfds) { + path = fd_path(fd); + if (!path) { + pathlen = 0; + } else { + pathlen = strlen(path) + 1; + } + } + +#ifdef PSEUDO_XATTRDB + if (buf) { + struct stat64 bufcopy = *buf; + int do_save = 0; + /* maybe use xattr instead */ + /* note: if we use xattr, logging won't work reliably + * because the server won't get messages if these work. + */ + switch (op) { + case OP_CHMOD: + case OP_FCHMOD: + case OP_CHOWN: + case OP_FCHOWN: + /* for these, we want to start with the existing db + * values. + */ + bufcopy = *buf; + result = pseudo_xattrdb_load(fd, path, buf); + if (result && result->result == RESULT_SUCCEED) { + pseudo_debug(PDBGF_XATTR, "merging existing values for xattr\n"); + switch (op) { + case OP_CHMOD: + case OP_FCHMOD: + bufcopy.st_uid = result->uid; + bufcopy.st_gid = result->gid; + break; + case OP_CHOWN: + case OP_FCHOWN: + bufcopy.st_rdev = result->rdev; + bufcopy.st_mode = result->mode; + break; + default: + break; + } + + } else { + switch (op) { + case OP_CHMOD: + case OP_FCHMOD: + bufcopy.st_uid = pseudo_fuid; + bufcopy.st_gid = pseudo_fgid; + break; + default: + break; + } + } + result = NULL; + do_save = 1; + break; + case OP_CREAT: + case OP_MKDIR: + case OP_MKNOD: + bufcopy.st_uid = pseudo_fuid; + bufcopy.st_gid = pseudo_fgid; + do_save = 1; + break; + case OP_LINK: + do_save = 1; + break; + case OP_FSTAT: + case OP_STAT: + result = pseudo_xattrdb_load(fd, path, buf); + break; + default: + break; + } + if (do_save) { + result = pseudo_xattrdb_save(fd, path, &bufcopy); + } + if (result) + goto skip_path; + } +#endif + + if (op == OP_RENAME) { + va_list ap; + if (!path) { + pseudo_diag("rename (%s) without new path.\n", + path ? path : "<nil>"); + pseudo_magic(); + return 0; + } + va_start(ap, buf); + path_extra_1 = va_arg(ap, char *); + va_end(ap); + /* last argument is the previous path of the file */ + if (!path_extra_1) { + pseudo_diag("rename (%s) without old path.\n", + path ? path : "<nil>"); + pseudo_magic(); + return 0; + } + path_extra_1len = strlen(path_extra_1); + pseudo_debug(PDBGF_PATH | PDBGF_FILE, "rename: %s -> %s\n", + path_extra_1, path); + } + + /* we treat the "create" and "replace" flags as logically + * distinct operations, because they can fail when set can't. + */ + if (op == OP_SET_XATTR || op == OP_CREATE_XATTR || op == OP_REPLACE_XATTR) { + va_list ap; + va_start(ap, buf); + path_extra_1 = va_arg(ap, char *); + path_extra_1len = strlen(path_extra_1); + path_extra_2 = va_arg(ap, char *); + path_extra_2len = va_arg(ap, size_t); + va_end(ap); + pseudo_debug(PDBGF_XATTR, "setxattr, name '%s', value %d bytes\n", + path_extra_1, (int) path_extra_2len); + pseudo_debug_call(PDBGF_XATTR | PDBGF_VERBOSE, pseudo_dump_data, "xattr value", path_extra_2, path_extra_2len); + } + if (op == OP_GET_XATTR || op == OP_REMOVE_XATTR) { + va_list ap; + va_start(ap, buf); + path_extra_1 = va_arg(ap, char *); + path_extra_1len = strlen(path_extra_1); + va_end(ap); + pseudo_debug(PDBGF_XATTR, "%sxattr, name '%s'\n", + op == OP_GET_XATTR ? "get" : "remove", path_extra_1); + } + + if (path) { + if (pathlen == (size_t) -1) { + pathlen = strlen(path) + 1; + } + /* path fixup has to happen in the specific functions, + * because they may have to make calls which have to be + * fixed up for chroot stuff already. + * ... but wait! / in chroot should not have that + * trailing /. + * (no attempt is made to handle a rename of "/" occurring + * in a chroot...) + */ + strip_slash = (pathlen > 2 && (path[pathlen - 2]) == '/'); + } else { + path = ""; + pathlen = 0; + strip_slash = 0; + } + + /* f*xattr operations can result in needing to send a path + * value even though we don't have one available. We use an + * empty path for that. + */ + if (path_extra_1) { + size_t full_len = path_extra_1len + 1 + pathlen; + size_t partial_len = pathlen - 1 - strip_slash; + if (path_extra_2) { + full_len += path_extra_2len + 1; + } + if (full_len > alloced_len) { + free(alloced_path); + alloced_path = malloc(full_len); + alloced_len = full_len; + if (!alloced_path) { + pseudo_diag("Can't allocate space for paths for a rename operation. Sorry.\n"); + alloced_len = 0; + pseudo_magic(); + return 0; + } + } + memcpy(alloced_path, path, partial_len); + alloced_path[partial_len] = '\0'; + memcpy(alloced_path + partial_len + 1, path_extra_1, path_extra_1len); + alloced_path[partial_len + path_extra_1len + 1] = '\0'; + if (path_extra_2) { + memcpy(alloced_path + partial_len + path_extra_1len + 2, path_extra_2, path_extra_2len); + } + alloced_path[full_len - 1] = '\0'; + path = alloced_path; + pathlen = full_len; + pseudo_debug_call(PDBGF_IPC | PDBGF_VERBOSE, pseudo_dump_data, "combined path buffer", path, pathlen); + } else { + if (strip_slash) { + if (pathlen > alloced_len) { + free(alloced_path); + alloced_path = malloc(pathlen); + alloced_len = pathlen; + if (!alloced_path) { + pseudo_diag("Can't allocate space for paths for a rename operation. Sorry.\n"); + alloced_len = 0; + pseudo_magic(); + return 0; + } + } + memcpy(alloced_path, path, pathlen); + alloced_path[pathlen - 2] = '\0'; + path = alloced_path; + } + } + +#ifdef PSEUDO_XATTRDB + /* If we were able to store things in xattr, we can easily skip + * most of the fancy path computations and such. + */ + skip_path: +#endif + + if (buf) + pseudo_msg_stat(&msg, buf); + + if (pseudo_util_debug_flags & PDBGF_OP) { + pseudo_debug(PDBGF_OP, "%s%s", pseudo_op_name(op), + (dirfd != -1 && dirfd != AT_FDCWD && op != OP_DUP) ? "at" : ""); + if (path_extra_1) { + pseudo_debug(PDBGF_OP, " %s ->", path_extra_1); + } + if (path) { + pseudo_debug(PDBGF_OP, " %s", path); + } + /* for OP_RENAME in renameat, "fd" is also used for the + * second dirfd. + */ + if (fd != -1 && op != OP_RENAME) { + pseudo_debug(PDBGF_OP, " [fd %d]", fd); + } + if (buf) { + pseudo_debug(PDBGF_OP, " (+buf)"); + if (fd != -1) { + pseudo_debug(PDBGF_OP, " [dev/ino: %d/%llu]", + (int) buf->st_dev, (unsigned long long) buf->st_ino); + } + pseudo_debug(PDBGF_OP, " (0%o)", (int) buf->st_mode); + } + pseudo_debug(PDBGF_OP, ": "); + } + msg.type = PSEUDO_MSG_OP; + msg.op = op; + msg.fd = fd; + msg.access = access; + msg.result = RESULT_NONE; + msg.client = getpid(); + + /* do stuff */ + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "processing request [ino %llu]\n", (unsigned long long) msg.ino); + switch (msg.op) { + case OP_CHDIR: + pseudo_client_getcwd(); + do_request = 0; + break; + case OP_CHROOT: + if (pseudo_client_chroot(path) == 0) { + /* return a non-zero value to show non-failure */ + result = &msg; + } + do_request = 0; + break; + case OP_OPEN: + pseudo_client_path(fd, path); + case OP_EXEC: /* fallthrough */ + do_request = pseudo_client_logging; + break; + case OP_CLOSE: + /* no request needed */ + if (fd >= 0) { + if (fd == connect_fd) { + connect_fd = pseudo_fd(connect_fd, COPY_FD); + if (connect_fd == -1) { + pseudo_diag("tried to close connection, couldn't dup: %s\n", strerror(errno)); + } + } else if (fd == pseudo_util_debug_fd) { + pseudo_util_debug_fd = pseudo_fd(fd, COPY_FD); + } else if (fd == pseudo_prefix_dir_fd) { + pseudo_prefix_dir_fd = pseudo_fd(fd, COPY_FD); + } else if (fd == pseudo_localstate_dir_fd) { + pseudo_localstate_dir_fd = pseudo_fd(fd, COPY_FD); + } else if (fd == pseudo_pwd_fd) { + pseudo_pwd_fd = pseudo_fd(fd, COPY_FD); + /* since we have a FILE * on it, we close that... */ + fclose(pseudo_pwd); + /* and open a new one on the copy */ + pseudo_pwd = fdopen(pseudo_pwd_fd, "r"); + } else if (fd == pseudo_grp_fd) { + pseudo_grp_fd = pseudo_fd(fd, COPY_FD); + /* since we have a FILE * on it, we close that... */ + fclose(pseudo_grp); + /* and open a new one on the copy */ + pseudo_grp = fdopen(pseudo_grp_fd, "r"); + } + } + pseudo_client_close(fd); + do_request = 0; + break; + case OP_DUP: + /* just copy the path over */ + pseudo_debug(PDBGF_CLIENT, "dup: fd_path(%d) = %p [%s], dup to %d\n", + fd, fd_path(fd), fd_path(fd) ? fd_path(fd) : "<nil>", + dirfd); + pseudo_client_path(dirfd, fd_path(fd)); + break; + /* operations for which we should use the magic uid/gid */ + case OP_CHMOD: + case OP_CREAT: + case OP_FCHMOD: + case OP_MKDIR: + case OP_MKNOD: + case OP_SYMLINK: + msg.uid = pseudo_fuid; + msg.gid = pseudo_fgid; + pseudo_debug(PDBGF_OP, "fuid: %d ", pseudo_fuid); + /* FALLTHROUGH */ + /* chown/fchown uid/gid already calculated, and + * a link or rename should not change a file's ownership. + * (operations which can create should be CREAT or MKNOD + * or MKDIR) + */ + case OP_CHOWN: + case OP_FCHOWN: + case OP_FSTAT: + case OP_LINK: + case OP_RENAME: + case OP_STAT: + case OP_UNLINK: + case OP_DID_UNLINK: + case OP_CANCEL_UNLINK: + case OP_MAY_UNLINK: + case OP_GET_XATTR: + case OP_LIST_XATTR: + case OP_SET_XATTR: + case OP_REMOVE_XATTR: + do_request = 1; + break; + default: + pseudo_diag("error: unknown or unimplemented operator %d (%s)", op, pseudo_op_name(op)); + break; + } + /* result can only be set when PSEUDO_XATTRDB resulted in a + * successful store to or read from the local database. + */ + if (do_request && !result) { +#ifdef PSEUDO_PROFILING + struct timeval tv1_ipc, tv2_ipc; +#endif + if (!pseudo_op_wait(msg.op)) + msg.type = PSEUDO_MSG_FASTOP; + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending request [ino %llu]\n", (unsigned long long) msg.ino); +#ifdef PSEUDO_PROFILING + gettimeofday(&tv1_ipc, NULL); +#endif + if (pseudo_local_only) { + /* disable server */ + result = NULL; + } else { + result = pseudo_client_request(&msg, pathlen, path); + } +#ifdef PSEUDO_PROFILING + gettimeofday(&tv2_ipc, NULL); + ++profile_data.messages; + profile_data.ipc_time.tv_sec += (tv2_ipc.tv_sec - tv1_ipc.tv_sec); + profile_data.ipc_time.tv_usec += (tv2_ipc.tv_usec - tv1_ipc.tv_usec); +#endif + if (result) { + pseudo_debug(PDBGF_OP, "(%d) %s", getpid(), pseudo_res_name(result->result)); + if (op == OP_STAT || op == OP_FSTAT) { + pseudo_debug(PDBGF_OP, " mode 0%o uid %d:%d", + (int) result->mode, + (int) result->uid, + (int) result->gid); + } else if (op == OP_CHMOD || op == OP_FCHMOD) { + pseudo_debug(PDBGF_OP, " mode 0%o", + (int) result->mode); + } else if (op == OP_CHOWN || op == OP_FCHOWN) { + pseudo_debug(PDBGF_OP, " uid %d:%d", + (int) result->uid, + (int) result->gid); + } + } else if (msg.type != PSEUDO_MSG_FASTOP) { + pseudo_debug(PDBGF_OP, "(%d) no answer", getpid()); + } + } else if (!result) { + pseudo_debug(PDBGF_OP, "(%d) (no request)", getpid()); + } + #ifdef PSEUDO_XATTRDB + else { + pseudo_debug(PDBGF_OP, "(%d) (handled through xattrdb: %s)", getpid(), pseudo_res_name(result->result)); + } + #endif + pseudo_debug(PDBGF_OP, "\n"); + +#ifdef PSEUDO_PROFILING + gettimeofday(&tv2_op, NULL); + profile_data.op_time.tv_sec += (tv2_op.tv_sec - tv1_op.tv_sec); + profile_data.op_time.tv_usec += (tv2_op.tv_usec - tv1_op.tv_usec); + if (profile_data.total_ops % profile_interval == 0) { + pseudo_profile_report(); + if (profile_interval < 100) { + profile_interval = profile_interval * 10; + } + } +#endif + /* reenable wrappers */ + pseudo_magic(); + + return result; +} + +/* stuff for handling paths and execs */ +static char *previous_path; +static char *previous_path_segs; +static char **path_segs; +static size_t *path_lens; + +/* Makes an array of strings which are the path components + * of previous_path. Does this by duping the path, then replacing + * colons with null terminators, and storing the addresses of the + * starts of the substrings. + */ +static void +populate_path_segs(void) { + size_t len = 0; + char *s; + int c = 0; + + free(path_segs); + free(previous_path_segs); + free(path_lens); + path_segs = NULL; + path_lens = NULL; + previous_path_segs = NULL; + + if (!previous_path) + return; + + for (s = previous_path; *s; ++s) { + if (*s == ':') + ++c; + } + path_segs = malloc((c+2) * sizeof(*path_segs)); + if (!path_segs) { + pseudo_diag("warning: failed to allocate space for %d path segments.\n", + c); + return; + } + path_lens = malloc((c + 2) * sizeof(*path_lens)); + if (!path_lens) { + pseudo_diag("warning: failed to allocate space for %d path lengths.\n", + c); + free(path_segs); + path_segs = 0; + return; + } + previous_path_segs = strdup(previous_path); + if (!previous_path_segs) { + pseudo_diag("warning: failed to allocate space for path copy.\n"); + free(path_segs); + path_segs = NULL; + free(path_lens); + path_lens = NULL; + return; + } + c = 0; + path_segs[c++] = previous_path; + len = 0; + for (s = previous_path; *s; ++s) { + if (*s == ':') { + *s = '\0'; + path_lens[c - 1] = len; + len = 0; + path_segs[c++] = s + 1; + } else { + ++len; + } + } + path_lens[c - 1] = len; + path_segs[c] = NULL; + path_lens[c] = 0; +} + +const char * +pseudo_exec_path(const char *filename, int search_path) { + char *path = getenv("PATH"); + char *candidate; + int i; + struct stat buf; + + if (!filename) + return NULL; + + pseudo_antimagic(); + if (!path) { + free(path_segs); + free(previous_path); + path_segs = 0; + previous_path = 0; + } else if (!previous_path || strcmp(previous_path, path)) { + free(previous_path); + previous_path = strdup(path); + populate_path_segs(); + } + + /* absolute paths just get canonicalized. */ + if (*filename == '/') { + candidate = pseudo_fix_path(NULL, filename, 0, 0, NULL, 0); + pseudo_magic(); + return candidate; + } + + if (!search_path || !path_segs) { + candidate = pseudo_fix_path(pseudo_cwd, filename, 0, pseudo_cwd_len, NULL, 0); + /* executable or not, it's the only thing we can try */ + pseudo_magic(); + return candidate; + } + + for (i = 0; path_segs[i]; ++i) { + path = path_segs[i]; + pseudo_debug(PDBGF_CLIENT, "exec_path: checking %s for %s\n", path, filename); + if (!*path || (*path == '.' && path_lens[i] == 1)) { + /* empty path or . is cwd */ + candidate = pseudo_fix_path(pseudo_cwd, filename, 0, pseudo_cwd_len, NULL, 0); + pseudo_debug(PDBGF_CLIENT, "exec_path: in cwd, got %s\n", candidate); + } else if (*path == '/') { + candidate = pseudo_fix_path(path, filename, 0, path_lens[i], NULL, 0); + pseudo_debug(PDBGF_CLIENT, "exec_path: got %s\n", candidate); + } else { + /* oh you jerk, making me do extra work */ + size_t len; + char *dir = pseudo_fix_path(pseudo_cwd, path, 0, pseudo_cwd_len, &len, 0); + if (dir) { + candidate = pseudo_fix_path(dir, filename, 0, len, NULL, 0); + pseudo_debug(PDBGF_CLIENT, "exec_path: got %s for non-absolute path\n", candidate); + } else { + pseudo_diag("couldn't allocate intermediate path.\n"); + candidate = NULL; + } + } + if (candidate && !stat(candidate, &buf) && !S_ISDIR(buf.st_mode) && (buf.st_mode & 0111)) { + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "exec_path: %s => %s\n", filename, candidate); + pseudo_magic(); + return candidate; + } + } + /* blind guess being as good as anything */ + pseudo_magic(); + return filename; +} + diff --git a/pseudo_client.h b/pseudo_client.h new file mode 100644 index 0000000..e732bae --- /dev/null +++ b/pseudo_client.h @@ -0,0 +1,89 @@ +/* + * pseudo_client.h, shared declarations for client + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +extern pseudo_msg_t *pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...); +#if PSEUDO_STATBUF_64 +#define base_lstat real_lstat64 +#define base_fstat real_fstat64 +#define base_stat real_stat64 +#define base_fstatat(dirfd, path, buf, flags) real___fxstatat64(_STAT_VER, dirfd, path, buf, flags) +#else +#define base_lstat real_lstat +#define base_fstat real_fstat +#define base_stat real_stat +#define base_fstatat(dirfd, path, buf, flags) real___fxstatat(_STAT_VER, dirfd, path, buf, flags) +#endif +extern void pseudo_antimagic(void); +extern void pseudo_magic(void); +extern void pseudo_client_touchuid(void); +extern void pseudo_client_touchgid(void); +extern char *pseudo_client_fdpath(int fd); +extern int pseudo_client_shutdown(void); +extern int pseudo_fd(int fd, int how); +#define MOVE_FD 0 +#define COPY_FD 1 +#define PSEUDO_MIN_FD 20 +extern uid_t pseudo_euid; +extern uid_t pseudo_fuid; +extern uid_t pseudo_suid; +extern uid_t pseudo_ruid; +extern gid_t pseudo_egid; +extern gid_t pseudo_sgid; +extern gid_t pseudo_rgid; +extern gid_t pseudo_fgid; +extern int pseudo_prefix_dir_fd; +extern int pseudo_localstate_dir_fd; +extern FILE *pseudo_pwd_open(void); +extern FILE *pseudo_grp_open(void); +extern void pseudo_pwd_close(void); +extern void pseudo_grp_close(void); +extern int pseudo_pwd_lck_open(void); +extern int pseudo_pwd_lck_close(void); +extern FILE *pseudo_pwd; +extern FILE *pseudo_grp; + +/* support related to chroot/getcwd/etc. */ +extern int pseudo_client_getcwd(void); +extern int pseudo_client_chroot(const char *); +extern char *pseudo_root_path(const char *, int, int, const char *, int); +extern const char *pseudo_exec_path(const char *filename, int); +#define PSEUDO_ROOT_PATH(x, y, z) pseudo_root_path(__func__, __LINE__, (x), (y), (z)); +extern char *pseudo_cwd; +extern size_t pseudo_cwd_len; +extern char *pseudo_cwd_rel; +extern char *pseudo_chroot; +extern char *pseudo_passwd; +extern size_t pseudo_chroot_len; +extern int pseudo_nosymlinkexp; + +extern int pseudo_umask; + +/* Root can read and write files, and enter directories which have no + * read, write, or execute permissions. (But can't execute files without + * execute permissions!) + * + * A non-root user can't. + * + * When doing anything which actually writes to the filesystem, we add in + * the user read/write/execute bits. When storing to the database, though, + * we mask out any such bits which weren't in the original mode. + */ +#define PSEUDO_FS_MODE(mode, isdir) (((mode) | S_IRUSR | S_IWUSR | ((isdir) ? S_IXUSR : 0)) & ~(S_IWGRP | S_IWOTH)) +#define PSEUDO_DB_MODE(fs_mode, user_mode) (((fs_mode) & ~0722) | ((user_mode & 0722))) + diff --git a/pseudo_db.c b/pseudo_db.c new file mode 100644 index 0000000..f60f0ab --- /dev/null +++ b/pseudo_db.c @@ -0,0 +1,2563 @@ +/* + * pseudo_db.c, sqlite3 interface + * + * Copyright (c) 2008-2010,2013 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/xattr.h> +#include <time.h> +#include <unistd.h> + +#include <sqlite3.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_db.h" + +/* #define NPROFILE */ + +#ifdef NPROFILE +void xProfile(void * pArg, const char * pQuery, sqlite3_uint64 pTimeTaken) +{ + pseudo_diag("profile: %lld %s\n", pTimeTaken, pQuery); +} +#endif + +struct log_history { + int rc; + unsigned long fields; + sqlite3_stmt *stmt; +}; + +struct pdb_file_list { + int rc; + sqlite3_stmt *stmt; +}; + +static int file_db_dirty = 0; +#ifdef USE_MEMORY_DB +static sqlite3 *real_file_db = 0; +#endif +static sqlite3 *file_db = 0; +static sqlite3 *log_db = 0; + +/* What's going on here, you might well ask? + * This contains a template to build the database. I suppose maybe it + * should have been elegantly done as a big chunk of embedded SQL, but + * this looked like a good idea at the time. + */ +typedef struct { char *fmt; int arg; } id_row; + +/* This seemed like a really good idea at the time. The idea is that these + * structures let me write semi-abstract code to "create a database" without + * duplicating as much of the code. + */ +static struct sql_table { + char *name; + char *sql; + char **names; + id_row *values; +} file_tables[] = { + { "files", + "id INTEGER PRIMARY KEY, " + "path VARCHAR, " + "dev INTEGER, " + "ino INTEGER, " + "uid INTEGER, " + "gid INTEGER, " + "mode INTEGER, " + "rdev INTEGER", + NULL, + NULL }, + { "xattrs", + "id INTEGER PRIMARY KEY, " + "file_id INTEGER REFERENCES files(id) ON DELETE CASCADE, " + "name VARCHAR, " + "value VARCHAR", + NULL, + NULL }, + { NULL, NULL, NULL, NULL }, +}, log_tables[] = { + { "logs", + "id INTEGER PRIMARY KEY, " + "stamp INTEGER, " + "op INTEGER, " + "client INTEGER, " + "fd INTEGER, " + "dev INTEGER, " + "ino INTEGER, " + "mode INTEGER, " + "path VARCHAR, " + "result INTEGER, " + "severity INTEGER, " + "text VARCHAR ", + NULL, + NULL }, + { NULL, NULL, NULL, NULL }, +}; + +/* similarly, this creates indexes generically. */ +static struct sql_index { + char *name; + char *table; + char *keys; +} file_indexes[] = { +/* { "files__path", "files", "path" }, */ + { "files__path_dev_ino", "files", "path, dev, ino" }, + { "files__dev_ino", "files", "dev, ino" }, + { "xattrs__file", "xattrs", "file_id" }, + { NULL, NULL, NULL }, +}, log_indexes[] = { + { NULL, NULL, NULL }, +}; + +static char *file_pragmas[] = { + "PRAGMA legacy_file_format = OFF;", + "PRAGMA journal_mode = OFF;", + /* the default page size produces painfully bad behavior + * for memory databases with some versions of sqlite. + */ + "PRAGMA page_size = 8192;", + "PRAGMA locking_mode = EXCLUSIVE;", + /* Setting this to NORMAL makes pseudo noticably slower + * than fakeroot, but is perhaps more secure. However, + * note that sqlite always flushes to the OS; what is lacking + * in non-synchronous mode is waiting for the OS to + * confirm delivery to media, and also a bunch of cache + * flushing and reloading which we probably don't really + * need. + */ + "PRAGMA synchronous = OFF;", + "PRAGMA foreign_keys = ON;", + NULL +}; + +static char *log_pragmas[] = { + "PRAGMA legacy_file_format = OFF;", + "PRAGMA journal_mode = OFF;", + "PRAGMA locking_mode = EXCLUSIVE;", + "PRAGMA synchronous = OFF;", + NULL +}; + +/* table migrations: */ +/* If there is no migration table, we assume "version -1" -- the + * version shipped with wrlinux 3.0, which had no version + * number. Otherwise, we check it for the highest version recorded. + * We then perform, and then record, each migration in sequence. + * The first migration is the migration to create the migrations + * table; this way, it'll work on existing databases. It'll also + * work for new databases -- the migrations get performed in order + * before the databases are considered to be set up. + */ + +static char create_migration_table[] = + "CREATE TABLE migrations (" + "id INTEGER PRIMARY KEY, " + "version INTEGER, " + "stamp INTEGER, " + "sql VARCHAR" + ");"; +static char index_migration_table[] = + "CREATE INDEX migration__version ON migrations (version)"; + +/* This used to be a { version, sql } pair, but version was always + * the same as index into the table, so I removed it. + * The first migration in each database is migration #0 -- the + * creation of the migration table now being used for versioning. + * The second is indexing on version -- sqlite3 can grab MAX(version) + * faster if it's indexed. (Indexing this table is very cheap, since + * there are very few migrations and each one produces exactly + * one insert.) + */ +static struct sql_migration { + char *sql; +} file_migrations[] = { + { create_migration_table }, + { index_migration_table }, + { "ALTER TABLE files ADD deleting INTEGER;" }, + { NULL }, +}, log_migrations[] = { + { create_migration_table }, + { index_migration_table }, + /* support for hostdeps merge -- this allows us to log "tags" + * along with events. + */ + { "ALTER TABLE logs ADD tag VARCHAR;" }, + /* the logs table was defined so early I hadn't realized I cared + * about UID and GID. + */ + { "ALTER TABLE logs ADD uid INTEGER;" }, + { "ALTER TABLE logs ADD gid INTEGER;" }, + /* track access types (read/write, etc) */ + { "ALTER TABLE logs ADD access INTEGER;" }, + /* client program/path */ + { "ALTER TABLE logs ADD program VARCHAR;" }, + /* message type (ping, op) */ + { "ALTER TABLE logs ADD type INTEGER;" }, + { NULL }, +}; + +/* cleanup database before getting started + * + * On a large build, the logs database gets GIGANTIC... And + * we rarely-if-ever delete things from it. So instead of + * doing the vacuum operation on it at startup, which can impose + * a several-minute delay, we do it only on deletions. + * + * There's no setup for log database right now. + */ +char *file_setups[] = { + "VACUUM;", + NULL, +}; + +struct database_info { + char *pathname; + struct sql_index *indexes; + struct sql_table *tables; + struct sql_migration *migrations; + char **pragmas; + char **setups; + struct sqlite3 **db; +}; + +static struct database_info db_infos[] = { + { + "logs.db", + log_indexes, + log_tables, + log_migrations, + log_pragmas, + NULL, + &log_db + }, + { + "files.db", + file_indexes, + file_tables, + file_migrations, + file_pragmas, + file_setups, +#ifdef USE_MEMORY_DB + &real_file_db + }, + { + ":memory:", + file_indexes, + file_tables, + file_migrations, + file_pragmas, + file_setups, +#endif + &file_db + }, + { 0, 0, 0, 0, 0, 0, 0 } +}; + +/* pretty-print error along with the underlying SQL error. */ +static void +dberr(sqlite3 *db, char *fmt, ...) { + va_list ap; + char debuff[8192]; + int len; + + va_start(ap, fmt); + len = vsnprintf(debuff, 8192, fmt, ap); + va_end(ap); + len = write(pseudo_util_debug_fd, debuff, len); + if (db) { + len = snprintf(debuff, 8192, ": %s\n", sqlite3_errmsg(db)); + len = write(pseudo_util_debug_fd, debuff, len); + } else { + len = write(pseudo_util_debug_fd, " (no db)\n", 9); + } +} + +#ifdef USE_MEMORY_DB + +static void +pdb_backup() { + sqlite3_backup *pBackup; + /* no point in doing this if we don't have a database to back up, + * or nothing's changed. + */ + if (!file_db || !real_file_db || !file_db_dirty) + return; + + pBackup = sqlite3_backup_init(real_file_db, "main", file_db, "main"); + if (pBackup) { + int rc; + (void)sqlite3_backup_step(pBackup, -1); + rc = sqlite3_backup_finish(pBackup); + if (rc != SQLITE_OK) { + dberr(real_file_db, "error during flush to disk"); + } + } + file_db_dirty = 0; +} + +static void +pdb_restore() { + sqlite3_backup *pBackup; + /* no point in doing this if we don't have a database to back up */ + if (!file_db || !real_file_db) + return; + + pBackup = sqlite3_backup_init(file_db, "main", real_file_db, "main"); + if (pBackup) { + int rc; + (void)sqlite3_backup_step(pBackup, -1); + rc = sqlite3_backup_finish(pBackup); + if (rc != SQLITE_OK) { + dberr(file_db, "error during load from disk"); + } + } + file_db_dirty = 0; +} + +int +pdb_maybe_backup(void) { + static int occasional = 0; + if (file_db && real_file_db) { + occasional = (occasional + 1) % 10; + if (occasional == 0) { + pdb_backup(); + return 1; + } + } + return 0; +} +#else /* USE_MEMORY_DB */ +int +pdb_maybe_backup(void) { + return 0; +} +#endif + +/* those who enjoy children, sausages, and databases, should not watch + * them being made. + */ +static int +make_tables(sqlite3 *db, + struct sql_table *sql_tables, + struct sql_index *sql_indexes, + struct sql_migration *sql_migrations, + char **existing, int rows) { + + static sqlite3_stmt *stmt; + sqlite3_stmt *update_version = 0; + struct sql_migration *m; + int available_migrations; + int version = -1; + int i, j; + char *sql; + char *errmsg; + int rc; + int found = 0; + + for (i = 0; sql_tables[i].name; ++i) { + found = 0; + printf("considering table %s\n", sql_tables[i].name); + for (j = 1; j <= rows; ++j) { + if (!strcmp(existing[j], sql_tables[i].name)) { + found = 1; + break; + } + } + if (found) + continue; + /* now to create the table */ + sql = sqlite3_mprintf("CREATE TABLE %s ( %s );", + sql_tables[i].name, sql_tables[i].sql); + rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg); + sqlite3_free(sql); + if (rc) { + dberr(db, "error trying to create %s", sql_tables[i].name); + return 1; + } + if (sql_tables[i].values) { + for (j = 0; sql_tables[i].values[j].fmt; ++j) { + char buffer[256]; + snprintf(buffer, sizeof(buffer), sql_tables[i].values[j].fmt, sql_tables[i].values[j].arg); + sql = sqlite3_mprintf("INSERT INTO %s ( %s ) VALUES ( %s );", + sql_tables[i].name, + *sql_tables[i].names, + buffer); + rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg); + sqlite3_free(sql); + if (rc) { + dberr(db, "error trying to populate %s", + sql_tables[i].name); + return 1; + } + } + } + for (j = 0; sql_indexes[j].name; ++j) { + if (strcmp(sql_indexes[j].table, sql_tables[i].name)) + continue; + sql = sqlite3_mprintf("CREATE INDEX %s ON %s ( %s );", + sql_indexes[j].name, + sql_indexes[j].table, + sql_indexes[j].keys); + rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg); + sqlite3_free(sql); + if (rc) { + dberr(db, "error trying to index %s", + sql_tables[i].name); + return 1; + } + } + } + /* now, see about migrations */ + found = 0; + for (j = 1; j <= rows; ++j) { + if (!strcmp(existing[j], "migrations")) { + found = 1; + break; + } + } + if (found) { + sql = "SELECT MAX(version) FROM migrations;"; + rc = sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, NULL); + if (rc) { + dberr(db, "couldn't examine migrations table"); + return 1; + } + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + version = (unsigned long) sqlite3_column_int64(stmt, 0); + rc = sqlite3_step(stmt); + } else { + version = -1; + } + if (rc != SQLITE_DONE) { + dberr(db, "not done after the single row we expected?", rc); + return 1; + } + pseudo_debug(PDBGF_DB, "existing database version: %d\n", version); + rc = sqlite3_finalize(stmt); + if (rc) { + dberr(db, "couldn't finalize version check"); + return 1; + } + } else { + pseudo_debug(PDBGF_DB, "no existing database version\n"); + version = -1; + } + for (m = sql_migrations; m->sql; ++m) + ; + available_migrations = m - sql_migrations; + /* I am pretty sure this can never happen. */ + if (version < -1) + version = -1; + /* I hope this can never happen. */ + if (version >= available_migrations) + version = available_migrations - 1; + for (m = sql_migrations + (version + 1); m->sql; ++m) { + int migration = (m - sql_migrations); + pseudo_debug(PDBGF_DB, "considering migration %d\n", migration); + if (version >= migration) + continue; + pseudo_debug(PDBGF_DB, "running migration %d\n", migration); + rc = sqlite3_prepare_v2(db, + m->sql, + strlen(m->sql), + &stmt, NULL); + if (rc) { + dberr(log_db, "couldn't prepare migration %d (%s)", + migration, m->sql); + return 1; + } + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + dberr(file_db, "migration %d failed", + migration); + return 1; + } + sqlite3_finalize(stmt); + /* this has to occur here, because the first migration + * executed CREATES the migration table, so you can't + * prepare this statement if you haven't already executed + * the first migration. + * + * Lesson learned: Yes, it actually WILL be a sort of big + * deal to add versioning later. + */ + static char *update_sql = + "INSERT INTO migrations (" + "version, stamp, sql" + ") VALUES (?, ?, ?);"; + rc = sqlite3_prepare_v2(db, + update_sql, + strlen(update_sql), + &update_version, NULL); + if (rc) { + dberr(db, "couldn't prepare statement to update migrations"); + return 1; + } + sqlite3_bind_int(update_version, 1, migration); + sqlite3_bind_int(update_version, 2, time(NULL)); + sqlite3_bind_text(update_version, 3, m->sql, -1, SQLITE_STATIC); + rc = sqlite3_step(update_version); + if (rc != SQLITE_DONE) { + dberr(db, "couldn't update migrations table (after migration to version %d)", + migration); + sqlite3_finalize(update_version); + return 1; + } else { + pseudo_debug(PDBGF_DB, "update of migrations (after %d) fine.\n", + migration); + } + sqlite3_finalize(update_version); + update_version = 0; + version = migration; + } + + return 0; +} + +/* registered with atexit */ +static void +cleanup_db(void) { + pseudo_debug(PDBGF_SERVER, "server exiting\n"); +#ifdef USE_MEMORY_DB + if (real_file_db) { + pdb_backup(); + sqlite3_close(real_file_db); + } +#endif + if (file_db) + sqlite3_close(file_db); + if (log_db) + sqlite3_close(log_db); +} + +/* This function has been rewritten and I no longer hate it. I just feel + * like it's important to say that. + */ +static int +get_db(struct database_info *dbinfo) { + int rc; + int i; + char *sql; + char **results; + int rows, columns; + char *errmsg; + static int registered_cleanup = 0; + char *dbfile; + sqlite3 *db; + + if (!dbinfo) + return 1; + if (!dbinfo->db) + return 1; + /* this database is perhaps already initialized? */ + if (*(dbinfo->db)) + return 0; + + dbfile = pseudo_localstatedir_path(dbinfo->pathname); +#ifdef USE_MEMORY_DB + if (!strcmp(dbinfo->pathname, ":memory:")) { + rc = sqlite3_open(dbinfo->pathname, &db); + } else +#endif + rc = sqlite3_open(dbfile, &db); + free(dbfile); + if (rc) { + pseudo_diag("Failed: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); + *(dbinfo->db) = NULL; + return 1; + } + /* we store this in the database_info, but hereafter we'll just use + * the name db, because it is shorter. + */ + *dbinfo->db = db; + if (!registered_cleanup) { + atexit(cleanup_db); + registered_cleanup = 1; + } + if (dbinfo->pragmas) { + for (i = 0; dbinfo->pragmas[i]; ++i) { + rc = sqlite3_exec(db, dbinfo->pragmas[i], NULL, NULL, &errmsg); + pseudo_debug(PDBGF_SQL | PDBGF_VERBOSE, "executed pragma: '%s', rc %d.\n", + dbinfo->pragmas[i], rc); + if (rc) { + dberr(db, dbinfo->pragmas[i]); + } + } + } + /* create database tables or die trying */ + sql = "SELECT name FROM sqlite_master " + "WHERE type = 'table' " + "ORDER BY name;"; + rc = sqlite3_get_table(db, sql, &results, &rows, &columns, &errmsg); + if (rc) { + pseudo_diag("Failed: %s\n", errmsg); + } else { + rc = make_tables(db, dbinfo->tables, dbinfo->indexes, dbinfo->migrations, results, rows); + sqlite3_free_table(results); + } + /* as of now, the only setup is a vacuum operation which we don't care about + * the results of. + */ + if (dbinfo->setups) { + for (i = 0; dbinfo->setups[i]; ++i) { + sqlite3_exec(db, dbinfo->setups[i], NULL, NULL, &errmsg); + } + } + return rc; +} + +static int +get_dbs(void) { + int err = 0; + int i; +#ifdef USE_MEMORY_DB + int already_loaded = 0; + if (file_db) + already_loaded = 1; +#endif + for (i = 0; db_infos[i].db; ++i) { + if (get_db(&db_infos[i])) { + pseudo_diag("Error getting '%s' database.\n", + db_infos[i].pathname); + err = 1; + } + } +#ifdef USE_MEMORY_DB + if (!already_loaded && file_db) + pdb_restore(); +#endif + return err; +} + +/* put a prepared log entry into the database */ +int +pdb_log_traits(pseudo_query_t *traits) { + pseudo_query_t *trait; + log_entry *e; + int rc; + + if (!log_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 1; + } + e = calloc(sizeof(*e), 1); + if (!e) { + pseudo_diag("can't allocate space for log entry."); + return 1; + } + for (trait = traits; trait; trait = trait->next) { + switch (trait->field) { + case PSQF_ACCESS: + e->access = trait->data.ivalue; + break; + case PSQF_CLIENT: + e->client = trait->data.ivalue; + break; + case PSQF_DEV: + e->dev = trait->data.ivalue; + break; + case PSQF_FD: + e->fd = trait->data.ivalue; + break; + case PSQF_FTYPE: + e->mode |= (trait->data.ivalue & S_IFMT); + break; + case PSQF_GID: + e->gid = trait->data.ivalue; + break; + case PSQF_INODE: + e->ino = trait->data.ivalue; + break; + case PSQF_MODE: + e->mode = trait->data.ivalue; + break; + case PSQF_OP: + e->op = trait->data.ivalue; + break; + case PSQF_PATH: + e->path = trait->data.svalue ? + strdup(trait->data.svalue) : NULL; + break; + case PSQF_PERM: + e->mode |= (trait->data.ivalue & ~(S_IFMT) & 0177777); + break; + case PSQF_PROGRAM: + e->program = trait->data.svalue ? + strdup(trait->data.svalue) : NULL; + break; + case PSQF_RESULT: + e->result = trait->data.ivalue; + break; + case PSQF_SEVERITY: + e->severity = trait->data.ivalue; + break; + case PSQF_STAMP: + e->stamp = trait->data.ivalue; + break; + case PSQF_TAG: + e->tag = trait->data.svalue ? + strdup(trait->data.svalue) : NULL; + break; + case PSQF_TEXT: + e->text = trait->data.svalue ? + strdup(trait->data.svalue) : NULL; + break; + case PSQF_TYPE: + e->type = trait->data.ivalue; + break; + case PSQF_UID: + e->uid = trait->data.ivalue; + break; + case PSQF_ID: + case PSQF_ORDER: + default: + pseudo_diag("Invalid trait %s for log creation.\n", + pseudo_query_field_name(trait->field)); + free(e); + return 1; + break; + } + } + rc = pdb_log_entry(e); + log_entry_free(e); + return rc; +} + +/* create a log from a given log entry, with tag and text */ +int +pdb_log_entry(log_entry *e) { + char *sql = "INSERT INTO logs " + "(stamp, op, access, client, dev, gid, ino, mode, path, result, severity, text, program, tag, type, uid)" + " VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + static sqlite3_stmt *insert; + int field; + int rc; + + if (!log_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 1; + } + + if (!insert) { + rc = sqlite3_prepare_v2(log_db, sql, strlen(sql), &insert, NULL); + if (rc) { + dberr(log_db, "couldn't prepare INSERT statement"); + return 1; + } + } + + field = 1; + if (e) { + if (e->stamp) { + sqlite3_bind_int(insert, field++, e->stamp); + } else { + sqlite3_bind_int(insert, field++, (unsigned long) time(NULL)); + } + sqlite3_bind_int(insert, field++, e->op); + sqlite3_bind_int(insert, field++, e->access); + sqlite3_bind_int(insert, field++, e->client); + sqlite3_bind_int(insert, field++, e->dev); + sqlite3_bind_int(insert, field++, e->gid); + sqlite3_bind_int64(insert, field++, e->ino); + sqlite3_bind_int(insert, field++, e->mode); + if (e->path) { + sqlite3_bind_text(insert, field++, e->path, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(insert, field++); + } + sqlite3_bind_int(insert, field++, e->result); + sqlite3_bind_int(insert, field++, e->severity); + if (e->text) { + sqlite3_bind_text(insert, field++, e->text, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(insert, field++); + } + if (e->program) { + sqlite3_bind_text(insert, field++, e->program, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(insert, field++); + } + if (e->tag) { + sqlite3_bind_text(insert, field++, e->tag, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(insert, field++); + } + sqlite3_bind_int(insert, field++, e->type); + sqlite3_bind_int(insert, field++, e->uid); + } else { + sqlite3_bind_int(insert, field++, (unsigned long) time(NULL)); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_null(insert, field++); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_null(insert, field++); + sqlite3_bind_null(insert, field++); + sqlite3_bind_null(insert, field++); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + } + + rc = sqlite3_step(insert); + if (rc != SQLITE_DONE) { + dberr(log_db, "insert may have failed"); + } + sqlite3_reset(insert); + sqlite3_clear_bindings(insert); + return rc != SQLITE_DONE; +} +/* create a log from a given message, with tag and text */ +int +pdb_log_msg(pseudo_sev_t severity, pseudo_msg_t *msg, const char *program, const char *tag, const char *text, ...) { + char *sql = "INSERT INTO logs " + "(stamp, op, access, client, dev, gid, ino, mode, path, result, uid, severity, text, program, tag, type)" + " VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + static sqlite3_stmt *insert; + char buffer[8192]; + int field; + int rc; + va_list ap; + + if (text) { + va_start(ap, text); + vsnprintf(buffer, 8192, text, ap); + va_end(ap); + text = buffer; + } + + if (!log_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 1; + } + + if (!insert) { + rc = sqlite3_prepare_v2(log_db, sql, strlen(sql), &insert, NULL); + if (rc) { + dberr(log_db, "couldn't prepare INSERT statement"); + return 1; + } + } + + field = 1; + sqlite3_bind_int(insert, field++, (unsigned long) time(NULL)); + if (msg) { + sqlite3_bind_int(insert, field++, msg->op); + sqlite3_bind_int(insert, field++, msg->access); + sqlite3_bind_int(insert, field++, msg->client); + sqlite3_bind_int(insert, field++, msg->dev); + sqlite3_bind_int(insert, field++, msg->gid); + sqlite3_bind_int64(insert, field++, msg->ino); + sqlite3_bind_int(insert, field++, msg->mode); + if (msg->pathlen) { + sqlite3_bind_text(insert, field++, msg->path, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(insert, field++); + } + sqlite3_bind_int(insert, field++, msg->result); + sqlite3_bind_int(insert, field++, msg->uid); + } else { + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_null(insert, field++); + sqlite3_bind_int(insert, field++, 0); + sqlite3_bind_int(insert, field++, 0); + } + sqlite3_bind_int(insert, field++, severity); + if (text) { + sqlite3_bind_text(insert, field++, text, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(insert, field++); + } + if (program) { + sqlite3_bind_text(insert, field++, program, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(insert, field++); + } + if (tag) { + sqlite3_bind_text(insert, field++, tag, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(insert, field++); + } + if (msg) { + sqlite3_bind_int(insert, field++, msg->type); + } else { + sqlite3_bind_int(insert, field++, 0); + } + + rc = sqlite3_step(insert); + if (rc != SQLITE_DONE) { + dberr(log_db, "insert may have failed"); + } + sqlite3_reset(insert); + sqlite3_clear_bindings(insert); + return rc != SQLITE_DONE; +} + +#define BFSZ 8192 +typedef struct { + size_t buflen; + char *data; + char *tail; +} buffer; + +static int +frag(buffer *b, char *fmt, ...) { + va_list ap; + static size_t curlen; + int rc; + + if (!b) { + pseudo_diag("frag called without buffer.\n"); + return -1; + } + curlen = b->tail - b->data; + va_start(ap, fmt); + rc = vsnprintf(b->tail, b->buflen - curlen, fmt, ap); + va_end(ap); + if ((rc > 0) && ((size_t) rc >= (b->buflen - curlen))) { + size_t newlen = b->buflen; + while (newlen <= (rc + curlen)) + newlen *= 2; + char *newbuf = malloc(newlen); + if (!newbuf) { + pseudo_diag("failed to allocate SQL buffer.\n"); + return -1; + } + memcpy(newbuf, b->data, curlen + 1); + b->tail = newbuf + curlen; + free(b->data); + b->data = newbuf; + b->buflen = newlen; + /* try again */ + va_start(ap, fmt); + rc = vsnprintf(b->tail, b->buflen - curlen, fmt, ap); + va_end(ap); + if ((rc > 0) && ((size_t) rc >= (b->buflen - curlen))) { + pseudo_diag("tried to reallocate larger buffer, failed. giving up.\n"); + return -1; + } + } + if (rc >= 0) + b->tail += rc; + return rc; +} + +sqlite3_stmt * +pdb_query(char *stmt_type, pseudo_query_t *traits, unsigned long fields, int unique, int want_results) { + pseudo_query_t *trait; + sqlite3_stmt *stmt; + int done_any = 0; + int field = 0; + const char *order_by = "id"; + char *order_dir = "ASC"; + int rc; + pseudo_query_field_t f; + static buffer *sql; + + if (!log_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return NULL; + } + + if (!stmt_type) { + pseudo_diag("can't prepare a statement without a type.\n"); + } + + if (!sql) { + sql = malloc(sizeof *sql); + if (!sql) { + pseudo_diag("can't allocate SQL buffer.\n"); + return NULL; + } + sql->buflen = 512; + sql->data = malloc(sql->buflen); + if (!sql->data) { + pseudo_diag("can't allocate SQL text buffer.\n"); + free(sql); + sql = 0; + return NULL; + } + } + sql->tail = sql->data; + /* should be DELETE or SELECT */ + frag(sql, "%s ", stmt_type); + + if (want_results) { + if (unique) + frag(sql, "DISTINCT "); + + done_any = 0; + for (f = PSQF_NONE + 1; f < PSQF_MAX; ++f) { + if (fields & (1 << f)) { + frag(sql, "%s%s", + done_any ? ", " : "", + pseudo_query_field_name(f)); + done_any = 1; + } + } + } + + frag(sql, " FROM logs "); + /* first, build up an SQL string with the fields and operators */ + done_any = 0; + for (trait = traits; trait; trait = trait->next) { + if (trait->field != PSQF_ORDER) { + if (done_any) { + frag(sql, "AND "); + } else { + frag(sql, "WHERE "); + done_any = 1; + } + } + switch (trait->field) { + case PSQF_PROGRAM: + case PSQF_TEXT: + case PSQF_TAG: + case PSQF_PATH: + switch (trait->type) { + case PSQT_LIKE: + frag(sql, "%s %s ('%' || ? || '%')", + pseudo_query_field_name(trait->field), + pseudo_query_type_sql(trait->type)); + break; + case PSQT_NOTLIKE: + case PSQT_SQLPAT: + frag(sql, "%s %s ?", + pseudo_query_field_name(trait->field), + pseudo_query_type_sql(trait->type)); + break; + default: + frag(sql, "%s %s ? ", + pseudo_query_field_name(trait->field), + pseudo_query_type_sql(trait->type)); + break; + } + break; + case PSQF_PERM: + switch (trait->type) { + case PSQT_LIKE: + case PSQT_NOTLIKE: + case PSQT_SQLPAT: + pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n"); + return 0; + break; + default: + break; + } + /* mask out permission bits */ + frag(sql, "(%s & %d) %s ? ", + "mode", + ~(S_IFMT) & 0177777, + pseudo_query_type_sql(trait->type)); + break; + case PSQF_FTYPE: + switch (trait->type) { + case PSQT_LIKE: + case PSQT_NOTLIKE: + case PSQT_SQLPAT: + pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n"); + return 0; + break; + default: + break; + } + /* mask out permission bits */ + frag(sql, "(%s & %d) %s ? ", + "mode", + S_IFMT, + pseudo_query_type_sql(trait->type)); + break; + case PSQF_ORDER: + order_by = pseudo_query_field_name(trait->data.ivalue); + switch (trait->type) { + case PSQT_LESS: + order_dir = "DESC"; + break; + case PSQT_EXACT: + /* this was already the default */ + break; + case PSQT_GREATER: + order_dir = "ASC"; + break; + default: + pseudo_diag("Ordering must be < or >.\n"); + return 0; + break; + } + break; + default: + switch (trait->type) { + case PSQT_LIKE: + case PSQT_NOTLIKE: + case PSQT_SQLPAT: + pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n"); + return 0; + break; + default: + frag(sql, "%s %s ? ", + pseudo_query_field_name(trait->field), + pseudo_query_type_sql(trait->type)); + break; + } + break; + } + } + if (want_results) + frag(sql, "ORDER BY %s %s;", order_by, order_dir); + pseudo_debug(PDBGF_SQL, "created SQL: <%s>\n", sql->data); + + /* second, prepare it */ + rc = sqlite3_prepare_v2(log_db, sql->data, strlen(sql->data), &stmt, NULL); + if (rc) { + dberr(log_db, "couldn't prepare %s statement", stmt_type); + return NULL; + } + + /* third, bind the fields */ + field = 1; + for (trait = traits; trait; trait = trait->next) { + switch (trait->field) { + case PSQF_ORDER: + /* this just creates a hunk of SQL above */ + break; + case PSQF_PROGRAM: + case PSQF_PATH: + case PSQF_TAG: + case PSQF_TEXT: + sqlite3_bind_text(stmt, field++, + trait->data.svalue, -1, SQLITE_STATIC); + break; + case PSQF_ACCESS: + case PSQF_CLIENT: + case PSQF_DEV: + case PSQF_FD: + case PSQF_FTYPE: + case PSQF_INODE: + case PSQF_GID: + case PSQF_PERM: + case PSQF_MODE: + case PSQF_OP: + case PSQF_RESULT: + case PSQF_SEVERITY: + case PSQF_STAMP: + case PSQF_TYPE: + case PSQF_UID: + sqlite3_bind_int64(stmt, field++, trait->data.ivalue); + break; + default: + pseudo_diag("Inexplicably invalid field type %d\n", trait->field); + sqlite3_finalize(stmt); + return NULL; + } + } + return stmt; +} + +int +pdb_delete(pseudo_query_t *traits, unsigned long fields) { + sqlite3_stmt *stmt; + + stmt = pdb_query("DELETE", traits, fields, 0, 0); + + /* no need to return it, so... */ + if (stmt) { + file_db_dirty = 1; + int rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + dberr(log_db, "deletion failed"); + return -1; + } else { + pseudo_diag("Deleted records, vacuuming log database (may take a while).\n"); + /* we can't do anything about it if this fails... */ + sqlite3_exec(log_db, "VACUUM;", NULL, NULL, NULL); + } + sqlite3_finalize(stmt); + return 0; + } + return -1; +} + +log_history +pdb_history(pseudo_query_t *traits, unsigned long fields, int unique) { + log_history h = NULL; + sqlite3_stmt *stmt; + + stmt = pdb_query("SELECT", traits, fields, unique, 1); + + if (stmt) { + /* fourth, return the statement, now ready to be stepped through */ + h = malloc(sizeof(*h)); + if (h) { + h->rc = 0; + h->fields = fields; + h->stmt = stmt; + } else { + pseudo_diag("failed to allocate memory for log_history\n"); + sqlite3_finalize(stmt); + } + return h; + } else { + return NULL; + } +} + +log_entry * +pdb_history_entry(log_history h) { + log_entry *l; + const unsigned char *s; + int column; + pseudo_query_field_t f; + + if (!h || !h->stmt) + return 0; + /* in case someone tries again after we're already done */ + if (h->rc == SQLITE_DONE) { + return 0; + } + h->rc = sqlite3_step(h->stmt); + if (h->rc == SQLITE_DONE) { + return 0; + } else if (h->rc != SQLITE_ROW) { + dberr(log_db, "statement failed"); + return 0; + } + l = calloc(sizeof(log_entry), 1); + if (!l) { + pseudo_diag("couldn't allocate log entry.\n"); + return 0; + } + + column = 0; + for (f = PSQF_NONE + 1; f < PSQF_MAX; ++f) { + if (!(h->fields & (1 << f))) + continue; + switch (f) { + case PSQF_ACCESS: + l->access = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_CLIENT: + l->client = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_DEV: + l->dev = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_FD: + l->fd = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_GID: + l->gid = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_INODE: + l->ino = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_MODE: + l->mode = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_OP: + l->op = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_PATH: + s = sqlite3_column_text(h->stmt, column++); + if (s) + l->path = strdup((char *) s); + break; + case PSQF_PROGRAM: + s = sqlite3_column_text(h->stmt, column++); + if (s) + l->program = strdup((char *) s); + break; + case PSQF_RESULT: + l->result = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_SEVERITY: + l->severity = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_STAMP: + l->stamp = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_TAG: + s = sqlite3_column_text(h->stmt, column++); + if (s) + l->tag = strdup((char *) s); + break; + case PSQF_TEXT: + s = sqlite3_column_text(h->stmt, column++); + if (s) + l->text = strdup((char *) s); + break; + case PSQF_TYPE: + l->type = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_UID: + l->uid = sqlite3_column_int64(h->stmt, column++); + break; + case PSQF_ORDER: + case PSQF_FTYPE: + case PSQF_PERM: + pseudo_diag("field %s should not be in the fields list.\n", + pseudo_query_field_name(f)); + return 0; + break; + default: + pseudo_diag("unknown field %d\n", f); + return 0; + break; + } + } + + return l; +} + +void +pdb_history_free(log_history h) { + if (!h) + return; + if (h->stmt) { + sqlite3_reset(h->stmt); + sqlite3_finalize(h->stmt); + } + free(h); +} + +void +log_entry_free(log_entry *e) { + if (!e) + return; + free(e->text); + free(e->path); + free(e->program); + free(e->tag); + free(e); +} + +/* Now for the actual file handling code! */ + +/* pdb_link_file: Creates a new file from msg, using the provided path + * or 'NAMELESS FILE'. + */ +int +pdb_link_file(pseudo_msg_t *msg, long long *row) { + static sqlite3_stmt *insert; + int rc; + char *sql = "INSERT INTO files " + " ( path, dev, ino, uid, gid, mode, rdev, deleting ) " + " VALUES (?, ?, ?, ?, ?, ?, ?, 0);"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!insert) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &insert, NULL); + if (rc) { + dberr(file_db, "couldn't prepare INSERT statement"); + return 1; + } + } + if (!msg) { + return 1; + } + if (msg->pathlen) { + sqlite3_bind_text(insert, 1, msg->path, -1, SQLITE_STATIC); + } else { + sqlite3_bind_text(insert, 1, "NAMELESS FILE", -1, SQLITE_STATIC); + } + sqlite3_bind_int(insert, 2, msg->dev); + sqlite3_bind_int64(insert, 3, msg->ino); + sqlite3_bind_int(insert, 4, msg->uid); + sqlite3_bind_int(insert, 5, msg->gid); + sqlite3_bind_int(insert, 6, msg->mode); + sqlite3_bind_int(insert, 7, msg->rdev); + pseudo_debug(PDBGF_DB | PDBGF_FILE, "linking %s: dev %llu, ino %llu, mode %o, owner %d\n", + (msg->pathlen ? msg->path : "<nil> (as NAMELESS FILE)"), + (unsigned long long) msg->dev, (unsigned long long) msg->ino, + (int) msg->mode, msg->uid); + file_db_dirty = 1; + rc = sqlite3_step(insert); + if (rc != SQLITE_DONE) { + dberr(file_db, "insert may have failed (rc %d)", rc); + } + /* some users care what the row ID is */ + if (row) { + *row = sqlite3_last_insert_rowid(file_db); + } + sqlite3_reset(insert); + sqlite3_clear_bindings(insert); + return rc != SQLITE_DONE; +} + +/* pdb_unlink_file_dev: Delete every instance of a dev/inode pair. */ +int +pdb_unlink_file_dev(pseudo_msg_t *msg) { + static sqlite3_stmt *sql_delete; + int rc; + char *sql = "DELETE FROM files WHERE dev = ? AND ino = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!sql_delete) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &sql_delete, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + if (!msg) { + return 1; + } + sqlite3_bind_int(sql_delete, 1, msg->dev); + sqlite3_bind_int64(sql_delete, 2, msg->ino); + file_db_dirty = 1; + rc = sqlite3_step(sql_delete); + if (rc != SQLITE_DONE) { + dberr(file_db, "delete by inode may have failed"); + } + sqlite3_reset(sql_delete); + sqlite3_clear_bindings(sql_delete); + return rc != SQLITE_DONE; +} + +/* provide a path for a 'NAMELESS FILE' entry */ +int +pdb_update_file_path(pseudo_msg_t *msg) { + static sqlite3_stmt *update; + int rc; + char *sql = "UPDATE files SET path = ? " + "WHERE path = 'NAMELESS FILE' and dev = ? AND ino = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!update) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL); + if (rc) { + dberr(file_db, "couldn't prepare UPDATE statement"); + return 1; + } + } + if (!msg || !msg->pathlen) { + pseudo_debug(PDBGF_DB, "can't update a file without a message or path.\n"); + return 1; + } + sqlite3_bind_text(update, 1, msg->path, -1, SQLITE_STATIC); + sqlite3_bind_int(update, 2, msg->dev); + sqlite3_bind_int64(update, 3, msg->ino); + file_db_dirty = 1; + rc = sqlite3_step(update); + if (rc != SQLITE_DONE) { + dberr(file_db, "update path by inode may have failed"); + } + sqlite3_reset(update); + sqlite3_clear_bindings(update); + return rc != SQLITE_DONE; +} + +/* mark a file for pending deletion by a given client */ +int +pdb_may_unlink_file(pseudo_msg_t *msg, int deleting) { + static sqlite3_stmt *mark_file; + int rc, exact; + char *sql_mark_file = "UPDATE files SET deleting = ? WHERE path = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!mark_file) { + rc = sqlite3_prepare_v2(file_db, sql_mark_file, strlen(sql_mark_file), &mark_file, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + if (!msg) { + return 1; + } + if (msg->pathlen) { + sqlite3_bind_int(mark_file, 1, deleting); + sqlite3_bind_text(mark_file, 2, msg->path, -1, SQLITE_STATIC); + } else { + pseudo_debug(PDBGF_DB, "cannot mark a file for pending deletion without a path."); + return 1; + } + file_db_dirty = 1; + rc = sqlite3_step(mark_file); + if (rc != SQLITE_DONE) { + dberr(file_db, "mark for deletion may have failed"); + return 1; + } + exact = sqlite3_changes(file_db); + pseudo_debug(PDBGF_DB, "(exact %d) ", exact); + sqlite3_reset(mark_file); + sqlite3_clear_bindings(mark_file); + /* indicate whether we marked something */ + if (exact > 0) + return 0; + else + return 1; +} + +/* unmark a file for pending deletion */ +int +pdb_cancel_unlink_file(pseudo_msg_t *msg) { + static sqlite3_stmt *mark_file; + int rc, exact; + char *sql_mark_file = "UPDATE files SET deleting = 0 WHERE path = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!mark_file) { + rc = sqlite3_prepare_v2(file_db, sql_mark_file, strlen(sql_mark_file), &mark_file, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + if (!msg) { + return 1; + } + if (msg->pathlen) { + sqlite3_bind_text(mark_file, 1, msg->path, -1, SQLITE_STATIC); + } else { + pseudo_debug(PDBGF_DB, "cannot unmark a file for pending deletion without a path."); + return 1; + } + file_db_dirty = 1; + rc = sqlite3_step(mark_file); + if (rc != SQLITE_DONE) { + dberr(file_db, "unmark for deletion may have failed"); + } + exact = sqlite3_changes(file_db); + pseudo_debug(PDBGF_DB, "(exact %d) ", exact); + sqlite3_reset(mark_file); + sqlite3_clear_bindings(mark_file); + return rc != SQLITE_DONE; +} + +/* delete all files attached to a given cookie; + * used for database fixup passes. + */ +int +pdb_did_unlink_files(int deleting) { + static sqlite3_stmt *delete_exact; + int rc, exact; + char *sql_delete_exact = "DELETE FROM files WHERE deleting = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!delete_exact) { + rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + if (deleting == 0) { + pseudo_diag("did_unlink_files: deleting must be non-zero.\n"); + return 0; + } + sqlite3_bind_int(delete_exact, 1, deleting); + file_db_dirty = 1; + rc = sqlite3_step(delete_exact); + if (rc != SQLITE_DONE) { + dberr(file_db, "cleanup of files marked for deletion may have failed"); + } + exact = sqlite3_changes(file_db); + pseudo_debug(PDBGF_DB, "(exact %d)\n", exact); + sqlite3_reset(delete_exact); + sqlite3_clear_bindings(delete_exact); + return rc != SQLITE_DONE; +} + +/* confirm deletion of a specific file by a given client */ +int +pdb_did_unlink_file(char *path, int deleting) { + static sqlite3_stmt *delete_exact; + int rc, exact; + char *sql_delete_exact = "DELETE FROM files WHERE path = ? AND deleting = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!delete_exact) { + rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + if (!path) { + pseudo_debug(PDBGF_DB, "cannot unlink a file without a path."); + return 1; + } + sqlite3_bind_text(delete_exact, 1, path, -1, SQLITE_STATIC); + sqlite3_bind_int(delete_exact, 2, deleting); + file_db_dirty = 1; + rc = sqlite3_step(delete_exact); + if (rc != SQLITE_DONE) { + dberr(file_db, "cleanup of file marked for deletion may have failed"); + } + exact = sqlite3_changes(file_db); + pseudo_debug(PDBGF_DB, "(exact %d)\n", exact); + sqlite3_reset(delete_exact); + sqlite3_clear_bindings(delete_exact); + return rc != SQLITE_DONE; +} + +/* unlink a file, by path */ +int +pdb_unlink_file(pseudo_msg_t *msg) { + static sqlite3_stmt *delete_exact; + int rc, exact; + char *sql_delete_exact = "DELETE FROM files WHERE path = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!delete_exact) { + rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + if (!msg) { + return 1; + } + if (msg->pathlen) { + sqlite3_bind_text(delete_exact, 1, msg->path, -1, SQLITE_STATIC); + } else { + pseudo_debug(PDBGF_DB, "cannot unlink a file without a path."); + return 1; + } + file_db_dirty = 1; + rc = sqlite3_step(delete_exact); + if (rc != SQLITE_DONE) { + dberr(file_db, "delete exact by path may have failed"); + } + exact = sqlite3_changes(file_db); + pseudo_debug(PDBGF_DB, "(exact %d) ", exact); + sqlite3_reset(delete_exact); + sqlite3_clear_bindings(delete_exact); + return rc != SQLITE_DONE; +} + +/* Unlink all the contents of directory + * SQLite performance limitations: + * path LIKE foo '/%' -> can't use index + * path = A OR path = B -> can't use index + * Solution: + * 1. From web http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html + * Use > and < instead of a glob at the end. + */ +int +pdb_unlink_contents(pseudo_msg_t *msg) { + static sqlite3_stmt *delete_sub; + int rc, sub; + char *sql_delete_sub = "DELETE FROM files WHERE " + "(path > (? || '/') AND path < (? || '0'));"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!delete_sub) { + rc = sqlite3_prepare_v2(file_db, sql_delete_sub, strlen(sql_delete_sub), &delete_sub, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + if (!msg) { + return 1; + } + if (msg->pathlen) { + sqlite3_bind_text(delete_sub, 1, msg->path, -1, SQLITE_STATIC); + sqlite3_bind_text(delete_sub, 2, msg->path, -1, SQLITE_STATIC); + } else { + pseudo_debug(PDBGF_DB, "cannot unlink a file without a path."); + return 1; + } + file_db_dirty = 1; + rc = sqlite3_step(delete_sub); + if (rc != SQLITE_DONE) { + dberr(file_db, "delete sub by path may have failed"); + } + sub = sqlite3_changes(file_db); + pseudo_debug(PDBGF_DB, "(sub %d) ", sub); + sqlite3_reset(delete_sub); + sqlite3_clear_bindings(delete_sub); + return rc != SQLITE_DONE; +} + +/* rename a file. + * If there are any other files with paths that are rooted in "file", then + * file must really be a directory, and they should be renamed. + * + * This is tricky: + * You have to rename everything starting with "path/", but also "path" itself + * with no slash. Luckily for us, SQL can replace the old path with the + * new path. + */ +int +pdb_rename_file(const char *oldpath, pseudo_msg_t *msg) { + static sqlite3_stmt *update_exact, *update_sub; + int rc; + char *sql_update_exact = "UPDATE files SET path = ? WHERE path = ?;"; + char *sql_update_sub = "UPDATE files SET path = replace(path, ?, ?) " + "WHERE (path > (? || '/') AND path < (? || '0'));"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!update_exact) { + rc = sqlite3_prepare_v2(file_db, sql_update_exact, strlen(sql_update_exact), &update_exact, NULL); + if (rc) { + dberr(file_db, "couldn't prepare UPDATE statement"); + return 1; + } + } + if (!update_sub) { + rc = sqlite3_prepare_v2(file_db, sql_update_sub, strlen(sql_update_sub), &update_sub, NULL); + if (rc) { + dberr(file_db, "couldn't prepare UPDATE statement"); + return 1; + } + } + if (!msg) { + return 1; + } + if (!msg->pathlen) { + pseudo_debug(PDBGF_DB, "rename: No path provided (ino %llu)\n", (unsigned long long) msg->ino); + return 1; + } + if (!oldpath) { + pseudo_debug(PDBGF_DB, "rename: No old path for %s\n", msg->path); + return 1; + } + pseudo_debug(PDBGF_DB, "rename: Changing %s to %s\n", oldpath, msg->path); + rc = sqlite3_bind_text(update_exact, 1, msg->path, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(update_exact, 2, oldpath, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(update_sub, 1, oldpath, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(update_sub, 2, msg->path, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(update_sub, 3, oldpath, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(update_sub, 4, oldpath, -1, SQLITE_STATIC); + + rc = sqlite3_exec(file_db, "BEGIN;", NULL, NULL, NULL); + + file_db_dirty = 1; + rc = sqlite3_step(update_exact); + if (rc != SQLITE_DONE) { + dberr(file_db, "update exact may have failed: rc %d", rc); + } + rc = sqlite3_step(update_sub); + if (rc != SQLITE_DONE) { + dberr(file_db, "update sub may have failed: rc %d", rc); + } + sqlite3_reset(update_exact); + sqlite3_reset(update_sub); + + rc = sqlite3_exec(file_db, "END;", NULL, NULL, NULL); + + sqlite3_clear_bindings(update_exact); + sqlite3_clear_bindings(update_sub); + return rc != SQLITE_DONE; +} + +/* renumber device only. + * this is used if the filesystem moves to a new device, without changing + * inode allocations. + */ +int +pdb_renumber_all(dev_t from, dev_t to) { + static sqlite3_stmt *update; + int rc; + char *sql = "UPDATE files " + " SET dev = ? " + " WHERE dev = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!update) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL); + if (rc) { + dberr(file_db, "couldn't prepare UPDATE statement"); + return 1; + } + } + rc = sqlite3_bind_int(update, 1, to); + if (rc) { + dberr(file_db, "error binding device numbers to update"); + } + rc = sqlite3_bind_int(update, 2, from); + if (rc) { + dberr(file_db, "error binding device numbers to update"); + } + + file_db_dirty = 1; + rc = sqlite3_step(update); + if (rc != SQLITE_DONE) { + dberr(file_db, "update may have failed: rc %d", rc); + } + sqlite3_reset(update); + sqlite3_clear_bindings(update); + pseudo_debug(PDBGF_DB, "updating device dev %llu to %llu\n", + (unsigned long long) from, (unsigned long long) to); + return rc != SQLITE_DONE; +} + +/* change dev/inode for a given path -- used only by RENAME for now. + */ +int +pdb_update_inode(pseudo_msg_t *msg) { + static sqlite3_stmt *update; + int rc; + char *sql = "UPDATE files " + " SET dev = ?, ino = ? " + " WHERE path = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!update) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL); + if (rc) { + dberr(file_db, "couldn't prepare UPDATE statement"); + return 1; + } + } + if (!msg) { + return 1; + } + if (!msg->pathlen) { + pseudo_diag("Can't update the inode of a file without its path.\n"); + return 1; + } + sqlite3_bind_int(update, 1, msg->dev); + sqlite3_bind_int64(update, 2, msg->ino); + rc = sqlite3_bind_text(update, 3, msg->path, -1, SQLITE_STATIC); + if (rc) { + /* msg->path can never be null, and if msg didn't + * have a non-zero pathlen, we'd already have exited + * above + */ + dberr(file_db, "error binding %s to select", msg->path); + } + + file_db_dirty = 1; + rc = sqlite3_step(update); + if (rc != SQLITE_DONE) { + dberr(file_db, "update may have failed: rc %d", rc); + } + sqlite3_reset(update); + sqlite3_clear_bindings(update); + pseudo_debug(PDBGF_DB, "updating path %s to dev %llu, ino %llu\n", + msg->path, + (unsigned long long) msg->dev, (unsigned long long) msg->ino); + return rc != SQLITE_DONE; +} + +/* change uid/gid/mode/rdev in any existing entries matching a given + * dev/inode pair. + */ +int +pdb_update_file(pseudo_msg_t *msg) { + static sqlite3_stmt *update; + int rc; + char *sql = "UPDATE files " + " SET uid = ?, gid = ?, mode = ?, rdev = ? " + " WHERE dev = ? AND ino = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!update) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL); + if (rc) { + dberr(file_db, "couldn't prepare UPDATE statement"); + return 1; + } + } + if (!msg) { + return 1; + } + sqlite3_bind_int(update, 1, msg->uid); + sqlite3_bind_int(update, 2, msg->gid); + sqlite3_bind_int(update, 3, msg->mode); + sqlite3_bind_int(update, 4, msg->rdev); + sqlite3_bind_int(update, 5, msg->dev); + sqlite3_bind_int64(update, 6, msg->ino); + + file_db_dirty = 1; + rc = sqlite3_step(update); + if (rc != SQLITE_DONE) { + dberr(file_db, "update may have failed: rc %d", rc); + } + sqlite3_reset(update); + sqlite3_clear_bindings(update); + pseudo_debug(PDBGF_DB, "updating dev %llu, ino %llu, new mode %o, owner %d\n", + (unsigned long long) msg->dev, (unsigned long long) msg->ino, + (int) msg->mode, msg->uid); + return rc != SQLITE_DONE; +} + +/* find file using both path AND dev/inode as key */ +int +pdb_find_file_exact(pseudo_msg_t *msg, long long *row) { + static sqlite3_stmt *select; + int rc; + char *sql = "SELECT * FROM files WHERE path = ? AND dev = ? AND ino = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!select) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL); + if (rc) { + dberr(file_db, "couldn't prepare SELECT statement"); + return 1; + } + } + if (!msg) { + return 1; + } + rc = sqlite3_bind_text(select, 1, msg->path, -1, SQLITE_STATIC); + if (rc) { + dberr(file_db, "error binding %s to select", msg->pathlen ? msg->path : "<nil>"); + } + sqlite3_bind_int(select, 2, msg->dev); + sqlite3_bind_int64(select, 3, msg->ino); + rc = sqlite3_step(select); + switch (rc) { + case SQLITE_ROW: + if (row) { + *row = sqlite3_column_int64(select, 0); + } + msg->uid = (unsigned long) sqlite3_column_int64(select, 4); + msg->gid = (unsigned long) sqlite3_column_int64(select, 5); + msg->mode = (unsigned long) sqlite3_column_int64(select, 6); + msg->rdev = (unsigned long) sqlite3_column_int64(select, 7); + msg->deleting = (int) sqlite3_column_int64(select, 8); + rc = 0; + break; + case SQLITE_DONE: + pseudo_debug(PDBGF_DB, "find_exact: sqlite_done on first row\n"); + rc = 1; + break; + default: + dberr(file_db, "find_exact: select returned neither a row nor done"); + rc = 1; + break; + } + sqlite3_reset(select); + sqlite3_clear_bindings(select); + return rc; +} + +/* find file using path as a key */ +int +pdb_find_file_path(pseudo_msg_t *msg, long long *row) { + static sqlite3_stmt *select; + int rc; + char *sql = "SELECT * FROM files WHERE path = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 1; + } + if (!select) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL); + if (rc) { + dberr(file_db, "couldn't prepare SELECT statement"); + return 1; + } + } + if (!msg) { + return 1; + } + if (!msg->pathlen) { + return 1; + } + rc = sqlite3_bind_text(select, 1, msg->path, -1, SQLITE_STATIC); + if (rc) { + dberr(file_db, "error binding %s to select", msg->pathlen ? msg->path : "<nil>"); + } + + rc = sqlite3_column_count(select); + rc = sqlite3_step(select); + switch (rc) { + case SQLITE_ROW: + if (row) { + *row = sqlite3_column_int64(select, 0); + } + msg->dev = sqlite3_column_int64(select, 2); + msg->ino = sqlite3_column_int64(select, 3); + msg->uid = sqlite3_column_int64(select, 4); + msg->gid = sqlite3_column_int64(select, 5); + msg->mode = sqlite3_column_int64(select, 6); + msg->rdev = sqlite3_column_int64(select, 7); + msg->deleting = (int) sqlite3_column_int64(select, 8); + rc = 0; + break; + case SQLITE_DONE: + pseudo_debug(PDBGF_DB, "find_path: sqlite_done on first row\n"); + rc = 1; + break; + default: + dberr(file_db, "find_path: select returned neither a row nor done"); + rc = 1; + break; + } + sqlite3_reset(select); + sqlite3_clear_bindings(select); + return rc; +} + +/* find path for a file, given dev and inode as keys */ +char * +pdb_get_file_path(pseudo_msg_t *msg) { + static sqlite3_stmt *select; + int rc; + char *sql = "SELECT path FROM files WHERE dev = ? AND ino = ?;"; + char *response; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!select) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL); + if (rc) { + dberr(file_db, "couldn't prepare SELECT statement"); + return 0; + } + } + if (!msg) { + return 0; + } + sqlite3_bind_int(select, 1, msg->dev); + sqlite3_bind_int64(select, 2, msg->ino); + rc = sqlite3_step(select); + switch (rc) { + case SQLITE_ROW: + response = (char *) sqlite3_column_text(select, 0); + if (response) { + if (strcmp(response, "NAMELESS FILE")) { + response = strdup(response); + } else { + response = 0; + } + } + break; + case SQLITE_DONE: + pseudo_debug(PDBGF_DB, "find_dev: sqlite_done on first row\n"); + response = 0; + break; + default: + dberr(file_db, "find_dev: select returned neither a row nor done"); + response = 0; + break; + } + sqlite3_reset(select); + sqlite3_clear_bindings(select); + return response; +} + +/* find file using dev/inode as key */ +int +pdb_find_file_dev(pseudo_msg_t *msg, long long *row, char **path) { + static sqlite3_stmt *select; + int rc; + char *sql = "SELECT * FROM files WHERE dev = ? AND ino = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!select) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL); + if (rc) { + dberr(file_db, "couldn't prepare SELECT statement"); + return 1; + } + } + if (!msg) { + return 1; + } + sqlite3_bind_int(select, 1, msg->dev); + sqlite3_bind_int64(select, 2, msg->ino); + rc = sqlite3_step(select); + switch (rc) { + case SQLITE_ROW: + if (row) { + *row = sqlite3_column_int64(select, 0); + } + msg->uid = (unsigned long) sqlite3_column_int64(select, 4); + msg->gid = (unsigned long) sqlite3_column_int64(select, 5); + msg->mode = (unsigned long) sqlite3_column_int64(select, 6); + msg->rdev = (unsigned long) sqlite3_column_int64(select, 7); + msg->deleting = (int) sqlite3_column_int64(select, 8); + /* stash path */ + if (path) { + *path = strdup((char *) sqlite3_column_text(select, 1)); + pseudo_debug(PDBGF_FILE, "find_file_dev: path %s\n", + *path ? *path : "<nil>"); + } + rc = 0; + break; + case SQLITE_DONE: + pseudo_debug(PDBGF_DB, "find_dev: sqlite_done on first row\n"); + rc = 1; + break; + default: + dberr(file_db, "find_dev: select returned neither a row nor done"); + rc = 1; + break; + } + sqlite3_reset(select); + sqlite3_clear_bindings(select); + return rc; +} + +int +pdb_get_xattr(long long file_id, char **value, size_t *len) { + static sqlite3_stmt *select; + int rc; + char *response; + size_t length; + char *sql = "SELECT value FROM xattrs WHERE file_id = ? AND name = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!select) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL); + if (rc) { + dberr(file_db, "couldn't prepare SELECT statement"); + return 1; + } + } + pseudo_debug(PDBGF_XATTR, "requested xattr named '%s' for file %lld\n", *value, file_id); + sqlite3_bind_int(select, 1, file_id); + rc = sqlite3_bind_text(select, 2, *value, -1, SQLITE_STATIC); + if (rc) { + dberr(file_db, "couldn't bind xattr name to SELECT."); + return 1; + } + rc = sqlite3_step(select); + switch (rc) { + case SQLITE_ROW: + response = (char *) sqlite3_column_text(select, 0); + length = sqlite3_column_bytes(select, 0); + pseudo_debug(PDBGF_XATTR, "got %d-byte results: '%s'\n", + (int) length, response); + if (response && length >= 1) { + /* not a strdup because the values can contain + * arbitrary bytes. + */ + *value = malloc(length); + memcpy(*value, response, length); + *len = length; + rc = 0; + } else { + *value = NULL; + *len = 0; + rc = 1; + } + break; + case SQLITE_DONE: + pseudo_debug(PDBGF_DB, "find_exact: sqlite_done on first row\n"); + rc = 1; + break; + default: + dberr(file_db, "find_exact: select returned neither a row nor done"); + rc = 1; + break; + } + sqlite3_reset(select); + sqlite3_clear_bindings(select); + return rc; +} + +int +pdb_list_xattr(long long file_id, char **value, size_t *len) { + static sqlite3_stmt *select; + size_t allocated = 0; + size_t used = 0; + char *buffer = 0; + int rc; + char *sql = "SELECT name FROM xattrs WHERE file_id = ?;"; + + /* if we don't have a record of the file, it must not have + * any extended attributes... + */ + if (file_id == -1) { + *value = NULL; + *len = 0; + return 0; + } + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!select) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL); + if (rc) { + dberr(file_db, "couldn't prepare SELECT statement"); + return 1; + } + } + sqlite3_bind_int(select, 1, file_id); + do { + rc = sqlite3_step(select); + if (rc == SQLITE_ROW) { + char *value = (char *) sqlite3_column_text(select, 0); + size_t len = sqlite3_column_bytes(select, 0); + if (!buffer) { + allocated = round_up(len, 256); + buffer = malloc(allocated); + } + if (used + len + 2 > allocated) { + size_t new_allocated = round_up(used + len + 2, 256); + char *new_buffer = malloc(new_allocated); + memcpy(new_buffer, buffer, used); + free(buffer); + allocated = new_allocated; + buffer = new_buffer; + } + memcpy(buffer + used, value, len); + buffer[used + len] = '\0'; + used = used + len + 1; + } else if (rc == SQLITE_DONE) { + *value = buffer; + *len = used; + } else { + dberr(file_db, "non-row response from select?"); + free(buffer); + *value = NULL; + *len = 0; + } + } while (rc == SQLITE_ROW); + sqlite3_reset(select); + sqlite3_clear_bindings(select); + return rc != SQLITE_DONE; +} + +int +pdb_remove_xattr(long long file_id, char *value, size_t len) { + static sqlite3_stmt *delete; + int rc; + char *sql = "DELETE FROM xattrs WHERE file_id = ? AND name = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!delete) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &delete, NULL); + if (rc) { + dberr(file_db, "couldn't prepare DELETE statement"); + return 1; + } + } + sqlite3_bind_int(delete, 1, file_id); + rc = sqlite3_bind_text(delete, 2, value, len, SQLITE_STATIC); + if (rc) { + dberr(file_db, "couldn't bind xattr name to DELETE."); + return 1; + } + file_db_dirty = 1; + rc = sqlite3_step(delete); + if (rc != SQLITE_DONE) { + dberr(file_db, "delete xattr may have failed"); + } + sqlite3_reset(delete); + sqlite3_clear_bindings(delete); + return rc != SQLITE_DONE; +} + +int +pdb_set_xattr(long long file_id, char *value, size_t len, int flags) { + static sqlite3_stmt *select, *update, *insert; + int rc; + long long existing_row = -1; + char *select_sql = "SELECT id FROM xattrs WHERE file_id = ? AND name = ?;"; + char *insert_sql = "INSERT INTO xattrs " + " ( file_id, name, value ) " + " VALUES (?, ?, ?);"; + char *update_sql = "UPDATE xattrs SET value = ? WHERE id = ?;"; + char *vname = value; + size_t vlen; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!select) { + rc = sqlite3_prepare_v2(file_db, select_sql, strlen(select_sql), &select, NULL); + if (rc) { + dberr(file_db, "couldn't prepare SELECT statement"); + return 1; + } + } + sqlite3_bind_int(select, 1, file_id); + rc = sqlite3_bind_text(select, 2, value, -1, SQLITE_STATIC); + if (rc) { + dberr(file_db, "couldn't bind xattr name to SELECT."); + return 1; + } + rc = sqlite3_step(select); + switch (rc) { + case SQLITE_ROW: + existing_row = sqlite3_column_int64(select, 0); + break; + case SQLITE_DONE: + pseudo_debug(PDBGF_DB | PDBGF_VERBOSE, "find_exact: sqlite_done on first row\n"); + existing_row = -1; + break; + default: + dberr(file_db, "set_xattr: select returned neither a row nor done"); + rc = 1; + break; + } + sqlite3_reset(select); + sqlite3_clear_bindings(select); + if (flags == XATTR_CREATE && existing_row != -1) { + pseudo_debug(PDBGF_DB, "XATTR_CREATE with an existing row, failing."); + return 1; + } + if (flags == XATTR_REPLACE && existing_row == -1) { + pseudo_debug(PDBGF_DB, "XATTR_REPLACE with no existing row, failing."); + return 1; + } + /* the material after the name is the value */ + vlen = strlen(value); + len = len - (vlen + 1); + value = value + vlen + 1; + pseudo_debug(PDBGF_XATTR, "trying to set a value for %lld: name is '%s' [%d/%d bytes], value is '%s' [%d bytes]. Existing row %lld.\n", + file_id, vname, (int) vlen, (int) (len + vlen + 1), value, (int) len, existing_row); + if (existing_row != -1) { + /* update */ + if (!update) { + rc = sqlite3_prepare_v2(file_db, update_sql, strlen(update_sql), &update, NULL); + if (rc) { + dberr(file_db, "couldn't prepare UPDATE statement"); + return 1; + } + } + rc = sqlite3_bind_text(update, 1, value, len, SQLITE_STATIC); + if (rc) { + dberr(file_db, "couldn't bind xattr value to UPDATE."); + return 1; + } + sqlite3_bind_int(update, 2, existing_row); + file_db_dirty = 1; + rc = sqlite3_step(update); + if (rc != SQLITE_DONE) { + dberr(file_db, "update xattr may have failed"); + } + sqlite3_reset(update); + sqlite3_clear_bindings(update); + return rc != SQLITE_DONE; + } else { + /* insert */ + if (!insert) { + rc = sqlite3_prepare_v2(file_db, insert_sql, strlen(insert_sql), &insert, NULL); + if (rc) { + dberr(file_db, "couldn't prepare INSERT statement"); + return 1; + } + } + pseudo_debug(PDBGF_XATTR, "insert should be getting ID %lld\n", file_id); + sqlite3_bind_int64(insert, 1, file_id); + rc = sqlite3_bind_text(insert, 2, vname, -1, SQLITE_STATIC); + if (rc) { + dberr(file_db, "couldn't bind xattr name to INSERT statement"); + return 1; + } + rc = sqlite3_bind_text(insert, 3, value, len, SQLITE_STATIC); + if (rc) { + dberr(file_db, "couldn't bind xattr value to INSERT statement"); + return 1; + } + file_db_dirty = 1; + rc = sqlite3_step(insert); + if (rc != SQLITE_DONE) { + dberr(file_db, "insert xattr may have failed"); + } + sqlite3_reset(insert); + sqlite3_clear_bindings(insert); + return rc != SQLITE_DONE; + } + return rc; +} + +/* find file using only inode as key. Unused for now, planned to come + * in for NFS usage. + */ +int +pdb_find_file_ino(pseudo_msg_t *msg, long long *row) { + static sqlite3_stmt *select; + int rc; + char *sql = "SELECT * FROM files WHERE ino = ?;"; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + if (!select) { + rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL); + if (rc) { + dberr(file_db, "couldn't prepare SELECT statement"); + return 1; + } + } + if (!msg) { + return 1; + } + sqlite3_bind_int64(select, 1, msg->ino); + rc = sqlite3_step(select); + switch (rc) { + case SQLITE_ROW: + if (row) { + *row = sqlite3_column_int64(select, 0); + } + msg->dev = (unsigned long) sqlite3_column_int64(select, 2); + msg->uid = (unsigned long) sqlite3_column_int64(select, 4); + msg->gid = (unsigned long) sqlite3_column_int64(select, 5); + msg->mode = (unsigned long) sqlite3_column_int64(select, 6); + msg->rdev = (unsigned long) sqlite3_column_int64(select, 7); + msg->deleting = (int) sqlite3_column_int64(select, 8); + rc = 0; + break; + case SQLITE_DONE: + pseudo_debug(PDBGF_DB, "find_ino: sqlite_done on first row\n"); + rc = 1; + break; + default: + dberr(file_db, "find_ino: select returned neither a row nor done"); + rc = 1; + break; + } + sqlite3_reset(select); + sqlite3_clear_bindings(select); + return rc; +} + +pdb_file_list +pdb_files(void) { + pdb_file_list l; + + if (!file_db && get_dbs()) { + pseudo_diag("%s: database error.\n", __func__); + return 0; + } + + l = malloc(sizeof(*l)); + if (!l) + return NULL; + + l->rc = sqlite3_prepare_v2(file_db, "SELECT path, dev, ino, uid, gid, mode, rdev FROM files", -1, &l->stmt, NULL); + if (l->rc) { + dberr(file_db, "Couldn't start SELECT from files.\n"); + free(l); + return NULL; + } + return l; +} + +pseudo_msg_t * +pdb_file(pdb_file_list l) { + const unsigned char *s; + pseudo_msg_t *m; + int column = 0; + + if (!l || !l->stmt) + return 0; + /* in case someone tries again after we're already done */ + if (l->rc == SQLITE_DONE) { + return 0; + } + l->rc = sqlite3_step(l->stmt); + if (l->rc == SQLITE_DONE) { + return 0; + } else if (l->rc != SQLITE_ROW) { + dberr(log_db, "statement failed"); + return 0; + } + s = sqlite3_column_text(l->stmt, column++); + m = pseudo_msg_new(0, (const char *) s); + if (!m) { + pseudo_diag("couldn't allocate file message.\n"); + return NULL; + } + pseudo_debug(PDBGF_DB, "pdb_file: '%s'\n", s ? (const char *) s : "<nil>"); + m->dev = sqlite3_column_int64(l->stmt, column++); + m->ino = sqlite3_column_int64(l->stmt, column++); + m->uid = sqlite3_column_int64(l->stmt, column++); + m->gid = sqlite3_column_int64(l->stmt, column++); + m->mode = sqlite3_column_int64(l->stmt, column++); + m->rdev = sqlite3_column_int64(l->stmt, column++); + return m; +} + +void +pdb_files_done(pdb_file_list l) { + if (!l) + return; + if (l->stmt) { + sqlite3_reset(l->stmt); + sqlite3_finalize(l->stmt); + } + free(l); +} diff --git a/pseudo_db.h b/pseudo_db.h new file mode 100644 index 0000000..07c547b --- /dev/null +++ b/pseudo_db.h @@ -0,0 +1,94 @@ +/* + * pseudo_db.h, declarations and definitions for database use + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +typedef struct { + time_t stamp; + pseudo_msg_type_t type; + pseudo_op_t op; + int access; + unsigned long client; + unsigned long fd; + unsigned long long dev; + unsigned long long ino; + unsigned long mode; + unsigned long gid; + unsigned long uid; + char *path; + pseudo_res_t result; + pseudo_sev_t severity; + char *text; + char *tag; + char *program; +} log_entry; + +extern int pdb_maybe_backup(void); +extern int pdb_cancel_unlink_file(pseudo_msg_t *msg); +extern int pdb_did_unlink_file(char *path, int deleting); +extern int pdb_did_unlink_files(int deleting); +extern int pdb_link_file(pseudo_msg_t *msg, long long *row); +extern int pdb_may_unlink_file(pseudo_msg_t *msg, int deleting); +extern int pdb_unlink_file(pseudo_msg_t *msg); +extern int pdb_unlink_file_dev(pseudo_msg_t *msg); +extern int pdb_update_file(pseudo_msg_t *msg); +extern int pdb_update_file_path(pseudo_msg_t *msg); +extern int pdb_update_inode(pseudo_msg_t *msg); +extern int pdb_unlink_contents(pseudo_msg_t *msg); +extern int pdb_rename_file(const char *oldpath, pseudo_msg_t *msg); +extern int pdb_renumber_all(dev_t from, dev_t to); +extern int pdb_find_file_exact(pseudo_msg_t *msg, long long *row); +extern int pdb_find_file_path(pseudo_msg_t *msg, long long *row); +extern int pdb_find_file_dev(pseudo_msg_t *msg, long long *row, char **path); +extern int pdb_find_file_ino(pseudo_msg_t *msg, long long *row); +extern char *pdb_get_file_path(pseudo_msg_t *msg); +extern int pdb_get_xattr(long long file_id, char **value, size_t *len); +extern int pdb_list_xattr(long long file_id, char **value, size_t *len); +extern int pdb_remove_xattr(long long file_id, char *value, size_t len); +extern int pdb_set_xattr(long long file_id, char *value, size_t len, int flags); + +struct log_history; +typedef struct log_history *log_history; + +union pseudo_query_data { + unsigned long long ivalue; + char *svalue; +}; + +typedef struct pseudo_query { + pseudo_query_type_t type; + pseudo_query_field_t field; + union pseudo_query_data data; + struct pseudo_query *next; +} pseudo_query_t; + +extern int pdb_log_entry(log_entry *e); +extern int pdb_log_msg(pseudo_sev_t severity, pseudo_msg_t *msg, const char *program, const char *tag, const char *text, ...); +extern int pdb_log_traits(pseudo_query_t *traits); + +struct pdb_file_list; +typedef struct pdb_file_list *pdb_file_list; + +extern pdb_file_list pdb_files(void); +extern pseudo_msg_t *pdb_file(pdb_file_list); +extern void pdb_files_done(pdb_file_list); + +extern int pdb_delete(pseudo_query_t *traits, unsigned long fields); +extern log_history pdb_history(pseudo_query_t *traits, unsigned long fields, int unique); +extern log_entry *pdb_history_entry(log_history h); +extern void pdb_history_free(log_history h); +extern void log_entry_free(log_entry *); diff --git a/pseudo_ipc.c b/pseudo_ipc.c new file mode 100644 index 0000000..82c6f70 --- /dev/null +++ b/pseudo_ipc.c @@ -0,0 +1,243 @@ +/* + * pseudo_ipc.c, IPC code for pseudo client/server + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <sys/stat.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" + +/* Short reads or writes can cause a sigpipe, killing the program, so we + * trap it and report that something happened. + */ +static sig_atomic_t pipe_error = 0; +static void (*old_handler)(int) = SIG_DFL; + +static void +sigpipe_trap(int unused __attribute__((unused))) { + pipe_error = 1; +} + +static void +ignore_sigpipe(void) { + pipe_error = 0; + old_handler = signal(SIGPIPE, sigpipe_trap); +} + +static void +allow_sigpipe(void) { + signal(SIGPIPE, old_handler); +} + +#if 0 +/* useful only when debugging crazy stuff */ +static void +display_msg_header(pseudo_msg_t *msg) { + pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "type: %d\n", msg->type); + pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "inode: %llu\n", (unsigned long long) msg->ino); + pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "uid: %d\n", msg->uid); + pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "pathlen: %d\n", (int) msg->pathlen); + if (msg->pathlen) { + pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "path: %s\n", msg->path); + } +} +#endif + +/* + * send message on fd + * return: + * 0 on success + * >0 on error + * <0 on error suggesting other end is dead + */ +int +pseudo_msg_send(int fd, pseudo_msg_t *msg, size_t len, const char *path) { + int r; + + if (!msg) + return 1; + + if (fd < 0) + return -1; + + if (path) { + pseudo_debug(PDBGF_IPC, "msg type %d (%s), external path %s, mode 0%o\n", + msg->type, pseudo_op_name(msg->op), path, (int) msg->mode); + if (len == (size_t) -1) + len = strlen(path) + 1; + msg->pathlen = len; + ignore_sigpipe(); + r = write(fd, msg, PSEUDO_HEADER_SIZE); + if (r == PSEUDO_HEADER_SIZE) { + r += write(fd, path, len); + } + allow_sigpipe(); + pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "wrote %d bytes\n", r); + if (pipe_error || (r == -1 && errno == EBADF)) + return -1; + return ((size_t) r != PSEUDO_HEADER_SIZE + len); + } else { + pseudo_debug(PDBGF_IPC, "msg type %d (%s), result %d (%s), path %.*s, mode 0%o\n", + msg->type, pseudo_op_name(msg->op), + msg->result, pseudo_res_name(msg->result), + msg->pathlen, msg->path, (int) msg->mode); + // display_msg_header(msg); + ignore_sigpipe(); + r = write(fd, msg, PSEUDO_HEADER_SIZE + msg->pathlen); + allow_sigpipe(); + pseudo_debug(PDBGF_IPC | PDBGF_VERBOSE, "wrote %d bytes\n", r); + if (pipe_error || (r == -1 && errno == EBADF)) + return -1; + return ((size_t) r != PSEUDO_HEADER_SIZE + msg->pathlen); + } +} + +/* attempts to receive a message from fd + * return is allocated message if one is provided + */ +pseudo_msg_t * +pseudo_msg_receive(int fd) { + static pseudo_msg_t *incoming; + static size_t incoming_pathlen; + pseudo_msg_t *newmsg, header; + int r; + + if (fd < 0) + return 0; + errno = 0; + r = read(fd, &header, PSEUDO_HEADER_SIZE); + if (r == -1) { + pseudo_debug(PDBGF_IPC, "read failed: %s\n", strerror(errno)); + return 0; + } + if (r < (int) PSEUDO_HEADER_SIZE) { + pseudo_debug(PDBGF_IPC, "got only %d bytes (%s)\n", r, strerror(errno)); + return 0; + } + pseudo_debug(PDBGF_IPC, "got header, type %d, pathlen %d\n", header.type, (int) header.pathlen); + // display_msg_header(&header); + if (!incoming || header.pathlen >= incoming_pathlen) { + newmsg = pseudo_msg_new(header.pathlen + 128, 0); + if (!newmsg) { + pseudo_diag("Couldn't allocate header for path of %d bytes.\n", + (int) header.pathlen); + return 0; + } + free(incoming); + incoming = newmsg; + incoming_pathlen = header.pathlen + 128; + } + *incoming = header; + if (incoming->pathlen) { + r = read(fd, incoming->path, incoming->pathlen); + if (r < (int) incoming->pathlen) { + pseudo_debug(PDBGF_IPC, "short read on path, expecting %d, got %d\n", + (int) incoming->pathlen, r); + return 0; + } + /* ensure null termination */ + incoming->path[r] = '\0'; + } + // display_msg_header(incoming); + return incoming; +} + +/* duplicate a message -- currently totally unused */ +pseudo_msg_t * +pseudo_msg_dup(pseudo_msg_t *old) { + pseudo_msg_t *newmsg; + if (!old) + return NULL; + newmsg = malloc(sizeof(pseudo_msg_t) + old->pathlen); + if (!newmsg) + return NULL; + memcpy(newmsg, old, sizeof(pseudo_msg_t) + old->pathlen); + return newmsg; +} + +/* allocate a message either with pathlen chars of storage or with enough + * storage for path + */ +pseudo_msg_t * +pseudo_msg_new(size_t pathlen, const char *path) { + pseudo_msg_t *newmsg; + if (pathlen) { + newmsg = malloc(sizeof(pseudo_msg_t) + pathlen); + if (newmsg) { + newmsg->pathlen = pathlen; + if (path) + memcpy(newmsg->path, path, pathlen); + newmsg->path[pathlen - 1] = '\0'; + } + return newmsg; + } else { + if (!path) { + /* no pathlen, no path == purely informational */ + newmsg = malloc(sizeof(pseudo_msg_t)); + if (newmsg) { + newmsg->pathlen = 0; + } + return newmsg; + } else { + pathlen = strlen(path) + 1; + newmsg = malloc(sizeof(pseudo_msg_t) + pathlen); + if (newmsg) { + memcpy(newmsg->path, path, pathlen); + newmsg->pathlen = pathlen; + } + return newmsg; + } + } +} + +/* The following functions populate messages from statbufs and vice versa. + * It is intentional that copying a message into a stat doesn't touch nlink; + * the nlink value was not stored in the database (it is in the message at + * all only so the server can do sanity checks). + */ + +void +pseudo_msg_stat(pseudo_msg_t *msg, const PSEUDO_STATBUF *buf) { + if (!msg || !buf) + return; + msg->uid = buf->st_uid; + msg->gid = buf->st_gid; + msg->dev = buf->st_dev; + msg->ino = buf->st_ino; + msg->mode = buf->st_mode; + msg->rdev = buf->st_rdev; + msg->nlink = buf->st_nlink; +} + +void +pseudo_stat_msg(PSEUDO_STATBUF *buf, const pseudo_msg_t *msg) { + if (!msg || !buf) + return; + buf->st_uid = msg->uid; + buf->st_gid = msg->gid; + buf->st_mode = msg->mode; + buf->st_rdev = msg->rdev; +} diff --git a/pseudo_ipc.h b/pseudo_ipc.h new file mode 100644 index 0000000..46277bc --- /dev/null +++ b/pseudo_ipc.h @@ -0,0 +1,68 @@ +/* + * pseudo_ipc.h, definitions and declarations for pseudo IPC code + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* The [] item at the end of the struct is a C99 feature, replacing the + * old (and unportable) "struct hack". + */ +typedef struct { + pseudo_msg_type_t type; + pseudo_op_t op; + pseudo_res_t result; + int access; + int client; + int fd; + dev_t dev; + unsigned long long ino; + uid_t uid; + gid_t gid; + unsigned long long mode; + dev_t rdev; + unsigned int pathlen; + int nlink; + int deleting; + char path[]; +} pseudo_msg_t; + +enum { + PSA_EXEC = 1, + PSA_WRITE = (PSA_EXEC << 1), + PSA_READ = (PSA_WRITE << 1), + PSA_APPEND = (PSA_READ << 1), +} pseudo_access_t; + +#define PSEUDO_ACCESS_MAP(mode, fcntl_access, pseudo_access) ((((mode) & O_ACCMODE) == (fcntl_access)) ? (pseudo_access) : (0)) +#define PSEUDO_ACCESS_FLAG(mode, fcntl_access, pseudo_access) (((mode) & (fcntl_access)) ? (pseudo_access) : (0)) +#define PSEUDO_ACCESS(mode) ( \ + PSEUDO_ACCESS_MAP(mode, O_RDONLY, PSA_READ) | \ + PSEUDO_ACCESS_MAP(mode, O_WRONLY, PSA_WRITE) | \ + PSEUDO_ACCESS_MAP(mode, O_RDWR, PSA_READ | PSA_WRITE) | \ + PSEUDO_ACCESS_FLAG(mode, O_APPEND, PSA_APPEND)) +extern int pseudo_access_fopen(const char *); + +#define PSEUDO_HEADER_SIZE (offsetof(pseudo_msg_t, path)) + +extern pseudo_msg_t *pseudo_msg_receive(int fd); +extern pseudo_msg_t *pseudo_msg_dup(pseudo_msg_t *); +extern pseudo_msg_t *pseudo_msg_dupheader(pseudo_msg_t *); +extern pseudo_msg_t *pseudo_msg_new(size_t, const char *); +extern int pseudo_msg_send(int fd, pseudo_msg_t *, size_t, const char *); + +void pseudo_msg_stat(pseudo_msg_t *msg, const PSEUDO_STATBUF *buf); +void pseudo_stat_msg(PSEUDO_STATBUF *buf, const pseudo_msg_t *msg); diff --git a/pseudo_profile.c b/pseudo_profile.c new file mode 100644 index 0000000..c4af803 --- /dev/null +++ b/pseudo_profile.c @@ -0,0 +1,64 @@ +#define _GNU_SOURCE + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "pseudo.h" + +int +main(int argc, char **argv) { + int fd; + pseudo_profile_t totals = { .total_ops = 0 }, item; + int count = 0; + + if (argc < 2) { + fprintf(stderr, "usage: pseudo_profile <profiling_data>\n"); + exit(1); + } + + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Can't open '%s': %s.\n", + argv[1], strerror(errno)); + exit(1); + } + while (read(fd, &item, sizeof(item)) == sizeof(item)) { + if (item.total_ops > 0) { + ++count; + totals.processes += item.processes; + totals.total_ops += item.total_ops; + totals.messages += item.messages; + totals.op_time.tv_sec += item.op_time.tv_sec; + totals.op_time.tv_usec += item.op_time.tv_usec; + if (totals.op_time.tv_usec >= 1000000) { + ++totals.op_time.tv_sec; + totals.op_time.tv_usec -= 1000000; + } + totals.ipc_time.tv_sec += item.ipc_time.tv_sec; + totals.ipc_time.tv_usec += item.ipc_time.tv_usec; + if (totals.ipc_time.tv_usec >= 1000000) { + ++totals.ipc_time.tv_sec; + totals.ipc_time.tv_usec -= 1000000; + } + totals.wrapper_time.tv_sec += item.wrapper_time.tv_sec; + totals.wrapper_time.tv_usec += item.wrapper_time.tv_usec; + if (totals.wrapper_time.tv_usec >= 1000000) { + ++totals.wrapper_time.tv_sec; + totals.wrapper_time.tv_usec -= 1000000; + } + } + } + printf("Found data for %d PIDs, %d processes.\n", + count, totals.processes); + printf("%lld messages for %lld ops.\n", totals.messages, totals.total_ops); + double otime = totals.op_time.tv_sec + (totals.op_time.tv_usec / 1000000.0); + double itime = totals.ipc_time.tv_sec + (totals.ipc_time.tv_usec / 1000000.0); + double wtime = totals.wrapper_time.tv_sec + (totals.wrapper_time.tv_usec / 1000000.0); + printf("%.4f msec wrapper time, %.4f msec op time, %.4f msec IPC time.\n", + wtime * 1000, otime * 1000, itime * 1000); + + return 0; +} diff --git a/pseudo_server.c b/pseudo_server.c new file mode 100644 index 0000000..1fdadcb --- /dev/null +++ b/pseudo_server.c @@ -0,0 +1,529 @@ +/* + * pseudo_server.c, pseudo's server-side logic and message handling + * + * Copyright (c) 2008-2010, 2013 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <errno.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <signal.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_server.h" +#include "pseudo_client.h" +#include "pseudo_db.h" + +static int listen_fd = -1; + +typedef struct { + int fd; + pid_t pid; + char *tag; + char *program; +} pseudo_client_t; + +pseudo_client_t *clients; + +/* active_clients: Number of clients we actually have right now. + * highest_client: Highest index into clients table of an active client. + * max_clients: Size of table. + */ +static int active_clients = 0, highest_client = 0, max_clients = 0; + +#define LOOP_DELAY 2 +int pseudo_server_timeout = 30; +static int die_peacefully = 0; +static int die_forcefully = 0; + +/* when the client is linked with pseudo_wrappers, these are defined there. + * when it is linked with pseudo_server, though, we have to provide different + * versions (pseudo_wrappers must not be linked with the server, or Bad Things + * happen). + */ +void pseudo_magic(void) { } +void pseudo_antimagic(void) { } + +void +quit_now(int signal) { + pseudo_diag("Received signal %d, quitting.\n", signal); + die_forcefully = 1; +} + +static int messages = 0, responses = 0; +static struct timeval message_time = { .tv_sec = 0 }; + +static void pseudo_server_loop(void); + +static int +pseudo_server_write_pid(pid_t pid) { + char *pseudo_path; + FILE *fp; + + pseudo_path = pseudo_localstatedir_path(PSEUDO_PIDFILE); + if (!pseudo_path) { + pseudo_diag("Couldn't get path for prefix/%s\n", PSEUDO_PIDFILE); + return 1; + } + fp = fopen(pseudo_path, "w"); + if (!fp) { + pseudo_diag("Couldn't open %s: %s\n", + pseudo_path, strerror(errno)); + return 1; + } + fprintf(fp, "%lld\n", (long long) pid); + fclose(fp); + free(pseudo_path); + return 0; +} + +int +pseudo_server_start(int daemonize) { + struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET }; + char *pseudo_path; + int rc, newfd; + +#if PSEUDO_PORT_DARWIN + sun.sun_len = strlen(PSEUDO_SOCKET) + 1; +#endif + + listen_fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (listen_fd < 0) { + pseudo_diag("couldn't create listening socket: %s\n", strerror(errno)); + return 1; + } + + if (listen_fd <= 2) { + newfd = fcntl(listen_fd, F_DUPFD, 3); + if (newfd < 0) { + pseudo_diag("couldn't dup listening socket: %s\n", strerror(errno)); + close(listen_fd); + return 1; + } else { + close(listen_fd); + listen_fd = newfd; + } + } + + /* cd to the data directory */ + pseudo_path = pseudo_localstatedir_path(NULL); + if (!pseudo_path || chdir(pseudo_path) == -1) { + pseudo_diag("can't get to '%s': %s\n", + pseudo_path, strerror(errno)); + return 1; + } + free(pseudo_path); + /* remove existing socket -- if it exists */ + unlink(sun.sun_path); + if (bind(listen_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) { + pseudo_diag("couldn't bind listening socket: %s\n", strerror(errno)); + return 1; + } + if (listen(listen_fd, 5) == -1) { + pseudo_diag("couldn't listen on socket: %s\n", strerror(errno)); + return 1; + } + if (daemonize) { + if ((rc = fork()) != 0) { + if (rc == -1) { + pseudo_diag("couldn't spawn server: %s\n", strerror(errno)); + return 0; + } + pseudo_debug(PDBGF_SERVER, "started server, pid %d\n", rc); + close(listen_fd); + /* Parent writes pid, that way it's always correct */ + return pseudo_server_write_pid(rc); + } + /* In child */ + pseudo_new_pid(); + fclose(stdin); + fclose(stdout); + pseudo_logfile(PSEUDO_LOGFILE); + } else { + /* Write the pid if we don't daemonize */ + pseudo_server_write_pid(getpid()); + } + + setsid(); + signal(SIGHUP, quit_now); + signal(SIGINT, quit_now); + signal(SIGALRM, quit_now); + signal(SIGQUIT, quit_now); + signal(SIGTERM, quit_now); + pseudo_server_loop(); + return 0; +} + +/* mess with internal tables as needed */ +static void +open_client(int fd) { + pseudo_client_t *new_clients; + int i; + + /* if possible, use first open client slot */ + for (i = 0; i < max_clients; ++i) { + if (clients[i].fd == -1) { + pseudo_debug(PDBGF_SERVER, "reusing client %d for fd %d\n", i, fd); + clients[i].fd = fd; + clients[i].pid = 0; + clients[i].tag = NULL; + clients[i].program = NULL; + ++active_clients; + if (i > highest_client) + highest_client = i; + return; + } + } + + /* otherwise, allocate a new one */ + new_clients = malloc(sizeof(*new_clients) * (max_clients + 16)); + if (new_clients) { + memcpy(new_clients, clients, max_clients * sizeof(*clients)); + free(clients); + for (i = max_clients; i < max_clients + 16; ++i) { + new_clients[i].fd = -1; + new_clients[i].pid = 0; + new_clients[i].tag = NULL; + new_clients[i].program = NULL; + } + clients = new_clients; + clients[max_clients].fd = fd; + clients[max_clients].pid = 0; + clients[max_clients].tag = NULL; + clients[max_clients].program = NULL; + highest_client = max_clients + 1; + + max_clients += 16; + ++active_clients; + } else { + pseudo_diag("error allocating new client, fd %d\n", fd); + close(fd); + } +} + +/* clear pid/fd. If this was the highest client, iterate downwards looking + * for a lower one to be the new highest client. + */ +static void +close_client(int client) { + pseudo_debug(PDBGF_SERVER, "lost client %d [%d], closing fd %d\n", client, + clients[client].pid, clients[client].fd); + /* client went away... */ + if (client > highest_client || client <= 0) { + pseudo_diag("tried to close client %d (highest is %d)\n", + client, highest_client); + return; + } + close(clients[client].fd); + clients[client].fd = -1; + free(clients[client].tag); + free(clients[client].program); + clients[client].pid = 0; + clients[client].tag = NULL; + clients[client].program = NULL; + --active_clients; + if (client == highest_client) + while (clients[highest_client].fd != -1 && highest_client > 0) + --highest_client; +} + +/* Actually process a request. + */ +static int +serve_client(int i) { + pseudo_msg_t *in; + int rc; + + pseudo_debug(PDBGF_SERVER, "message from client %d [%d:%s - %s] fd %d\n", + i, (int) clients[i].pid, + clients[i].program ? clients[i].program : "???", + clients[i].tag ? clients[i].tag : "NO TAG", + clients[i].fd); + in = pseudo_msg_receive(clients[i].fd); + if (in) { + char *response_path = 0; + size_t response_pathlen; + int send_response = 1; + pseudo_debug(PDBGF_SERVER | PDBGF_VERBOSE, "got a message (%d): %s\n", in->type, (in->pathlen ? in->path : "<no path>")); + /* handle incoming ping */ + if (in->type == PSEUDO_MSG_PING && !clients[i].pid) { + pseudo_debug(PDBGF_SERVER, "new client: %d -> %d", + i, in->client); + clients[i].pid = in->client; + if (in->pathlen) { + size_t proglen; + proglen = strlen(in->path); + + pseudo_debug(PDBGF_SERVER, " <%s>", in->path); + free(clients[i].program); + clients[i].program = malloc(proglen + 1); + if (clients[i].program) { + snprintf(clients[i].program, proglen + 1, "%s", in->path); + } + if (in->pathlen > proglen) { + pseudo_debug(PDBGF_SERVER, " [%s]", in->path + proglen + 1); + clients[i].tag = malloc(in->pathlen - proglen); + if (clients[i].tag) + snprintf(clients[i].tag, in->pathlen - proglen, + "%s", in->path + proglen + 1); + } + } + pseudo_debug(PDBGF_SERVER, "\n"); + } + /* sanity-check client ID */ + if (in->client != clients[i].pid) { + pseudo_debug(PDBGF_SERVER, "uh-oh, expected pid %d for client %d, got %d\n", + (int) clients[i].pid, i, in->client); + } + /* regular requests are processed in place by + * pseudo_server_response. + */ + if (in->type != PSEUDO_MSG_SHUTDOWN) { + if (in->type == PSEUDO_MSG_FASTOP) + send_response = 0; + /* most messages don't need these, but xattr may */ + response_path = 0; + response_pathlen = -1; + if (pseudo_server_response(in, clients[i].program, clients[i].tag, &response_path, &response_pathlen)) { + in->type = PSEUDO_MSG_NAK; + } else { + in->type = PSEUDO_MSG_ACK; + pseudo_debug(PDBGF_SERVER | PDBGF_VERBOSE, "response: %d (%s)\n", + in->result, pseudo_res_name(in->result)); + } + in->client = i; + if (response_path) { + in->pathlen = response_pathlen; + } else { + in->pathlen = 0; + } + } else { + /* the server's listen fd is "a client", and + * so is the program connecting to request a shutdown. + * it should never be less than 2, but crazy things + * happen. >2 implies some other active client, + * though. + */ + if (active_clients > 2) { + int j; + char *s; + + response_path = malloc(8 * active_clients); + in->type = PSEUDO_MSG_NAK; + in->fd = active_clients - 2; + s = response_path; + for (j = 1; j <= highest_client; ++j) { + if (clients[j].fd != -1 && j != i) { + s += snprintf(s, 8, "%d ", (int) clients[j].pid); + } + } + in->pathlen = (s - response_path) + 1; + /* exit quickly once clients go away, though */ + pseudo_server_timeout = 1; + } else { + in->type = PSEUDO_MSG_ACK; + in->pathlen = 0; + in->client = i; + die_peacefully = 1; + } + } + if (send_response) { + if ((rc = pseudo_msg_send(clients[i].fd, in, in->pathlen, response_path)) != 0) { + pseudo_debug(PDBGF_SERVER, "failed to send response to client %d [%d]: %d (%s)\n", + i, (int) clients[i].pid, rc, strerror(errno)); + } + } else { + rc = 1; + } + free(response_path); + return rc; + } else { + /* this should not be happening, but the exceptions aren't + * being detected in select() for some reason. + */ + pseudo_debug(PDBGF_SERVER, "client %d: no message\n", (int) clients[i].pid); + close_client(i); + return 0; + } +} + +/* get clients, handle messages, shut down. + * This doesn't actually do any work, it just calls a ton of things which + * do work. + */ +static void +pseudo_server_loop(void) { + struct sockaddr_un client; + socklen_t len; + fd_set reads, writes, events; + int max_fd, current_clients; + struct timeval timeout; + int i; + int rc; + int fd; + int loop_timeout = pseudo_server_timeout; + + clients = malloc(16 * sizeof(*clients)); + + clients[0].fd = listen_fd; + clients[0].pid = getpid(); + + for (i = 1; i < 16; ++i) { + clients[i].fd = -1; + clients[i].pid = 0; + clients[i].tag = NULL; + clients[i].program = NULL; + } + + active_clients = 1; + max_clients = 16; + highest_client = 0; + + pseudo_debug(PDBGF_SERVER, "server loop started.\n"); + if (listen_fd < 0) { + pseudo_diag("got into loop with no valid listen fd.\n"); + exit(1); + } + pdb_log_msg(SEVERITY_INFO, NULL, NULL, NULL, "server started (pid %d)", getpid()); + + FD_ZERO(&reads); + FD_ZERO(&writes); + FD_ZERO(&events); + FD_SET(clients[0].fd, &reads); + FD_SET(clients[0].fd, &events); + max_fd = clients[0].fd; + timeout = (struct timeval) { .tv_sec = LOOP_DELAY, .tv_usec = 0 }; + + /* EINTR tends to come from profiling, so it is not a good reason to + * exit; other signals are caught and set the flag causing a graceful + * exit. */ + while ((rc = select(max_fd + 1, &reads, &writes, &events, &timeout)) >= 0 || (errno == EINTR)) { + if (rc == 0 || (rc == -1 && errno == EINTR)) { + /* If there's no clients, start timing out. If there + * are active clients, never time out. + */ + if (active_clients == 1) { + loop_timeout -= LOOP_DELAY; + /* maybe flush database to disk */ + pdb_maybe_backup(); + if (loop_timeout <= 0) { + pseudo_debug(PDBGF_SERVER, "no more clients, got bored.\n"); + die_peacefully = 1; + } else { + /* display this if not exiting */ + pseudo_debug(PDBGF_SERVER | PDBGF_BENCHMARK, "%d messages handled in %.4f seconds, %d responses\n", + messages, + (double) message_time.tv_sec + + (double) message_time.tv_usec / 1000000.0, + responses); + } + } + } else if (rc > 0) { + loop_timeout = pseudo_server_timeout; + for (i = 1; i <= highest_client; ++i) { + if (clients[i].fd == -1) + continue; + if (FD_ISSET(clients[i].fd, &events)) { + /* this should happen but doesn't... */ + close_client(i); + } else if (FD_ISSET(clients[i].fd, &reads)) { + struct timeval tv1, tv2; + int rc; + gettimeofday(&tv1, NULL); + rc = serve_client(i); + gettimeofday(&tv2, NULL); + ++messages; + if (rc == 0) + ++responses; + message_time.tv_sec += (tv2.tv_sec - tv1.tv_sec); + message_time.tv_usec += (tv2.tv_usec - tv1.tv_usec); + if (message_time.tv_usec < 0) { + message_time.tv_usec += 1000000; + --message_time.tv_sec; + } else while (message_time.tv_usec > 1000000) { + message_time.tv_usec -= 1000000; + ++message_time.tv_sec; + } + } + if (die_forcefully) + break; + } + if (!(die_peacefully || die_forcefully) && + (FD_ISSET(clients[0].fd, &events) || + FD_ISSET(clients[0].fd, &reads))) { + len = sizeof(client); + if ((fd = accept(listen_fd, (struct sockaddr *) &client, &len)) != -1) { + pseudo_debug(PDBGF_SERVER, "new client fd %d\n", fd); + open_client(fd); + } + } + pseudo_debug(PDBGF_SERVER, "server loop complete [%d clients left]\n", active_clients); + } + if (die_peacefully || die_forcefully) { + pseudo_debug(PDBGF_SERVER, "quitting.\n"); + pseudo_debug(PDBGF_SERVER | PDBGF_BENCHMARK, "server %d exiting: handled %d messages in %.4f seconds\n", + getpid(), messages, + (double) message_time.tv_sec + + (double) message_time.tv_usec / 1000000.0); + pdb_log_msg(SEVERITY_INFO, NULL, NULL, NULL, "server %d exiting: handled %d messages in %.4f seconds", + getpid(), messages, + (double) message_time.tv_sec + + (double) message_time.tv_usec / 1000000.0); + close(clients[0].fd); + exit(0); + } + FD_ZERO(&reads); + FD_ZERO(&writes); + FD_ZERO(&events); + FD_SET(clients[0].fd, &reads); + FD_SET(clients[0].fd, &events); + max_fd = clients[0].fd; + /* current_clients is a sanity check; note that for + * purposes of select(), the server is one of the fds, + * and thus, "a client". + */ + current_clients = 1; + for (i = 1; i <= highest_client; ++i) { + if (clients[i].fd != -1) { + ++current_clients; + FD_SET(clients[i].fd, &reads); + FD_SET(clients[i].fd, &events); + if (clients[i].fd > max_fd) + max_fd = clients[i].fd; + } + } + if (current_clients != active_clients) { + pseudo_debug(PDBGF_SERVER, "miscount of current clients (%d) against active_clients (%d)?\n", + current_clients, active_clients); + } + /* reinitialize timeout because Linux select alters it */ + timeout = (struct timeval) { .tv_sec = LOOP_DELAY, .tv_usec = 0 }; + } + pseudo_diag("select failed: %s\n", strerror(errno)); +} diff --git a/pseudo_server.h b/pseudo_server.h new file mode 100644 index 0000000..06598e7 --- /dev/null +++ b/pseudo_server.h @@ -0,0 +1,23 @@ +/* + * pseudo_server.h, pseudo server declarations and definitions + * + * Copyright (c) 2008-2009 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +extern int pseudo_server_start(int); +extern int pseudo_server_response(pseudo_msg_t *msg, const char *program, const char *tag, char **response_path, size_t *response_len); +extern int pseudo_server_timeout; +extern int opt_l; diff --git a/pseudo_util.c b/pseudo_util.c new file mode 100644 index 0000000..c81df5a --- /dev/null +++ b/pseudo_util.c @@ -0,0 +1,1495 @@ +/* + * pseudo_util.c, miscellaneous utility functions + * + * Copyright (c) 2008-2013 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* we need access to RTLD_NEXT for a horrible workaround */ +#define _GNU_SOURCE + +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <regex.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +/* see the comments below about (*real_regcomp)() */ +#include <dlfcn.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_db.h" + +struct pseudo_variables { + char *key; + size_t key_len; + char *value; +}; + +/* The order below is not arbitrary, but based on an assumption + * of how often things will be used. + */ +static struct pseudo_variables pseudo_env[] = { + { "PSEUDO_PREFIX", 13, NULL }, + { "PSEUDO_BINDIR", 13, NULL }, + { "PSEUDO_LIBDIR", 13, NULL }, + { "PSEUDO_LOCALSTATEDIR", 20, NULL }, + { "PSEUDO_PASSWD", 13, NULL }, + { "PSEUDO_CHROOT", 13, NULL }, + { "PSEUDO_UIDS", 11, NULL }, + { "PSEUDO_GIDS", 11, NULL }, + { "PSEUDO_OPTS", 11, NULL }, + { "PSEUDO_DEBUG", 12, NULL }, + { "PSEUDO_DEBUG_FILE", 17, NULL }, + { "PSEUDO_TAG", 10, NULL }, + { "PSEUDO_ENOSYS_ABORT", 19, NULL }, + { "PSEUDO_NOSYMLINKEXP", 19, NULL }, + { "PSEUDO_DISABLED", 15, NULL }, + { "PSEUDO_UNLOAD", 13, NULL }, + { "PSEUDO_ALLOW_FSYNC", 18, NULL }, +#ifdef PSEUDO_PROFILING + { "PSEUDO_PROFILE_PATH", 19, NULL }, +#endif + { NULL, 0, NULL } /* Magic terminator */ +}; + +/* -1 - init hasn't been run yet + * 0 - init has been run + * 1 - init is running + * + * There are cases where the constructor is run AFTER the + * program starts playing with things, so we need to do our + * best to handle that case. + */ +static int pseudo_util_initted = -1; /* Not yet run */ + +/* bypass wrapper logic on path computations */ +int (*pseudo_real_lstat)(const char *path, PSEUDO_STATBUF *buf) = NULL; + +#if 0 +static void +dump_env(char **envp) { + size_t i = 0; + for (i = 0; envp[i]; i++) { + pseudo_debug(PDBGF_ENV, "dump_envp: [%d]%s\n", (int) i, envp[i]); + } + + for (i = 0; pseudo_env[i].key; i++) { + pseudo_debug(PDBGF_ENV, "dump_envp: {%d}%s=%s\n", (int) i, pseudo_env[i].key, pseudo_env[i].value); + } + + pseudo_debug(PDBGF_ENV, "dump_envp: _in_init %d\n", pseudo_util_initted); +} +#endif + +int +pseudo_has_unload(char * const *envp) { + static const char unload[] = "PSEUDO_UNLOAD"; + static size_t unload_len = sizeof(unload) - 1; + size_t i = 0; + + /* Is it in the caller environment? */ + if (NULL != getenv(unload)) + return 1; + + /* Is it in the environment cache? */ + if (pseudo_util_initted == -1) + pseudo_init_util(); + while (pseudo_env[i].key && strcmp(pseudo_env[i].key, unload)) + ++i; + if (pseudo_env[i].key && pseudo_env[i].value) + return 1; + + /* Is it in the operational environment? */ + while (envp && *envp) { + if ((!strncmp(*envp, unload, unload_len)) && ('=' == (*envp)[unload_len])) + return 1; + ++envp; + } + return 0; +} + +/* Caller must free memory! */ +char * +pseudo_get_value(const char *key) { + size_t i = 0; + char * value; + + if (pseudo_util_initted == -1) + pseudo_init_util(); + + for (i = 0; pseudo_env[i].key && memcmp(pseudo_env[i].key, key, pseudo_env[i].key_len + 1); i++) + ; + + /* Check if the environment has it and we don't ... + * if so, something went wrong... so we'll attempt to recover + */ + if (pseudo_env[i].key && !pseudo_env[i].value && getenv(pseudo_env[i].key)) + pseudo_init_util(); + + if (pseudo_env[i].value) + value = strdup(pseudo_env[i].value); + else + value = NULL; + + if (!pseudo_env[i].key) + pseudo_diag("Unknown variable %s.\n", key); + + return value; +} + +/* We make a copy, so the original values should be freed. */ +int +pseudo_set_value(const char *key, const char *value) { + int rc = 0; + size_t i = 0; + + if (pseudo_util_initted == -1) + pseudo_init_util(); + + for (i = 0; pseudo_env[i].key && memcmp(pseudo_env[i].key, key, pseudo_env[i].key_len + 1); i++) + ; + + if (pseudo_env[i].key) { + if (pseudo_env[i].value) + free(pseudo_env[i].value); + if (value) { + char *new = strdup(value); + if (new) + pseudo_env[i].value = new; + else + pseudo_diag("warning: failed to save new value (%s) for key %s\n", + value, key); + } else + pseudo_env[i].value = NULL; + } else { + if (!pseudo_util_initted) pseudo_diag("Unknown variable %s.\n", key); + rc = -EINVAL; + } + + return rc; +} + +void +pseudo_init_util(void) { + size_t i = 0; + char * env; + + pseudo_util_initted = 1; + + for (i = 0; pseudo_env[i].key; i++) { + if (getenv(pseudo_env[i].key)) + pseudo_set_value(pseudo_env[i].key, getenv(pseudo_env[i].key)); + } + + pseudo_util_initted = 0; + + /* Somewhere we have to set the debug level.. */ + env = pseudo_get_value("PSEUDO_DEBUG"); + if (env) { + int i; + int level = atoi(env); + if (level > 0) { + for (i = 0; i < level; ++i) { + pseudo_debug_verbose(); + } + } else { + pseudo_debug_set(env); + } + pseudo_debug_flags_finalize(); + } + free(env); +} + +unsigned long pseudo_util_debug_flags = 0; +int pseudo_util_debug_fd = 2; +static int debugged_newline = 1; +static char pid_text[32]; +static size_t pid_len; +static int pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, int leave_this); +static int pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *elements, size_t elen, int leave_last); +extern char **environ; +static ssize_t pseudo_max_pathlen = -1; +static ssize_t pseudo_sys_max_pathlen = -1; + +/* in our installed system, we usually use a name of the form + * libpseudoCHECKSUM.so, where CHECKSUM is an md5 checksum of the host + * libc.so -- this forces rebuilds of the library when the C library + * changes. The problem is that the pseudo binary may be + * a prebuilt, in which case it doesn't know about CHECKSUM, so it + * has to determine whether a given PRELINK_LIBRARIES contains libpseudo.so + * or libpseudoCHECKSUM.so, without prior knowledge... Fancy! + * + * We search for anything matching libpseudo*.so, where * is any + * sequence of non-spaces (including an empty string), with either + * the beginning of the string or a space in front of it, and either + * the end of the string or a space after it. + */ +static char *libpseudo_name = "libpseudo.so"; +/* this used to look for a "libpseudo*.so", but it turns out you can + * specify a path even on Linux. + */ +static char *libpseudo_pattern = "(^|=| )[^ ]*libpseudo[^ ]*\\.so($| )"; +static regex_t libpseudo_regex; +static int libpseudo_regex_compiled = 0; + +/* Okay, so, there's a funny story behind this. On one of the systems + * we need to run on, /usr/bin/find happens to provide its own + * definitions of regcomp and regexec which are INCOMPATIBLE with the + * ones in the C library, and not only that, but which have buggy and/or + * incompatible semantics, such that they trash elements of the pmatch + * array. So we do our best to call the "real" regcomp/regexec in the + * C library. If we can't find them, we just do our best and hope that + * no one called us from a program with incompatible variants. + * + */ +#if PSEUDO_PORT_LINUX +static int (*real_regcomp)(regex_t *__restrict __preg, const char *__restrict __pattern, int __cflags); +static int (*real_regexec)(const regex_t *__restrict __preg, const char *__restrict __string, size_t __nmatch, regmatch_t __pmatch[__restrict_arr], int __eflags); +#else +#define real_regcomp regcomp +#define real_regexec regexec +#endif /* PSEUDO_PORT_LINUX */ + +static int +libpseudo_regex_init(void) { + int rc; + + if (libpseudo_regex_compiled) + return 0; +#if PSEUDO_PORT_LINUX + real_regcomp = dlsym(RTLD_NEXT, "regcomp"); + if (!real_regcomp) + real_regcomp = regcomp; + real_regexec = dlsym(RTLD_NEXT, "regexec"); + if (!real_regexec) + real_regexec = regexec; +#endif + rc = (*real_regcomp)(&libpseudo_regex, libpseudo_pattern, REG_EXTENDED); + if (rc == 0) + libpseudo_regex_compiled = 1; + return rc; +} + +/* given a space-or-colon-separated list of files, ala PRELINK_LIBRARIES, + # return that list without any variants of libpseudo*.so. + */ +static char * +without_libpseudo(char *list) { + regmatch_t pmatch[1]; + int counter = 0; + int skip_start = 0; + + if (libpseudo_regex_init()) + return NULL; + + if (list[0] == '=' || list[0] == PSEUDO_LINKPATH_SEPARATOR[0]) + skip_start = 1; + + if ((*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) { + return list; + } + list = strdup(list); + while (!(*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) { + char *start = list + pmatch[0].rm_so; + char *end = list + pmatch[0].rm_eo; + /* don't copy over the space or = */ + start += skip_start; + memmove(start, end, strlen(end) + 1); + ++counter; + if (counter > 5) { + pseudo_diag("Found way too many libpseudo.so in environment, giving up.\n"); + return list; + } + } + return list; +} + +static char * +with_libpseudo(char *list, char *libdir_path) { + regmatch_t pmatch[1]; + + if (libpseudo_regex_init()) + return NULL; + if ((*real_regexec)(&libpseudo_regex, list, 1, pmatch, 0)) { + size_t len; +#if PSEUDO_PORT_DARWIN + /* <%s:%s/%s\0> */ + len = strlen(list) + 1 + strlen(libdir_path) + 1 + strlen(libpseudo_name) + 1; +#else + /* suppress warning */ + (void) libdir_path; + /* <%s %s\0> */ + len = strlen(list) + 1 + strlen(libpseudo_name) + 1; +#endif + char *new = malloc(len); + if (new) { + /* insert space only if there were previous bits */ + /* on Darwin, we have to provide the full path to + * libpseudo + */ +#if PSEUDO_PORT_DARWIN + snprintf(new, len, "%s%s%s/%s", list, + *list ? PSEUDO_LINKPATH_SEPARATOR : "", + libdir_path ? libdir_path : "", + libpseudo_name); +#else + snprintf(new, len, "%s%s%s", list, + *list ? PSEUDO_LINKPATH_SEPARATOR : "", + libpseudo_name); +#endif + } + return new; + } else { + return strdup(list); + } +} + +char *pseudo_version = PSEUDO_VERSION; + +/* going away soon */ +static int max_debug_level = 0; + +void +pseudo_debug_terse(void) { + char s[2] = { pseudo_debug_type_symbolic(max_debug_level) }; + + if (max_debug_level > 0) { + --max_debug_level; + pseudo_debug_clear(s); + } +} + +void +pseudo_debug_verbose(void) { + char s[2] = { pseudo_debug_type_symbolic(max_debug_level + 1) }; + + if (s[0]) { + pseudo_debug_set(s); + ++max_debug_level; + } +} + +/* This exists because we don't want to allocate a bunch of strings + * and free them immediately if you have several flags set. + */ +void +pseudo_debug_flags_finalize(void) { + char buf[PDBG_MAX + 1] = "", *s = buf; + for (int i = 0; i < PDBG_MAX; ++i) { + if (pseudo_util_debug_flags & (1 << i)) { + *s++ = pseudo_debug_type_symbolic(i); + } + } + pseudo_set_value("PSEUDO_DEBUG", buf); +} + +void +pseudo_debug_set(char *s) { + if (!s) + return; + for (; *s; ++s) { + int id = pseudo_debug_type_symbolic_id(*s); + if (id > 0) { + pseudo_util_debug_flags |= (1 << id); + } + } +} + +void +pseudo_debug_clear(char *s) { + if (!s) + return; + for (; *s; ++s) { + int id = pseudo_debug_type_symbolic_id(*s); + if (id > 0) { + pseudo_util_debug_flags &= ~(1 << id); + } + } +} + +int +pseudo_diag(char *fmt, ...) { + va_list ap; + char debuff[8192]; + int len; + /* gcc on Ubuntu 8.10 requires that you examine the return from + * write(), and won't let you cast it to void. Of course, if you + * can't print error messages, there's nothing to do. + */ + int wrote = 0; + + va_start(ap, fmt); + len = vsnprintf(debuff, 8192, fmt, ap); + va_end(ap); + + if (len > 8192) + len = 8192; + + if (debugged_newline && (pseudo_util_debug_flags & PDBGF_PID)) { + wrote += write(pseudo_util_debug_fd, pid_text, pid_len); + } + debugged_newline = (debuff[len - 1] == '\n'); + + wrote += write(pseudo_util_debug_fd, debuff, len); + return wrote; +} + +/* store pid in text form for prepending to messages */ +void +pseudo_new_pid() { + int pid = getpid(); + pid_len = snprintf(pid_text, 32, "%d: ", pid); + pseudo_debug(PDBGF_PID, "new pid: %d\n", pid); +} + +/* helper function for pseudo_fix_path + * adds "element" to "newpath" at location current, if it can, then + * checks whether this now points to a symlink. If it does, expand + * the symlink, appending each element in turn the same way. + */ +static int +pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurrent, const char *element, size_t elen, int leave_this) { + static int link_recursion = 0; + size_t curlen; + char *current; + PSEUDO_STATBUF buf; + if (!newpath || + !pcurrent || !*pcurrent || + !root || !element) { + pseudo_diag("pseudo_append_element: invalid args.\n"); + return -1; + } + current = *pcurrent; + /* sanity-check: ignore // or /./ */ + if (elen == 0 || (elen == 1 && *element == '.')) { + return 1; + } + /* backtrack for .. */ + if (elen == 2 && element[0] == '.' && element[1] == '.') { + /* if newpath's whole contents are '/', do nothing */ + if (current <= root + 1) + return 1; + /* backtrack to the character before the / */ + current -= 2; + /* now find the previous slash */ + while (current > root && *current != '/') { + --current; + } + /* and point to the nul just past it */ + *(++current) = '\0'; + *pcurrent = current; + return 1; + } + curlen = current - newpath; + /* current length, plus / <element> / \0 */ + /* => curlen + elen + 3 */ + if (curlen + elen + 3 > allocated) { + pseudo_diag("pseudo_append_element: path too long (wanted %lu bytes).\n", (unsigned long) curlen + elen + 3); + return -1; + } + memcpy(current, element, elen); + current += elen; + /* nul-terminate, and we now point to the nul after the slash */ + *current = '\0'; + /* now, the moment of truth... is that a symlink? */ + /* if lstat fails, that's fine -- nonexistent files aren't symlinks */ + if (!leave_this) { + int is_link; + is_link = (pseudo_real_lstat) && (pseudo_real_lstat(newpath, &buf) != -1) && S_ISLNK(buf.st_mode); + if (link_recursion >= PSEUDO_MAX_LINK_RECURSION && is_link) { + pseudo_diag("link recursion too deep, not expanding path '%s'.\n", newpath); + is_link = 0; + } + if (is_link) { + char linkbuf[pseudo_path_max() + 1]; + ssize_t linklen; + int retval; + + linklen = readlink(newpath, linkbuf, pseudo_path_max()); + if (linklen == -1) { + pseudo_diag("uh-oh! '%s' seems to be a symlink, but I can't read it. Ignoring.", newpath); + return 0; + } + /* null-terminate buffer */ + linkbuf[linklen] = '\0'; + /* absolute symlink means start over! */ + if (*linkbuf == '/') { + current = newpath + 1; + } else { + /* point back at the end of the previous path... */ + current -= elen; + } + /* null terminate at the new pointer */ + *current = '\0'; + /* append all the elements in series */ + *pcurrent = current; + ++link_recursion; + retval = pseudo_append_elements(newpath, root, allocated, pcurrent, linkbuf, linklen, 0); + --link_recursion; + return retval; + } + } + /* okay, not a symlink, go ahead and append a slash */ + *(current++) = '/'; + *current = '\0'; + *pcurrent = current; + return 1; +} + +static int +pseudo_append_elements(char *newpath, char *root, size_t allocated, char **current, const char *element, size_t elen, int leave_last) { + int retval = 1; + const char * start = element; + if (!newpath || !root || + !current || !*current || + !element) { + pseudo_diag("pseudo_append_elements: invalid arguments."); + return -1; + } + while (element < (start + elen) && *element) { + size_t this_elen; + int leave_this = 0; + char *next = strchr(element, '/'); + if (!next) { + next = strchr(element, '\0'); + leave_this = leave_last; + } + this_elen = next - element; + switch (this_elen) { + case 0: /* path => '/' */ + break; + case 1: /* path => '?/' */ + if (*element != '.') { + if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, leave_this) == -1) { + retval = -1; + } + } + break; + default: + if (pseudo_append_element(newpath, root, allocated, current, element, this_elen, leave_this) == -1) { + retval = -1; + } + break; + } + /* and now move past the separator */ + element += this_elen + 1; + } + return retval; +} + +/* don't do so many allocations */ +#define PATHBUFS 16 +static char *pathbufs[PATHBUFS] = { 0 }; +static int pathbuf = 0; + +/* Canonicalize path. "base", if present, is an already-canonicalized + * path of baselen characters, presumed not to end in a /. path is + * the new path to be canonicalized. The tricky part is that path may + * contain symlinks, which must be resolved. + * if "path" starts with a /, then it is an absolute path, and + * we ignore base. + */ +char * +pseudo_fix_path(const char *base, const char *path, size_t rootlen, size_t baselen, size_t *lenp, int leave_last) { + size_t newpathlen, pathlen; + char *newpath; + char *current; + char *effective_root; + int trailing_slash = 0; + + if (!path) { + pseudo_diag("can't fix empty path.\n"); + return 0; + } + newpathlen = pseudo_path_max(); + if (!pathbufs[pathbuf]) { + pathbufs[pathbuf] = malloc(newpathlen); + } + newpath = pathbufs[pathbuf]; + pathbuf = (pathbuf + 1) % PATHBUFS; + pathlen = strlen(path); + /* a trailing slash has special meaning */ + if (pathlen > 0 && path[pathlen - 1] == '/') { + trailing_slash = 1; + } + /* allow a bit of slush. overallocating a bit won't + * hurt. rounding to 256's in the hopes that it makes life + * easier for the library. + */ + if (!newpath) { + pseudo_diag("allocation failed seeking memory for path (%s).\n", path); + return 0; + } + newpath[0] = '\0'; + current = newpath; + if (baselen && (path[0] != '/' || rootlen)) { + memcpy(current, base, baselen); + current += baselen; + } + /* "root" is a pointer to the beginning of the *modifiable* + * part of the string; you can't back up over it. + */ + effective_root = newpath + rootlen; + *current++ = '/'; + *current = '\0'; + /* at any given point: + * current points to just after the last / of newpath + * path points to the next element of path + * newpathlen is the total allocated length of newpath + * (current - newpath) is the used length of newpath + */ + if (pseudo_append_elements(newpath, effective_root, newpathlen, ¤t, path, pathlen, leave_last) != -1) { + --current; + if (*current == '/' && current > effective_root && !trailing_slash) { + *current = '\0'; + } + pseudo_debug(PDBGF_PATH, "%s + %s => <%s>\n", + base ? base : "<nil>", + path ? path : "<nil>", + newpath ? newpath : "<nil>"); + if (lenp) { + *lenp = current - newpath; + } + return newpath; + } else { + return 0; + } +} + +/* remove the libpseudo stuff from the environment (leaving other preloads + * alone). + * There's an implicit memory leak here, but this is called only right + * before an exec(), or at most once in a given run. + * + * we don't try to fix the library path. + */ +void pseudo_dropenv() { + char *ld_preload = getenv(PRELINK_LIBRARIES); + + if (ld_preload) { + ld_preload = without_libpseudo(ld_preload); + if (!ld_preload) { + pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES); + } + if (ld_preload && strlen(ld_preload)) { + pseudo_diag("ld_preload without: <%s>\n", ld_preload); + setenv(PRELINK_LIBRARIES, ld_preload, 1); + } else { + unsetenv(PRELINK_LIBRARIES); + } + } +} + +char ** +pseudo_dropenvp(char * const *envp) { + char **new_envp; + int i, j; + + for (i = 0; envp[i]; ++i) ; + + new_envp = malloc((i + 1) * sizeof(*new_envp)); + if (!new_envp) { + pseudo_diag("fatal: can't allocate new environment.\n"); + return NULL; + } + + j = 0; + for (i = 0; envp[i]; ++i) { + if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) { + char *new_val = without_libpseudo(envp[i]); + if (!new_val) { + pseudo_diag("fatal: can't allocate new environment variable.\n"); + return 0; + } else { + /* don't keep an empty value; if the whole string is + * PRELINK_LIRBARIES=, we just drop it. */ + if (strcmp(new_val, PRELINK_LIBRARIES "=")) { + new_envp[j++] = new_val; + } + } + } else { + new_envp[j++] = envp[i]; + } + } + new_envp[j++] = NULL; + return new_envp; +} + +/* add pseudo stuff to the environment. + */ +void +pseudo_setupenv() { + size_t i = 0; + + pseudo_debug(PDBGF_CLIENT, "setting up pseudo environment.\n"); + + /* Make sure everything has been evaluated */ + free(pseudo_get_prefix(NULL)); + free(pseudo_get_bindir()); + free(pseudo_get_libdir()); + free(pseudo_get_localstatedir()); + + while (pseudo_env[i].key) { + if (pseudo_env[i].value) { + setenv(pseudo_env[i].key, pseudo_env[i].value, 0); + pseudo_debug(PDBGF_ENV | PDBGF_VERBOSE, "pseudo_env: %s => %s\n", + pseudo_env[i].key, pseudo_env[i].value); + } + i++; + } + + const char *ld_library_path = getenv(PRELINK_PATH); + char *libdir_path = pseudo_libdir_path(NULL); + if (!ld_library_path) { + size_t len = strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1; + char *newenv = malloc(len); + if (!newenv) { + pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH); + } + snprintf(newenv, len, "%s:%s64", libdir_path, libdir_path); + setenv(PRELINK_PATH, newenv, 1); + } else if (!strstr(ld_library_path, libdir_path)) { + size_t len = strlen(ld_library_path) + 1 + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1; + char *newenv = malloc(len); + if (!newenv) { + pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH); + } + snprintf(newenv, len, "%s:%s:%s64", ld_library_path, libdir_path, libdir_path); + setenv(PRELINK_PATH, newenv, 1); + } else { + /* nothing to do, ld_library_path exists and contains + * our preferred path */ + } + + char *ld_preload = getenv(PRELINK_LIBRARIES); + if (ld_preload) { + ld_preload = with_libpseudo(ld_preload, libdir_path); + if (!ld_preload) { + pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES); + } + setenv(PRELINK_LIBRARIES, ld_preload, 1); + free(ld_preload); + } else { + ld_preload = with_libpseudo("", libdir_path); + if (!ld_preload) { + pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES); + } + setenv(PRELINK_LIBRARIES, ld_preload, 1); + free(ld_preload); + } + + /* we kept libdir path until now because with_libpseudo might + * need it + */ + free(libdir_path); + + +#if PSEUDO_PORT_DARWIN + char *force_flat = getenv("DYLD_FORCE_FLAT_NAMESPACE"); + if (!force_flat) { + setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1); + } +#endif +} + +/* add pseudo stuff to the environment. + * We can't just use setenv(), because one use case is that we're trying + * to modify the environment of a process about to be forked through + * execve(). + */ +char ** +pseudo_setupenvp(char * const *envp) { + char **new_envp; + + size_t i, j, k; + size_t env_count = 0; + + size_t size_pseudoenv = 0; + + char *ld_preload = NULL, *ld_library_path = NULL; + + pseudo_debug(PDBGF_ENV, "setting up envp environment.\n"); + + /* Make sure everything has been evaluated */ + free(pseudo_get_prefix(NULL)); + free(pseudo_get_bindir()); + free(pseudo_get_libdir()); + free(pseudo_get_localstatedir()); + + for (i = 0; envp[i]; ++i) { + if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) { + ld_preload = envp[i]; + } + if (STARTSWITH(envp[i], PRELINK_PATH "=")) { + ld_library_path = envp[i]; + } + ++env_count; + } + + for (i = 0; pseudo_env[i].key; i++) { + size_pseudoenv++; + } + + env_count += size_pseudoenv; /* We're going to over allocate */ + + j = 0; + new_envp = malloc((env_count + 1) * sizeof(*new_envp)); + if (!new_envp) { + pseudo_diag("fatal: can't allocate new environment.\n"); + return NULL; + } + + char *libdir_path = pseudo_libdir_path(NULL); + if (!ld_library_path) { + size_t len = strlen(PRELINK_PATH "=") + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1; + char *newenv = malloc(len); + if (!newenv) { + pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH); + } + snprintf(newenv, len, PRELINK_PATH "=%s:%s64", libdir_path, libdir_path); + new_envp[j++] = newenv; + } else if (!strstr(ld_library_path, libdir_path)) { + size_t len = strlen(ld_library_path) + 1 + strlen(libdir_path) + 1 + (strlen(libdir_path) + 2) + 1; + char *newenv = malloc(len); + if (!newenv) { + pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_PATH); + } + snprintf(newenv, len, "%s:%s:%s64", ld_library_path, libdir_path, libdir_path); + new_envp[j++] = newenv; + } else { + /* keep old value */ + new_envp[j++] = ld_library_path; + } + + if (ld_preload) { + ld_preload = with_libpseudo(ld_preload, libdir_path); + if (!ld_preload) { + pseudo_diag("fatal: can't allocate new %s variable.\n", PRELINK_LIBRARIES); + } + new_envp[j++] = ld_preload; + } else { + ld_preload = with_libpseudo("", libdir_path); + size_t len = strlen(PRELINK_LIBRARIES "=") + strlen(ld_preload) + 1; + char *newenv = malloc(len); + snprintf(newenv, len, PRELINK_LIBRARIES "=%s", ld_preload); + new_envp[j++] = newenv; + free(ld_preload); + } + + free(libdir_path); + + for (i = 0; envp[i]; ++i) { + if (STARTSWITH(envp[i], PRELINK_LIBRARIES "=")) continue; + if (STARTSWITH(envp[i], PRELINK_PATH "=")) continue; + new_envp[j++] = envp[i]; + } + + for (i = 0; pseudo_env[i].key; i++) { + int found = 0; + for (k = 0; k < j; k++) { + if (!strncmp(pseudo_env[i].key,new_envp[k],strlen(pseudo_env[i].key))) { + found = 1; + break; + } + } + if (!found && pseudo_env[i].key && pseudo_env[i].value) { + size_t len = strlen(pseudo_env[i].key) + 1 + strlen(pseudo_env[i].value) + 1; + char *newenv = malloc(len); + if (!newenv) { + pseudo_diag("fatal: can't allocate new variable.\n"); + } + snprintf(newenv, len, "%s=%s", pseudo_env[i].key, pseudo_env[i].value); + new_envp[j++] = newenv; + } + } + new_envp[j++] = NULL; + return new_envp; +} + +/* Append the file value to the prefix value. */ +char * +pseudo_append_path(const char * prefix, size_t prefix_len, char *file) { + char *path; + + if (!file) { + return strdup(prefix); + } else { + size_t len = prefix_len + strlen(file) + 2; + path = malloc(len); + if (path) { + char *endptr; + int rc; + + rc = snprintf(path, len, "%s", prefix); + /* this certainly SHOULD be impossible */ + if ((size_t) rc >= len) + rc = len - 1; + endptr = path + rc; + /* strip extra slashes. + * This probably has no real effect, but I don't like + * seeing "//" in paths. + */ + while ((endptr > path) && (endptr[-1] == '/')) + --endptr; + snprintf(endptr, len - (endptr - path), "/%s", file); + } + return path; + } +} + + +/* get the full path to a file under $PSEUDO_PREFIX. Other ways of + * setting the prefix all set it in the environment. + */ +char * +pseudo_prefix_path(char *file) { + char * rc; + char * prefix = pseudo_get_prefix(NULL); + + if (!prefix) { + pseudo_diag("You must set the PSEUDO_PREFIX environment variable to run pseudo.\n"); + exit(1); + } + + rc = pseudo_append_path(prefix, strlen(prefix), file); + free(prefix); + + return rc; +} + +/* get the full path to a file under $PSEUDO_BINDIR. */ +char * +pseudo_bindir_path(char *file) { + char * rc; + char * bindir = pseudo_get_bindir(); + + if (!bindir) { + pseudo_diag("You must set the PSEUDO_BINDIR environment variable to run pseudo.\n"); + exit(1); + } + + rc = pseudo_append_path(bindir, strlen(bindir), file); + free(bindir); + + return rc; +} + +/* get the full path to a file under $PSEUDO_LIBDIR. */ +char * +pseudo_libdir_path(char *file) { + char * rc; + char * libdir = pseudo_get_libdir(); + + if (!libdir) { + pseudo_diag("You must set the PSEUDO_LIBDIR environment variable to run pseudo.\n"); + exit(1); + } + + rc = pseudo_append_path(libdir, strlen(libdir), file); + free(libdir); + + return rc; +} + +/* get the full path to a file under $PSEUDO_LOCALSTATEDIR. */ +char * +pseudo_localstatedir_path(char *file) { + char * rc; + char * localstatedir = pseudo_get_localstatedir(); + + if (!localstatedir) { + pseudo_diag("You must set the PSEUDO_LOCALSTATEDIR environment variable to run pseudo.\n"); + exit(1); + } + + rc = pseudo_append_path(localstatedir, strlen(localstatedir), file); + free(localstatedir); + + return rc; +} + +char * +pseudo_get_prefix(char *pathname) { + char *s = pseudo_get_value("PSEUDO_PREFIX"); + + /* Generate the PSEUDO_PREFIX if necessary, and possible... */ + if (!s && pathname) { + char mypath[pseudo_path_max()]; + char *dir; + char *tmp_path; + + if (pathname[0] == '/') { + snprintf(mypath, pseudo_path_max(), "%s", pathname); + s = mypath + strlen(mypath); + } else { + if (!getcwd(mypath, pseudo_path_max())) { + mypath[0] = '\0'; + } + s = mypath + strlen(mypath); + s += snprintf(s, pseudo_path_max() - (s - mypath), "/%s", + pathname); + } + tmp_path = pseudo_fix_path(NULL, mypath, 0, 0, 0, AT_SYMLINK_NOFOLLOW); + /* point s to the end of the fixed path */ + if ((int) strlen(tmp_path) >= pseudo_path_max()) { + pseudo_diag("Can't expand path '%s' -- expansion exceeds %d.\n", + mypath, (int) pseudo_path_max()); + } else { + s = mypath + snprintf(mypath, pseudo_path_max(), "%s", tmp_path); + } + + while (s > (mypath + 1) && *s != '/') + --s; + *s = '\0'; + dir = s - 1; + while (dir > mypath && *dir != '/') { + --dir; + } + /* strip bin directory, if any */ + if (!strncmp(dir, "/bin", 4)) { + *dir = '\0'; + } + /* degenerate case: /bin/pseudo should yield a pseudo_prefix "/" */ + if (*mypath == '\0') { + strcpy(mypath, "/"); + } + + pseudo_diag("Warning: PSEUDO_PREFIX unset, defaulting to %s.\n", + mypath); + pseudo_set_value("PSEUDO_PREFIX", mypath); + s = pseudo_get_value("PSEUDO_PREFIX"); + } + return s; +} + +char * +pseudo_get_bindir(void) { + char *s = pseudo_get_value("PSEUDO_BINDIR"); + if (!s) { + char *pseudo_bindir = pseudo_prefix_path(PSEUDO_BINDIR); + if (pseudo_bindir) { + pseudo_set_value("PSEUDO_BINDIR", pseudo_bindir); + s = pseudo_bindir; + } + } + return s; +} + +char * +pseudo_get_libdir(void) { + char *s = pseudo_get_value("PSEUDO_LIBDIR"); + if (!s) { + char *pseudo_libdir; + pseudo_libdir = pseudo_prefix_path(PSEUDO_LIBDIR); + if (pseudo_libdir) { + pseudo_set_value("PSEUDO_LIBDIR", pseudo_libdir); + s = pseudo_libdir; + } + } +#if PSEUDO_PORT_DARWIN + /* on Darwin, we need lib64, because dyld won't search */ +#else + /* If we somehow got lib64 in there, clean it down to just lib... */ + if (s) { + size_t len = strlen(s); + if (s[len-2] == '6' && s[len-1] == '4') { + s[len-2] = '\0'; + pseudo_set_value("PSEUDO_LIBDIR", s); + } + } +#endif + + return s; +} + +char * +pseudo_get_localstatedir() { + char *s = pseudo_get_value("PSEUDO_LOCALSTATEDIR"); + if (!s) { + char *pseudo_localstatedir = pseudo_prefix_path(PSEUDO_LOCALSTATEDIR); + if (pseudo_localstatedir) { + pseudo_set_value("PSEUDO_LOCALSTATEDIR", pseudo_localstatedir); + s = pseudo_localstatedir; + } + } + return s; +} + +/* these functions define the sizes pseudo will try to use + * when trying to allocate space, or guess how much space + * other people will have allocated; see the GNU man page + * for realpath(3) for an explanation of why the sys_path_max + * functions exists, approximately -- it's there to be a size + * that I'm pretty sure the user will have allocated if they + * provided a buffer to that defective function. + */ +/* I'm pretty sure this will be larger than real PATH_MAX */ +#define REALLY_BIG_PATH 16384 +/* A likely common value for PATH_MAX */ +#define SORTA_BIG_PATH 4096 +ssize_t +pseudo_path_max(void) { + if (pseudo_max_pathlen == -1) { + long l = pathconf("/", _PC_PATH_MAX); + if (l < 0) { + if (_POSIX_PATH_MAX > 0) { + pseudo_max_pathlen = _POSIX_PATH_MAX; + } else { + pseudo_max_pathlen = REALLY_BIG_PATH; + } + } else { + if (l <= REALLY_BIG_PATH) { + pseudo_max_pathlen = l; + } else { + pseudo_max_pathlen = REALLY_BIG_PATH; + } + } + } + return pseudo_max_pathlen; +} + +ssize_t +pseudo_sys_path_max(void) { + if (pseudo_sys_max_pathlen == -1) { + long l = pathconf("/", _PC_PATH_MAX); + if (l < 0) { + if (_POSIX_PATH_MAX > 0) { + pseudo_sys_max_pathlen = _POSIX_PATH_MAX; + } else { + pseudo_sys_max_pathlen = SORTA_BIG_PATH; + } + } else { + if (l <= SORTA_BIG_PATH) { + pseudo_sys_max_pathlen = l; + } else { + pseudo_sys_max_pathlen = SORTA_BIG_PATH; + } + } + } + return pseudo_sys_max_pathlen; +} + +/* complicated because in theory you can have modes like * 'ab+' + * which is the same as 'a+' in POSIX. The first letter really does have + * to be one of r, w, a, though. + */ +int +pseudo_access_fopen(const char *mode) { + int access = 0; + for (; *mode; ++mode) { + switch (*mode) { + case 'a': + access |= (PSA_APPEND | PSA_WRITE); + break; + case 'r': + access |= PSA_READ; + break; + case 'w': + access |= PSA_WRITE; + break; + case 'x': + /* special case -- note that this conflicts with a + * rarely-used glibc extension + */ + access |= PSA_EXEC; + break; + case 'b': /* binary mode */ + break; + case 'c': case 'e': case 'm': /* glibc extensions */ + break; + case '+': + /* one of these will already be set, presumably */ + access |= (PSA_READ | PSA_WRITE); + break; + default: + access = -1; + break; + } + } + return access; +} + +/* find a passwd/group file to use + * uses in order: + * - PSEUDO_CHROOT/etc/<file> (only if CHROOT is set) + * - PSEUDO_PASSWD/etc/<file> + * - /etc/<file> + */ + +#if PSEUDO_PORT_DARWIN +/* on Darwin, you can't just use /etc/passwd for system lookups, + * you have to use the real library calls because they know about + * Directory Services. So... + * + * We make up fake fds and FILE * objects that can't possibly be + * valid. + */ +int pseudo_host_etc_passwd_fd = -3; +int pseudo_host_etc_group_fd = -4; +static FILE pseudo_fake_passwd_file; +static FILE pseudo_fake_group_file; +FILE *pseudo_host_etc_passwd_file = &pseudo_fake_passwd_file; +FILE *pseudo_host_etc_group_file = &pseudo_fake_group_file; + +#endif + +int +pseudo_etc_file(const char *file, char *realname, int flags, const char **search_dirs, int dircount) { + char filename[pseudo_path_max()]; + int rc = -1; + + if (!file) { + pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: needs argument, usually passwd/group\n"); + errno = ENOENT; + return -1; + } + int i; + if (!search_dirs || dircount == 0) { + pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: no search dirs.\n"); + errno = ENOENT; + return -1; + } + for (i = 0; i < dircount; ++i) { + const char *s = search_dirs[i]; + /* we used to pass in some paths as NULL when unset, + * so we skipped those. Now NULL entries don't get + * put in, so the only NULL should be the sentinel + * value, and this should never get hit. + * + * "should" is not comforting to me. + */ + if (!s) + break; +#if PSEUDO_PORT_DARWIN + /* special magic: empty string implies our emulation + * of the passwd/group files. + */ + if (!*s) { + if (!strcmp("passwd", file)) { + pseudo_debug(PDBGF_CHROOT, "Darwin hackery: pseudo_etc_passwd returning magic passwd fd\n"); + return pseudo_host_etc_passwd_fd; + } else if (!strcmp("group", file)) { + pseudo_debug(PDBGF_CHROOT, "Darwin hackery: pseudo_etc_passwd returning magic group fd\n"); + return pseudo_host_etc_group_fd; + } + } +#endif + snprintf(filename, pseudo_path_max(), "%s/etc/%s", + s, file); + rc = open(filename, flags, 0600); + if (rc >= 0) { + if (realname) + strcpy(realname, filename); + pseudo_debug(PDBGF_CHROOT, "pseudo_etc_file: using '%s' for '%s'.\n", + filename, file); + return rc; + } else { + pseudo_debug(PDBGF_CHROOT | PDBGF_VERBOSE, "didn't find <%s>\n", + filename); + } + } + return rc; +} + +/* set up a log file */ +int +pseudo_logfile(char *defname) { + char *pseudo_path; + char *filename = pseudo_get_value("PSEUDO_DEBUG_FILE"); + char *s; +#if PSEUDO_PORT_LINUX + extern char *program_invocation_short_name; /* glibcism */ +#else + char *program_invocation_short_name = "unknown"; +#endif + int fd; + + if (!filename) { + if (!defname) { + pseudo_debug(PDBGF_INVOKE, "no special log file requested, using stderr.\n"); + return -1; + } + pseudo_path = pseudo_localstatedir_path(defname); + if (!pseudo_path) { + pseudo_diag("can't get path for prefix/%s\n", PSEUDO_LOGFILE); + return -1; + } + } else { + char *pid = NULL, *prog = NULL; + size_t len; + for (s = filename; *s; ++s) { + if (s[0] == '%') { + switch (s[1]) { + case '%': /* skip the %% */ + ++s; + break; + case 'd': + if (pid) { + pseudo_diag("found second %%d in PSEUDO_DEBUG_FILE, ignoring.\n"); + return -1; + } else { + pid = s; + } + break; + case 's': + if (prog) { + pseudo_diag("found second %%s in PSEUDO_DEBUG_FILE, ignoring.\n"); + return -1; + } else { + prog = s; + } + break; + default: + if (isprint(s[1])) { + pseudo_diag("found unknown format character '%c' in PSEUDO_DEBUG_FILE, ignoring.\n", + s[1]); + } else { + pseudo_diag("found unknown format character '\\x%02x' in PSEUDO_DEBUG_FILE, ignoring.\n", + (unsigned char) s[1]); + } + return -1; + break; + } + } + } + len = strlen(filename) + 1; + if (pid) + len += 8; + if (prog) + len += strlen(program_invocation_short_name); + pseudo_path = malloc(len); + if (!pseudo_path) { + pseudo_diag("can't allocate space for debug file name.\n"); + return -1; + } + if (pid && prog) { + if (pid < prog) { + snprintf(pseudo_path, len, filename, getpid(), program_invocation_short_name); + } else { + snprintf(pseudo_path, len, filename, program_invocation_short_name, getpid()); + } + } else if (pid) { + snprintf(pseudo_path, len, filename, getpid()); + } else if (prog) { + snprintf(pseudo_path, len, filename, program_invocation_short_name); + } else { + strcpy(pseudo_path, filename); + } + free(filename); + } + fd = open(pseudo_path, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd == -1) { + pseudo_diag("help: can't open log file %s: %s\n", pseudo_path, strerror(errno)); + } else { + /* try to force fd to 2. We do this because glibc's malloc + * debug unconditionally writes to fd 2, and we don't want + * a client process ending op on fd 2, or server debugging + * becomes a nightmare. + */ + if (fd != 2) { + int newfd; + close(2); + newfd = dup2(fd, 2); + if (newfd != -1) { + fd = newfd; + } + } + pseudo_util_debug_fd = fd; + } + free(pseudo_path); + if (fd == -1) + return -1; + else + return 0; +} + +void +pseudo_stat32_from64(struct stat *buf32, const struct stat64 *buf) { + buf32->st_dev = buf->st_dev; + buf32->st_ino = buf->st_ino; + buf32->st_mode = buf->st_mode; + buf32->st_nlink = buf->st_nlink; + buf32->st_uid = buf->st_uid; + buf32->st_gid = buf->st_gid; + buf32->st_rdev = buf->st_rdev; + buf32->st_size = buf->st_size; + buf32->st_blksize = buf->st_blksize; + buf32->st_blocks = buf->st_blocks; + buf32->st_atime = buf->st_atime; + buf32->st_mtime = buf->st_mtime; + buf32->st_ctime = buf->st_ctime; +} + +void +pseudo_stat64_from32(struct stat64 *buf64, const struct stat *buf) { + buf64->st_dev = buf->st_dev; + buf64->st_ino = buf->st_ino; + buf64->st_mode = buf->st_mode; + buf64->st_nlink = buf->st_nlink; + buf64->st_uid = buf->st_uid; + buf64->st_gid = buf->st_gid; + buf64->st_rdev = buf->st_rdev; + buf64->st_size = buf->st_size; + buf64->st_blksize = buf->st_blksize; + buf64->st_blocks = buf->st_blocks; + buf64->st_atime = buf->st_atime; + buf64->st_mtime = buf->st_mtime; + buf64->st_ctime = buf->st_ctime; +} + +/* pretty-dump some data. + * expects to be called using pseudo_debug_call() so it doesn't have + * to do debug checks. + */ +void +pseudo_dump_data(char *name, const void *v, size_t len) { + char hexbuf[128]; + char asciibuf[32]; + const unsigned char *base = v; + const unsigned char *data = base; + int remaining = len; + pseudo_diag("%s at %p [%d byte%s]:\n", + name ? name : "data", v, (int) len, len == 1 ? "" : "s"); + while (remaining > 0) { + char *hexptr = hexbuf; + char *asciiptr = asciibuf; + for (int i = 0; i < 16 && i < remaining; ++i) { + hexptr += snprintf(hexptr, 4, "%02x ", data[i]); + if (isprint(data[i])) { + *asciiptr++ = data[i]; + } else { + *asciiptr++ = '.'; + } + if (i % 4 == 3) { + *hexptr++ = ' '; + } + } + *hexptr = '\0'; + *asciiptr = '\0'; + pseudo_diag("0x%06x %-50.50s '%.16s'\n", + (int) (data - base), + hexbuf, asciibuf); + remaining = remaining - 16; + data = data + 16; + } +} diff --git a/pseudo_wrappers.c b/pseudo_wrappers.c new file mode 100644 index 0000000..34d7f23 --- /dev/null +++ b/pseudo_wrappers.c @@ -0,0 +1,292 @@ +/* + * pseudo_wrappers.c, shared code for wrapper functions + * + * Copyright (c) 2008-2012 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <assert.h> +#include <stdlib.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> +#include <signal.h> + +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <dlfcn.h> + +/* used for various specific function arguments */ +#include <dirent.h> +#include <fts.h> +#include <ftw.h> +#include <glob.h> +#include <grp.h> +#include <pwd.h> +#include <utime.h> + +#include "pseudo.h" +#include "pseudo_wrapfuncs.h" +#include "pseudo_ipc.h" +#include "pseudo_client.h" + +/* Types and declarations we need in advance. */ +#include "pseudo_wrapper_table.c" + +static void pseudo_enosys(const char *); +static int pseudo_check_wrappers(void); +static volatile int antimagic = 0; +static pthread_mutex_t pseudo_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_t pseudo_mutex_holder; +static int pseudo_mutex_recursion = 0; +static int pseudo_getlock(void); +static void pseudo_droplock(void); +static size_t pseudo_dechroot(char *, size_t); +static void pseudo_sigblock(sigset_t *); + +extern char *program_invocation_short_name; +static sigset_t pseudo_saved_sigmask; + +/* Constructor only exists in libpseudo */ +static void _libpseudo_init(void) __attribute__ ((constructor)); + +static int _libpseudo_initted = 0; + +#ifdef PSEUDO_PROFILING +extern struct timeval *pseudo_wrapper_time; +/* profiling shared postamble */ +#define PROFILE_START \ + struct timeval tv1, tv2; \ + do { gettimeofday(&tv1, NULL); } while(0) +#define PROFILE_DONE do { \ + gettimeofday(&tv2, NULL); \ + pseudo_wrapper_time->tv_sec += tv2.tv_sec - tv1.tv_sec; \ + pseudo_wrapper_time->tv_usec += tv2.tv_usec - tv1.tv_usec; } while(0) +#else +#define PROFILE_START do {} while(0) +#define PROFILE_DONE do {} while(0) +#endif + +#ifdef PSEUDO_XATTRDB +extern ssize_t (*pseudo_real_lgetxattr)(const char *, const char *, void *, size_t); +extern ssize_t (*pseudo_real_fgetxattr)(int, const char *, void *, size_t); +extern int (*pseudo_real_lsetxattr)(const char *, const char *, const void *, size_t, int); +extern int (*pseudo_real_fsetxattr)(int, const char *, const void *, size_t, int); +#endif +/* later, the init code can change these to refer to the real calls and + * skip the wrappers. + */ +extern int (*pseudo_real_lstat)(const char *path, PSEUDO_STATBUF *buf); + +static void +_libpseudo_init(void) { + pseudo_getlock(); + pseudo_antimagic(); + _libpseudo_initted = 1; + + pseudo_init_util(); + pseudo_init_wrappers(); + pseudo_init_client(); + + pseudo_magic(); + pseudo_droplock(); +} + +void +pseudo_reinit_libpseudo(void) { + _libpseudo_init(); +} + +static void +pseudo_init_one_wrapper(pseudo_function *func) { + int (*f)(void) = (int (*)(void)) NULL; + + char *e; + if (*func->real != NULL) { + /* already initialized */ + return; + } + dlerror(); + +#if PSEUDO_PORT_LINUX + if (func->version) + f = dlvsym(RTLD_NEXT, func->name, func->version); + /* fall through to the general case, if that failed */ + if (!f) +#endif + f = dlsym(RTLD_NEXT, func->name); + if (f) { + *func->real = f; + } else { + e = dlerror(); +#ifdef PSEUDO_NO_REAL_AT_FUNCTIONS + char *s = func->name; + s += strlen(s) - 2; + /* *at() don't have to exist */ + if (!strcmp(s, "at")) { + return; + } +#else + if (e != NULL) { + pseudo_diag("No real function for %s: %s\n", func->name, e); + } +#endif + } +} + +void +pseudo_init_wrappers(void) { + int i; + static int done = 0; + + pseudo_getlock(); + pseudo_antimagic(); + + /* We only ever want to run this once, even though we might want to + * "re-init" at specific times... + */ + if (!done) { + for (i = 0; pseudo_functions[i].name; ++i) { + pseudo_init_one_wrapper(&pseudo_functions[i]); + } + done = 1; + } + +#ifdef PSEUDO_XATTRDB + pseudo_real_lgetxattr = real_lgetxattr; + pseudo_real_fgetxattr = real_fgetxattr; + pseudo_real_lsetxattr = real_lsetxattr; + pseudo_real_fsetxattr = real_fsetxattr; +#endif + pseudo_real_lstat = base_lstat; + + /* Once the wrappers are setup, we can now use open... so + * setup the logfile, if necessary... + */ + pseudo_logfile(NULL); + + pseudo_magic(); + pseudo_droplock(); +} + +static void +pseudo_sigblock(sigset_t *saved) { + sigset_t blocked; + + /* these are signals for which the handlers often + * invoke operations, such as close(), which are handled + * by pseudo and could result in a deadlock. + */ + sigemptyset(&blocked); + sigaddset(&blocked, SIGALRM); /* every-N-seconds tasks */ + sigaddset(&blocked, SIGCHLD); /* reaping child processes */ + sigaddset(&blocked, SIGHUP); /* idiomatically, reloading config */ + sigaddset(&blocked, SIGTERM); /* shutdown/teardown operations */ + sigaddset(&blocked, SIGUSR1); /* reopening log files, sometimes */ + sigaddset(&blocked, SIGUSR2); /* who knows what people do */ + sigprocmask(SIG_BLOCK, &blocked, saved); +} + +static int +pseudo_getlock(void) { + if (pthread_equal(pseudo_mutex_holder, pthread_self())) { + ++pseudo_mutex_recursion; + return 0; + } else { + if (pthread_mutex_lock(&pseudo_mutex) == 0) { + pseudo_mutex_recursion = 1; + pseudo_mutex_holder = pthread_self(); + return 0; + } else { + return -1; + } + } +} + +static void +pseudo_droplock(void) { + if (--pseudo_mutex_recursion == 0) { + pseudo_mutex_holder = 0; + pthread_mutex_unlock(&pseudo_mutex); + } +} + +void +pseudo_antimagic() { + ++antimagic; +} + +void +pseudo_magic() { + if (antimagic > 0) + --antimagic; +} + +static void +pseudo_enosys(const char *func) { + pseudo_diag("pseudo: ENOSYS for '%s'.\n", func ? func : "<nil>"); + char * value = pseudo_get_value("PSEUDO_ENOSYS_ABORT"); + if (value) + abort(); + free(value); + errno = ENOSYS; +} + +/* de-chroot a string. + * note that readlink() yields an unterminated buffer, so + * must pass in the correct length. Buffers are null-terminated + * unconditionally if they are modified -- the modification would + * shorten the string, so there will be space for the NUL, so + * this is safe even for stuff like readlink(). + */ +static size_t +pseudo_dechroot(char *s, size_t len) { + if (len == (size_t) -1) + len = strlen(s); + if (pseudo_chroot_len && len >= pseudo_chroot_len && + !memcmp(s, pseudo_chroot, pseudo_chroot_len)) { + if (s[pseudo_chroot_len] == '/') { + memmove(s, s + pseudo_chroot_len, len - pseudo_chroot_len); + len -= pseudo_chroot_len; + s[len] = '\0'; + } else if (s[pseudo_chroot_len] == '\0') { + s[0] = '/'; + len = 1; + s[len] = '\0'; + } + /* otherwise, it's not really a match... */ + } + return len; +} + +static int +pseudo_check_wrappers(void) { + if (!_libpseudo_initted) + pseudo_reinit_libpseudo(); + + return _libpseudo_initted; +} + +/* the generated code goes here */ +#include "port_wrappers.c" +#include "pseudo_wrapfuncs.c" + diff --git a/pseudodb.c b/pseudodb.c new file mode 100644 index 0000000..1be8651 --- /dev/null +++ b/pseudodb.c @@ -0,0 +1,49 @@ +/* + * pseudodb.c, (unimplemented) database maintenance utility. + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_db.h" + +int +main(int argc, char **argv) { + pseudo_msg_t *msg; + int rc; + + if (argc < 2) { + fprintf(stderr, "Usage: pseudodb <filename>\n"); + exit(1); + } + msg = pseudo_msg_new(0, argv[1]); + rc = pdb_find_file_path(msg, NULL); + if (rc) { + printf("error.\n"); + return 1; + } else { + printf("%s: %o %x\n", msg->path, (int) msg->mode, (int) msg->rdev); + return 0; + } +} diff --git a/pseudolog.1 b/pseudolog.1 new file mode 100644 index 0000000..ebf8443 --- /dev/null +++ b/pseudolog.1 @@ -0,0 +1,395 @@ +.\" +.\" pseudolog(1) man page +.\" +.\" Copyright (c) 2010 Wind River Systems, Inc. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the Lesser GNU General Public License version 2.1 as +.\" published by the Free Software Foundation. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.\" See the Lesser GNU General Public License for more details. +.\" +.\" You should have received a copy of the Lesser GNU General Public License +.\" version 2.1 along with this program; if not, write to the Free Software +.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +.TH pseudolog 1 "pseudo - pretending to be root" +.SH NAME +pseudolog \- pseudo log parser +.SH SYNOPSIS +.B pseudolog \-l +.RB [ \-Pv ] +[ +.B \-E +.I timeformat +] +[ +.B \-x +.I flags +] +.RI [ SPECIFICATIONS ] +.PP +.B pseudolog +.RB [ \-UPv ] +[ +.B \-E +.I timeformat +] +[ +.B \-F +.I format +] +[ +.B \-x +.I flags +] +.PP +.B pseudolog \-h +.PP +.B pseudolog \-D +.RB [ \-Pv ] +[ +.B \-E +.I timeformat +] +[ +.B \-x +.I flags +] +.RI [ SPECIFICATIONS ] +.RI [ SPECIFICATIONS ] +.SH DESCRIPTION +The +.I pseudolog +utility displays, creates, or deletes log entries associated with the +.I pseudo +daemon. Creation of log entries is useful only to +create timestamps or notes; for instance, you could create a log entry before +beginning a process, so there would be a timestamp for the beginning of +that process. There are a number of special options used to match or create +the components of a log entry; these are called +.IR specifications , +and are detailed in the +.B SPECIFICATIONS +section below. + +The following other options are supported: + +.TP 8 +.B \-h +Print a usage message and exit. +.TP 8 +.B \-D +Delete rows selected by the query. This is not reversible. +.TP 8 +.BI \-E \ timeformat +Specify a format string (for +.I strptime(3) +or +.I strftime(3) +to use) for displaying or interpreting time stamps. The same format +is used both for parsing and displaying stamps. +.TP 8 +.BI \-F \ format +Specifies a format string for displaying log entries. This format cannot +be used to create log entries, only for display. The format string is +a +.I printf(3) +type format string, with format specifiers matching the option characters +used in specifications (see +.BR SPECIFICATIONS ). +There are some limitations on allowed formats, and misuse of this feature +could cause interesting or surprising failures. +.TP 8 +.B \-l +Create a log entry. This option is mutually exclusive with the +.B \-F +option, or with any relative specifications (see below). +.TP 8 +.BI \-P \ path +Specify that +.I path +should be used as the +.B PSEUDO_PREFIX +value, overriding any environment setting. +.TP 8 +.B \-U +Restrict query output to unique rows. Rows will have members defined by +the +.B \-F +(format) option. If all members are the same between two rows, only one +is displayed. Applies only to queries. +.TP 8 +.B \-v +Increase verbosity (debug level). Not useful except when debugging pseudo. +Deprecated; use +.BR \-x . +.TP 8 +.BI \-x flags +Specify debugging flags of interest. Not useful except when debugging pseudo. + +Other option characters are defined as specifications, and all of those +require arguments to specify their values. + +.SH SPECIFICATIONS + +The various components of a log entry can be specified, either as command-line +options, or as format specifiers. In either case, the same character is used +for a given component of a log entry. When querying values, one of the +following prefixes may be prepended to a value; otherwise, the value is +used for a literal match (an SQL +.B = +operator). + +.TP 8 +.B > +Greater than; true if the related field is greater than the provided value. +.TP 8 +.B < +Less than; true if the related field is less than the provided value. +.TP 8 +.B & +Bitwise and; true if the related field, bitwise-and the provided value, +is non-zero. (This is useful primarily for permissions or modes.) +.TP 8 +.B = +Equal to. (This is a no-op, as of this writing.) +.TP 8 +.B ! +Not equal to. +.TP 8 +.B % +Similar to +.BR ~ . +This is valid only on text fields, and is equivalent to +the SQL +.B LIKE +operator, with +.B % +patterns on the ends; it performs an unanchored, case-insensitive match. +.TP 8 +.B ~ +Similar to +.BR % . +This is valid only on text fields, and is equivalent +to the SQL +.B LIKE +operator, but performs an anchored match. The match is +case-insensitive. The specifier +.B ~%foo% +is equivalent to the specifier +.BR %foo . +.TP 8 +.B ^ +Unlike. This is the inverse of ~; it specifies +.BR NOT\ LIKE . +.TP 8 +.B \\ +Escape the string. This is useful if you want to have one of the +other modifiers at the beginning of the string. + +.PP +Only +.BR = and \\ +modifiers may be used in conjunction with the +.B \-l +option. + +The following characters correspond to specific fields in a log entry. +In general, numeric values are parsed in the standard C idiom (where +a leading +.B 0 +indicates an octal value, and a leading +.B 0x +indicates a hexadecimal value, and any other number is decimal). A +few fields are parsed or displayed in other ways, as detailed in their +entries. + +.TP 8 +.B a +Access mode. This is an access mode specified in the form used by +.IR fopen(3) , +such as "r+" to indicate read/write access. Note that specifying +.B \&a +as an access mode will include non-append writes, as the "a" mode +implies write and append both. This feature is slightly experimental +and may not correctly identify the access type of every access. The +string +.B x +may be specified to indicate execute access. +.TP 8 +.B c +Client ID (the PID of a client). +.TP 8 +.B d +Device number (from a stat buffer). +.TP 8 +.B f +File descriptor. In some cases, messages have an associated file descriptor +identified. +.TP 8 +.B g +GID. The group ID associated with an entry. +.TP 8 +.B G +Tag. This is a text field. In log entries created by +.IR pseudo , +this field holds the value that the environment variable +.B PSEUDO_TAG +had in the client's environment. +.TP 8 +.B i +Inode number (from a stat buffer). +.TP 8 +.TP 8 +.B I +ID. This is the database row number. Normally these are assigned +as monotonically increasing values as rows are inserted, making them +a more reliable sorting mechanism than timestamps. The default +ordering is by ID. +.B m +Permissions. These can be entered as an octal value or as a symbolic +mode string, similar to the output of +.I ls(1) +.BR -l. +The file type component is ignored. +.TP 8 +.B M +Mode. This can be entered as an octal value or as a symbolic mode +string, similar to the output of +.I ls(1) +.BR -l. +This is tested against the whole file mode, including both the type +and permissions bits. In general, it is more useful to use the +.B m +or +.B t +specifiers. +.TP 8 +.B o +Operation. This is the name of the file system operation +(e.g., "open" or "rename"). +.TP 8 +.B O +Order. This takes another specification character as the field +on which to order results. A '<' implies a descending order sort, +a '>' or no modifier specifies an ascending order sort. +By default, records are sorted by ID. +.TP 8 +.B p +File path. This is a text field. +.TP 8 +.B r +Result. This is the +.I pseudo +result code, most often "fail" or +"succeed". Note that "fail" doesn't mean that an underlying +operation failed; for instance, if a "stat" operation fails, it +usually means that there was no entry in the +.I pseudo +database. +.TP 8 +.B R +Program. This is the program name (as retrieved by glibc's +.I program_invocation_name +variable), which has the full path if and only if the program +was invoked by full path name. +.TP 8 +.B s +Timestamp. The format of this field is controlled by the +.B \-E +format string, which is used with +.I strftime(3) +when displaying entries, or with +.I strptime(3) +when interpreting command line values. There is a small selection of +common default time formats understood by the parser. Time fields not +specified default to the current time. Note that specifying a time +stamp when creating a log entry may yield confusing results. +.TP 8 +.B S +Severity. Log messages can have a severity, with the default for file +operations being "info". +.B t +File type. This corresponds to the first letter of a mode string, or +the values accepted by the +.B \-type +option to +.IR find(1) . +This is compared only against the file type bits of a mode. +.TP 8 +.B T +Text. This is an optional field available for user use when creating +log entries, or to hold the text of an error message when an error is +logged. It is, of course, a text field. +.TP 8 +.B u +UID. The user ID associated with an entry. +.TP 8 +.B y +Type. This is usually "op" for operations, or "ping" for the ping +messages clients send to confirm server availability. Other types +should rarely occur, but include "ack" and "nak" for server +responses (which are never logged), and "halt" for shutdown messages +(currently not logged). + +.SH EXAMPLES +The following examples illustrate some of the likely usage patterns for +.IR pseudolog . + +.TP 8 +.B pseudolog -m '&020' -t d +Report on all directories which are group-writeable. +.TP 8 +.B pseudolog -m 755 -t f +Report on all plain files which have the mode rwxr-xr-x. +.TP 8 +.B pseudolog -s '>03:19:00' -s '<03:20:00' +Report on all entries created after 03:19:00 and before 03:20:00 on the +current +date. +.TP 8 +.B pseudolog -p '~/usr/bin/%' -F '%-8o %p' +Report on every entry with a path beginning with the string '/usr/bin', +displaying the operation name (in a space-padded field of eight characters, +left-adjusted) followed by the path. +.TP 8 +.B pseudolog -l -T 'stamp test' +Create an entry with all fields zero or blank, except for the +text field, which is set to the text "stamp test", and the timestamp, +which is set to the current time. +.TP 8 +.B pseudolog -D -r succeed -F '%p' -O p +Display all paths for which operations succeeded, sorted by path value. + +.SH ENVIRONMENT +The only environment variable supported by +.I pseudolog +is: +.TP 8 +.B PSEUDO_PREFIX +If set, the variable +.B PSEUDO_PREFIX +is used to determine the path to use to find the +.I logs.db +database file, in +.BR PSEUDO_PREFIX /var/pseudo. + +.SH BUGS +The user might think our intent is to replace all of SQL. It's not. If the +options here aren't enough, rather than adding more options to this already +fairly elaborate program, just do raw SQL queries on the +.I logs.db +file. + +The formatting options are handled by converting them into +.I printf(3) +format strings, without much checking. As a result, it +is possible for a malformed format string to cause +.I printf() +to explode unexpectedly. + +.SH SEE ALSO +pseudo(1), sqlite3(1) diff --git a/pseudolog.c b/pseudolog.c new file mode 100644 index 0000000..ced421d --- /dev/null +++ b/pseudolog.c @@ -0,0 +1,901 @@ +/* + * pseudolog.c, pseudo database viewer (preliminary) + * + * Copyright (c) 2008-2010 Wind River Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the Lesser GNU General Public License version 2.1 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * version 2.1 along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* We need _XOPEN_SOURCE for strptime(), but if we define that, + * we then don't get S_IFSOCK... _GNU_SOURCE turns on everything. */ +#define _GNU_SOURCE + +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "pseudo.h" +#include "pseudo_ipc.h" +#include "pseudo_client.h" +#include "pseudo_db.h" + +static int opt_D = 0; +static int opt_U = 0; +static int opt_l = 0; + +/* when the client is linked with pseudo_wrappers, these are defined there. + * when it is linked with pseudo_server, though, we have to provide different + * versions (pseudo_wrappers must not be linked with the server, or Bad Things + * happen). + */ +void pseudo_magic(void) { } +void pseudo_antimagic(void) { } + +static void display(log_entry *, char *format); +static int format_scan(char *format); + +void +usage(int status) { + static char *options[] = { + "a access (rwax)", + "c client pid", + "d device number", + "f file descriptor", + "g gid", + "G tag (text)", + "i inode number", + "I id (database row)", + "m permission bits (octal)", + "M file mode (octal)", + "o operation (e.g. 'open')", + "O order by (< DESC > ASC)", + "p file path", + "P program", + "r result (e.g. 'succeed')", + "s timestamp", + "S severity", + "t type (like find -type)", + "T text (text field)", + "u uid", + "y type (op/ping/shutown)", + NULL, + }; + FILE *f = (status == EXIT_SUCCESS) ? stdout : stderr; + int i; + + fputs("pseudolog: create or report log entries. usage:\n", f); + fputs("pseudolog -l [SPECIFIERS] -- create entries\n", f); + fputs("pseudolog [-U] [-F format] [SPECIFIERS] -- report entries\n", f); + fputs("pseudolog -D [SPECIFIERS] -- delete entries\n", f); + fputs("shared options: [-P prefix] [-E timeformat] [-x flags]\n", f); + fputs(" format is a printf-like format string using the option letters\n", f); + fputs(" listed below as format specifiers for the corresponding field.\n", f); + fputs(" timeformat is a strftime-like format string, the default is '%x %X'.\n", f); + fputs(" prefix is the PSEUDO_PREFIX in which to find the database.\n", f); + fputs(" flags are characters from the same debug flag set used by pseudo.\n", f); + fputs("\n", f); + fputs("SPECIFIERS are options of the form -X <value>, where X is one of\n", f); + fputs("the following option letters, and value is the value to match.\n", f); + fputs("values may be prefixed with ! (not equal to), > (greater than),\n", f); + fputs("< (less than), & (bitwise and), ~ (LIKE match, anchored at both\n", f); + fputs("ends, text fields only), ^ (NOT LIKE match, anchored at both\n", f); + fputs("ends, or % (LIKE match, text fields only).\n", f); + fputs("\n", f); + fputs("OPTION LETTERS:\n", f); + for (i = 0; options[i]; ++i) { + fprintf(f, " %-28s%s", options[i], (i % 2) ? "\n" : " "); + } + if (i % 2 == 1) { + fprintf(f, "\n"); + } + exit(status); +} + +pseudo_query_field_t opt_to_field[UCHAR_MAX + 1] = { + ['a'] = PSQF_ACCESS, + ['c'] = PSQF_CLIENT, + ['d'] = PSQF_DEV, + ['f'] = PSQF_FD, + ['g'] = PSQF_GID, + ['G'] = PSQF_TAG, + ['I'] = PSQF_ID, + ['i'] = PSQF_INODE, + ['m'] = PSQF_PERM, + ['M'] = PSQF_MODE, + ['o'] = PSQF_OP, + ['O'] = PSQF_ORDER, + ['p'] = PSQF_PATH, + ['r'] = PSQF_RESULT, + ['R'] = PSQF_PROGRAM, + ['s'] = PSQF_STAMP, + ['S'] = PSQF_SEVERITY, + ['t'] = PSQF_FTYPE, + ['T'] = PSQF_TEXT, + ['u'] = PSQF_UID, + ['y'] = PSQF_TYPE, +}; + +pseudo_query_type_t +plog_query_type(char **string) { + pseudo_query_type_t type = PSQT_EXACT; + if (!string || !*string) + return PSQT_UNKNOWN; + switch (**string) { + case '\0': + pseudo_diag("Error: Value may not be an empty string."); + return PSQT_UNKNOWN; + break; + case '>': + type = PSQT_GREATER; + ++*string; + break; + case '<': + type = PSQT_LESS; + ++*string; + break; + case '!': + type = PSQT_NOTEQUAL; + ++*string; + break; + case '=': + ++*string; + break; + case '&': + type = PSQT_BITAND; + ++*string; + break; + case '%': + type = PSQT_LIKE; + ++*string; + break; + case '^': + type = PSQT_NOTLIKE; + ++*string; + break; + case '~': + type = PSQT_SQLPAT; + ++*string; + break; + case '\\': + /* no special type, but allows one of the others to be the + * first character of the effective string + */ + ++*string; + break; + } + if (opt_l && type != PSQT_EXACT) { + pseudo_diag("Error: Non-exact match requested while trying to create a log entry.\n"); + type = PSQT_UNKNOWN; + } + return type; +} + +static char *time_formats[] = { + "%s", + "%F %r", + "%F %T", + "%m-%d %r", + "%m-%d %T", + "%r", + "%T", + NULL, +}; +static char *timeformat = "%X"; + +mode_t +parse_file_type(char *string) { + switch (*string) { + case 'b': + return S_IFBLK; + break; + case 'c': + return S_IFCHR; + break; + case 'd': + return S_IFDIR; + break; + case '-': /* FALLTHROUGH */ + case 'f': + return S_IFREG; + break; + case 'l': + return S_IFLNK; + break; + case 'p': + return S_IFIFO; + break; + case 's': + return S_IFSOCK; + break; + default: + pseudo_diag("unknown file type %c; should be one of [-bcdflps]\n", + isprint(*string) ? *string : '?'); + return -1; + break; + } +} + +mode_t +parse_partial_mode(char *string) { + mode_t mode = 0; + switch (string[0]) { + case 'r': + mode |= 04; + break; + case '-': + break; + default: + pseudo_diag("unknown mode character: %c\n", string[0]); + return -1; + break; + } + switch (string[1]) { + case 'w': + mode |= 02; + break; + case '-': + break; + default: + pseudo_diag("unknown mode character: %c\n", string[1]); + return -1; + break; + } + switch (string[2]) { + case 'x': + mode |= 01; + break; + case 't': /* FALLTHROUGH */ + case 's': + mode |= 011; + break; + case 'T': /* FALLTHROUGH */ + case 'S': + mode |= 010; + break; + case '-': + break; + default: + pseudo_diag("unknown mode character: %c\n", string[2]); + return -1; + break; + } + return mode; +} + +mode_t +parse_mode_string(char *string) { + size_t len = strlen(string); + mode_t mode = 0; + mode_t bits = 0; + + if (len != 9 && len != 10) { + pseudo_diag("mode strings must be of the form [-]rwxr-xr-x\n"); + return -1; + } + if (len == 10) { + mode |= parse_file_type(string); + ++string; + if (mode == (mode_t) -1) { + pseudo_diag("mode strings with a file type must use a valid type [-bcdflps]\n"); + return -1; + } + } + bits = parse_partial_mode(string); + if (bits == (mode_t) -1) + return -1; + if (bits & 010) { + mode |= S_ISUID; + bits &= ~010; + } + mode |= bits << 6; + string += 3; + bits = parse_partial_mode(string); + if (bits == (mode_t) -1) + return -1; + if (bits & 010) { + mode |= S_ISGID; + bits &= ~010; + } + mode |= bits << 3; + string += 3; + bits = parse_partial_mode(string); + if (bits == (mode_t) -1) + return -1; + if (bits & 010) { + mode |= S_ISVTX; + bits &= ~010; + } + mode |= bits; + return mode; +} + +static time_t +parse_timestamp(char *string) { + time_t stamp_sec; + struct tm stamp_tm; + int i; + char *s; + char timebuf[4096]; + + stamp_sec = time(0); + + /* try the user's provided time format first, if there is one: */ + localtime_r(&stamp_sec, &stamp_tm); + s = strptime(string, timeformat, &stamp_tm); + if (s && !*s) { + return mktime(&stamp_tm); + } + + for (i = 0; time_formats[i]; ++i) { + char *s; + localtime_r(&stamp_sec, &stamp_tm); + s = strptime(string, time_formats[i], &stamp_tm); + if (s && !*s) { + break; + } + } + if (!time_formats[i]) { + pseudo_diag("Couldn't parse <%s> as a time. Current time in known formats is:\n", + string); + localtime_r(&stamp_sec, &stamp_tm); + for (i = 0; time_formats[i]; ++i) { + strftime(timebuf, sizeof(timebuf), time_formats[i], &stamp_tm); + pseudo_diag("\t%s\n", timebuf); + } + pseudo_diag("Or, specify your own with -E; see strptime(3).\n"); + return -1; + } + return mktime(&stamp_tm); +} + +pseudo_query_t * +plog_trait(int opt, char *string) { + pseudo_query_t *new_trait; + char *endptr; + + if (opt < 0 || opt > UCHAR_MAX) { + pseudo_diag("Unknown/invalid option value: %d\n", opt); + return 0; + } + if (!opt_to_field[opt]) { + if (isprint(opt)) { + pseudo_diag("Unknown option: -%c\n", opt); + } else { + pseudo_diag("Unknown option: 0x%02x\n", opt); + } + return 0; + } + if (!*string) { + pseudo_diag("invalid empty string for -%c\n", opt); + return 0; + } + new_trait = calloc(sizeof(*new_trait), 1); + if (!new_trait) { + pseudo_diag("Couldn't allocate requested trait (for -%c %s)\n", + opt, string ? string : "<nil>"); + return 0; + } + new_trait->field = opt_to_field[opt]; + new_trait->type = plog_query_type(&string); + if (new_trait->type == PSQT_UNKNOWN) { + pseudo_diag("Couldn't comprehend trait type for '%s'\n", + string ? string : "<nil>"); + free(new_trait); + return 0; + } + switch (new_trait->field) { + case PSQF_ACCESS: + new_trait->data.ivalue = pseudo_access_fopen(string); + if (new_trait->data.ivalue == (unsigned long long) -1) { + pseudo_diag("access flags should be specified like fopen(3) mode strings (or x for exec).\n"); + free(new_trait); + return 0; + } + break; + case PSQF_FTYPE: + /* special magic: allow file types ala find */ + /* This is implemented by additional magic over in the database code */ + /* must not be more than one character. The test against + * the first character is because in theory, if the + * first character is the terminating NUL, we may not + * access the second. */ + if (string[0] && string[1]) { + pseudo_diag("file type must be a single character [-bcdflps].\n"); + free(new_trait); + return 0; + } + new_trait->data.ivalue = parse_file_type(string); + if (new_trait->data.ivalue == (mode_t) -1) { + free(new_trait); + return 0; + } + break; + case PSQF_OP: + new_trait->data.ivalue = pseudo_op_id(string); + break; + case PSQF_ORDER: + if (string[0] && string[1]) { + pseudo_diag("order type must be a single specifier character.\n"); + free(new_trait); + return 0; + } + new_trait->data.ivalue = opt_to_field[(unsigned char) string[0]]; + if (!new_trait->data.ivalue) { + pseudo_diag("Unknown field type: %c\n", string[0]); + } + break; + case PSQF_RESULT: + new_trait->data.ivalue = pseudo_res_id(string); + break; + case PSQF_SEVERITY: + new_trait->data.ivalue = pseudo_sev_id(string); + break; + case PSQF_STAMP: + new_trait->data.ivalue = parse_timestamp(string); + if ((time_t) new_trait->data.ivalue == (time_t) -1) { + free(new_trait); + return 0; + } + break; + case PSQF_TYPE: + new_trait->data.ivalue = pseudo_msg_type_id(string); + break; + case PSQF_CLIENT: + case PSQF_DEV: + case PSQF_FD: + case PSQF_GID: + case PSQF_INODE: + case PSQF_UID: + new_trait->data.ivalue = strtoll(string, &endptr, 0); + if (*endptr) { + pseudo_diag("Unexpected garbage after number (%llu): '%s'\n", + new_trait->data.ivalue, endptr); + free(new_trait); + return 0; + } + break; + case PSQF_MODE: + case PSQF_PERM: + new_trait->data.ivalue = strtoll(string, &endptr, 8); + if (!*endptr) { + break; + } + /* maybe it's a mode string? */ + new_trait->data.ivalue = parse_mode_string(string); + if (new_trait->data.ivalue == (mode_t) -1) { + free(new_trait); + return 0; + } + if (new_trait->field == PSQF_PERM) { + /* mask out file type */ + new_trait->data.ivalue &= ~S_IFMT; + } + break; + case PSQF_PATH: /* FALLTHROUGH */ + case PSQF_PROGRAM: /* FALLTHROUGH */ + case PSQF_TEXT: /* FALLTHROUGH */ + case PSQF_TAG: + /* Plain strings */ + new_trait->data.svalue = strdup(string); + break; + default: + pseudo_diag("I don't know how I got here. Unknown field type %d.\n", + new_trait->field); + free(new_trait); + return 0; + break; + } + return new_trait; +} + +/* You can either create a query or create a log entry. They use very + * similar syntax, but: + * - if you're making a query, you can use >, <, etc. + * - if you're logging, you can't. + * This is tracked by recording whether any non-exact relations + * have been requested ("query_only"), and refusing to set the -l + * flag if they have, and refusing to accept any such relation + * if the -l flag is already set. + */ +int +main(int argc, char **argv) { + pseudo_query_t *traits = 0, *current = 0, *new_trait = 0; + char *s; + log_history history; + int query_only = 0; + int o; + int bad_args = 0; + char *format = "%s %-12.12R %-4y %7o: [mode %04m, %2a] %p %T"; + + while ((o = getopt(argc, argv, "vla:c:d:DE:f:F:g:G:hi:I:m:M:o:O:p:P:r:R:s:S:t:T:u:Ux:y:")) != -1) { + switch (o) { + case 'P': + s = PSEUDO_ROOT_PATH(AT_FDCWD, optarg, AT_SYMLINK_NOFOLLOW); + if (!s) + pseudo_diag("Can't resolve prefix path '%s'\n", optarg); + pseudo_set_value("PSEUDO_PREFIX", s); + break; + case 'v': + pseudo_debug_verbose(); + break; + case 'x': + pseudo_debug_set(optarg); + break; + case 'l': + opt_l = 1; + break; + case 'D': + opt_D = 1; + query_only = 1; + break; + case 'E': + timeformat = strdup(optarg); + break; + case 'F': + /* disallow specifying -F with -l */ + format = strdup(optarg); + query_only = 1; + break; + case 'U': + opt_U = 1; + query_only = 1; + break; + case 'I': /* PSQF_ID */ + query_only = 1; + /* FALLTHROUGH */ + case 'a': /* PSQF_ACCESS */ + case 'c': /* PSQF_CLIENT */ + case 'd': /* PSQF_DEV */ + case 'f': /* PSQF_FD */ + case 'g': /* PSQF_GID */ + case 'G': /* PSQF_TAG */ + case 'i': /* PSQF_INODE */ + case 'm': /* PSQF_PERM */ + case 'M': /* PSQF_MODE */ + case 'o': /* PSQF_OP */ + case 'O': /* PSQF_ORDER */ + case 'p': /* PSQF_PATH */ + case 'r': /* PSQF_RESULT */ + case 'R': /* PSQF_PROGRAM */ + case 's': /* PSQF_STAMP */ + case 'S': /* PSQF_SEVERITY */ + case 't': /* PSQF_FTYPE */ + case 'T': /* PSQF_TEXT */ + case 'u': /* PSQF_UID */ + case 'y': /* PSQF_TYPE */ + new_trait = plog_trait(o, optarg); + if (!new_trait) { + bad_args = 1; + } + break; + case 'h': + usage(EXIT_SUCCESS); + break; + case '?': /* FALLTHROUGH */ + default: + fprintf(stderr, "unknown option '%c'\n", optopt); + usage(EXIT_FAILURE); + break; + } + if (new_trait) { + if (current) { + current->next = new_trait; + current = current->next; + } else { + traits = new_trait; + current = new_trait; + } + new_trait = 0; + } + } + pseudo_debug_flags_finalize(); + + if (optind < argc) { + pseudo_diag("Error: Extra arguments not associated with any option.\n"); + usage(EXIT_FAILURE); + } + + if (query_only && opt_l) { + pseudo_diag("Error: -l cannot be used with query-only options or flags.\n"); + bad_args = 1; + } + + /* should be set only if we have already diagnosed the bad arguments. */ + if (bad_args) + exit(EXIT_FAILURE); + + if (!pseudo_get_prefix(argv[0])) { + pseudo_diag("Can't figure out prefix. Set PSEUDO_PREFIX or invoke with full path.\n"); + exit(EXIT_FAILURE); + } + + if (!pseudo_get_bindir()) { + pseudo_diag("Can't figure out bindir. Set PSEUDO_BINDIR.\n"); + exit(EXIT_FAILURE); + } + + if (!pseudo_get_libdir()) { + pseudo_diag("Can't figure out libdir. Set PSEUDO_LIBDIR.\n"); + exit(EXIT_FAILURE); + } + + if (!pseudo_get_localstatedir()) { + pseudo_diag("Can't figure out localstatedir. Set PSEUDO_LOCALSTATEDIR.\n"); + exit(EXIT_FAILURE); + } + + if (opt_l) { + pdb_log_traits(traits); + } else { + int fields; + fields = format_scan(format); + if (fields == -1) { + pseudo_diag("couldn't parse format string (%s).\n", format); + return EXIT_FAILURE; + } + if (opt_D) { + if (pdb_delete(traits, fields)) { + pseudo_diag("errors occurred trying to delete entries.\n"); + } + } else { + history = pdb_history(traits, fields, opt_U); + if (history) { + log_entry *e; + while ((e = pdb_history_entry(history)) != NULL) { + display(e, format); + log_entry_free(e); + } + pdb_history_free(history); + } else { + pseudo_diag("could not retrieve history.\n"); + return EXIT_FAILURE; + } + } + } + return 0; +} + +/* print a single member of log, based on a single format specifier; + * returns the address of the last character of the format specifier. + */ +static char * +format_one(log_entry *e, char *format) { + char fmtbuf[256]; + size_t len = strcspn(format, "acdfgGimMoprRsStTuy"), real_len; + char scratch[4096]; + time_t stamp_sec; + struct tm stamp_tm; + char *s; + + if (!e || !format) { + pseudo_diag("invalid log entry or format specifier.\n"); + return 0; + } + real_len = snprintf(fmtbuf, sizeof(fmtbuf), "%.*s", (int) len + 1, format); + if (real_len >= sizeof(fmtbuf) - 1) { + pseudo_diag("Format string way too long starting at %.10s", + format - 1); + return 0; + } + /* point to the last character */ + s = fmtbuf + real_len - 1; + + /* The * modifier for width or precision requires additional + * parameters -- this doesn't make sense here. + */ + if (strchr(fmtbuf, '*') || strchr(fmtbuf, 'l') || strchr(fmtbuf, 'h')) { + pseudo_diag("Sorry, you can't use *, h, or l format modifiers.\n"); + return 0; + } + + switch (*s) { + case 'a': /* PSQF_ACCESS */ + *scratch = '\0'; + if (e->access == -1) { + strcpy(scratch, "invalid"); + } else if (e->access != 0) { + if (e->access & PSA_READ) { + strcpy(scratch, "r"); + if (e->access & PSA_WRITE) + strcat(scratch, "+"); + } else if (e->access & PSA_WRITE) { + if (e->access & PSA_APPEND) { + strcpy(scratch, "a"); + } else { + strcpy(scratch, "w"); + } + if (e->access & PSA_READ) + strcat(scratch, "+"); + } else if (e->access & PSA_EXEC) { + strcpy(scratch, "x"); + } + /* this should be impossible... should. */ + if (e->access & PSA_APPEND && !(e->access & PSA_WRITE)) { + strcat(scratch, "?a"); + } + } else { + strcpy(scratch, "-"); + } + strcpy(s, "s"); + printf(fmtbuf, scratch); + break; + case 'c': /* PSQF_CLIENT */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->client); + break; + case 'd': /* PSQF_DEV */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->dev); + break; + case 'f': /* PSQF_FD */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->fd); + break; + case 'g': /* PSQF_GID */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->gid); + break; + case 'G': /* PSQF_TAG */ + strcpy(s, "s"); + printf(fmtbuf, e->tag ? e->tag : ""); + break; + case 'i': /* PSQF_INODE */ + strcpy(s, "llu"); + printf(fmtbuf, (unsigned long long) e->ino); + break; + case 'm': /* PSQF_PERM */ + strcpy(s, "o"); + printf(fmtbuf, (unsigned int) e->mode & ALLPERMS); + break; + case 'M': /* PSQF_MODE */ + strcpy(s, "o"); + printf(fmtbuf, (unsigned int) e->mode); + break; + case 'o': /* PSQF_OP */ + strcpy(s, "s"); + printf(fmtbuf, pseudo_op_name(e->op)); + break; + case 'p': /* PSQF_PATH */ + strcpy(s, "s"); + printf(fmtbuf, e->path ? e->path : ""); + break; + case 'r': /* PSQF_RESULT */ + strcpy(s, "s"); + printf(fmtbuf, pseudo_res_name(e->result)); + break; + case 'R': /* PSQF_PROGRAM */ + strcpy(s, "s"); + printf(fmtbuf, e->program ? e->program : ""); + break; + case 's': /* PSQF_STAMP */ + strcpy(s, "s"); + stamp_sec = e->stamp; + localtime_r(&stamp_sec, &stamp_tm); + strftime(scratch, sizeof(scratch), timeformat, &stamp_tm); + printf(fmtbuf, scratch); + break; + case 'S': /* PSQF_SEVERITY */ + strcpy(s, "s"); + printf(fmtbuf, pseudo_sev_name(e->severity)); + break; + case 't': /* PSQF_FTYPE */ + strcpy(s, "s"); + if (S_ISREG(e->mode)) { + strcpy(scratch, "file"); + } if (S_ISLNK(e->mode)) { + strcpy(scratch, "link"); + } else if (S_ISDIR(e->mode)) { + strcpy(scratch, "dir"); + } else if (S_ISFIFO(e->mode)) { + strcpy(scratch, "fifo"); + } else if (S_ISBLK(e->mode)) { + strcpy(scratch, "block"); + } else if (S_ISCHR(e->mode)) { + strcpy(scratch, "char"); + } else { + snprintf(scratch, sizeof(scratch), "?%o", (unsigned int) e->mode & S_IFMT); + } + printf(fmtbuf, scratch); + break; + case 'T': /* PSQF_TEXT */ + strcpy(s, "s"); + printf(fmtbuf, e->text ? e->text : ""); + break; + case 'u': /* PSQF_UID */ + strcpy(s, "d"); + printf(fmtbuf, (int) e->uid); + break; + case 'y': /* PSQF_TYPE */ + strcpy(s, "s"); + printf(fmtbuf, pseudo_msg_type_name(e->type)); + break; + } + return format + len; +} + +static int +format_scan(char *format) { + char *s; + size_t len; + int fields = 0; + pseudo_query_field_t field; + + for (s = format; (s = strchr(s, '%')) != NULL; ++s) { + len = strcspn(s, "acdfgGimMoprRsStTuy"); + s += len; + if (!*s) { + pseudo_diag("Unknown format: '%.3s'\n", + (s - len)); + return -1; + } + field = opt_to_field[(unsigned char) *s]; + switch (field) { + case PSQF_PERM: /* FALLTHROUGH */ + case PSQF_FTYPE: + fields |= (1 << PSQF_MODE); + break; + case PSQF_ACCESS: /* FALLTHROUGH */ + case PSQF_CLIENT: /* FALLTHROUGH */ + case PSQF_DEV: /* FALLTHROUGH */ + case PSQF_FD: /* FALLTHROUGH */ + case PSQF_GID: /* FALLTHROUGH */ + case PSQF_TAG: /* FALLTHROUGH */ + case PSQF_INODE: /* FALLTHROUGH */ + case PSQF_MODE: /* FALLTHROUGH */ + case PSQF_OP: /* FALLTHROUGH */ + case PSQF_PATH: /* FALLTHROUGH */ + case PSQF_PROGRAM: /* FALLTHROUGH */ + case PSQF_RESULT: /* FALLTHROUGH */ + case PSQF_STAMP: /* FALLTHROUGH */ + case PSQF_SEVERITY: /* FALLTHROUGH */ + case PSQF_TEXT: /* FALLTHROUGH */ + case PSQF_TYPE: /* FALLTHROUGH */ + case PSQF_UID: + fields |= (1 << field); + break; + case '\0': + /* if there are no more formats, that may be wrong, but + * we can ignore it here + */ + break; + default: + pseudo_diag("error: invalid format specifier %c (at %s)\n", *s, s); + return -1; + break; + } + } + return fields; +} + +static void +display(log_entry *e, char *format) { + for (; *format; ++format) { + switch (*format) { + case '%': + format = format_one(e, format); + if (!format) + return; + break; + default: + putchar(*format); + break; + } + } + putchar('\n'); +} diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..0eced9f --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,48 @@ +#!/bin/bash +opt_verbose= + +usage() +{ + echo >&2 "usage:" + echo >&2 " run_tests [-v|--verbose]" + exit 1 +} + +for arg +do + case $arg in + --) shift; break ;; + -v | --verbose) + opt_verbose=-v + ;; + *) + usage + ;; + esac +done + +#The tests will be run on the build dir, not the installed versions +#This requires to following be set properly. +export PSEUDO_PREFIX=${PWD} + +num_tests=0 +num_passed_tests=0 + +for file in test/test*.sh +do + filename=${file#test/} + let num_tests++ + mkdir -p var/pseudo + ./bin/pseudo $file ${opt_verbose} + if [ "$?" -eq "0" ]; then + let num_passed_tests++ + if [ "${opt_verbose}" == "-v" ]; then + echo "${filename%.sh}: Passed." + fi + else + echo "${filename/%.sh}: Failed." + fi + rm -rf var/pseudo/* +done +echo "${num_passed_tests}/${num_tests} test(s) passed." + diff --git a/table_templates/pseudo_tables.c b/table_templates/pseudo_tables.c new file mode 100644 index 0000000..f802187 --- /dev/null +++ b/table_templates/pseudo_tables.c @@ -0,0 +1,43 @@ +@name pseudo_tables.c +@header +/* Tables matching enums to strings */ + +/* This file is generated and should not be modified. See the maketables + * script if you want to modify this. */ + +#include "pseudo_tables.h" + +@body +/* tables for ${name} */ + +static const char *${name}_id_to_name[] = { + "none", + ${names}, + NULL +}; +${column_names} + +/* functions for ${name} */ +extern const char * +pseudo_${name}_name(pseudo_${name}_t id) { + if (id < 0 || id >= ${prefix}_MAX) + return "unknown"; + return ${name}_id_to_name[id]; +} + +extern pseudo_${name}_t +pseudo_${name}_id(const char *name) { + int id; + + if (!name) + return -1; + + for (id = 0; id < ${prefix}_MAX; ++id) + if (!strcmp(${name}_id_to_name[id], name)) + return id; + + return -1; +} +${column_funcs} + +@footer diff --git a/table_templates/pseudo_tables.h b/table_templates/pseudo_tables.h new file mode 100644 index 0000000..6d0e355 --- /dev/null +++ b/table_templates/pseudo_tables.h @@ -0,0 +1,24 @@ +@name pseudo_tables.h +@header +/* standard ranges/values/keys */ + +/* This file is generated and should not be modified. See the maketables + * script if you want to modify this. */ + +/* NULL, strcmp */ +#include <string.h> +@body +/* tables for ${name} */ +${comment} +typedef enum { + ${prefix}_UNKNOWN = -1, + ${prefix}_NONE = 0, + ${enums}, + ${prefix}_MAX +} pseudo_${name}_t; +${flag_enums} +extern const char *pseudo_${name}_name(pseudo_${name}_t); +extern pseudo_${name}_t pseudo_${name}_id(const char *); +${column_protos} + +@footer diff --git a/templatefile.py b/templatefile.py new file mode 100644 index 0000000..2789b22 --- /dev/null +++ b/templatefile.py @@ -0,0 +1,111 @@ +from string import Template +import os + +class TemplateFile: + """A template for creating a source file""" + + def __init__(self, path): + # default values... + # no name or file yet + self.name = '' + self.sections = {} + self.file = None + self.path = None + # open a new file for each item + self.file_per_item = False + + # empty footer if none specified: + self.sections['footer'] = [] + # empty per-port if none specified: + self.sections['port'] = [] + + # lines appended to body by default + self.sections['body'] = [] + current = self.sections['body'] + + self.template = open(path) + for line in self.template: + line = line.rstrip() + if line.startswith('@'): + if ' ' in line: + leading, trailing = line.split(' ', 1) + else: + leading, trailing = line, None + + if leading == '@name': + if not trailing: + raise Exception("@name requires a file name.") + self.path = trailing + if '$' in self.path: + self.file_per_item = True + else: + section = leading[1:] + if section not in self.sections: + self.sections[section] = [] + current = self.sections[section] + else: + current.append(line) + self.template.close() + for section, data in self.sections.items(): + if len(data) > 0: + self.sections[section] = Template("\n".join(data)) + else: + self.sections[section] = None + + # You need a file if this isn't a file-per-item + if not self.file_per_item: + self.file = open(self.path, 'w') + + def close(self): + """Close the associated file.""" + if self.file: + self.file.close() + self.file = None + + def __repr__(self): + strings = [] + if self.file_per_item: + strings.append("path: %s (per item)" % self.path) + else: + strings.append("path: %s" % self.path) + for name, data in self.sections.items(): + strings.append("%s:" % name) + strings.append(data.safe_substitute({})) + return "\n".join(strings) + + def get_file(self, item): + if self.file_per_item: + if not item: + return + path = Template(self.path).safe_substitute(item) + if os.path.exists(path): + # print "We don't overwrite existing files." + return + self.file = open(path, 'w') + if not self.file: + print "Couldn't open '%s' (expanded from %s), " \ + "not emitting '%s'." % \ + (path, self.path, template) + return + + def emit(self, template, item=None): + """Emit a template, with optional interpolation of an item.""" + if template == "copyright": + # hey, at least it's not a global variable, amirite? + self.get_file(item) + if self.file: + self.file.write(TemplateFile.copyright) + elif template in self.sections: + templ = self.sections[template] + if templ: + self.get_file(item) + if self.file: + self.file.write(templ.safe_substitute(item)) + self.file.write("\n") + else: + print "Warning: Unknown template '%s'." % template + + if self.file_per_item: + if self.file: + self.file.close() + self.file = None diff --git a/templates/func_deps b/templates/func_deps new file mode 100644 index 0000000..ace0a2f --- /dev/null +++ b/templates/func_deps @@ -0,0 +1,4 @@ +@name func_deps.mk +${funcdeps} +@header +# Per-function extra dependencies diff --git a/templates/guts b/templates/guts new file mode 100644 index 0000000..e2c9880 --- /dev/null +++ b/templates/guts @@ -0,0 +1,16 @@ +@name ports/${port}/guts/${name}.c +@header +@body +/* + * Copyright (c) ${date} Wind River Systems; see + * guts/COPYRIGHT for information. + * + * ${comment} + * ${rc_decl} + */ + + ${rc_assign} real_${name}(${call_args}); + +/* ${rc_return} + * } + */ diff --git a/templates/port_deps b/templates/port_deps new file mode 100644 index 0000000..8f53db7 --- /dev/null +++ b/templates/port_deps @@ -0,0 +1,5 @@ +@name port_deps.mk +@port +${portdeps} +@header +# port dependencies diff --git a/templates/port_wrappers b/templates/port_wrappers new file mode 100644 index 0000000..7ff7ff8 --- /dev/null +++ b/templates/port_wrappers @@ -0,0 +1,5 @@ +@name port_wrappers.c +@header +/* additional hand-written wrappers for ports which provide them */ +@port +${include} diff --git a/templates/pseudo_ports b/templates/pseudo_ports new file mode 100644 index 0000000..113a2b6 --- /dev/null +++ b/templates/pseudo_ports @@ -0,0 +1,6 @@ +@name pseudo_ports.h +@header +/* #defines for port-specific hackery */ +@port +${define} +${portdefs} diff --git a/templates/wrapfuncs.c b/templates/wrapfuncs.c new file mode 100644 index 0000000..3859183 --- /dev/null +++ b/templates/wrapfuncs.c @@ -0,0 +1,95 @@ +@name pseudo_wrapfuncs.c +@header +/* wrapper functions. generated automatically. */ + +/* IF YOU ARE SEEING COMPILER ERRORS IN THIS FILE: + * If you are seeing a whole lot of errors, make sure you aren't actually + * trying to compile pseudo_wrapfuncs.c directly. This file is #included + * from pseudo_wrappers.c, which has a lot of needed include files and + * static declarations. + */ + +/* This file is generated and should not be modified. See the makewrappers + * script if you want to modify this. */ +@body + +static ${type} (*real_${name})(${decl_args}) = ${real_init}; + +${maybe_skip} + +${type} +${name}(${decl_args}) { + sigset_t saved; + ${variadic_decl} + ${rc_decl} + PROFILE_START; + +${maybe_async_skip} + + if (!pseudo_check_wrappers() || !real_$name) { + /* rc was initialized to the "failure" value */ + pseudo_enosys("${name}"); + PROFILE_DONE; + ${rc_return} + } + + ${variadic_start} + + if (pseudo_disabled) { + ${rc_assign} (*real_${name})(${call_args}); + ${variadic_end} + PROFILE_DONE; + ${rc_return} + } + + pseudo_debug(PDBGF_WRAPPER, "wrapper called: ${name}\n"); + pseudo_sigblock(&saved); + pseudo_debug(PDBGF_WRAPPER | PDBGF_VERBOSE, "${name} - signals blocked, obtaining lock\n"); + if (pseudo_getlock()) { + errno = EBUSY; + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER, "${name} failed to get lock, giving EBUSY.\n"); + PROFILE_DONE; + ${def_return} + } + + int save_errno; + if (antimagic > 0) { + /* call the real syscall */ + pseudo_debug(PDBGF_SYSCALL, "${name} calling real syscall.\n"); + ${rc_assign} (*real_${name})(${call_args}); + } else { + ${fix_paths} + /* exec*() use this to restore the sig mask */ + pseudo_saved_sigmask = saved; + ${rc_assign} wrap_$name(${call_args}); + } + ${variadic_end} + save_errno = errno; + pseudo_droplock(); + sigprocmask(SIG_SETMASK, &saved, NULL); + pseudo_debug(PDBGF_WRAPPER | PDBGF_VERBOSE, "${name} - yielded lock, restored signals\n"); +#if 0 +/* This can cause hangs on some recentish systems which use locale + * stuff for strerror... + */ + pseudo_debug(PDBGF_WRAPPER, "wrapper completed: ${name} returns ${rc_format} (errno: %s)\n", ${rc_value}, strerror(save_errno)); +#endif + pseudo_debug(PDBGF_WRAPPER, "wrapper completed: ${name} returns ${rc_format} (errno: %d)\n", ${rc_value}, save_errno); + errno = save_errno; + PROFILE_DONE; + ${rc_return} +} + +static ${type} +wrap_${name}(${wrap_args}) { + $rc_decl + ${maybe_variadic_decl} + ${maybe_variadic_start} + +#include "ports/${port}/guts/${name}.c" + + ${rc_return} +} + +${end_maybe_skip} diff --git a/templates/wrapfuncs.h b/templates/wrapfuncs.h new file mode 100644 index 0000000..1ce4fcc --- /dev/null +++ b/templates/wrapfuncs.h @@ -0,0 +1,12 @@ +@name pseudo_wrapfuncs.h +@header +/* wrapper functions. generated automatically. */ + +/* This file is generated and should not be modified. See the makewrappers + * script if you want to modify this. */ +@body +/* ${comment} */ +static ${type} wrap_${name}(${wrap_args}); +static ${type} (*real_${name})(${decl_args}); +${real_predecl} +@footer diff --git a/templates/wrapper_table b/templates/wrapper_table new file mode 100644 index 0000000..bb30530 --- /dev/null +++ b/templates/wrapper_table @@ -0,0 +1,24 @@ +@name pseudo_wrapper_table.c +@header +/* The table of wrapper functions to populate */ + +/* This file is generated and should not be modified. See the makewrappers + * script if you want to modify this. */ +typedef struct { + char *name; /* the name */ + int (**real)(void); /* the underlying syscall */ + int (*wrapper)(void); /* the wrapper from guts/name.c */ + char *version; /* the version, if we know and care */ +} pseudo_function; + +static pseudo_function pseudo_functions[] = { +@body + { /* ${comment}; */ + "${name}${maybe_inode64}", + (int (**)(void)) &real_${name}, + (int (*)(void)) wrap_${name}, + ${version} + }, +@footer + { NULL, NULL, NULL, NULL }, +}; diff --git a/test/test-chroot.sh b/test/test-chroot.sh new file mode 100755 index 0000000..02c9ff6 --- /dev/null +++ b/test/test-chroot.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Return vals: 2 - invalid arg list +# 1 - chroot failed +# 0 - chroot succeeded +cat > chroot_test.c << EOF +#include <unistd.h> +int main(int argc, char *argv[]) { + if (argc != 2) + return 2; + return (chroot(argv[1]) == -1); +} +EOF + +gcc -o chroot_test chroot_test.c + +./chroot_test `pwd` + +if [ "$?" = "0" ] +then + #echo "Passed." + rm -f chroot_test chroot_test.c + exit 0 +fi +#echo "Failed" +rm -f chroot_test chroot_test.c +exit 1 diff --git a/test/test-dir-move.sh b/test/test-dir-move.sh new file mode 100755 index 0000000..37182f3 --- /dev/null +++ b/test/test-dir-move.sh @@ -0,0 +1,14 @@ +#!/bin/bash +mkdir d1 +touch d1/f1 +mv d1 d2 +fileuid=`\ls -n1 d2/f1 | awk '{ print $3 }'` +if [ "$fileuid" == "$UID" ] +then + #echo "Passed." + rm -rf d2 + exit 0 +fi +rm -rf d2 +#echo "Failed" +exit 1 diff --git a/test/test-env_i.sh b/test/test-env_i.sh new file mode 100755 index 0000000..c38cf1d --- /dev/null +++ b/test/test-env_i.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +env -i A=A B=B C=C env | grep -q "PSEUDO_" + +if [ "$?" = "0" ] +then + #echo "Passed." + exit 0 +fi +#echo "Failed" +exit 1 diff --git a/test/test-execl.sh b/test/test-execl.sh new file mode 100755 index 0000000..1a1c580 --- /dev/null +++ b/test/test-execl.sh @@ -0,0 +1,21 @@ +#!/bin/bash +cat > execl_test.c << EOF +#include <unistd.h> +int main() { + return execl("/usr/bin/env", "/usr/bin/env", "A=A", "B=B", "C=C", NULL); +} +EOF + +gcc -o execl_test execl_test.c + +./execl_test | grep -q "C=C" + +if [ "$?" = "0" ] +then + #echo "Passed." + rm -f execl_test execl_test.c + exit 0 +fi +#echo "Failed" +rm -f execl_test execl_test.c +exit 1 diff --git a/test/test-pseudo_disable-fork-env_i.sh b/test/test-pseudo_disable-fork-env_i.sh new file mode 100755 index 0000000..1e679aa --- /dev/null +++ b/test/test-pseudo_disable-fork-env_i.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Verify normal operation... +uid=`env -i id -u` +gid=`env -i id -g` +if [ $uid -ne 0 -o $gid -ne 0 ]; then + exit 1 +fi + +export PSEUDO_DISABLED=1 +# Verify we dropped OUT of pseudo control, even with env -i +# This checks that env -i replacement functionality still works +# as expected +uid=`env -i id -u` +gid=`env -i id -g` +if [ $uid -eq 0 -o $gid -eq 0 ]; then + exit 1 +fi + +export PSEUDO_DISABLED=1 +# Verify we can change PSEUDO_DISABLED, even with env -i +# This checks that env -i replacement functionality still works +# as expected +uid=`env -i PSEUDO_DISABLED=0 id -u` +gid=`env -i PSEUDO_DISABLED=0 id -g` +if [ $uid -ne 0 -o $gid -ne 0 ]; then + exit 1 +fi + +exit 0 diff --git a/test/test-pseudo_disable-fork.sh b/test/test-pseudo_disable-fork.sh new file mode 100755 index 0000000..13a42a4 --- /dev/null +++ b/test/test-pseudo_disable-fork.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Verify normal operation... +uid=`id -u` +gid=`id -g` +if [ $uid -ne 0 -o $gid -ne 0 ]; then + exit 1 +fi + +export PSEUDO_DISABLED=1 +# Verify we dropped OUT of pseudo control +uid=`id -u` +gid=`id -g` +if [ $uid -eq 0 -o $gid -eq 0 ]; then + exit 1 +fi + +exit 0 diff --git a/test/test-pseudo_unload-fork-env_i.sh b/test/test-pseudo_unload-fork-env_i.sh new file mode 100755 index 0000000..d6658e0 --- /dev/null +++ b/test/test-pseudo_unload-fork-env_i.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Verify normal operation... +uid=`env -i id -u` +gid=`env -i id -g` +if [ $uid -ne 0 -o $gid -ne 0 ]; then + exit 1 +fi + +export PSEUDO_UNLOAD=1 +# Verify we dropped OUT of pseudo control, even with env -i +# This checks that env -i replacement functionality still works +# as expected +uid=`env -i id -u` +gid=`env -i id -g` +if [ $uid -eq 0 -o $gid -eq 0 ]; then + exit 1 +fi + +export PSEUDO_UNLOAD=1 +# Verify that once PSEUDO_UNLOAD has been issued, that +# we can't restore pseudo into memory +uid=`env -i PSEUDO_DISABLED=0 id -u` +gid=`env -i PSEUDO_DISABLED=0 id -g` +if [ $uid -eq 0 -o $gid -eq 0 ]; then + exit 1 +fi + +exit 0 diff --git a/test/test-pseudo_unload-fork.sh b/test/test-pseudo_unload-fork.sh new file mode 100755 index 0000000..9f79198 --- /dev/null +++ b/test/test-pseudo_unload-fork.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Verify normal operation... +uid=`id -u` +gid=`id -g` +if [ $uid -ne 0 -o $gid -ne 0 ]; then + exit 1 +fi + +export PSEUDO_UNLOAD=1 +# Verify we dropped OUT of pseudo control +uid=`id -u` +gid=`id -g` +if [ $uid -eq 0 -o $gid -eq 0 ]; then + exit 1 +fi + +exit 0 diff --git a/test/test-reexec-chroot.sh b/test/test-reexec-chroot.sh new file mode 100755 index 0000000..bde93f1 --- /dev/null +++ b/test/test-reexec-chroot.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Test if we re-invoke pseudo that chroot still works + +# Return vals: 2 - invalid arg list +# 1 - chroot failed +# 0 - chroot succeeded +cat > chroot_test.c << EOF +#include <unistd.h> +int main(int argc, char *argv[]) { + if (argc != 2) + return 2; + return (chroot(argv[1]) == -1); +} +EOF + +gcc -o chroot_test chroot_test.c + +# The following should just run chroot_test since pseudo is already loaded +./bin/pseudo ./chroot_test `pwd` + +if [ "$?" = "0" ] +then + #echo "Passed." + rm -f chroot_test chroot_test.c + exit 0 +fi +#echo "Failed" +rm -f chroot_test chroot_test.c +exit 1 diff --git a/test/test-umask.sh b/test/test-umask.sh new file mode 100755 index 0000000..48cb3b1 --- /dev/null +++ b/test/test-umask.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +mode() { + ls -l "$1" | awk '{ print $1 }' +} + +# Verify normal operation... +umask 022 +touch a +umask 0 +touch b + +case $(mode a) in +-rw-r--r--*) ;; +*) exit 1;; +esac +case $(mode b) in +-rw-rw-rw-*) ;; +*) exit 1;; +esac + +export PSEUDO_DISABLED=1 + +case $(mode a) in +-rw-r--r--*) ;; +*) exit 1;; +esac +case $(mode b) in +-rw-r--r--*) ;; +*) exit 1;; +esac + diff --git a/test/test-xattr.sh b/test/test-xattr.sh new file mode 100755 index 0000000..7d818d2 --- /dev/null +++ b/test/test-xattr.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Return vals: 2 - Unable to run xattr commands +# 1 - Invalid return value +# 0 - Pass + +touch f1 +attrs=`getfattr -d f1 | grep -v '^#'` +if [ -n "$attrs" ] +then + #echo "Fail, unexpected getfattr result '$attr'" + rm -f f1 + exit 1 +fi + +setfattr -n "user.dummy" -v "test_f1" f1 +if [ $? -ne 0 ] +then + #echo "Fail, unable to call setfattr" + rm -f f1 + exit 2 +fi + +attrs=`getfattr -d f1 | grep -v '^#'` +if [ "$attrs" != 'user.dummy="test_f1"' ] +then + #echo "Fail, unexpected getfattr result '$attr'" + rm -f f1 + exit 1 +fi + +setfattr -n "security.dummy" -v "test_f2" f1 +if [ $? -ne 0 ] +then + #echo "Fail, unable to call setfattr" + rm -f f1 + exit 2 +fi + +attrs=`getfattr -n "security.dummy" f1 | grep -v '^#'` +if [ "$attrs" != 'security.dummy="test_f2"' ] +then + #echo "Fail, unexpected getfattr result '$attr'" + rm -f f1 + exit 1 +fi + +#echo "Passed." +rm -f f1 +exit 0 |