pax_global_header00006660000000000000000000000064147037472170014526gustar00rootroot0000000000000052 comment=7be9fa759d0d4dca3b36d701c3fa71456b18bd10 pipexec-2.6.2/000077500000000000000000000000001470374721700131725ustar00rootroot00000000000000pipexec-2.6.2/.github/000077500000000000000000000000001470374721700145325ustar00rootroot00000000000000pipexec-2.6.2/.github/workflows/000077500000000000000000000000001470374721700165675ustar00rootroot00000000000000pipexec-2.6.2/.github/workflows/codeql-analysis.yml000066400000000000000000000027061470374721700224070ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ "master" ] pull_request: # The branches below must be a subset of the branches above branches: [ "master" ] schedule: - cron: '38 2 * * 2' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'cpp' ] steps: - name: Checkout repository uses: actions/checkout@v4 with: # This is needed as the version.sh script uses # the history. fetch-depth: 0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - run: | bash -x build/init_autotools.sh mkdir -p COMPILE cd COMPILE CC=gcc-14 ../configure make - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 pipexec-2.6.2/.github/workflows/compile-check.yml000066400000000000000000000052301470374721700220150ustar00rootroot00000000000000--- name: CompileCheck on: [push] jobs: compile: name: ${{ matrix.cenv.runs-on }}-${{ matrix.cenv.cc }} strategy: matrix: cenv: - { runs-on: ubuntu-20.04, cc: gcc-9, pkg: gcc-9 } - { runs-on: ubuntu-20.04, cc: gcc-10, pkg: gcc-10 } - { runs-on: ubuntu-20.04, cc: i686-linux-gnu-gcc-9, pkg: gcc-9-i686-linux-gnu } - { runs-on: ubuntu-20.04, cc: i686-linux-gnu-gcc-10, pkg: gcc-10-i686-linux-gnu } - { runs-on: ubuntu-20.04, cc: clang-10, pkg: clang-10 } - { runs-on: ubuntu-20.04, cc: clang-11, pkg: clang-11 } - { runs-on: ubuntu-20.04, cc: clang-12, pkg: clang-12 } - { runs-on: ubuntu-22.04, cc: gcc-9, pkg: gcc-9 } - { runs-on: ubuntu-22.04, cc: gcc-10, pkg: gcc-10 } - { runs-on: ubuntu-22.04, cc: gcc-11, pkg: gcc-11 } - { runs-on: ubuntu-22.04, cc: gcc-12, pkg: gcc-12 } - { runs-on: ubuntu-22.04, cc: i686-linux-gnu-gcc-9, pkg: gcc-9-i686-linux-gnu } - { runs-on: ubuntu-22.04, cc: i686-linux-gnu-gcc-10, pkg: gcc-10-i686-linux-gnu } - { runs-on: ubuntu-22.04, cc: i686-linux-gnu-gcc-11, pkg: gcc-11-i686-linux-gnu } - { runs-on: ubuntu-22.04, cc: i686-linux-gnu-gcc-12, pkg: gcc-12-i686-linux-gnu } - { runs-on: ubuntu-22.04, cc: clang-12, pkg: clang-12 } - { runs-on: ubuntu-22.04, cc: clang-13, pkg: clang-13 } - { runs-on: ubuntu-22.04, cc: clang-14, pkg: clang-14 } - { runs-on: ubuntu-24.04, cc: gcc-12, pkg: gcc-12 } - { runs-on: ubuntu-24.04, cc: gcc-13, pkg: gcc-13 } - { runs-on: ubuntu-24.04, cc: gcc-14, pkg: gcc-14 } - { runs-on: ubuntu-24.04, cc: i686-linux-gnu-gcc-12, pkg: gcc-12-i686-linux-gnu } - { runs-on: ubuntu-24.04, cc: i686-linux-gnu-gcc-13, pkg: gcc-13-i686-linux-gnu } - { runs-on: ubuntu-24.04, cc: i686-linux-gnu-gcc-14, pkg: gcc-14-i686-linux-gnu } - { runs-on: ubuntu-24.04, cc: clang-16, pkg: clang-16 } - { runs-on: ubuntu-24.04, cc: clang-17, pkg: clang-17 } - { runs-on: ubuntu-24.04, cc: clang-18, pkg: clang-18 } runs-on: ${{ matrix.cenv.runs-on }} steps: - uses: actions/checkout@v4 with: # This is needed as the version.sh script uses # the history. fetch-depth: 0 - name: "Install compiler - should in most cases be a noop" run: sudo apt-get install -y ${{ matrix.cenv.pkg }} - run: | bash -x build/init_autotools.sh mkdir -p COMPILE cd COMPILE CC=${{ matrix.cenv.cc }} ../configure make bash ../test/basic_tests.sh shell: bash pipexec-2.6.2/.gitignore000066400000000000000000000002321470374721700151570ustar00rootroot00000000000000*~ Makefile.in aclocal.m4 ar-lib autom4te.cache compile config.guess config.h.in config.sub configure depcomp install-sh ltmain.sh m4 missing test-driver pipexec-2.6.2/.travis.yml000066400000000000000000000002541470374721700153040ustar00rootroot00000000000000language: c compiler: - gcc - clang addons: apt: sources: - ubuntu-toolchain-r-test script: - ./build/init_autotools.sh - ./configure - make pipexec-2.6.2/COPYING000077700000000000000000000000001470374721700152252LICENSEustar00rootroot00000000000000pipexec-2.6.2/LICENSE000066400000000000000000000431511470374721700142030ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 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. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, 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 or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's 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 give any other recipients of the Program a copy of this License along with the Program. 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 Program or any portion of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, 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 Program, 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 Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) 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; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, 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 executable. However, as a special exception, the source code 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. If distribution of executable or 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 counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program 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. 5. 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 Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program 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 to this License. 7. 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 Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program 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 Program. 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. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program 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. 9. The Free Software Foundation may publish revised and/or new versions of the 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 Program 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 Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, 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 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 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 Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. 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. {description} Copyright (C) {year} {fullname} 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 2 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. You should have received a copy of the GNU General Public License along with this program; 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. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License.pipexec-2.6.2/Makefile.am000066400000000000000000000010001470374721700152150ustar00rootroot00000000000000# # This is the Master-Makefile # # Rules: # o NEVER EVER use recursive Make (cd xxx && make) # Use include instead # o Do not put rules / dependencies in this file: # Instead use the Makefile parts and include them # from here # o If unsure: ask! # AUTOMAKE_OPTIONS=subdir-objects ACLOCAL_AMFLAGS=-I m4 bin_PROGRAMS = lib_LTLIBRARIES = noinst_PROGRAMS = noinst_LTLIBRARIES = BUILT_SOURCES = CLEANFILES = src/app_version.c TESTS = # Generics include src/Makefile.inc include test/Makefile.inc pipexec-2.6.2/README.md000066400000000000000000000142211470374721700144510ustar00rootroot00000000000000pipexec ======= Build a network of processes and connecting pipes - and have them act like a single process. [![Build Status](https://github.com/flonatel/pipexec/actions/workflows/compile-check.yml/badge.svg)](https://github.com/flonatel/pipexec/actions/workflows/compile-check.yml) [![Code Analysis](https://github.com/flonatel/pipexec/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/flonatel/pipexec/actions/workflows/codeql-analysis.yml) [![Release](https://img.shields.io/github/release/flonatel/pipexec.svg)](https://github.com/flonatel/pipexec/releases/latest) [![License](https://img.shields.io/github/license/flonatel/pipexec.svg)](#license) [![Issues](https://img.shields.io/github/issues/flonatel/pipexec.svg)](https://github.com/flonatel/pipexec/issues) # Introduction and Purpose # *pipexec* has two major use cases. ## Use Case 1: Handling Arbitrary Pipes between Processes ## ### Basics ### When it comes to pipes in shells many tutorials introduce stdin, stdout and stderr which map to file descriptors 0, 1 and 2 respectively. If you want to know how many lines contains the word *bird* in chapter 1 and 2 of your text, you can use a command like: $ cat Chap1.txt Chap2.txt | grep bird | wc -l And pictures like this are shown to explain what happens internally: ![Simple Pipe](doc/imgs/PipeSimpel1.png) ### Advanced ### The more advanced sections provide information how to use constructs like 2>&1 to redirect stderr to stdout. And then you might come to the sections for the pros and nerds. There is explained that you can build up a whole tree of processes like $ find / 1> >(grep .txt) 2> >(wc >/tmp/w.log) ![Simple Tree](doc/imgs/PipeTree1.png) ### The Hidden Universe of File-Descriptors, Processes and Pipes ### Nobody will tell you: 1. stdin, stdout and stderr are artificial definitions. 2. Also the relation to file descriptors 0, 1 and 2 is artificial. 3. There are more than three file descriptors. On a typical Linux system each process has by default 1024 - which can be increased if needed. 4. From starting up processes and generating pipes between them there is mostly no limitation on system level; shells only support this in a very limited way. This is the moment when *pipexec* drops in: with *pipexec* you can start up any kind of processes and build up pipes between them as you want. #### Cyclic #### $ pipexec -- [ A /usr/bin/cmd1 ] [ B /usr/bin/cmd2 ] "{A:1>B:0}" "{B:1>A:0}" gives ![Pipexec Cycle](doc/imgs/PipexecCycle.png) #### Complex #### *pipexec* supports any directed graph of processes and pipes like ![Pipexec Complex](doc/imgs/PipexecComplex.png) ## Use Case 2: Handle Bunch of Processes like one single Process ## Most systems to start and run processes during system start-up time do not support pipe. If you need to run a pipe of programs from an /etc/init.d script you are mostly lost. Depending on your distribution you can be happy if it starts up - but when it comes to stopping, at least the current Debian start-stop-daemon and RHEL 6 daemon function fail. Also here *pipexec* comes in: it starts up processes piped together, acts like a single process and supports pid file handling. # Usage $ ./pipexec -h pipexec version 2.5.5 (c) 2014-2015,2022 by Andreas Florath License GPLv2+: GNU GPL version 2 or later . Usage: pipexec [options] -- process-pipe-graph Options: -h display this help -k kill all child processes when one terminates abnormally -l logfd set fd which is used for logging -p pidfile specify a pidfile -s sleep_time time to wait before a restart process-pipe-graph is a list of process descriptions and pipe descriptions. process description: '[ NAME /path/to/proc ]' pipe description: '{NAME1:fd1>NAME2:fd2}' Example: $ pipexec -- [ LS /bin/ls -l ] [ GREP /bin/grep LIC ] '{LS:1>GREP:0}' -rw-r--r-- 1 florath florath 18025 Mar 16 19:36 LICENSE Be sure to escape pipe descriptions. Brackets for the command '[]' must be separated by space! Definitions for pipes '{}' must not contain spaces! It is possible to specify a fd for logging. $ pipexec -l 2 -- [ LS /bin/ls -l ] [ GREP /bin/grep LIC ] '{LS:1>GREP:0}' 2014-05-15 16:30:35;pipexec;23978;pipexec version 2.4 2014-05-15 16:30:35;pipexec;23978;Number of commands in command line [2] 2014-05-15 16:30:35;pipexec;23978;Number of pipes in command line [1] 2014-05-15 16:30:35;pipexec;23978;[LS] command_info path [/bin/ls] 2014-05-15 16:30:35;pipexec;23978;[GREP] command_info path [/bin/grep] 2014-05-15 16:30:35;pipexec;23978;{0} Pipe [LS] [1] > [GREP] [0] 2014-05-15 16:30:35;pipexec;23978;Cannot set restart flag - process will terminate 2014-05-15 16:30:35;pipexec;23978;Start all [2] children [...] Or $ pipexec -l 7 -- [ LS /bin/ls -l ] [ GREP /bin/grep LIC ] '{LS:1>GREP:0}' 7>/tmp/pipexec.log -rw-r--r-- 1 florath florath 18025 Mar 16 19:53 LICENSE $ head -2 /tmp/pipexec.log 2014-05-15 16:30:35;pipexec;23978;pipexec version 2.4 2014-05-15 16:30:35;pipexec;23978;Number of commands in command line [2] # Installation # ## From Packages ## The following Linux distributions include the package. You can install pipexec with the distribution's package manager: * [Debian](https://packages.debian.org/stretch/pipexec) * [Ubuntu](http://packages.ubuntu.com/wily/pipexec) * [Archlinux](https://aur.archlinux.org/packages/pipexec) * [Raspbian](https://www.raspbian.org/) * [Kali](http://www.kali.org) * [AOS](http://aos.ion.nu/buildwiki/pipexec.html) ## From Source ## [Download the latest tar ball](https://github.com/flonatel/pipexec/releases) $ tar -xf pipexec-X.Y.Z.tar.xz $ mkdir PIPEXECBUILD $ cd PIPEXECBUILD $ ${PWD}/../pipexec-X.Y.Z/configure $ make There will be three binaries in the bin directory: pipexec, ptee and peet. You can copy / install them as you need. # Copyright # copyright 2015, 2022, 2024 by Andreas Florath License: see LICENSE file pipexec-2.6.2/ReleaseNotes.md000066400000000000000000000031131470374721700161030ustar00rootroot00000000000000# Version 2.6.2 * Fixes by-one buffer overflow in rare cases: When logging is enabled and at position 4096 a comma needs to be written, then there was an buffer overwrite by one. * Upgraded CodeQL check to new version. * Upgraded Compile check to new version and added the following compilers: - gcc: 12, 13, 14 (using Ubuntu 24.04) - clang: 16, 17, 18 (using Ubuntu 24.04) # Version 2.6.1 * Fixes 32 bit build On 32 bit platforms size_t is not unsigned long. Fixes size_t output. # Version 2.6.0 * Check for duplicate pipe definition pipe endpoints can only be used once. If two times the same source or sink were specified in the command line, the behavior was (and still is) undefined. Now a check was added that emits a error log, if a pipe endpoint is specified twice. * Added json logging Using the -j option pipexec now logs in json format that can be parsed. This provides information about status and exit codes of child processes. * Exit with 1 when one child fails When at least one child fails, pipexec also fails with '1'. * Check for rubbish on the command line Additional or unparsable command line arguments are now seen as an error. Before there were silently ignored. * Fixed false positive dangling pointer check of gcc 12 GCC introduces a new dangling pointer check which runs into a false positive. * Add compile check for wide range of compilers As public travis access was switched off some time, pipexec now uses github CI/CD for compile check. The following compilers are checked: - gcc: 9, 10, 11, 12 - clang: 10, 11, 12, 13, 14 pipexec-2.6.2/build/000077500000000000000000000000001470374721700142715ustar00rootroot00000000000000pipexec-2.6.2/build/create_tar.sh000066400000000000000000000007571470374721700167470ustar00rootroot00000000000000#!/bin/sh # # Create release tarball # PKGBUILDDIR="../pbuild" set -e if test $# -ne 1; then echo "Usage: create_tar.sh " exit 1 fi RELNUM=$1 rm -fr ${PKGBUILDDIR} mkdir -p ${PKGBUILDDIR} git tag ${RELNUM} git archive --format=tar --prefix=pipexec-${RELNUM}/ ${RELNUM} | tar -C ${PKGBUILDDIR} -xf - cd ${PKGBUILDDIR}/pipexec-${RELNUM} echo ${RELNUM} >version.txt bash ./build/init_autotools.sh cd .. tar -cf - pipexec-${RELNUM} | xz -c -9 >pipexec-${RELNUM}.tar.xz pipexec-2.6.2/build/init_autotools.sh000077500000000000000000000003061470374721700177030ustar00rootroot00000000000000#!/bin/sh # # This must be called from within the top source dir. # set -e set -x bash -x ./version.sh libtoolize --copy aclocal -I m4 autoheader automake --add-missing --copy automake autoconf pipexec-2.6.2/build/version-gen.sh000066400000000000000000000003641470374721700170640ustar00rootroot00000000000000#!/bin/sh set -e TOPSRCDIR="" if test $# -eq 1; then TOPSRCDIR="$1/" fi VERSION=$("${TOPSRCDIR}version.sh" "$@") OFILE=${PWD}/src/app_version.c cat <"${OFILE}" #include "src/version.h" char const app_version[] = "${VERSION}"; EOF pipexec-2.6.2/configure.ac000066400000000000000000000046021470374721700154620ustar00rootroot00000000000000AC_INIT([pipexec], m4_esyscmd([./version.sh | tr -d '\n']), [andreas@florath.net]) AC_CONFIG_SRCDIR([src/pipexec.c]) AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) AC_CONFIG_MACRO_DIR([m4]) AM_SILENT_RULES([yes]) m4_pattern_allow([AM_PROG_AR]) AM_PROG_AR LT_INIT AC_PROG_CC AM_PROG_CC_C_O COMMON_FLAGS="-Wall -Wextra -Werror" # debug compilation support AC_MSG_CHECKING([whether to build with debug information]) AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [enable debug data generation (def=no)])], [debugit="$enableval"], [debugit=no]) AC_MSG_RESULT([$debugit]) if test x"$debugit" = x"yes"; then AC_DEFINE([WSS_DEBUG],[],[Debug Mode]) COMMON_FLAGS="${COMMON_FLAGS} -ggdb" else AC_DEFINE([WSS_NDEBUG],[],[No-debug Mode]) COMMON_FLAGS="${COMMON_FLAGS} -O3" fi # profiling AC_MSG_CHECKING([whether to build with profiling support]) AC_ARG_ENABLE([profiling], [AS_HELP_STRING([--enable-profiling], [enable profiling (def=no)])], [profiling="$enableval"], [profiling=no]) AC_MSG_RESULT([$profiling]) if test x"$profiling" = x"yes"; then AC_DEFINE([WSS_PROFILING],[],[Profiling Mode]) COMMON_FLAGS="${COMMON_FLAGS} -pg" LDFLAGS="${LDFLAGS} -pg" fi # coverage compilation support AC_MSG_CHECKING([whether to build with coverage]) AC_ARG_ENABLE([coverage], [AS_HELP_STRING([--enable-coverage], [enable coverage data generation (def=no)])], [coverit="$enableval"], [coverit=no]) AC_MSG_RESULT([$coverit]) # Support for running test cases using valgrind: use_valgrind=false AC_ARG_ENABLE(valgrind, [ --enable-valgrind Use valgrind when running unit tests. ], [ use_valgrind=true ]) if test "$use_valgrind" = "true" ; then AC_CHECK_PROG(HAVE_VALGRIND, valgrind, yes, no) if test "$HAVE_VALGRIND" = "no" ; then AC_MSG_ERROR([Valgrind not found in PATH. ]) fi fi AC_SUBST(HAVE_VALGRIND) AM_CONDITIONAL(USE_VALGRIND, $use_valgrind) if test x"$coverit" = x"yes"; then AC_DEFINE([WSS_COVERAGE],[],[Coverage Mode]) COMMON_FLAGS="${COMMON_FLAGS} --coverage" LIBS="${LIBS} -lgcov" else AC_DEFINE([WSS_NCOVERAGE],[],[No-coverage Mode]) fi CFLAGS="-std=c99 ${COMMON_FLAGS}" CXXFLAGS="-std=c++11 ${COMMON_FLAGS}" AM_CONDITIONAL([USE_VERSION_FILE], [test -f src/app_version.c]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([ Makefile ]) AC_OUTPUT pipexec-2.6.2/doc/000077500000000000000000000000001470374721700137375ustar00rootroot00000000000000pipexec-2.6.2/doc/Readme.Debianize000066400000000000000000000003131470374721700167450ustar00rootroot00000000000000These commands must be given to build the package dh_make --copyright gpl2 --email pipexec@flonatel.org --single --addmissing --packagename pipexec_2.1 --createorig dpkg-buildpackage -rfakeroot -us -ucpipexec-2.6.2/doc/imgs/000077500000000000000000000000001470374721700146765ustar00rootroot00000000000000pipexec-2.6.2/doc/imgs/PePipes01.odg000066400000000000000000000370061470374721700171050ustar00rootroot00000000000000PKm8D.++mimetypeapplication/vnd.oasis.opendocument.graphicsPKm8D8SThumbnails/thumbnail.pngPNG  IHDRg?IDATx xTյW ISDz b^*n}$#>H{E&K)I9DQb3S[B-[wWOrQؐ!;M ԦTx{4e]'sDȥ2#8-х,^oʞw%IznK_L9OSD֛r9uJ/ߢu i6*^A{ߦyklɥ,?B~6/;tßQOF<cDO̖'鋿^-vWb9O~qI2ZQy!҇?K-R}YBQ̢ޡL!WP(E>""ݨj8JuԵ]LY pTA8* M趁1ItzqI~Mb!{o[ʽM.A ))qTuY]r?IN\H^'F75^Slkػ1Mʫr[ O¤JI=&R%UCq["-v}qVVnzuk0i&(9s).h̽#}4}q'[4lAOt?|RXҟL;nQM>̷-捉kT1)KyE\t|YR$az -)K軅'aG1vKLYBW;̙R(cɄ{ɮĦ 'a5}6^Y[t,SHR-Ƚ[(ץOBDyE8 , xNR$6-۰fƈ̟'Va+%ߙ˘۴[xqO"h@RH 0If ) $3`@RH 0If ) $YI9'TMy~k1OJ7SqKiI+bVRo_Uv久 s)n&.  0v~|f삋 @J*ta.ߛQ:nBJ*ta s* Ӆ遞k2L0ۉ%$wR&]J4 N!%e" >tF{J!D;OrT7K$.~=d2`IrVsKzyhjrݙS^N {,“ fP3<<1,m ISNK1Zs뽏 Zc07PXR*;И½A뜔Wq$ߠ n8%u[zmL, ~Yp.Ui._cXR;+8wBK|{ԙꍋ+&ыŒ:Γť(ktBSuh`:H-FR2=8~"kyLe@DQRA/oj nky) Ph&*\ݠaC͜kE5˸Ӟ"rz0TJ=*O*=O'ܠbšKY}Ϻ s᫞Υ9YTy'` @RebA`Av]R*)@n;Mۅn;$e  ؃".%ueP&t ;@.Ӕu%63vğ*SǮ:7u*$^ '>30vğN%^9D%&QF+8אs]~k ٹtTCm ʧO.G$ĵϠ'EI*p{wpc#qit)EK*{G/ny Z0۫AsV#S2"~JIlR ^RRzN!^^RFn/1()BĦ@RH 0If ) $3`@RH 0))4v1"(>~SӦV˒0͈O1Vj`$L3Υ@rUj3 JJL1#ZYRJéovV+J aF{5D، :rZRz}s@M4xuM|ny of4oARzZ3b%UԺ-}}XVi֗zP!%~X@!RC@3.Rk͈,ł2@|3?/\Pe 8{^UẌ,/D۹UG)羫4HU^!L3ִ͠}M^$sJ Soi_.Ax#_*  5AUj(ҼFB01^*56 QRCzL9Skc{@R=e;+`%:FBOM[*58 'IR_UAiX~"Pp=f9lR!0߬ F m H 0If~6'),끀3`@RH 0If ) $3!J9,SSIea'F"<{).=5=`W\ 0If"'),J:űxk)dF fIJJ \_>^#VȺe"=rmq}T%iJfRFkPTTҁ%/Pfu؅~fP*OГfL #= ceǗBN%5$ZiXc_5.Ԋ*ܞ>nMoF2>T)0 zeW^,eJ>iif$8NIJJN8mO/KN]/FiW?An3#ĆSayzB'aj :`)?ZgdLu)a*%_5ЎEN0HsX_6;pnWk>@N$18jٮaRUCI\9K8m |tY7е5X(r,KJ*뗞`PֲPd_|]; Z)6QfKj?GRfZ)*FHиm-gNyKaU= ab6I-*&̫e@#)(#)tQ qXAB]l I8BR2~ˆͣMMoճYb H}p}qVVnzuCv[~$nl\>ƵL\Q%3 UG6|96#6/RV]SYa,]L_rqϠ4Cz6x00-VtYvz3(;ǐ*bfزJ/==٣4gϞa~Wχ~6kHZS?غ:gӝ˔gԣ_ѣ];ľ-_Mw:uhKj =x!%Jl߮B-zm {eczTSޡϝy#_M͓=U\~oKWl_ojk6_v[*3ΊĐ]/viVlOzx/iԐ%/7B%2~.Sggnyڹ|dSC$Z}GhuKWRi{PSL6.}^};-Rj"Z<>ǔجCOΓKޕoOЇZ- hO'=åqU[i3xeV%5LJs@럨#V'ߟw~S8^.1N9?ߖp)Kij~8BlLzz>Q^#oҥnzRyrKJGհnHExZzDJ-T4>S5}-x,붃NSvL(eĆ[ilkAO?c~?Lة :DX@RicQ4θ!ctU"[:̙T K;F;\ 0If ) $3`@RH 0If ) $3`@RH 0If ) $3`@RH 0If ) $3`@RH 0If ) $3`@RH 0If ) $3`BIENDB`PKm8Dmeta.xml0} D `VaV[Dl/ldM5RhHGOM2[ rQm`r Ta 3gSChwJ`Yj(!XLL)xNc'4j.~ocZ x q(UPeE'3vv%լWLlVS=;*m4)՘kI)ϦzaD׷*l@dZCi)xskp])Jkb=v4eOO;l""w7T4R{*KRC[5ˊ$[`Qa"4ceFN H0qk!Y 7}b;FgQSNC_hX?fH큭`i>b\{Apw\tzNoň  q~t?p}yKi"ޔ/PKRPKm8D settings.xmlZ]s8}_aawv$m$t B qo, x< K]]^s@Nѫ\ȝ 3*7tOϕ?.#Q2? yT]ĉNEyq*qZf@`Q @,2 ] +.dg*72, qgO E4 Uא#LF>fgOEY^{ XL,^209+Y*VP֬nj_.֚r?ܠr4,W9EY~(~ܮq?qH `qV7tρd6xpT0 _ǎHoו6jTY2l6(~p]<Vsjmu׭vlw ͺ?w kkV 7h0v?AٜS跋 zG%QO [oYSPKƙT#PKm8D content.xml]ے}W$e;Er*]kwr␠D/o!ь"|I Hv1F#`y_y#E& $^ve{^AzOMLRҤp'M]{Maq1)nJ&Hrݰ7ս꒢|&W,wIʩ̔u߹"fNeT=H2?8s˰'C&nnvl5%WK8β:lG-ID͊%RвINҲ"%USrR tsZC,dtݯF|2*b*?*nyTVpSEi9UyyMfMiډJ^UU_ 6K3ArύNi<4CK=|7";mwo?xk;8&E&;qM~ @;Z7 'C aT2'Y ; t.h܄ږt )-1CjytzL149W|kn~CFVQvõ>(&̴9D4Lq@f7 7aZst~og!<||u2uԚ;1^nڎˮ F\>?uR}M]|.\GKqWβyE(<݄c/E? 7M_{tuyEz/M݇Q+ħHwMQ/"9!#1UࣻNڻWqݗc)w7eJ'Wt#d3ݬ]1]g]{֡gnNӜB_6,rƉe 6/8J!JU=/u 5Eа+<-vNvpMQc6iUn5 Wk>G'̮5jr=6湚$ݒYD4ר3\CrRz, I<u xqxhқ@?̖1#tn|rNMV]~$fm'UוZ}Y &GgU^w-|GmnSwb@T~h?ZaaЅMRS3>U"L~plo7ҷ2ejafL'&a>W6V4>c=5!(0=\ΈGvjͰn?jM]"m!Hj-] ~kٺdpa#C]_^jF[ZƉK|XҌ +ct1ZcaKVM?1USJ)^aVWц>R0Eo P@YMARSR}6%Um]Blu$¬ 6{ Y[幐G7!;[;_CA4HYAsd>X 8{Tn)|L",a˯%,_-uE~.v"h44:ؖNήAA 2].;ac0ў8L 2΢ե Sbl؆}4ȅE.,rkɅ]"b[w/ rg5+{TS4"XhR ȑ %_L߆-TLE7̴,^W)~)""uHj#̑:8a9Pc3aC/b/(V?)vѭEob= b+ "+>d vbm۹G闡FHPoQ"}' ٶq^K$מ$ppqA1?jDjy4b"h=nGCc]"n¸wyvLީ3ۭE<`,7lt髗//YFn኏/_p#&hIBu-\` }ea'IX|*S_#Eմê'=nЏhPѽ؎s/ӯ8cWpfcbH$" %g743! ~̜d/Ma/RߓgKCKa.d.ts s)̥0\e{'/a.g˹w[fk'tbR.Očp#v\ ncoLa/B;|k\ n=l{Xgy1f}V?@0Ùɖ6+y`R&~8ix[) gk¦\ pzﲟzE{2pzmCQ5.8WsۚsۮsW7Qw'[~(걗9km<ܶ\<s3 9_]>auDL-\AfLLӕH؞KALjlZ9Υ_X3-! QUx>_e|fZ[.8|t犎51398~>4c}ǍTz*GbzY1j:n$ϊѸr ;z)9*F!A =L,4p6ea ;8~4`qd*6bYA]>h/@J p5ms@Pl}CZ5=#IKqxLh0c9aPBg/RA Y =Lk( *R$V,qV< f҇Ԉ5;5b@*v/csLߋ_cc(31K2F<~9ƱӴӣR3ۮeS+<0|VƽfGZ=` Q]Ͱyc6H)!$[HâGֳI<޴qX> ! f m|Fg+‡$tǝͰs6F:IͶ>ؔB?Ӻ sWt' #7(LVdSo?PK+hPKm8D styles.xml[n8}߯<}uN3`IӽZlvK@v"ulɑ;N'3bɪSʻ2Yp]7rXgѧO7y'☇lpLB=$p`rV, zBЂXp!rU6BoeFbCkf{bjdmͥ;kf{v$ndL:HH,H(Ҝ*'}³/ףRuwx7 r|jj-pXh(tYpǾ[Lѡ!-RIL*z`\X@]a sZ] pM`?mWD]esS=ro?nkJCBT`5 =_QLkqϛeIã!Mqv| mkS x{f|0{pJ j@Iv ꔱViҟ2Zdu8/ra: ;|tj}jfs "OA!WQM;}$GMEkZ%ZfbE# tur꘎1 X7Lz].9sCq{*RȊe D)Z9W!-h,yv·r}!JsQm hZ{S.Ĝۻ^W<ϑgѬ&2*"ޱMxn_Z|3 ^9;#xN9ό-!a8GܷghX{^\/+Ev4'xsyd:` 2HOeg;2 9d{y Ce{$q>1rbIZ2Liѕ|&@*/_։Ō%81Or(X`)"rsFJ7kmI (Q!1aXik7Y6xk4ǓR2 zmjB%ea9~,*(.b RUztZ5;u`*H޼DR k ՓhM.#iXBՆ`VIe \{h \1;TuE)w??/~ L*zÚs˚֤5iEAe;$ p@`" Fvx'cʤ(ʟz8D-p#ӑ5#֠?5BHQ ”7vF !-(PJaEKXWkb x[N)rFM|$V_y?x$h=(L$a1D{<zQ;szN3%Pj[_n_rFw`t)kѤN9ُ:ʎiz3pY2(YChr6|ozΆP0I.ΆMsy>|h:B7gDmi;D?We^G[ 7T;+TTe QVD{T)@)uc`|~ԣ9[տ(6 74Qk)65)D=([88FFѫ :KDCBi廅n[ <|.)F~x=,Rbw9˶UJɸ*6 rz_z+!+FBez#sL Lb9*$Q9H:;@{Vٯ5ʡ?su-KĚȼ1>@ThECb'`]Tbtb%OT&A%حp:c)0U1h#z |v^Pl`AB齅j ɤN2v?.mIe@ ͋J@ @8Y@ @ N@ @8Y@ <ȩjث\łGb\܅Kndإ(4 y 8B5tuI.(0 2BUVP jvj=L >+ (?{NvWh0"Y6JCM0,Eo.L5a+U8e&IX\0%-*Z/fO2S uBYx˗]8VP= FjOwI]}pW[5^+N wO1҆$YXņRƏj7LjD;P-8P❞wz.äo#[8[Y|GNem( B]QVZj˫s:W}4rh[按Q8[j;IOɲGv!1!RY%&'uI9^yhdޜܑl'LHr ]l$L%t.M(7RjIt_!g{nB0v[a)j>!p Yy]bAN:&i&O(=1,d>X̲%{ Ih-=<04mH@q}6;%AB(v4gDg T+%c`z!m3gR>$vU/RJp6%BJ:n Fe (.)duC㍟,>>BX-ur|ꚾTz {%.f. S+'|Evjf20giR&*dl8_Gq߈(\GnJ6I^FUaWʪ0q i;R"ʘzl'YemӺ6k d'jr*qtPIb͌ӓ۷Rl $}pb[>)e GK&CZHƻ~<6Z!f`2tmODH+n;#VRbgGU~nŨ4RXCKYw柿z՜?>ľJDדIg!|'+o*ؖу_N&w}"{kYoS/b稩bZBJJX,*o`@>¼r.KkUog0jnEh֗oou!tsLHGH 9̈MݹevI&62ɣ{܌Pu /m$V:3y,eofBLg}ߛՅw-hCkrC>rHdɞik9s*;zHl0ѱ,9|M?h#G'@-?fjb*WMK̭i%!RMX@h^'e)|c3uZ`8cbϡx^&O4*ՁwL'xãvm,>Q$AÎmE6R CШ|KBxl NL1ATX#]D33 bfF,y KzatnT >x@.+잧N?ϞC,t,G'gp#o`6"4<@Ž 3Iذp,^F,,Ox_z\$W:O' {g}B ^F,;ʳ_|}8Y%Tv%߰s[{^2Λ-O0~\Aa|(EOX]+K 7DKW$dn!ړ G'I \gvKM&3d R]J^/7v.е_% r~gtxj^y:V3<&-Gv ;[In9!hAq?@ﯚݖ&_K*%"^s/8j6]Q9.F/ 89B/<4Y9zKZCfNN-JtsKkMT{ o>+=mj!pAʹ {^<1Zpv0-U:PUmAѸ'KP'>1dm\Bɰa6ѓ%_&lfsSƜ$":$ص͒!@|/ c0rl@r+% ]yFnNVCe0"8#C>={U6a¯y(^K,,N=wnC Co*+n&+\}W񞽹e/mR^q҉}Gg|ӝ6d]3 8`/1v]97'nkáÄx.8sk%Lܗ=~KʡHՎ@>~l1J+tP]S{oVkj껡Ca0?5W=7 ?s1j= UG\~4gLe.l`cř}U/^!חw#99Wzg2E[{W+iSr+i6h|}gCGsx`}soj˧gWĊ> jv fVivB3/PX oL`3NVmMpKgHOU^]AY1,[_,=1kz>A\HZ(-;a,%+}wZ-D*{ _ (v'ԋ9] RC`5WoB`1Î_!?wtg?7Bv!(B ;ƒCIaSw4vrr7ŎNR{=(dU=lh0< TV*`@.$5+dZgdPIQso ܙdO:箁 xN? [ܸKIGAdj=.V*v]4!#ʕ>jc {jEqrjgÞ/w.ds[;"Ї@u??r#*mNttaZƃzj- '6VӞpT*J󷋿-O;Ѡou:*K oK]TȺ]wu06 o8K `dɺK ¿^:Y2\qc] {jge🝄{t|2P4H!qh;;A>/1[$k#rL\ʰZJVLؾ>z^2Dʡh=̀W'=.y>P9S{aꨰ?ݢ, (ȾtdJ* /_eUaΥNXuR5O}'ѓUѯJuy困3er@)VuL6/54-;$^υ~. rEY Q_*C{(jxһX2dxaM 4\ѓUsïN[)tÔ \ t&ЪjxY-su'/<(K{2xUko{5(gnSWW)Ѫ}l5zbڒ"jB[DQ& BQ7'-?) ei}ӍaR-u~A hVHς9* iOמOWR}aZ_ed]Щ(K0e7*{4̀qJ]XedY TVPet+>%oF)CS{^wf́{5b,[t۫(O^K]EPz}>W<ѨARʊQ9i͇|+oҐxxWӞVzRy(VIR$y2FeϷ^wQ۴ɲ/G-/a ME6c4R^i#p4^Hvy+O{z12jrwvXU V4BlSkSKud{U'KȨ V{v' vG~ F lYLU+/i.\l8*˕I+Q}l|s,p鵅9W崫شxBY\rr}q:VSZåmR 3x_!zpQV6Tc9rs%Vj3^帄.F֯<%u e^nG]e[ '+-EȚʼnB{J0){x+=d7w@&Ɛ"ȇ*o<T[\V$t:;kÞd#+r(=g8 M'4z*89Xк`fO a+ ׆)#O·f\fBxmFNS;FCᓞ.Í@膋(ԫ!:owjpX#>N!ձKUzRͶlL%=G;%š8F{Cv;$0U(!  q? 1.ҽ7&*S/gwd7 "907*eDVY r:%I).l٪T!^dջzmjxG_AC%YW–ޜ,naaͣ"_xTs|s,E߰MFwNE#;Phb?r#  ܕ>ܽjGO!bReR sF#w zwlvΝ'?/˥L:uF+Gɡdǒa%Q6CI> he!i}ʛ~o{dCK,<쳨f_6v:\~{Dxg5*y'H݋+F+KDy!>PQ ·+jCJ:d*g2eBsxsky4*mL+e1L&˗/K.tСE|T~'8*luଢ଼aQv Fs}k|`8֎*&}hM/;Y'/dƧ2mg&buOk(,zl/Z݂7)/ՁYEv,qђ$Ipppμyh׮]dnd؟#"ΕM\YzlSaWja7k-@u(eƎW((潱/dhR~i%!X6OM, dW_q˒$I#FࡇbرTͳȽ`S8un+ң{.LET6PW`J矸/Mo\'ȪӷK_L^)) =Y5vh=ٳqrjXkG_I/sC{SjO_pdp6ȠM|q7Y[؜? 2cƌC;yi۶mu|Cb[($#1~էNa|P^ɔOѰhSW[*X];T;{Ν^x;+J >ٿB&Y[p-s\˙ca A+^ɑX㻺bevA qqG/_i=믿.]w5vV)tkNk8a;`5ƞn_F.GJ&yxkeԷlf`鷨F)$!j wYlfkD8/G}Z[RIV6 Vl惌({x~ԝ/ 5<?jKåL/4ƈL9ʤ1g腏l?Oɑ J v)̚5k䨨(SNuϱ`|g嵦i=$ݿ! P^#z)1UM']bK]oYV֮]+5Jر#~!z}u]?)'HBscW=WgtM݃Q$o;FK,)Zk=;Fp0PpRlcQɉ3a^ڻkD|$wϋ,;vL7nNj/ry\IT{ϼBBm>ٛ !HSb44gQYmkY?~\PqjL1Iψ-fKcb=#9wn1[̖FbMrP^&ί[Eװ)xZ 96Dݨ$(:}X5r~a{؛zʾH~d777n"'碑=MкM'G#ށhr^ ;ptM'wJWs䴮UȬD~뭷XyҤIlڴ=AߢuUx lc ۢuӀ<=Y9yduy JOwT!rμbF(/y<}[c4Yl"'$$e2 c|09)G'ƨ*q$F.9ʞ, +W*wڕEQ^EIע!d gVhS} uIR{6U&qsdUיF_k3,5T}8Uz=~-~׳lr$ɶi'2,V,ƨt*Vƌmʞj[Y>,}.^[P%aO6Oe!W*"KEaiU9Y&#^f4]~q>NVLf=6Dgc2A]^I[Ҟ,*FznqI-좸u.uvvs.WgsdSuߣͅFSʾ^NF:\-at‚A+A:SI-"Tŝj1 Y%[4 ߦL ֠jq{*1xbvuJlKjOctMMӂ ŞygHKKG*B`1H]^Bk%WDoۣqz9Yי8mgjX0uWVkGy/OI~o'oV%;I֭7n$''GzG$Iv%)<Pbªq\Yo{UOuKt¢E`RppآU'!- _r5;܌VӸz9Y.*M+Xם[M9X}[h יT[GHa/:Dh[NNN\dffrAiСxAly4՞d$݅=Z/cY.ą{۽=T*&L HJJ*]l@`۾E &`z`upJl+ $m)oIiɁvd&Ԛ!/o!O|[ on`CűєF !,Nx؛ݹn"9T(ڵZ-fB׳b )$$Fг?3zbU-fO+#UY '˖ޟ-Zfچe̜9'Nj*iذaPwe[zbʴk( ua dKZǖ GŠ1O{AU5| F)V&N }Ԟe@96FzL<(r| hgi׮=:N3gTg $2@f>-fO (6Ti lRO_d7Gg$=NadPOtt4s%33sJ111WTa7Ֆ2,az  lcO'F`\jU,reDb˾N֨ɮޯkTgeFX},}o'}y]c:X[X^*v͟ʕyn1$'GÇLjjtM75ț{}SXݞTEL^X6HJ^;EzIrPŔ1v]2+WrIfΜ)yzz#Ih&-=:m 7K(QG:JRTpI݄`|b=O|XnΥ32a֎ypl):/aC,#FZ0mk,777NʛoFiRE6Ut|'Ƿ%x3t2 m^(2`J]W6[7oZF綡^p!”)S9s&]vm-e땟X49̫BW|W~#FTi= 2z^w767=L4΍q(,&]FS g)DRdfs'@t=K4iyٛӖA) b/LDhh׼!d]YI([Qpo86lYyjch957&7^pcVEޟ6Z06=k{I>lZ_ƣsְ,93g VAn{ %#8JxQ^f6brSe^Yہ[HSGmuCr.(i|< $/$V{k?c0RiYvО XTbR  U+aR5nQu;vl{Czlrg5B).oLMqQ_qtɑdzE* ԕd??!P;`&cOG%J6ʘ}-FS]ȲNb^'n%5.Gy(/\)=vc+Rʛ![$E6>NLm븩`#N/N eÞrːf ͸ҽN+M 7]I#JֵWgF_r*B9YSf:Epo@3o1ȡ]B) ˃WWp UMTf)'& ̷NjYl$1xBMGZb,xRh#4CY+Jmbi{:7t$Fwz`R wZ"ǍFJbד,xFn1&r_oXU™QI/Y"9Oҝ״E%IޞM2=ŴG]|ٷx~(NKq(ھ84sO?s\)NueT,x;&>[Jh닧cgsm9NyT1F6h9wm4{0tY5 *x>gsl_bLoA%L-֗!tX9].gURRQJ_wcՕžΓbj!Je]Y5U&4j'|<] z# ez#eH|&]jNtԯS z_U?P>Uj3z)%p-mKd @ 7@ %@ ,@ d @ %@ ,@ d @ %@ ,@ d @ %@`;?iTÞeIENDB`pipexec-2.6.2/doc/imgs/PipeTree1.png000066400000000000000000000367411470374721700172150ustar00rootroot00000000000000PNG  IHDR?bKGD pHYs+tIME . tEXtCommentCreated with GIMPW IDATxwxnzB%z(]kAW((bAޑzM!!l?dCBly}93{d=3g("B\m:!pBHB!$!#BB!G!!Ⲭ hT@jARE;γxOY^1LRc@s'I՗Qp6X[>LBJ.ɟ9;V%'؏"N:91b_&l '6 4\ytd ,$!.EL.ޭ{лe;|>^pkV[M oKFR! :ORZHBXעm~ i$~Q_v#rLL}{"ͤBn~GN/%o7Sws.%K+ب`'C;+͚S 8BaRq:YG*B.X"G[Nsxs0XP`4UJTF(9%DsJ`_2,ZF lEѣ{Ke4p *wf$ !#>>:^?ӗg?(V*3sNz8B4r]V$Qt8NNgO1Fƞݭy8Gni뗖ݑ^yһ!DI0B_wۆH4P6yEo#peIm_{+{HEKF, 9zHe4`ϝ~Z-W 6*f +>GNKBԏr..h/@[-yo.YI=F8oe6qIs]j^QTHTFdE9-=>Щ%_9[KCT<겻fP>=JB4v#Wq"ww\Y_6i`H88zԉڲ̗NrGWZOR!DQ:UI˓ך_Ѡ2y6OBHA%1ޝ}W- kS}M7pJ!hH]cyK-}u\3r!h< SrB>5 "l[KBXmA{[mdCa 5[QOv~HBG57#xnLK怾@ENWeB4@2k737ËǡO$Pl+åO~# 1WHQFalD\1ƪ'䔚B4@No{;SE tkU/,=!hLr̅H} 1e@<'!ׯk+D=ΔMr]zEKB4RKpvzCy9l;CGW^ϑ&?8Uod r-0 ^O\B4,o,nq.=N }G_g*(Ah fݩ]a[б7eòEt$wmEZPc9Ձߍ5.&ludAQIң7#{1?v6fG@!D3-lrٹZKCٓZPgټ\KGܭQoz>&ρLSX>y [k&n)%0tŮrKG!^#v]$'RRׁtxdzRK'+'t[e~:ӭ'mv%!nl60fHOSzmp㐑|i@*-zI _Qk"U#A^var޾M`,ծ( w_y\޻x:ANDݠiK pwf'Z{Թ!Dà[d%iz 4O1[B~xr$™Zw?-u^rDQSɥب`pȺI`e_ |6C0a+ag)ônu^LsLL̦o{Gg<ܛm==&d.5!>ӎd%ogl=iɴ&; %,v%)|qyol֧7`t: m+Y1̝ ͗_nMMЂW0it٠_l'#@B>xgco]J~%Τz*ߵ9`-GQL2iCD^uh? ?r[%vd>lK'] G;dlŒr`9NeP.*۾̎}Y{)7ޑ= pE^Č:DOCfMf3;I*y<}#0vOCv==(qݘ7e8M}%PGѣrwbggWسU^%,V4=z ȏ)k^[BxG{zr(?@:5߶#5'㏫^^^<#έw`V5 6 JnrG~N[ cI72~zC]d F|GCwV|L"8z 0}>Lx|c[l |ƲPCBi ?jVͭ'5Fis!n4{VٳgZrP뾤廎`5>I{o68c!X4zc4Wmx$jgܯjW]ỵ(*M%aayՠ _c=>U._MKg<?Opqni˖o6_~s*MRpMƱ0]O "xyQCh㓡n9,GYii)_׏KR^^z2spIgHxe ߗѡq;,ba1wW?7gn6xm%hرCPΝKfvoZÁGsZZDǾ`m'pJJ Nh~ ѿW8hUDʐ#DcΜ9SmҤ <ǏX5X]'@KéAN~uGсTX sAnR^tKUcYG*BԂ(qZqqxbjkiU_: ১ba]TBPTPYOzN ^u'Gz8B4`sQף_6V5ȬlGW!_XGz֫pO$D-\kѢEuvt3sF dTSiO]^dڬ6rX [orJ\w:2qwTL딚ԍ*`$e(eZ_\_v6\='܏VtTUԩSiѢ~\7:a6{.eODvtٙZڢi?RD;XFIC8:m> 8L[YqJz5e 4ű=fUb.@=şSQ'__ $$)SCRoGShKi2zvM`.- oW~x3 ĤǡVбGAԄ>m#G*ΝwR;6%!U}#J̾[Rϴi7nz?Nl=3~%ܝa?iۯY/9lpąӿ_uݯlI)7je}v7!"zdJ\![[[>ϖ-[?~r`0o#P\ ak|mMWGo,@y|9Y,ޖ@WhvG1F8Grʘ`'Ψ::'v渟RP,jԆ qqq|tU?vgTwwͳt. DqqG ZIҹ9768y߬QnQʩ$5)b g^>Jit҅Srwbkk[ژt|9Oګv|zM^7( Vr[}[ZȀtȚ-o˕iQozSSTPC_Ψ56>1;ZRAF+Q|7'_0wx5:ڵ+3m4Omz2qwG2>2 +d:݊[ڮ営kvȬv<ɪ/@y>XY)Ջ7~j}wO{yC^+*CE7ز#:/y},#h˴ң]EּtW%S.gd5% mLZ{>l[P3\c ݘ7ef/vO/w۬_boURjyq>D I HW6x$l V5_ҕ/O#:eܣG~mi&gǻ/a|uW\·oaY=|>cpsJxNRo鰋MsfYxJK-T;bv⠵Av  8ar Qv08'8'm%X+"m~rK[ܜpgnT N) T.KjSڣl㱐 !0~ 0*Fq~wt_-37sQ\VgDtҷDt,Z̞"GN%+A]o!~NTT[woDZyڟroW*e4%pTMᑑÁC_cZ) ES8l9u!^ZZCў"Gu%">WozO{8r`CdK)7M <63`Ul*/{yk3wt/p.6)sK(5V`okA>VESP\}l?&瓚ULyI,w{B_ת q)%=c g;Bj:,?<r4P?%eL|;7z>,Z Z;ӡ{[0iEKa8e:r۲o!JE}pޤo,j &u`˅3l?k=NԖVuζq ߬Z#HN&0fyj75b&]Po[&b,S7BHV{i\#nkc>]-|J!.X2si{7i6Wu+Մ#~W[_Êح0.fK !G4V%VzwCa }0/ EQTKyI !G\C&cE&r<&%s& vB-+B&TE5p53n}~/>_T`Qs%L&D!/j%Տ3YM/` ^1Fn;gQ܅#ca8y66ְ h߬v0wZa^RBHU{ 8p|…5ü)0z¨@umY7E{]kkծ)=4߶刴Ⱦ?DN !=Fdt峿g}ncIK@##_aLr !=Fd˯Z:M^J"Uzˬ-ϓUZonޜuBNcsa.;{ 46my77:Թ,(Zq?m}Um+c?/Vl uQ]r0 g\a) '^dt &ȵ!nXr *(_v (> l@xUmg~X5GCywN,zt"͝+?`L-L9N65Пڃ*/IO=!$Z{A-p\TR~kUdxzxC;ĩQ3[6!ߓAZ2! (3''2G-;N3w+-qwcF ꏻS^.΍xa!_ԫB+~PW5m^pDy]٤$ϓ,$#co-ynzBHu4-ڬ_̑zBHu> Y0 '$hꍟEy=BrFT`^1Y}xK(ʇ`֝?|4A|S?ƚ wgZ"%v6rO͕NmcvʑSټ5+8ڔsζ%.${q:+b[d: lejsS\nyE9Y~gCw&Q >֔9hSbW|@:6)eT ypjʲX~mU\ުO{;ӱQk0i;|'m,(TqccV̭Ct7+5QuV6gV\yܜaTUg=AiJfBhO~cbhR{rȚU,bL̸/D*ZLf>N_'CKXG ;o=[om_ΤvrpĚ TNd韉9f旀oh:lqRt8;ܸ`Yyg*TV)7\wwuܴA?dN)rPڴ@o^E~rO3iˢI;4!'ğ"g:p_зbʶ)u$vH79]yo#LdWqitCya <ݜp7Epc<Oh'56%>J\e:xdRYzW{|j8Iqb7y{u S"GO$7Vkwg1#b1Cx&Νip?(nb_7zQ$jBd5:G,CilY|%\t0&V3$|[&8?S}9+ܖcrbuZDbpn)BX+"$;D* I#[Ȣ_ƒL&D9V1@qSnc>o\ ibo3hoS # ˹:83)^s|Lgݾ8^WpʊB+aXx [{Ʋ vM參)VrK0QM2%N̝>}+ži{GSWhmo_NWt:vad&l 8kyެvlNF 9o3,_OvU:L|J[B54 ntl ;ksP!%b(Pqs_wE>ucRU3H-! d=> 3ݨ]AMTO) @Md\쏔(%:#14sːHQEnJoFLVݛqJWVpܨ筄r~x{zJ.7hVp"{:JZK泽𭸸x*3NՙC)_{@v8E~]nR Ԅٷ5󇿉M%˲۽0UXҡB}]LoL y_Q3rK$!ꉪb FN˫Hp4tj A03>6^f<)G!q?s6# VWMZ<.`m0ul6?S8dKB,t 7ڀkG4b"GVKl 2 %!DcĹ8/"^Gb4M7CB!x+K-}uzaSB4@ix;dxv{kXET+=IBȶ?oi/l7G=F 8B*.TyG4} #@iX[=\{!hRH+Q\xd, />Ƴ:{ t#Qe J3 (3]b>vˆlXÑSjB:OU!A!lǬ0tJ=Q]z8B:84j|Ň'!x jKvpzɶ^SAᧅp_w ]A7my7E{K Yw/{@3sh6nC`v%Cqu"¸ybEƖpotU{}唚²dQpbߥGn^eAwI8hJ#ԁKG^kk|٫{'kUwu:=G"9*f远 פt73bGp֟ qm_.k.DMXEPUUQ>Ӓ,xoƯj7[NM ;@0ms5,x0Hr/^O8 t`+4mOAoghppM=k08Է`-ڶй/,Vc]E.pO3:"&0iˉW5Ktᗵ}`s)|Jt9g^WaYw:OWq?ͤZOڦvrxrtf^9h3tk`2xjle&ZTuZSF 8E.#3c;;G< jwg5FX6UO;so~-)q$-AvHqn Ha'{Z2#6Y|6eg3zkmnj{.z8q{j5څWŲOO\ӄ!b+i}g}).#5OC62,r%:3T p+a@'o!$NV&-Ķ3-ѭ61Z{ŢPݤ*Ho}Y3PwHe~(5 o#vk,Т5۳ fBv󬙶q<eu`ed:G3[7TQ9s.^oÖdS)sJ,r"ph |c&O |I}wOƽDۥ#J0;q0kKkL >t*#V)v6=Beܼ`גfy`j2|՜<ښgֽ΢g(-]m?eXAQQ};vT \ʧsM}9CW 6g¿PPaU3#fƲkS1ASB"Om47Ǯ}`E9!ك޻lP 6nWKo :RU7qFYf<ʃ>K~U~*dUymX v0g21L0mX @qH9،=^J18Oh/Kb=720cfgÿ^׎Mݺklkq#9s O?ڤI'$::Fe&omՇ~rmZ:~y#'OcUR*da@lk&u]f6>`3.vO.?mQ꧇+G … 6mڨFbڵTwuދ}n^~q0&^.`IQ}]y?vl9b-KGU!ӗ<`SV V%Fi ]!<]Ʋ J&D-]z2L꯿Jvv ~='VV :2l5c oTtX-wʛFPR]5ٖR=6ѧp!WWWڟ0i\.]<[u ޸` EuE(R oEQHUUhѢ:fkkˤI8t7o&"bYT,-/< 9Afg.Rׂ~?~JG4Qu:؟ngW򬗲$aaz=&L`DFFr*666)͖׭- R^kn:VO[ 'Wd)U܂¦3T?m/G s=Ǚ3gXt)}5dnD9MI/4욥sU6R7/H +4@mџ q}*v*埫rePWz)OE qjՊSr@M%цI]*9üګ60{T Tf }?:jMckh *#u( Æ ?ɓL>9۟TF|A;%H^d*A3CȒƛw&>F-y7Gy˝0::;8@olmAoЫ+VZ?xK Krl63?١iG;XO2¿7Oe^*ЕoQOat}L⣃Oqʼi\(GP^Vd8uTۓ i3S*?ƺBɡX; GPnʈM*F=Aj?9M}(vuU'g-b2aSXCvSѝN_;oaeY6EƩ#۞`He5@ Vq7qR[ mM)SB՟ѽlv +"GF(+J)(.w'[8(3@m_JB8[lKn!צW&U BB 8B!!pBHB!$!#BHB!G!!~l\1IENDB`pipexec-2.6.2/doc/imgs/PipexecComplex.png000066400000000000000000000761621470374721700203450ustar00rootroot00000000000000PNG  IHDRU6HgbKGD pHYs+tIME##tEXtCommentCreated with GIMPW IDATxw|Od #bjEj5ZCV[FJk5jo*"FDDd?>V|<>K>y=\EUUB!Dh$ B!$B!J!B*!B!AB!UB!T !BHP%B!$B!J!B*!B B!UB!M뫀ߌ5c׷-BnqV$'[HWffiXeP޴%`/1(~Oʼ8GZoCVX(*V)89d+~^<Ѫ#nv0BTtSUk\v]\ʒYdgk6 UDZҘ~Jv ($(P6`6/*Z3;eHUHXUHoÝ:\]qT&~&\ Q->XK!ԧH'$7|Xf<6ƞ{`ka$"o/E:8;GJT lҭ7Si!rPu~ {.@_?[RVo+O$'o}ϻsڭVIj(3Ә +2w\'vU.JHɋs~i#jk%UGgWDDp%a ge1tRe o^ʢ]06ԓDh^@U\+^$!wEοo|Vyĥ UM% | ]R$U)pbSsg]"Y Q IG?`ivyl$F 2𙷹y-I !BPMώ&WR2|&K_T3>fԧ.Ĩar1'fh%AjO! 5LRrrG<ݖxD:oK8:FKb@=pI !*;{6R?,Xhu磥c#yEmBF:޳QRƅ*5)E%IB$TUn)FrϝkoRzMgK_`kX'aڞТ-"[?Co?;*duvؙ.L=oQYYR[Vpf?܉jW><d^gK>d@i]WW31y=rO}o>uԨQJzLT :Vsůe¾:ps*/╳~Z6mѣ9w\m<'9K~$5uQwKPYG7Ԅ];˅m?3MuUFde@T7˱RegK>U5r}* Y׏z-*\/##Kv]5k~YHP%xc TUA[{=6OmPZ/346]P_X O˗-ZP;w/Bnnn붯9;|3a`Ii<[ 9n ?{B.^g12yX~?ТdeG bflKd=_uʿlͿ^oiVSK_9ݾ}w}^R/\O#EHP%_`T;H[;c2\3]ْo,;~g՜\\ t!uȐ!'s!!!HYݿ,0 {]AJs"L OvmM|֯~yr /@ c?&`i 6в p= ~ 99bc^q4u dŮ%>ӧO3|pՙ3gqqq1UUVlNE5x1HJC+n?~@x*xjySgpbˑRu骋+\/y |-wAXo[AP߭m6GKGׇ ੱWieۗ^eJuOU 1xk&$elNN6l]vnZ]f- 0xs07Clz=/J3驽0;tltgaT[h,a| شHNW;:c:¾_> !|bJ?Cz\ 򂪮JKt0LFzH7},8ۃ98x]7B,C{Kv:[Lm4]nh} )_g}x:zIHH`xzz2tPRGJm9zHOi i|>NKoBaXzX:'~I.; 8aJ *3ϥeo^qyuq(bm v4)Dǧlg*!RU}K0aG߃>LmԱ,۩iUwH`Ҽ%R-C"D5r)[Bkzx)ֲ; _/>zmg?؀ j} *V+U@x{v;欄"Fk /%]~]Q`hE䃫y udp9|#J/tӎ}v/lL@ݢ{gfwuiꓒ!B"ܹ^zѨQ#~U*!AU5sI9ƦẌ́A:&;k1?L[66 | >7j .~ ?v ,u[O;qθYG+ڸ> nUo?}pA"5B׻X?_OSt?W(8K/[ ݹZkedE7Exa m{kt˷R~‡`i+?M)kFm]E 5%ھw, ohTvB:v;U~Y7˻Mp;GtfȧlLD7 R_SzzX$f[2kn|?$քȂZ̔A)P=z4RU-m 29zY2qѕ=s!A8Uw2)6 NoRJ~TnݺuVBBBx ] oŸ h:yOnTq\lk+t-T3׀F%UhZ\IbeOٴiSeٲeJdd$3gΤN:RUՉO^GO݊?v/sjo姙W@KZv& =sB!=yO~}Djn#%!266f̘1w^h4|v-Sx>E7毃gZJ7_BxNZ~Uxk`DK8 ^|W^9,[8?7$[zk4o{{{eƌʍ7u SHPUtyR7]57WW( wK'hς.INe3+?LLhs-WzG@#O84 m=7Xhն}8۸ Qڵk駟ɒ%K&M(PxqS#}|T~ܳeZBֺ/3i֯}ԗ6)B5uF;jIg6'8ՔT ~ƍM,ӶN :T9~r)X!AUU9ݘ1ueepX6Fקb?ݡ`Hѽb:ݨpxoU#ݺOm|K8 >`h{RV\t\ێi[B ի wQK/ <4Y}9L P_od,-ſGw{-RѽfgJݿǟ43Ks,:3B!Kx*Z$*VRn޼OZM)˘m/.ZgϞ; jT/ɓ5 t}.]g'C]/76Щ,;`e[,a hQ썺 ~"(ы})l7/)QC2}tX~=m۶-_q>1&w`ͅXk /1ȡGߤ94F 3J}r=MWl䧛)022bȑʙ3g#GOcjj!Y][D*{|CfPJ9(r_.%8B:(&,77== {|:&F7_U|磠1\abb&>ىMFUY4ZD3i˳w E R%AxD[|&J 'ؒdNqQ-S#}Ij(~Rl[1{lya<%Q ZjDi ذ=+[W7d`'wTIP% cط{]en(BX*q>\(Ovt-k\S2=2k)>vW 9# %7ѷ::vڍIHp`1DExL9gIB#:,5y=;)BI!BHP%B!AB!DM!}BF%Y[%o-"UB!GɖnMlA$fXҠN( Ѡ)jGJ !ABi  E OBԱP֩Ls.zsf+=1.Z\ݮK" !AB<ڲfފ7q4M`nYԷ+pYKP7o{7к= F+ *De@*ڇ]QAaHnnyBnZm='GC/㏚xvЀ 49 i C^#!ԝE%#äǠjBQNReo{ݭS̹z%."<֍drzhU 憩[~ F_?'xn5WҔ;ħYU5÷^0-7ߧ苣G166HL6Vf7s6-/z;F)S>:4 dܤOJb|'[)BTzPd܎rU;޶ϭܕucny,%[q'ŁSQ~8t빅vQ%:#[P7lNqgU[q6go7f˪Ѥ<Z;Xi V\FP~DծDGU%*1]&wYo@ v]p1 GmX'V|iw˖_G0pb˝[-pw2.Dy(ӻV騟ԑ/|U>pmG'~=2/ؚ/ֺ!q,: S9ԭ^%X/җUGt< bx!ߝ}1/CC?mP٫GK8>3IrtawbezIׯ̓gX0>ǑM?nVK.(:ʻ cʲr%)ɺGeϱ0~.S-+v@P>ɄfkX0CT~@ϖƱc)5@i<|s/RdRQ7=O5 c">-KIJeO8yAOdC)%)rl7%JITQt:Z2zi 6k0L`, IDATM/x\;օd2쾅܎T^#FTl g϶/ěc`cTu"c`—it~~=Q70fSNEvNz1W_g͙jkM < ;+˗=̖Қ)-ʁl`tU7mYou"z}Bg\o !D \X8놩J'iz&B@brˎh;N6[1Z)t{To{|\В( 9c3fQOm4"hG/tRζ)hg ?w+'"[tv{Ap1_u(߮},# 3+WIݸ].,GC_[Y=`70"3Әk-ҟ[`1ShHfr!SU\+?N|)KP/euZ1hJqF YҲ1+,/sF h[ \&,J\8h8t͓N~NB Ci ܜ0Dd _؏"U>ĤZ[˽_Gr؛#:eNdi[0qc_&i"Q F=Rr4}vƐݼm2Faw}!Zur[BTcN슁MmK^maL؞wkj]WVJTa#‹37uZ(%ڮƌǶQ2Ɇs93dO%DM~'sDMQ"aN@l-hjoq}~BZUgBxwuX-YΪQwn3Fzm-U6 JW6_Ud*H$f>$?ө"Fh?CFvRFA<,| !Q:}%BՋaqHQ8ǒ2kٛ\΢[:q0磪p8+viH}r)s/ӉDo޴!26]ѳ^BȗyP,p-*I<m[#?"K_:))Y/^x \͕vy٢Wt˔BĄ(AU$&&⢦P~}ҥKh4ɤBBcPmK@HHm6zB!:Aguhˋ[no^9rDBiBcP-Z֭[ ѣ' !Boٳ !JvM^=HFCHH]/4 !~*RW_8O_UB!DU*88???c411!""B_!-BH~4U//==yɕB!DU*::www5++e퉈PLLLE_!XUٖS ..KB!DU*%%}p\rEӓ_!-BH~|dKKP\vM6B!DU*''ooon޼YkӦ 'NvѳDBʵTm޼Tɓ'9p\U!BHP5w2W_rYBU+)Bpp[}.4 !~*RUЋKBUUiB!:JKUvv6jNNNeccCBBB%BH $?VU@ XxrĉBŋ/Lnb !UʏJ\I%L1hɐVh0ŀZ1 $A~BUTU 8=\P=nEٓn*% 0KD-:4Maj`/#%LZUbKYR]]Ӽq< hI0GɏT96֊;B𰉤%\ncg əfIBL}45n7࢔*L?hU p20-RmO_эNM6 K2AH~(ASqe%]k0 hӄ5`,c([X)eULEWVl|L3^l'Zlö^<]շ\I'$? 9eggc``PaiKN1;,RJ~VYϿ5\xd X+z&bv3~N{`;נI@Evݖ˭1wn(6Վ&MllPjQGG:W3'NB9r$gΜ)Kƨ=3Tz\F}pRSpqx;<w ;K7c-V.}މebT%YnaW{ԔlB!AUfff?j 6]/4T介~}p~E'ݧ0o-ddh=aE.*PGTxY!$ ;СCUOOOu֬YŕrK*+6RLx|6.\4E4z3պxU|Z󗐞 }OVO3060sr+S v$oۛE.XN P6!QBHPUsܺu3f...1cԠ|y|-wAXo[A=^ zΪ 9Ӂt D<^*g~-x(t{TbV7|ʏۃIJۀB!AU ehڴc+En9}؛wC[/gy *\ŮO9F)Bg+C;?: CkxF^бݽFʀMttӭ3#uu\j\@zQ9 =6Ȅ6 ]Po.pߩEp~;.UB $ j0h [;w..$r7[N ;սuM|,4$*ʭLr^?7$]4lXP邤S >$'{+\: _OupBw{.-YXFqѺjX:I u!;_mz=D<OBh1XªO`W% >uK B*I/<<7xC[:n8ʕ+L 2p*pW=>8 rx KL&61C B*hHMMWիcZ-!zvGDFV ܼ]߹Z7{XrZwuv @\XMw 3n?L[66 | >7j .?[~BO( &f`a =`XU[qj_"]Qx:fqfBHP%-{nx QFQ8-< KMUUp ﷎][mMG `kDݴ|'zyCl%ʿLVX^׵CcqYw+3dٯuB -*~Ti'IKB*QE(9rHʕ+^Qju!,ܴ}٘$bYc բs=:~=fiϿ<8MZ}xXu֞ 0 T뛘n ϻc`'b|BHP% emm( {f߾}mMVN|XOʋǬ g#R4{ҭoQD2i;yReK'} /teƩfVx~19L,0 !$޽{tСL233cܸq\|;v(]vU쭭M-t=!,Z3찲N(-+ux_M1`7O$>TsBqqI?RK!$Us֭[|wJír-f&|wݑlݨcMtVRe^w+QerkYH]7.ib\َ6*61=B qwG!A:tƍ~2m4/F> JbFvM笀 &A+ٯO]ooRO^GO݊OWvB^raz:3| V'7VY}QpB D chhs=Ǚ3g8|SO)zzz/}}-{/pVЭ5g_SOg\]G_B-b㝨Wbϫ˓骹旼7]ign|ͧ-90T@Otݼc޸X]8[Ź; I2<[B*Q9::{qMV\lٲ_s{vRV &+G?i#k СnrK}~}Ӎ u,L|\7fV&ootOn]$^Q{ndT8]=C)~r=kwrst/w놏pv(q(+ tHQZ)OvpJSTz=cՏjՎ;>0BӦMyyg#ҿ0n7gBzG|`|xʴ0'$3F[ ^~ߙzi&^Gsbw ;肰x7~~-7b]//$CuT\ʽȓj@גhx'ٿ?ʨQP9kWtr;(%~ij(s@~U. ;Ky0Hf0wh4Hoj~maF O:Z uks` ?p5IFQo9tIaFR0ivN>͞={aÆ)+hm﷩oVmh̙6 dU5?#5 c̲ټ1Mn4pr/.$? D~FيccqV{Mxr*d0ʗφ5/q7_uҼFϊQY߂_i4g'^]H~T\ui ^6ak* +]̑:en}Hs__vlʐ&3w+Ÿ-q>}" &5SQfҚ:fR܅G!A(-`+j]$:_k/M/caam_ 9ފЦme$bjI+@::NqƫFqONF@HR3atw zEʽJHP%J\|#A1^WG%a`h5Mw3Ij}ηҥfGJ%zzҪ=hc`J)rBUrAE1wCj5eH0&X7qsz񓊔{9w!$?$( ,I8}r$!d*!B B!$B!J!BHP%B!AB!UB!T &JB`Y171B DE$>Q'[I!JTF5XXH|m5B!AH[.5!u9|چn-kKb!U"K=w]Ĩ|HBrm|BHP%*O(kVM!7W, IDATO1UnLX?!JT-ҠN=uKbj=֮nJfΒ B!AxX>َ׺+{UTm9QwswG6BRPTU^(*ro HHd¼u;~c%Q~^1\囉XJsBc)K4b]2룆ai$L5`/a>`kMCSM;|0/_.ֲ/]YA||<۷o'33ȀPRNix7chos/ 03Kĩ e=Ғ]ImεЖ=TJ{/66ZjQqjU/;V]hQlk۶mJ߾}% DR%MǦNoR3!qʟX35&!4%Xk05ҧǞGԦ-[7TB+N\\\(ڴiS_lаܶ%(C)-UB70y2UDoquuUʴ#Gb |W2yOQƌuѷo_|}`&&&L0ۙ4id$!$B<;vl~N2*VeE?~bbRWwޝ͛KT !L8C ТE tRmޞQFz7xC2T !3=\Z[Oմo'OV4WǾSZJ!1eʔʠA{{{VJĹq"s33o=(VV\\_β!˵jJ]@l#7O Ӡ'&ŞZGvA27)ubQkV#R!Jq2O&,Q5[uUrJ+_{ܰ1==QWmTw\iߍ7.QyHlsuXs'{bo/eP& =B|rz0&Ck'VoPΆem~J (nzZ ES7t!$vlOSAy9Z! %boxk:(oyxLg_Ԗ'j16fɅJΤ㰯\)ûI },jX*j|={eK]Ks}y\#A.shXbZLY|';)-I( ( 8[Fbm^uv]d|uUG=μ{(6"rttƯP'vP_&Ȅa;P(NJDdYx[j~Ji^m,iM*"e|: *5.`QW@Fǚ3LM0i2*z,/CV:״>`C?Lˋ6ugN|>|PPbFS7fʊŧ(rM>jm- ,~ET 1Ԍp>^rKh-<=fx;8sa3g;~A *~g7T?:XsaƂc2>_M4 Ŷǂ]nu |(񁋪{q+W2^5K?A_y0mh?1 i5Pغ}CetӰnXYWcf ?M~7șTɇ$fgJ b&[k?߸шxr|K:KԪt E]n<`EO͗e*ԅpL}z5~]ݼϳrGV B)f%(wQ{ 7'E'ھ?1~z/k4 b͞kew G)&~193YV EǢ]nPQeU;N)xRUi`0@qNMxA(9L<& T3>WG05rOrnEgOM z߿ .g?}AP4{Nc mű;,.LQ*Z >Рvs)$N }\u5_؄< 6@}Tʘ_ >mzg4X z)"cqNXs< ^ 0{03:F% B 4 7ۼ䪪M'r+l}XT6~7BX+P6\챢  |ʭp)jO"Ĕ A(ĦXao16݈ۦk[>2)ؚ'u*/,C2 *SB3/Xm_Sda%k BTYy%3R$槻-ʖLl0JMS\17Ӊ eM{/KA8*w.$#sjC&~dUHCnPa :dV4>-n-rK&225ֻ:\R}WyU&x%)Q)~8ڛ l,1$p >0{,J.)JVS 켴]ǓrLB]uMuu mޯR<ږBD)o2{M :{g*]|sSdP,O T2/11N$'[~j8Z7G`wqmMn;aΦc!L%^֬Z_ga?~點* xnLiԈ.0/!컲r)Ar!)і򗏦z=z}..pR:7EݱkWT0 55'ftЪq>Svvwp 4ÛvӶ@51g`Eo1NF2ky/ NJ}#XjmmMy89acst&gNΦЯ3L[_,v0_̥ڿtoY11)[3Oy{jxԮdo੧{uw=Ɇ(ʕz,;Y+WظD⵶jZ -Zg89k6E)uacifAt3WՍZ2wu,Cz(Cr_J/4iƠ?Q<,CDreѕ_iv+ )* >е%}F~wD'h5َ 9AەG,UйVNj@:wHHp-uWkuqqAcDa W?FWgx\+5by*nn!жDo/G뽕ꮶz'L?:W ÉcT&&tn'{lH ~O:UӱV)-c>YE lz18N2Eȗ2|ZZEOx7_Au[зneynu07F5+.YgºC}ys̳ݶB7KF7<^%4cV d;x {Q-,Ù %=:KgPFNyM/b1zfڣ]!lҁ1y[[HپgCaf_R2} Qx;ݩedI9Hˊo՞=о}cݻc0\to6A{lڞJzMVV𣬚Kދn5],C&o]mj]%eK 81swiPlQ5obmyoE>G.`ܶU(K3|S~CS. -[~xI2C[Rl,9=*L1,CHO|x}{tꛟ3y(΄-2L>> 뿤e-.nY9ࡷ;V3WUg-X:߶]xy ˞OI`&;!?F>7[,n$"T_FG{NVW xzv^Rc%3I㏭N3+yEe+s揉M+yTLjʘS8h |YϾ ooϖ+|(%oyj6b4_wI'l>vT_EҠSsmw] 'MTĿJ'u(@p@&MeƆPL_*z@|?_wŗfӬ> "ot"-yoM!"E91}/Y4k: 4+W Ug|?a/hx*UBip6[ pJ,okNaEDg&mx5+07/G2IOuS?#6kNetCY]<țHKvܠm]gi%.`nިƺtՋ(M+_-k-9smG?r̋#9g4sFIlϙhy~nxj~~Ǝ1WUaľ?v:uLn뱲*ii7;kkSXEu\Nωќݧ;bœO.׹_WZ2qyYɮ5mXod){ӹu˃c~&)Z'`0舍w@Q x{Ӡ1 uBVn%JFr#̂ի_f)ܸ^ u6V dfiu8Ͻ :sTzZCG6pxviƷ^ # {LMIL%>ի_)^zu5kṆ{SѷûYY,nf( cx= {l= &$ؓhTr<t|(8U;*W({x^}X*'٢gboS,VwҪN͉s # xlmKa8=>x*GRDh1 [8lmer|Ž(]l%-@Y ~X<ٸNDAAD  *AAU  *AAU  "AA*&B<&Ng .UY +s1JFJ(Gd\*zK*ZhKz9ffiId=MαkOqXRֆ*Y6foߩ!,:.G&"3t}-w3ǂf]6claI Pн5T/W>P]okj6NDva8YhCl ["Q2QbET=wiH5PWoT{/4J6 ަ &z]9P[# uaU,m4mvcj!"b-bZy$+:׈E_BMv  T'gXpF#֜I`\=eē~<E^.ػ;lTLS`oIVވ? 0Ѭrd;ZdP*Y$0Zy'[qzc~ͭZѽUo1p|jKiM~4ϲ ?֜I@\5+:`G=Y_,Uٯh{:d} ﷟GG \D>; 'go׺am!+'jn>m(Tab߫عW59 Q[KI[*g^8U'B}|ߛԩ|b2-7U_,R;𯱴P mv̀#Cd(XXC0hޥ* T/$oݿMX8k<#yOwo÷IcY;dfJKe^\UjJ0*QL>+ӻ[3?[3y&3ݪP9sЯ_?K9sggO UՐWLM o0v~qO.ͧa~bpr/L~tzxc>{`?yTVQUƸr}6z6w\:&l;7# -.\?C!"4;}כ)~ȟHON͚52dzС%-Ug_+Y5K (3415cs1p紷 \dpDU&<ޙ:n͇‹)Pypw!+< 6\,W^wS}7 .7lF=yݓݿD 6x<| ? 3~Α͗i#·FjP>XXA-gxemEkJU!3b<Ǵ #?/?Ƃ_꧜Pi ۾؝~BW)|ќ?CP{:{,ÇZjL0AqD{agm#r߀o-ؿ* bawl7u4lND}.\< zhD&OIDAT\djrڜ=D˃N ܊N)%%%wKݴiS+Q)G*tIPa.s4Qp: yT&C *߶Cp Ug?ٷ" 7ὧ6N0;fAunйtqҝ߱[W‹m t7zܽ^S48ji^VYN`Ͽ[{͟0Zj &o ξ9E#s?YClEݻՔSȊ;նUqgK?6~`i>`'q 7W[dϠ@{#&CHk V6ؗniw5wdS Ƌ_UUٲe {VׯΝ;Ul٤6v;G=@em3ܜ| r,目a!miǟkj][BET x5Xtx͠ 4C:7$ABn#p;a09''hZM]K9fy`mxl=փgIš*;o+njD5ݡ{﵄ܹsW}aZBȊeij7gD|Z+ş\`v/{G3ޛ9yiip hJ [kg/4U33 ”=mGBH,.K.1j(S}rJdw`H?s=䰿Hݛ`r))Vm3<RTefg &B}M x^5'Qc0<aa7=M`,zZۿ萦3i97 kœ'iBi:4/q9`/^r-9C>мZi@X;D=8oìXQ)Iu06ZPkmǽ1b]%c#KUoooo߾l߾{a׃nE*r=O@@ZF2w>4Ia9SuEkp5CMm4{ߚzWO_-QUD6-P -ip.0r* Yk;<SC`n >-`\7߫0e im tZ8 -h}#~ w=/M0>bC6WdZ d7/" U|9)nQpk+d=p hAjpɞiڴ);w? ++K$4:۰jOQec1YmlvDFVϥjOZz`jŞB' ֝SGKʃ #4L﯎1BZ*|-nw_oMM|s'*YmFHkl&Ұ?URa(KQ]kpC7;]^ p3mGr6r߷+)eWyG'%`o{Ulm0ǹo\oRL`m h]װ&OĉTիȯc>e9ׄ!/mw5t5g aH/My%8QbE_jΞwʒ|An8XD˗Ϝ9SbN6M=p,vykamӍ{k[|¥-c$߶ioX* fڔ ΁C%3DT=pb'%tr+Zz|̼ʮ9Vo)Z-ȑ# :sss{JpfyHaڼ_;yqDw@D<?kb==An\Rvf@@o6vvv VSBni6ȹo68y_j;Nam3BMm{ڌnVK*RTyg֝g|%M=N~ /y5a^"'B}1:o##.>y>MێLY93 R`ix/{xw̉w;:G͕ŋ+׮]cҤI"?6͆(V`ڽKгvl=fв2)K,yOUWg-L8ͤנi=Ugh~wiNeUS3kQ5[=m~Uo~}V "Cq>v<0F(IUL2`.\HFn aU*ZIܻ@זZb p՟褭0t6\I@kpdm1ҶݞQ=TyMN̓1w˽i|m3B*].@:\˽FW4JqVi1|ü~/7{@ ^ NZ~^?)^VޘNE⒙iʔ2W4v=_?Ơ#YAgͱK9\~]?>9gf RU&ꨈgo|D}E+gGcK<0%iVa:mVL-ltVpSʰY9R6I??*zѣi 3 JiJmJd6H6nfN_񴎮_Zȼ'K4ܮ~=CZSt͋qѕ&v0{L^M:hV6>_MV11`3 🷉O+hA nÀε$<==iӦ)!!!O?m  3=c5S&?Kn$ Gϵ%.*K/kuS3m8|tE2O_!5d3~P^]@^?T.W\aմo>.031#.նĞ1]ccqsd:6u& }T^{st:dpf/#ɸ=z)g>8L{E<3fPW^Ͱa6lօnʋoVTϜ;l›o}"](*U?I\_vg"'U_O%4֔9(6U߽ʇQa'D*[<1z &d;iǚ%Of ~x]wQuhd,rd+L_{ԐS|"mqzs><a1Uxkd,-ŀDTE'*t uZ5r )X1`4_33sqDPwJ]ynOlҽP^Uτf }&v 5frI&vۮ- Ί j*nNWZ)Au۾'Vв ϭdžˏھ7'553,>A%9*N]/kO~O[c jKeͩ$*~>tn&XN9y99Rpfsv?ڳ-Ou'-Ü~Ӱ1rʥ X0 f h.np4Z7t`3:V 0QF𥕏Ksbz)5:6좃x0Z +N )ņ'-qb( <ЙdM8ۤaSLMTZг'GJ Iˑ l<307T] 8ۤcPLM2 ,sVvѠ1E+稪s9t#6#=jfئa0#&ٔS⓲6 г5zQl8ӡ3U7]eۭ8SBShl?-ZaQU`L$[cxy只*FoDƥSmpd5FU9E=gZsƠ܈HV"bSME(T5}3_DWvl(*>'=N~~LLtT1Nʱ7r#ڄIJDl*)wm>ڮڗb* ZN:E5U]sAP}NOgkyBɕ  "AADT  AADT  AAQ%  .E[S CNy͔.6.{JKTUqJ%*EN&!Odb%#*sQU*0JV,QUJ %oܕ̌/<̕ڒk=~_^ o[&z/jڔKT=ؗcG۩8ќ\%#˱%#*,:}kz;[Lsx;nMsr U@ңq?=$3ѼW&QɈ|cSGzx.U * m9oFRb*+ ^}r8TUz*V]ՂVu;+S_-Lx%3*zK}\O6RV,|* z֯5J6feKT Iuӳ*v#О^Bt!#y3ssj|"bPVύkbV>BLqLazRDqOSz 7V<:_783֘TfBN:Y#a-'AP^[֘ߣS>ށ gcD*Nf᪚zf$UUKD7#5g~OEϒ7X ,8m!k906d'!ቼ9{t}C3H79p`"6fvJuWɔ;Me7;Uj{xzȗfH* wˊUV_ʥO%$3z5Qy儨AZ|1RdP hztɔrBxK`2mx -$Sgˎ/oiddJ9!!;~Gh)3FR~kI`P8Tμ?Ϩ&QljuvmdP!)Ɇ\cT)q\8߯;&EѺaj݆ubQU2 fA|/7ĉ\1QoePZ[TDr< orZz32i6-+YdoAꕔNh۰ 榹1BdT\`PN^R#bODMKŷ%I\*g ƨ7K$3K,`WbCm;Sc7:bearlD  Ä AAQ%  JAAD   JAAD  *AAU  *AAU  "AADT  "AADT  AAQ%  AAQ%  JAAD  Px1'dIENDB`pipexec-2.6.2/doc/imgs/PipexecCycle.png000066400000000000000000000230321470374721700177610ustar00rootroot00000000000000PNG  IHDRv7bKGD pHYs+tIME#astEXtCommentCreated with GIMPW IDATxy\Us/e7YDDp M3-3̥lXZXf=mbdo * ~ǵL@T.\~kx13gf~s̙sUUAF@AA10AAAAA LAA10AAA10AAAAA LAjLrJ8Jd`X9ޒ!&H(f?\DB2dnX+B7bөՕb8[2͇pp+5%ŗ|GlE&]>NNJKlZA,V1r2Kzb{:tFA䘉ӕ蘍wΎ{: O @OR<neӦjPQڇ$NNt%!?ߑ,wN &6 x3kE2C{zu; :Es%L29ٮ=ȑ9q"=K'޺WoYSĦIylT8s&?G m,bd'iԨ>k"37Eȷ _dҨJVb`M5'^/B瞟OD&αca,M>y>Jižrr/[Pg'/QI q0e%P''9f;mW搓Bv^.Uprp 3p:9.TOx_}$hN'1GIPpW5SPnȡk/wud5_i. Nɐԃԏb/PjgYUf*K-usҭ }XcR/䒨7dDoGQ!;6ekG^-Ki4xU\#&%-gng{Q{_o\uu`w)ǩ̸̗ Jq{|7z_,3+l;p,#I=Esw[e>rkh ԙldD^{ AWG&cUCv tKXz)M]ˆRZfŔ^(+WVAx/2=w,Ĝ%1j¹C >~[|m)C|qi \Ciq;^T$팠’A7׻n- 1jaIB &4 N cYոMPce+ZEgOgP\V).8چ1FӍE1 u4] L#>_Omjڼ^:%{A:'akQl4tn~b`Pm-1Ǔ׆׾K>)EQ)1wH|FF Lh:lu}[„poڷu)$+L2\)s&qѝ'3TZ`Pzs,>zP zz4^]7TV5n&MN״sa콰x&he8ZH 7:bʭ2~10IIZ{ʃhxc|5n"HsIfwn=2H4nRKb-&4G[_u:6s`y"Kbdh(S_ 7e^1PZkZphp݈ MαlO^Y#)50ێvOëj` \2pqp-ՍjƎxKzy5I O`ܺm{_"[NTj4r zIO:1c'vfp7_)Xv6V4`[7|y{۳Oc鷼/Shy+jõZ`Boφ~6sLfO}n(Rm0o 1&LQnX>T qb8(*ϙJ7^E*o[GŒ33{'4_b`@J"2YZ^ aûv06B{xIy)̰OaǻæW?6<XؼY|ylSw>Kn5ʗyǏt*: (٘na_w.*7^>ZX1_oQ2g\sL;3wݕVʭ4m?}>Q9^?{&} 6@/ HKr;lX }xeNhgK±zx[ Mdoxÿ_KĚz==7d#C6VU8[ +zq4YUFCnD7|Vu(VEf' qh}sUCY}!VXroc  x0!8{BB~,ox’?e+5`м%> S3 p3R+:AbW^~>{U/̆= {}6{]XK7 bl7l-+(w;, =^.ӭU,ͥ%i-SNR 9/|.X_LJ!>Xu>{6jC7>|]08{ ˏu|)0:MDUyrp /Lux:[+aθ oD7F=pe<7o2uWڑkok+A>uu#}m?c'])B Bܽm̕_ 1q_)BwoxU^>+_ ìl}#B4nKaPo~8~nGa޳jlj10_sVUF%e&n}LeH{sp8)s10$c*αЋ5cf5o%C F7oۼ_3Q*ol3s#T-e:,Atc48X[6 ֠;_^kL. /y4)3Atc g aBCkbCOJA d b''zN/pC;ռ;ߤM}~& b`&M Es gge?G 5t'L)3AtS9=?.+D{a]/ ,̛Kcx^?%K5 l\j1/ /ݤ$Fl5Rʶ%hͬ%U f3<JcMhO~ :_ϼ˾V'*2T Ets5w^`h]F*,0Ӛ~QG]'JI`^,t0쉳U!慄!0j/B, ߜc_7LO6ō_&;*Pa #ql9?C"9CVV˱Bi㎎kFsKuU7,G28d;]bX[bgQH ڄ'0 օkM;(,kHiʵhѸp%eokQ]A[t ܤJ*7}Uڪ=Opo OfUz-9dZ;BN^Nh9́Mhsv d7}#y^ĦlJUW}GH0}Y*6~Av7B[NO}tj~ 8Yb"ԞBWebgr/k-D ^}`E[%n}w܏y G}vE.Joǽ;*i|kӴ L_og@O˝ _uvvQuN éu=lq Ѕ`>CnUkg˿s x5:G3yn|uz2tt]Qvܛ _* P2><#4'22(X,@}vMt%bޱ<\ڵ; j Gf{B$ESuD_ǎg=zӄ\vM޽nwy}:Rp]i~}oB4f ck^t{7]W'x7!Q gggXС'NdȑXZ̝j9_D^꼁o+gbɌ{rHxkc8tsE wB0u:NRB[:71BqssSƏSO=w4v./}㤾}:xPZ>>L2No Ӫ5r5_G`fx6nx%rr\$M]ɜީyy5oygՌFB u̙CgϞo]kmɽ>ּU7?D{wo N*م߿;}ɼ{B56=n>h`$3223f~~~9YYk}ԙ}फ~`|8r }qx}Q?dvSm}eL]=.`p |ɽ`ҥjddڹsg;˯E.ަoM7̎͊QP`/AjO-/}t睨(V/ oT,YFFF]taTTۛ$#`@r?m5L=IO!!D ;3r躧=6+IHb^LL:zhO>}:u7!n91~<C릕s~p R$P/2zMizʁW|rK:uXԻnl{vL>75oC!k4+w<!' ߑGc"TLhтc(?l:}w~Wdt;\j#TTȜ"ĹCtZ98âk;F~(ʪѣ je"O0 ptlGI `5!4ZvBVۍ7,Z{"4 RV#""=z|soX^VJ'&}iF?NZ3MѳgOV\ɓ'y饗չWɻMr9&s.3-n 20///ONrr2_}z wWjMW[ð|[q63&)2{EKK}+=ؖ-[2djw Aʩ?Ԛ ȩl_'!--^7&a`;vdѢE={V*ky[oERR ,P:tPĕ^Xqb+FU`o|gvPՄhv㚘Ɗ t i6l;v &&F5jbaQ{ fXYM BPR ^F 5wt~oDZ8lcJx|^tЁhΞ=ԩS|wr>FMLEE`51:b;≔WBk+ӧYlҭ[kjv>62?Z[a?̂־`ia= ?Kީ9sG6%M~V0oh`2<*$jZ –-[8paUq3V0 z'Iw&^ax$gL֑۾6ヂ tٷ`ʱV-_'.&Ќ]] @>cΝ;ǜ9s5/;RjƧ1c|c| }BmvIDAT~t {j.Xpphy5>ɴqn^HII@vJjy?kv ^y',v>!70֎賋XOǂI=g;Fb∈@eNAOE࿂qPm|We+Z [@Dǝ&N~#|HU77c$ Sz49of` NuL;4HF;fO~M#;h<!󵽘 ̙TgɻR^Uu1;̔;޲/(w{>',ld!'ەMY{WY/kYvclGc< |F|ffDbZCQF+Ld{tZEgZoPbe:\W,x"|=ojhzc`d `aRUq]x~yYf^lx/v[T_K&}w";ҲFu p>J9gȗ;CpRQ0Zc,X>Cu?m*NlGJ1v@k۔"'cګp:jFkA~;)-9~{Zx G{O$f8Rs^'0x?n' f(XPQڜv$&tcwg06XȂu i]B3اju֒2Ҋ[qbs&@Rl OKu"jJV1eh5 VxX)- *=^dijJ-,x(!N٩9&3GILW2)(@\vTuiqr+oyE29x2KM*!3 xZӲ-ģa'qyQeW*k"p3̋<0jiy39|:[M*&3*%.4WbkPܒ& -F/ F LAA10AAAAA LAA10AAA10AAAAA LAA10AAA10AzvsgAA  \KcJU1% H LAAA LAA10AAAAA LAAA LAA10AAAn:d/t1IENDB`pipexec-2.6.2/doc/man/000077500000000000000000000000001470374721700145125ustar00rootroot00000000000000pipexec-2.6.2/doc/man/peet.1000066400000000000000000000041051470374721700155310ustar00rootroot00000000000000.\" .\" Man page for pipexec .\" .\" For license, see the 'LICENSE' file. .\" .TH peet 1 2023-03-27 "User Commands" "User Commands" .SH NAME peet \- piped reverse tee: read from many file descriptors and copy to one .SH SYNOPSIS peet [\-h] [\-b size] [\-w outfd] infd1 [infd2 ...] .SH DESCRIPTION .B peet reads from many file descriptors and copies everything to one file descriptor. If no file descriptor is given ('\-w' option), 1 (stdout) is used. .P .B peet without the \-b option reads the data which is available on each fd and writes it out to the output file descriptor. For each input fd one read is executed. This reads maximum 4096 bytes at once. For each read one write is executed. This means that the output data might be scattered randomly between the different input streams. .P When the \-b option is specified, the number is seen as bytes in a block. A write is executed only of complete blocks on the input buffer. .P .B peet can be used with .B pipexec(1) to de-multiplex text based output. .SH OPTIONS .TP \fB\-h\fR print help and version information .TP \fB\-b num\fR Reads always num bytes before writing them. .TP \fB\-w outfd\fR use the given outfd as output file descriptor. If this option is not specified, 1 (stdout) is used. .SH EXAMPLES Read from stdin (fd 0), and file descriptors 9 and 11 and write to stdout. .nf peet 0 9 11 .fi .P Using pipexec(1): start two commands, both write their log to stdout and use one instance of rotatelogs(1) to write the logs to disk into a common log file: (The file descriptors 8 and 11 are chosen by random.) .nf pipexec [ CMD1 /usr/bin/cmd1 ] [ CMD2 /usr/bin/cmd2 ] \\ [ PEET /usr/bin/peet 8 11 ] \\ [ RLOGS /usr/bin/rotatelogs /var/log/%Y%m%d_cmd.log ] \\ "{CMD1:1>PEET:8}" "{CMD2:1>PEET:11}" \\ "{PEET:1>RLOGS:0}" .fi .SH "SEE ALSO" .BR pipexec(1), .BR peet(1), .BR rotatelogs(1), .BR tee(1) .SH AUTHOR Written by Andreas Florath (andreas@florath.net) .SH COPYRIGHT Copyright \(co 2015,2023 by Andreas Florath (andreas@florath.net). License GPLv2+: GNU GPL version 2 or later . pipexec-2.6.2/doc/man/pipexec.1000066400000000000000000000166551470374721700162460ustar00rootroot00000000000000.\" .\" Man page for pipexec .\" .\" Copyright 2015,2022 by Andreas Florath .\" For license, see the 'LICENSE' file. .\" SPDX-License-Identifier: GPL-2.0-or-later .\" .TH pipexec 1 2022-07-18 "User Commands" "User Commands" .SH NAME pipexec \- create a directed graph of processes and pipes .SH SYNOPSIS pipexec [OPTION]... [PROCESS DESCRIPTION]... [PIPE DESCRIPTION]... .SH DESCRIPTION .B pipexec creates an arbitrary network (directed graph) of processes and pipes in between - even cycles are possible. It overcomes the shortcomings of shells that are typically only able to create non cyclic trees. .P .B pipexec also monitors all it's child processes and is able to restart the whole network of processes and pipes if one crashes. Therefore .B pipexec can be used in SYSV-init or systemd configuration to run a network of processes. .SH OPTIONS .TP \fB\-h\fR print help and version information .TP \fB\-l logfd\fR use the given file descriptor for text logging. If a 's' is specified, syslog is used. Example: Specifying '2' means log to stderr. .TP \fB\-j logfd\fR use the given file descriptor for json logging. If a 's' is specified, syslog is used. Example: Specifying '2' means log to stderr. As this is meant to be parsed by other programs, this is an official and supported interface which is described in the JSON LOGGING chapter. .TP \fB\-p pidfile\fR with .B pipexec it is possible to handle pipes within SYSV-init scripts. In some environments (e.g. RHEL6, Debian7) the start and stop routines need a pid file. If this option is given, pipexec writes its own pid into the file shortly after start of pipexec. .TP \fB\-k\fR if one sub-process (child) gets killed and this options is given, all other sub-processes are also killed. Afterwards all processes are restarted. .TP \fB\-s sleep_time\fR the time interval in seconds before a restart. This option makes only sense when also the '\-k' option is specified. .SH BACKGROUND Inside a shell it is possible to start processes and redirect the output to other processes. .P Example: .nf cat Chap1.txt Chap2.txt | grep bird | wc \-l .fi .P Three processes are created: the standard output (file descriptor (fd) 1) of the 'cat' process is connected to the standard input (fd 0) of the 'grep' command, and the standard output of the 'grep' command is connected to the standard input (fd 0) of the 'wc' process. .P Please note that the assignment between names and file descriptor number is pure historical and has no technical background. .P Example: .nf find / 1> >(grep .txt) 2> >(wc >/tmp/w.log) .fi .P In this more complex example, the fd 1 of the 'find' process is connected to fd 0 of 'grep' and fd 2 is connected to fd 0 of 'wc'. .P The limitation using this way of specifying processes and pipes is, that it is not possible to have any cycles. It is impossible to e.g. pass a fd of 'wc' either to 'grep' or to 'find'. .P .B pipexec overcomes these limitations. It makes it possible to link any two arbitrary file descriptors in a set of processes. .SH USAGE When building up a network of processes and pipes, there is the need to specify each element separately. .P The processes will be the nodes in the network (directed graph), the connections of the file descriptors between to processes are the edges. Each node (process) has a unique name assigned to it. This makes it possible to differentiate between using the same command more than once. .P The format of specifying a process is .nf [ NAME /path/to/command arg1 arg2 ... argN ] .fi .P The first parameter 'NAME' must be a unique name. The second parameter must be the full path of the command to execute. Please note that always the full path must be specified, there is no PATH environment variable handling (execv(2) is used internally to span new processes). The following parameters are the parameters passed to the command. .P The whole definition must be enclosed in square brackets. The square brackets must be given separately - before and after them must be a space. .P The format of specifying a pipe between processes is .nf {NAME_1:FD1>NAME_2:FD2} .fi .P Example .nf {LS:1>GREP:0} .fi .P The names are the names of the processes, the numbers are the number of the file descriptor that should be used to build the pipe in between. When using pipexec from a shell (like bash) there is the need to escape the brackets or use quotation marks. .SH JSON LOGGING .B pipexec can log in JSON format. This is an official supported interface which is defined in this chapter. This can be seen as stable as no changes will be made during small version upgrades. Of course additions will be made also within minor upgrades, but this should be fine because of the underlaying JSON format. .P Each JSON log object (e.g. line) will be in a separate line. .P The following keys are always present: .TP \fBtimestamp\fR The timestamp is the time(0) (seconds since epoch) when the log in generated. .TP \fBpipexec_pid\fR The pid of the pipexec process. .TP \fBid\fR An id that specifies defined log objects. .TP \fBtype\fR An indicator in which internal module this log was generated. .TP \fBserverity\fR One of debug, info, warning, error. .TP \fBmessage\fR A short message describing the event. .P Depending on the id there might be additional fields which are described in the next section. .TP \fBid = 0\fR id of 0 is a special case: this is used for internal logs only. The logs are not documented and may change at any time. The information can be used to get an idea what currently happens in pipexec. .TP \fBid = 1\fR This gives information about the process ids (pids) of the commands. The field \fBcommand\fR contains the command (i.e. the part in the command before the colon. The field \fBcommand_pid\fR contains the pid of the command. .TP \fBid = 2\fR This log message is emitted when a child (command) exits. It contains some information about the exit status and termination reason of the command. The field \fBcommand_pid\fR contains the pid of the command which just terminated. The field \fBstatus\fR is the value which is set by \fBwaitpid(2)\fR. \fBnormal_exit\fR, \fBchild_status\fR and \fBchild_signaled\fR are \fBWIFEXITED\fR, \fBWEXITSTATUS\fR and \fBWIFSIGNALED\fR of the status respectively. .SH RETURN pipexec returns 1 if any of the child processes fails else 0 is returned. .SH EXAMPLES The shell command .nf cat Chap1.txt Chap2.txt | grep bird | wc \-l .fi .P is equivalent to .nf pipexec [ CAT /bin/cat Chap1.txt Chap2.txt ] \\ [ GREP /usr/bin/grep bird ] [ WC /usr/bin/wc \-l ] \\ "{CAT:1>GREP:0}" "{GREP:1>WC:0}" .fi .P The pipexec equivalent is longer and more complex in this example. But pipexec can build cycles that are impossible within a shell: .nf pipexec [ A /bin/cmd1 ] [ B /bin/cmd2 ] "{A:1>B:0}" "{B:1>A:0}" .fi .P When using json log, you get output like: .nf {"timestamp":1655706460,"pipexec_pid":42850,"id":1,"type":"exec","serverity":"info","message":"New child forked","command":"A","command_pid":"42851"} {"timestamp":1655706886,"pipexec_pid":42869,"id":2,"type":"tracing","serverity":"info","message":"child exit","command_pid":"42870","status":"1","normal_exit":"1","child_status":"1","child_signaled":"0"} .fi .P For more examples see the ptee(1) and peet(1) man pages. .SH "SEE ALSO" .BR bash(1), .BR ptee(1), .BR peet(1), .BR execv(2) .SH AUTHOR Written by Andreas Florath (andreas@florath.net) .SH COPYRIGHT Copyright \(co 2015,2022 by Andreas Florath (andreas@florath.net). License GPLv2+: GNU GPL version 2 or later . pipexec-2.6.2/doc/man/ptee.1000066400000000000000000000031301470374721700155260ustar00rootroot00000000000000.\" .\" Man page for pipexec .\" .\" For license, see the 'LICENSE' file. .\" .TH ptee 1 2015-03-14 "User Commands" "User Commands" .SH NAME ptee \- piped tee: read from one file descriptor and copy to many .SH SYNOPSIS ptee [\-h] [\-r infd] outfd1 [outfd2 ...] .SH DESCRIPTION .B ptee reads from one file descriptor (0 / stdin by default) and copies everything to all given output file descriptors. ptee is a piped version of tee(1). .P .B ptee can be used with .B pipexec(1) to fit the output of one command into many other commands. .SH OPTIONS .TP \fB\-h\fR print help and version information .TP \fB\-r infd\fR use the given infd as input file descriptor. If this is not specified, 0 (stdin) is used. .SH EXAMPLES Duplicate all data from stdin to stdout, stderr and fd 7: .nf ptee 1 2 7 .fi .P The command .nf tee output.txt .fi is equivalent to the shell command .nf ptee 5 5>output.txt .fi .P Using pipexec(1): count all files in the file system and additionally count only those that have a uppercase 'A' in their name. The file system will be scanned only once. No temporary files are generated. .nf pipexec [ LS /bin/ls \-R / ] [ PTEE /usr/bin/ptee 3 4 ] \\ [ WC1 /usr/bin/wc \-l ] [ GREP /bin/grep A ] \\ [ WC2 /usr/bin/wc \-l ] "{LS:1>PTEE:0}" "{PTEE:3>WC1:0}" \\ "{PTEE:4>GREP:0}" "{GREP:1>WC2:0}" .fi .SH "SEE ALSO" .BR pipexec(1), .BR peet(1), .BR tee(1) .SH AUTHOR Written by Andreas Florath (andreas@florath.net) .SH COPYRIGHT Copyright \(co 2015 by Andreas Florath (andreas@florath.net). License GPLv2+: GNU GPL version 2 or later . pipexec-2.6.2/examples.txt000066400000000000000000000017271470374721700155600ustar00rootroot00000000000000./pipexec -l 2 -- [A /bin/ls -l ] [B /bin/grep LIC ] "{A:1>B:0}" "{PARENT:0=A:0}" "{PARENT:1=B:1}" "{PARENT:2=B:2}" ./pipexec -l 2 -- [A /bin/echo Hallo ] [B /bin/echo Bye ] [WC /usr/bin/wc ] "{A:1>WC:0}" "{B:1>WC:0}" "{PARENT:0=A:0}" "{PARENT:1=WC:1}" "{PARENT:2=WC:2}" ./pipexec -l 2 -- [A cmd1 arg] [B cmd2 arg arg] [C cmd3] "{IN:0=A:0}" "{A:1>B:4}" "{B:4>C:0}" "{C:1>A:0}" "{C:5>B:1}" "{A:7>C:12}" "{C:4=OUT:1}" ./pipexec -l 2 -- [PRINT /usr/bin/printf "Hello\nThis is pipexec test\nAssume this is a long SQL query with a very long output\nAnd you need to do two things with the result.\n" ] [PTEE /home/florath/devel/pe2/BUILD/bin/ptee 5 6 ] [GLOBWC /usr/bin/wc ] [GREP /bin/grep pipe ] [LOCWC /usr/bin/wc ] "{PRINT:1>PTEE:0}" "{PTEE:5>GLOBWC:0}" "{PTEE:6>GREP:0}" "{GREP:1>LOCWC:0}" ./pipexec -l 2 -- [PTEST /home/florath/devel/pe2/pipexec/ptest ] [TEE /usr/bin/tee /tmp/ptest1.log ] [DD /bin/dd of=/tmp/ptest2.log ] "{PTEST:1>TEE:0}" "{TEE:1>PTEST:0}" "{PTEST:3>DD:0}" pipexec-2.6.2/src/000077500000000000000000000000001470374721700137615ustar00rootroot00000000000000pipexec-2.6.2/src/Makefile.inc000066400000000000000000000011211470374721700161640ustar00rootroot00000000000000 if !USE_VERSION_FILE .FORCE: src/app_version.c src/app_version.c: /bin/sh $(top_srcdir)/build/version-gen.sh ${top_srcdir} endif # pipexec itself bin_PROGRAMS += bin/pipexec bin_pipexec_SOURCES = \ src/pipexec.c \ src/logging.c \ src/version.c \ src/app_version.c \ src/command_info.c \ src/pipe_info.c # ptee bin_PROGRAMS += bin/ptee bin_ptee_SOURCES = \ src/version.c \ src/app_version.c \ src/ptee.c # peet bin_PROGRAMS += bin/peet bin_peet_SOURCES = \ src/version.c \ src/app_version.c \ src/peet.c # Local Variables: # mode: makefile # End: pipexec-2.6.2/src/command_info.c000066400000000000000000000040001470374721700165500ustar00rootroot00000000000000#include "src/command_info.h" #include "src/logging.h" #include #include unsigned int command_info_clp_count( int const start_argc, int const argc, char * const argv[]) { unsigned int cnt = 0; for(int i = start_argc; ipath = *argv; self->argv = argv; } #endif void command_info_print(command_info_t const * const self) { logging(lid_internal, "command", "info", "command_info", 2, "command", self->cmd_name, "path", self->path); } pipexec-2.6.2/src/command_info.h000066400000000000000000000014101470374721700165570ustar00rootroot00000000000000#ifndef PIPEXEC_COMMAND_INFO_H #define PIPEXEC_COMMAND_INFO_H /** * Path and parameters for exec one program. * Please note that here are only stored pointers - * typically to a memory of argv. */ struct command_info { char * cmd_name; char * path; char ** argv; }; typedef struct command_info command_info_t; unsigned int command_info_array_constrcutor( command_info_t * icmd, int const start_argc, int const argc, char * argv[]); void command_info_array_print( command_info_t const * const icmd, unsigned long const cnt); void command_info_print( command_info_t const * const self); // Helper: // Counts the command line parameters unsigned int command_info_clp_count( int const start_argc, int const argc, char * const argv[]); #endif pipexec-2.6.2/src/logging.c000066400000000000000000000147441470374721700155650ustar00rootroot00000000000000/* * Logging * * Copyright 2015,2022 by Andreas Florath * SPDX-License-Identifier: GPL-2.0-or-later */ #include "src/logging.h" #include #include #include #include #include #include #include #include /** * Logging is done by means of an additional file descriptor * which can be passed in by command line parameter. */ int g_log_text_fd = -1; int g_log_text_use_syslog = 0; int g_log_json_fd = -1; int g_log_json_use_syslog = 0; void logging_text_set_global_log_fd(int fd) { g_log_text_fd = fd; } void logging_text_set_global_use_syslog() { g_log_text_use_syslog = 1; } void logging_json_set_global_log_fd(int fd) { g_log_json_fd = fd; } void logging_json_set_global_use_syslog() { g_log_json_use_syslog = 1; } static unsigned long log_fd_write_time(char * buf, unsigned long free_bytes) { time_t t = time(NULL); // No need to use the thread-safe version: we are in the one-threaded // universe here. struct tm *tmp = localtime(&t); return strftime(buf, free_bytes, "%F %T", tmp); } static unsigned long log_fd_write_pname_and_pid( char * buf, unsigned long free_bytes) { return snprintf(buf, free_bytes, ";pipexec;%d;", getpid()); } static unsigned long log_fd_write_kv( char * buf, unsigned long free_bytes, char const * const key, char const * const value) { return snprintf(buf, free_bytes, "[%s]=[%s];", key, value); } static unsigned long log_fd_write_str( char * buf, unsigned long free_bytes, char const * const s) { return snprintf(buf, free_bytes, "%s;", s); } static unsigned long log_fd_write_newline( char * buf, unsigned long free_bytes) { return snprintf(buf, free_bytes, "\n"); } /** * Log the state, events and actions. * The format of a logging line contains the date and time, * the pid of this process and the passed in parameters. */ void logging_text(enum logid lid, char const * const type, char const * const serverity, char const * const msg, unsigned int const count, va_list * va) { unsigned int free_bytes = 1024; char pbuf[free_bytes]; char * cbuf = pbuf; unsigned int written_bytes; written_bytes = log_fd_write_time(cbuf, free_bytes); cbuf += written_bytes; free_bytes -= written_bytes; written_bytes = log_fd_write_pname_and_pid(cbuf, free_bytes); cbuf += written_bytes; free_bytes -= written_bytes; ITOCHAR(slogid, 20, (int)lid); written_bytes = log_fd_write_str(cbuf, free_bytes, slogid); cbuf += written_bytes; free_bytes -= written_bytes; written_bytes = log_fd_write_str(cbuf, free_bytes, type); cbuf += written_bytes; free_bytes -= written_bytes; written_bytes = log_fd_write_str(cbuf, free_bytes, serverity); cbuf += written_bytes; free_bytes -= written_bytes; written_bytes = log_fd_write_str(cbuf, free_bytes, msg); cbuf += written_bytes; free_bytes -= written_bytes; for(unsigned int idx = 0; idx < count; ++idx) { char const * const key = va_arg(*va, char*); char const * const value = va_arg(*va, char*); written_bytes = log_fd_write_kv(cbuf, free_bytes, key, value); cbuf += written_bytes; free_bytes -= written_bytes; } written_bytes = log_fd_write_newline(cbuf, free_bytes); cbuf += written_bytes; free_bytes -= written_bytes; if(g_log_text_fd!=-1) { // Ignore the result: // What to do when the result shows a failure? Logging? ssize_t wr = write(g_log_text_fd, pbuf, cbuf-pbuf); (void)wr; } if(g_log_text_use_syslog) { syslog(LOG_PID | LOG_DAEMON, "%s", pbuf); } } static void json_add_kv_str(char * buf, unsigned int * used_bytes, unsigned int * free_bytes, char const * const key, char const * const value) { int slen = snprintf(buf + *used_bytes, *free_bytes, "\"%s\":\"%s\"", key, value); *used_bytes += slen; *free_bytes -= slen; } static void json_add_kv_d(char * buf, unsigned int * used_bytes, unsigned int * free_bytes, char const * const key, unsigned int value) { int slen = snprintf(buf + *used_bytes, *free_bytes, "\"%s\":%d", key, value); *used_bytes += slen; *free_bytes -= slen; } static void json_add_kv_ld(char * buf, unsigned int * used_bytes, unsigned int * free_bytes, char const * const key, unsigned long value) { int slen = snprintf(buf + *used_bytes, *free_bytes, "\"%s\":%ld", key, value); *used_bytes += slen; *free_bytes -= slen; } static void json_add_comma(char * buf, unsigned int * used_bytes, unsigned int * free_bytes) { buf[*used_bytes] = ','; ++(*used_bytes); --(*free_bytes); } void logging_json(enum logid lid, char const * const type, char const * const serverity, char const * const msg, unsigned int const count, va_list * va) { unsigned int free_bytes = 4096; unsigned int used_bytes = 0; char pbuf[free_bytes]; // Initial '{' pbuf[0] = '{'; ++used_bytes; --free_bytes; json_add_kv_ld(pbuf, &used_bytes, &free_bytes, "timestamp", time(0)); json_add_comma(pbuf, &used_bytes, &free_bytes); json_add_kv_d(pbuf, &used_bytes, &free_bytes, "pipexec_pid", getpid()); json_add_comma(pbuf, &used_bytes, &free_bytes); json_add_kv_d(pbuf, &used_bytes, &free_bytes, "id", (int)lid); json_add_comma(pbuf, &used_bytes, &free_bytes); json_add_kv_str(pbuf, &used_bytes, &free_bytes, "type", type); json_add_comma(pbuf, &used_bytes, &free_bytes); json_add_kv_str(pbuf, &used_bytes, &free_bytes, "serverity", serverity); json_add_comma(pbuf, &used_bytes, &free_bytes); json_add_kv_str(pbuf, &used_bytes, &free_bytes, "message", msg); for(unsigned int idx = 0; idx < count; ++idx) { char const * const key = va_arg(*va, char*); char const * const value = va_arg(*va, char*); json_add_comma(pbuf, &used_bytes, &free_bytes); json_add_kv_str(pbuf, &used_bytes, &free_bytes, key, value); } assert( free_bytes > 2 ); pbuf[used_bytes++] = '}'; pbuf[used_bytes++] = '\n'; if(g_log_json_fd!=-1) { // Ignore the result: // What to do when the result shows a failure? Logging? ssize_t wr = write(g_log_json_fd, pbuf, used_bytes); (void)wr; } } void logging(enum logid lid, char const * const type, char const * const serverity, char const * const msg, unsigned int const count, ...) { va_list ap; va_start(ap, count); if(g_log_text_fd!=-1 || g_log_text_use_syslog==1) { logging_text(lid, type, serverity, msg, count, &ap); return; } if(g_log_json_fd!=-1 || g_log_json_use_syslog==1) { logging_json(lid, type, serverity, msg, count, &ap); return; } va_end(ap); } pipexec-2.6.2/src/logging.h000066400000000000000000000014721470374721700155640ustar00rootroot00000000000000#ifndef PIPEXEC_LOGGING_H #define PIPEXEC_LOGGING_H /* * Logging * * The logging system writes its output to a given fd. * * Copyright 2015,2022 by Andreas Florath * SPDX-License-Identifier: GPL-2.0-or-later */ #define ITOCHAR(vNaMe, sIzE, vAr) char vNaMe[sIzE]; snprintf(vNaMe, sIzE, "%d", vAr) #define SIZETTOCHAR(vNaMe, sIzE, vAr) char vNaMe[sIzE]; snprintf(vNaMe, sIzE, "%zu", vAr) void logging_text_set_global_log_fd(int fd); void logging_text_set_global_use_syslog(); void logging_json_set_global_log_fd(int fd); void logging_json_set_global_use_syslog(); enum logid { lid_internal = 0, lid_command_pid = 1, lid_child_exit = 2 }; void logging(enum logid lid, char const * const type, char const * const serverity, char const * const msg, unsigned int const count, ...); #endif pipexec-2.6.2/src/peet.c000066400000000000000000000132131470374721700150620ustar00rootroot00000000000000/* * peet * * eet (a reverse tee) for pipes / fds. * The program reads from different file descriptors and writes to one. * * Copyright 2015,2022 by Andreas Florath * SPDX-License-Identifier: GPL-2.0-or-later */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "src/version.h" size_t const buffer_size = 4096; /* This is the structure which is created for each file descriptor. As the boundary read / write needs always complete blocks, this includes also the buffer and the amount of valid data in the buffer. */ struct fddata_t { char * m_buffer; ssize_t m_buffer_size; ssize_t m_buffer_used; int m_use_boundary; int m_eof_seen; }; void fddata_init(struct fddata_t *self, struct pollfd *pfd, int fd, int use_boundary, int block_size) { pfd->fd = fd; pfd->events = POLLIN; int const flags = fcntl(pfd->fd, F_GETFL, 0); int const fret = fcntl(pfd->fd, F_SETFL, flags | O_NONBLOCK); if (fret == -1) { perror("fcntl nonblocking"); exit(2); } self->m_buffer_size = block_size; self->m_buffer_used = 0; self->m_buffer = malloc(block_size); self->m_use_boundary = use_boundary; self->m_eof_seen = 0; } void fddata_write(struct fddata_t *self, int write_fd, int use_debug_log) { if (use_debug_log) { fprintf(stderr, "WRITE len [%zd]\n", self->m_buffer_used); } ssize_t const wr = write(write_fd, self->m_buffer, self->m_buffer_used); if (wr == -1) { perror("write"); exit(2); } if (wr != self->m_buffer_used) { perror("Could not write all data"); exit(2); } self->m_buffer_used = 0; } // Returns // 0 - if everything is fine - e.g. no state change // 1 - state change: eof_seen the first time int fddata_read_write(struct fddata_t *self, struct pollfd *pfd, int write_fd, int use_debug_log) { // Ignore entries where eof was seen. if(self->m_eof_seen) { return 0; } if (pfd->revents & POLLIN) { size_t const bytes_to_read = self->m_buffer_size - self->m_buffer_used; ssize_t const bytes_read = read( pfd->fd, self->m_buffer + self->m_buffer_used, bytes_to_read); if (bytes_read < 0 && errno == EINTR) return 0; if (bytes_read < 0 && errno == EAGAIN) // Fd would block return 0; if (bytes_read <= 0) { // EOF from this fd // The handling of this was finished. // Write possible remaining data. fddata_write(self, write_fd, use_debug_log); if(use_debug_log) { fprintf(stderr, "EOF [%d]", pfd->fd); } self->m_eof_seen = 1; pfd->fd = -1; return 1; } pfd->revents &= (!POLLIN); self->m_buffer_used += bytes_read; assert(self->m_buffer_used <= self->m_buffer_size); if (! self->m_use_boundary || self->m_buffer_used == self->m_buffer_size) { fddata_write(self, write_fd, use_debug_log); return 0; } } if (pfd->revents & POLLHUP) { fddata_write(self, write_fd, use_debug_log); self->m_eof_seen = 1; pfd->fd = -1; return 1; } // There are no fds to read from return 0; } static void usage() { fprintf(stderr, "peet from pipexec version %s\n", app_version); fprintf(stderr, "%s\n", desc_copyight); fprintf(stderr, "%s\n", desc_license); fprintf(stderr, "\n"); fprintf(stderr, "Usage: peet [options] fd [fd ...]\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -h display this help\n"); fprintf(stderr, " -b num read num bytes from each input\n"); fprintf(stderr, " -d print some debug output\n"); fprintf(stderr, " -w fd fd to write to\n"); exit(1); } int readable_fd_available(struct fddata_t *fddata, size_t fd_cnt) { for (size_t fdidx = 0; fdidx < fd_cnt; ++fdidx) { if (! fddata[fdidx].m_eof_seen) { return 1; } } return 0; } // Returns: // 0 - no readable fd available // 1 - everything ok int read_write(struct fddata_t * fddata, struct pollfd * fds, int fd_cnt, int write_fd, int use_debug_log) { for (int fdidx = 0; fdidx < fd_cnt; ++fdidx) { int const eof_seen = fddata_read_write(&fddata[fdidx], &fds[fdidx], write_fd, use_debug_log); if (eof_seen) { if (use_debug_log) { fprintf(stderr, "EOF SEEN idx [%d]\n", fdidx); } if (!readable_fd_available(fddata, fd_cnt)) { return 0; } } } return 1; } int main(int argc, char *argv[]) { int write_fd = 1; int use_debug_log = 0; int block_size = 4096; int use_boundary = 0; int opt; while ((opt = getopt(argc, argv, "b:dhw:")) != -1) { switch (opt) { case 'b': block_size = atoi(optarg); use_boundary = 1; break; case 'd': use_debug_log = 1; break; case 'h': usage(); break; case 'w': write_fd = atoi(optarg); break; default: /* '?' */ usage(); } } if (optind == argc) { fprintf(stderr, "Error: No fds given\n"); usage(); } // All parameters are fds. size_t fd_cnt = argc - optind; // The structure for the fd data structs struct fddata_t fddata[fd_cnt]; struct pollfd fds[fd_cnt]; size_t fdidx = 0; for (int aidx = optind; aidx < argc; ++aidx, ++fdidx) { fddata_init(&fddata[fdidx], &fds[fdidx], atoi(argv[aidx]), use_boundary, block_size); } while (1) { // Wait forever int const ret = poll(fds, fd_cnt, -1); // Check if poll actually succeed if (ret == -1) { perror("poll"); exit(2); } else if (ret == 0) { perror("timeout"); exit(2); } if( ! read_write(fddata, fds, fd_cnt, write_fd, use_debug_log) ) { break; } } return 0; } pipexec-2.6.2/src/pipe_info.c000066400000000000000000000200711470374721700160750ustar00rootroot00000000000000// Copyright 2015,2022 by Andreas Florath // SPDX-License-Identifier: GPL-2.0-or-later #include "src/pipe_info.h" #include "src/logging.h" #include #include #include #include #include char *pipes_end_info_parse(pipes_end_info_t *const pend, char *const str) { /* GCC 12 introduces a new dangling pointer check. > dangling pointer ‘colon’ to ‘end_fd’ may be used This is at this point a false positive as there is no way to access 'end_fd' using 'colum' after returning from this function. Limit the diagnostics ignorance to GCC >= 12 as dangling-pointer is not known to gcc-11 or clang 13. */ #if __GNUC__ >= 12 #ifndef __clang__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdangling-pointer" #endif #endif char *const colon = strchr(str, ':'); if (colon == NULL) { logging(lid_internal, "command_line", "error", "Invalid syntax: no colon in pipe desc found", 0); exit(1); } *colon = '\0'; pend->name = str; char *end_fd; pend->fd = strtol(colon + 1, &end_fd, 10); return end_fd; #if __GNUC__ >= 12 #ifndef __clang__ #pragma GCC diagnostic pop #endif #endif } void pipe_info_print(pipe_info_t const *const ipipe, unsigned long const cnt) { for (unsigned int pidx = 0; pidx < cnt; ++pidx) { ITOCHAR(spidx, 16, pidx); ITOCHAR(sin_pipe_fd, 16, ipipe[pidx].from.fd); ITOCHAR(sout_pipe_fd, 16, ipipe[pidx].to.fd); logging(lid_internal, "pipe", "info", "pipe_info", 5, "pipe_index", spidx, "from_pipe_name", ipipe[pidx].from.name, "from_pipe_fd", sin_pipe_fd, "to_pipe_name", ipipe[pidx].to.name, "to_pipe_fd", sout_pipe_fd); } } unsigned int pipe_info_clp_count(int const start_argc, int const argc, char *const argv[]) { unsigned int cnt = 0; for (int i = start_argc; i < argc; ++i) { if (argv[i][0] == '{' && strchr(argv[i], '>') != NULL) { ++cnt; } } return cnt; } void pipe_info_parse(pipe_info_t *const ipipe, int const start_argc, int const argc, char *const argv[], char const sep) { unsigned int pipe_no = 0; for (int i = start_argc; i < argc; ++i) { if (argv[i] == NULL) { continue; } if (argv[i][0] == '{' && strchr(argv[i], sep) != NULL) { char *const end_from = pipes_end_info_parse(&ipipe[pipe_no].from, &argv[i][1]); if (*end_from != sep) { logging(lid_internal, "command_line", "error", "Invalid syntax: no ':' in pipe desc found", 0); exit(1); } char *const end_to = pipes_end_info_parse(&ipipe[pipe_no].to, end_from + 1); if (*end_to != '}') { logging(lid_internal, "command_line", "error", "Invalid syntax: no '}' closing pipe desc found", 0); exit(1); } ++pipe_no; } } } void pipe_info_create_pipes(pipe_info_t *const ipipe, unsigned long const pipe_cnt) { // Open up all the pipes. for (size_t pidx = 0; pidx < pipe_cnt; ++pidx) { int const pres = pipe(ipipe[pidx].pipefds); if (pres == -1) { perror("pipe"); exit(10); } SIZETTOCHAR(spidx, 20, pidx); ITOCHAR(sfrom_fd, 16, ipipe[pidx].pipefds[1]); ITOCHAR(sto_fd, 16, ipipe[pidx].pipefds[0]); logging(lid_internal, "pipe", "info", "pipe_created", 3, "pipe_index", spidx, "from_fd", sfrom_fd, "to_fd", sto_fd); } } // This is similar to the parent_pipe_info_dup_in_piped_for_pipe_end // function - but has some differences in the data. // Might be hard to refactor (unify). static void pipe_info_dup_in_piped_for_pipe_end(size_t const pidx, char *cmd_name, pipes_end_info_t const *const pend, int pipe_fd, int close_unused) { if (strcmp(cmd_name, pend->name) == 0) { SIZETTOCHAR(spidx, 20, pidx); ITOCHAR(from_pipe_fd, 16, pipe_fd); ITOCHAR(to_pipe_fd, 16, pend->fd); logging(lid_internal, "pipe", "info", "dup", 5, "pipe_index", spidx, "command", cmd_name, "pipe_name", pend->name, "from_pipe_fd", from_pipe_fd, "to_pipe_fd", to_pipe_fd); close(pend->fd); int const bfd = dup2(pipe_fd, pend->fd); if (bfd != pend->fd) { ITOCHAR(serrno, 16, errno); logging(lid_internal, "pipe", "error", "dup2", 7, "pipe_index", spidx, "command", cmd_name, "pipe_name", pend->name, "from_pipe_fd", from_pipe_fd, "to_pipe_fd", to_pipe_fd, "errno", serrno, "error", strerror(errno)); abort(); } } else { if (close_unused) { SIZETTOCHAR(spidx, 20, pidx); ITOCHAR(from_pipe_fd, 16, pipe_fd); ITOCHAR(to_pipe_fd, 16, pend->fd); logging(lid_internal, "pipe", "info", "closing", 5, "pipe_index", spidx, "command", cmd_name, "pipe_name", pend->name, "from_pipe_fd", from_pipe_fd, "to_pipe_fd", to_pipe_fd); close(pipe_fd); } } } void pipe_info_dup_in_pipes(pipe_info_t *ipipe, unsigned long pipe_cnt, char *cmd_name, int close_unused) { for (size_t pidx = 0; pidx < pipe_cnt; ++pidx) { pipe_info_dup_in_piped_for_pipe_end(pidx, cmd_name, &ipipe[pidx].from, ipipe[pidx].pipefds[1], close_unused); pipe_info_dup_in_piped_for_pipe_end(pidx, cmd_name, &ipipe[pidx].to, ipipe[pidx].pipefds[0], close_unused); } } void pipe_info_close_all(pipe_info_t const *const ipipe, unsigned long const pipe_cnt) { for (size_t pidx = 0; pidx < pipe_cnt; ++pidx) { SIZETTOCHAR(spidx, 20, pidx); ITOCHAR(from_fd, 16, ipipe[pidx].pipefds[1]); logging(lid_internal, "pipe", "info", "closing fd from", 2, "pipe_index", spidx, "fd", from_fd); close(ipipe[pidx].pipefds[1]); ITOCHAR(to_fd, 16, ipipe[pidx].pipefds[0]); logging(lid_internal, "pipe", "info", "closing fd to", 2, "pipe_index", spidx, "fd", to_fd); close(ipipe[pidx].pipefds[0]); } } static void block_fd(pipes_end_info_t const *const pend, int blocking_fd) { if (pend->fd > 2 && pend->fd != blocking_fd) { ITOCHAR(pipe_fd, 16, pend->fd); ITOCHAR(sbfd, 16, blocking_fd); logging(lid_internal, "pipe", "info", "blocking_fd", 2, "pipe_fd", pipe_fd, "blocking_fd", sbfd); int const bfd = dup2(blocking_fd, pend->fd); if (bfd != pend->fd) { abort(); } } } // Block all FDs for really used pipes: // o open an additional unused pipe // o dup2() one fd of this unused pipe for all later on used fds. void pipe_info_block_used_fds(pipe_info_t const *const ipipe, unsigned long const cnt) { logging(lid_internal, "pipe", "info", "Blocking used fds", 0); logging(lid_internal, "pipe", "info", "Creating extra pipe for blocking fds", 0); int block_pipefds[2]; int const pres = pipe(block_pipefds); if (pres == -1) { perror("pipe"); exit(10); } // One is enough: close(block_pipefds[1]); ITOCHAR(sbfd, 16, block_pipefds[0]); logging(lid_internal, "pipe", "info", "fd for blocking", 1, "fd", sbfd); for (unsigned int pidx = 0; pidx < cnt; ++pidx) { block_fd(&ipipe[pidx].from, block_pipefds[0]); block_fd(&ipipe[pidx].to, block_pipefds[0]); } } #define PI_CHECK_FOR_DUPS(iPiPe, cNt, fOrT) \ do { \ for(unsigned long ocmp = 0; ocmp < cNt - 1; ++ocmp) { \ for(unsigned long tcmp = ocmp + 1; tcmp < cNt; ++tcmp) { \ if(strcmp(iPiPe[ocmp].fOrT.name, iPiPe[tcmp].fOrT.name) == 0 && \ (iPiPe[ocmp].fOrT.fd == iPiPe[tcmp].fOrT.fd)) { \ fprintf(stderr, "ERROR: Duplicate pipe in command line: [%s] [%s] [%d]\n", \ #fOrT, iPiPe[ocmp].fOrT.name, iPiPe[ocmp].fOrT.fd); \ exit(1); \ } \ } \ } \ } while(0) // Check for any duplicates in the from or the to pipes void pipe_info_check_for_duplicates(pipe_info_t const *const ipipe, unsigned long const cnt) { // Handle simple case where there is no or only one pipe info if(cnt <= 1) { return; } PI_CHECK_FOR_DUPS(ipipe, cnt, from); PI_CHECK_FOR_DUPS(ipipe, cnt, to); } pipexec-2.6.2/src/pipe_info.h000066400000000000000000000027511470374721700161070ustar00rootroot00000000000000#ifndef PIPEXEC_PIPE_INFO_H #define PIPEXEC_PIPE_INFO_H /* * Information about one pipe's end: * The name of the process and the fd it should get. */ struct pipes_end_info { char *name; int fd; }; typedef struct pipes_end_info pipes_end_info_t; char *pipes_end_info_parse(pipes_end_info_t *const pend, char *const str); /* * Pipe information * * This contains the source (name and fd) and the destination * (also name and fd). */ struct pipe_info { pipes_end_info_t from; pipes_end_info_t to; int pipefds[2]; }; typedef struct pipe_info pipe_info_t; void pipe_info_parse(pipe_info_t *const ipipe, int const start_argc, int const argc, char *const argv[], char const sep); void pipe_info_create_pipes(pipe_info_t *const ipipe, unsigned long const pipe_cnt); void pipe_info_close_all(pipe_info_t const *const ipipe, unsigned long const pipe_cnt); void pipe_info_dup_in_pipes(pipe_info_t *ipipe, unsigned long pipe_cnt, char *cmd_name, int close_unused); void pipe_info_print(pipe_info_t const *const ipipe, unsigned long const cnt); void pipe_info_check_for_duplicates( pipe_info_t const *const ipipe, unsigned long const cnt); unsigned int pipe_info_clp_count(int const start_argc, int const argc, char *const argv[]); void pipe_info_block_used_fds(pipe_info_t const *const ipipe, unsigned long const cnt); #endif pipexec-2.6.2/src/pipexec.c000066400000000000000000000376031470374721700155730ustar00rootroot00000000000000/* * pipexec * * Build up a directed graph of processes and pipes. * * Copyright 2015,2022 by Andreas Florath * SPDX-License-Identifier: GPL-2.0-or-later */ #define _POSIX_C_SOURCE 200809L #define _DEFAULT_SOURCE #include "src/logging.h" #include "src/version.h" #include "src/command_info.h" #include "src/pipe_info.h" #include #include #include #include #include #include #include #include #include #include #include #include /** * Globals * Used for communication between signal handler and main program. */ volatile int g_restart = 0; volatile int g_terminate = 0; volatile int g_kill_child_processes = 0; /** * Should the processes restart - pass in a 1. * If the process in the termination phase (e.g. it received itself * a signal) - this has no effect. */ void set_restart(int rs) { if (g_terminate) { logging(lid_internal, "status", "warning", "Cannot set restart flag - process will terminate", 0); return; } g_restart = rs; } /** * If an appropriate signal was sent to this process, * it will terminate - indeptendent of the results the childs return * during their shutdown. */ void set_terminate() { g_terminate = 1; g_restart = 0; } /** * An array with the pids of all child processes. * If a child is not running (e.g. during cleanup or restart phase) * the appropriate entry it set to 0. * * This needs to be global, because it is also accessed from the * interrupt handler. */ volatile unsigned int g_child_cnt = 0; volatile pid_t *g_child_pids = NULL; /** * Unset the given pid. */ void child_pids_unset(pid_t cpid) { for (unsigned int child_idx = 0; child_idx < g_child_cnt; ++child_idx) { if (g_child_pids[child_idx] == cpid) { g_child_pids[child_idx] = 0; return; } } ITOCHAR(spid, 16, cpid); logging(lid_internal, "status", "warning", "child_pids_unset: PID not found in list", 1, "pid", spid); } void child_pids_print() { int const pilen = 4096; char pbuf[pilen]; pbuf[0] = '['; int poffset = 1; int plen = pilen - poffset; bool first = true; for (unsigned int child_idx = 0; child_idx < g_child_cnt; ++child_idx) { if (g_child_pids[child_idx] == 0) { continue; } if(! first && plen > 0) { pbuf[poffset] = ','; ++poffset; } plen = pilen - poffset; poffset += snprintf(pbuf + poffset, plen, "%d", g_child_pids[child_idx]); first = false; } if(plen > 2) { pbuf[poffset] = ']'; pbuf[poffset+1] = '\0'; } logging(lid_internal, "status", "info", "Child pids", 1, "pids", pbuf); } void child_pids_kill_all() { if(! g_kill_child_processes) { logging(lid_internal, "tracing", "info", "Do not kill child processes", 0); return; } for (unsigned int child_idx = 0; child_idx < g_child_cnt; ++child_idx) { if (g_child_pids[child_idx] != 0) { pid_t const to_kill = g_child_pids[child_idx]; ITOCHAR(skill, 16, to_kill); logging(lid_internal, "tracing", "info", "Sending SIGTERM", 1, "pid", skill); kill(to_kill, SIGTERM); } } } void child_pids_wait_all() { for (unsigned int child_idx = 0; child_idx < g_child_cnt; ++child_idx) { if (g_child_pids[child_idx] != 0) { pid_t const to_wait = g_child_pids[child_idx]; ITOCHAR(swait, 16, to_wait); logging(lid_internal, "tracing", "info", "Wait for pid to terminate", 1, "pid", swait); int status; pid_t const rw = waitpid(to_wait, &status, 0); if (rw == -1) { ITOCHAR(swait, 16, to_wait); ITOCHAR(serrno, 16, errno); logging(lid_internal, "tracing", "error", "Error waiting", 3, "pid", swait, "error", strerror(errno), "errno", serrno); } else { ITOCHAR(spid, 16, to_wait); ITOCHAR(sstatus, 16, status); ITOCHAR(snormal_exit, 16, WIFEXITED(status)); ITOCHAR(schild_status, 16, WEXITSTATUS(status)); ITOCHAR(schild_signaled, 16, WIFSIGNALED(status)); logging(lid_child_exit, "exec", "info", "child exit", 5, "command_pid", spid, "status", schild_status, "normal_exit", snormal_exit, "child_status", schild_status, "child_signaled", schild_signaled); if (WIFSIGNALED(status)) { logging(lid_internal, "tracing", "info", "Signaled child", 2, "pid", spid, "signaled_with", WTERMSIG(status)); if (WTERMSIG(status) != SIGTERM) { logging(lid_internal, "tracing", "error", "Child terminated because of a different signal - not SIGTERM " "Do not restart", 1, "pid", spid); set_terminate(); } } } child_pids_unset(to_wait); } } logging(lid_internal, "tracing", "debug", "Finished waiting for all children", 0); } void child_pids_kill_all_and_wait() { child_pids_kill_all(); child_pids_wait_all(); } /** * Signal Related. */ void sh_term(int signum, siginfo_t *siginfo, void *ucontext) { (void)siginfo; (void)ucontext; ITOCHAR(ssignum, 16, signum); logging(lid_internal, "signal", "info", "signal terminate handler called - signal received", 1, "signal", ssignum); // Kill all children and stop set_terminate(); child_pids_kill_all_and_wait(); } void sh_restart(int signum, siginfo_t *siginfo, void *ucontext) { (void)siginfo; (void)ucontext; ITOCHAR(ssignum, 16, signum); logging(lid_internal, "signal", "info", "signal restart handler called - signal received", 1, "signal", ssignum); // Kill all children and restart set_restart(1); child_pids_kill_all_and_wait(); } void install_signal_handler() { struct sigaction sa_term; sa_term.sa_sigaction = sh_term; sigemptyset(&sa_term.sa_mask); sa_term.sa_flags = SA_SIGINFO | SA_NODEFER; struct sigaction sa_restart; sa_restart.sa_sigaction = sh_restart; sigemptyset(&sa_restart.sa_mask); sa_restart.sa_flags = SA_SIGINFO | SA_NODEFER; sigaction(SIGHUP, &sa_restart, NULL); sigaction(SIGINT, &sa_term, NULL); sigaction(SIGQUIT, &sa_term, NULL); sigaction(SIGTERM, &sa_term, NULL); } void uninstall_signal_handler() { struct sigaction sa_default; sa_default.sa_handler = SIG_DFL; sigemptyset(&sa_default.sa_mask); sa_default.sa_flags = SA_SIGINFO | SA_NODEFER; sigaction(SIGHUP, &sa_default, NULL); sigaction(SIGINT, &sa_default, NULL); sigaction(SIGQUIT, &sa_default, NULL); sigaction(SIGTERM, &sa_default, NULL); } // Functions using the upper data structures static void pipe_execv_one(command_info_t const *params, pipe_info_t *const ipipe, size_t const pipe_cnt) { pipe_info_dup_in_pipes(ipipe, pipe_cnt, params->cmd_name, 1); logging(lid_internal, "exec", "info", "Calling execv", 2, "command", params->cmd_name, "path", params->path); execv(params->path, params->argv); ITOCHAR(serrno, 16, errno); logging(lid_internal, "exec", "error", "Calling execv", 2, "command", params->cmd_name, "path", params->path, "errno", serrno, "error", strerror(errno)); abort(); } static pid_t pipe_execv_fork_one(command_info_t const *params, pipe_info_t *const ipipe, size_t const pipe_cnt) { command_info_print(params); pid_t const fpid = fork(); if (fpid == -1) { ITOCHAR(serrno, 16, errno); logging(lid_internal, "exec", "error", "Error during fork()", 2, "errno", serrno, "error", strerror(errno)); exit(10); } else if (fpid == 0) { uninstall_signal_handler(); pipe_execv_one(params, ipipe, pipe_cnt); // Neverreached abort(); } ITOCHAR(spid, 16, fpid); logging(lid_command_pid, "exec", "info", "New child forked", 2, "command", params->cmd_name, "command_pid", spid); // fpid>0: parent return fpid; } int pipe_execv(command_info_t *const icmd, size_t const command_cnt, pipe_info_t *const ipipe, size_t const pipe_cnt, pid_t *child_pids) { pipe_info_block_used_fds(ipipe, pipe_cnt); pipe_info_create_pipes(ipipe, pipe_cnt); // Looks that messing around with the pipes (storing them and propagating // them to all children) is not a good idea. // ... but in this case there is no other way.... for (size_t cidx = 0; cidx < command_cnt; ++cidx) { child_pids[cidx] = pipe_execv_fork_one(&icmd[cidx], ipipe, pipe_cnt); } pipe_info_close_all(ipipe, pipe_cnt); return 0; } unsigned int next_running_child() { for (unsigned int child_idx = 0; child_idx < g_child_cnt; ++child_idx) { if (g_child_pids[child_idx] != 0) { return child_idx; } } return g_child_cnt; } static void usage() { fprintf(stderr, "pipexec version %s\n", app_version); fprintf(stderr, "%s\n", desc_copyight); fprintf(stderr, "%s\n", desc_license); fprintf(stderr, "\n"); fprintf(stderr, "Usage: pipexec [options] -- process-pipe-graph\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -h display this help\n"); fprintf(stderr, " -j logfd set fd which is used for json logging\n"); fprintf(stderr, " -k kill all child processes when one \n"); fprintf(stderr, " terminates abnormally\n"); fprintf(stderr, " -l logfd set fd which is used for text logging\n"); fprintf(stderr, " -p pidfile specify a pidfile\n"); fprintf(stderr, " -s sleep_time time to wait before a restart\n"); fprintf(stderr, "\n"); fprintf(stderr, "process-pipe-graph is a list of process descriptions\n"); fprintf(stderr, " and pipe descriptions.\n"); fprintf(stderr, "process description: '[ NAME /path/to/proc ]'\n"); fprintf(stderr, "pipe description: '{NAME1:fd1>NAME2:fd2}'\n"); exit(1); } static void write_pid_file(char const *const pid_file) { ITOCHAR(spid, 16, getpid()); logging(lid_internal, "tracing", "info", "Writing pid file", 2, "pid_file", pid_file, "pid", spid); char pbuf[20]; int const plen = snprintf(pbuf, 20, "%d\n", getpid()); int const fd = open(pid_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); if (fd == -1) { ITOCHAR(serrno, 16, errno); logging(lid_internal, "tracing", "error", "Cannot open pid file", 2, "error", strerror(errno), "errno", serrno); close(fd); return; } ssize_t const written = write(fd, pbuf, plen); if (written != plen) { ITOCHAR(serrno, 16, errno); logging(lid_internal, "tracing", "error", "Write error writing pid", 2, "error", strerror(errno), "errno", serrno); } close(fd); } static void remove_pid_file(char const *const pid_file) { ITOCHAR(spid, 16, getpid()); logging(lid_internal, "tracing", "info", "Removing pid file", 2, "pid_file", pid_file, "pid", spid); int const rval = unlink(pid_file); if (rval == -1) { ITOCHAR(serrno, 16, errno); logging(lid_internal, "tracing", "error", "Cannot remove pid file", 2, "error", strerror(errno), "errno", serrno); } } int main(int argc, char *argv[]) { int sleep_timer = 0; char *pid_file = NULL; int opt; while ((opt = getopt(argc, argv, "hj:kl:p:s:-")) != -1) { switch (opt) { case 'h': usage(); break; case 'j': { if(*optarg=='s') { logging_json_set_global_use_syslog(); } else { int const logfd = atoi(optarg); logging_json_set_global_log_fd(logfd); } } break; case 'k': g_kill_child_processes = 1; break; case 'l': { if(*optarg=='s') { logging_text_set_global_use_syslog(); } else { int const logfd = atoi(optarg); logging_text_set_global_log_fd(logfd); } } break; case 'p': pid_file = optarg; break; case 's': sleep_timer = atoi(optarg); break; case '-': // The rest are commands..... break; default: /* '?' */ usage(); } } if (optind == argc) { fprintf(stderr, "Error: No command-pipe given\n"); usage(); } if(sleep_timer==0) { // When there is no restart give - terminate all processes when done set_restart(0); set_terminate(); } logging(lid_internal, "version", "info", "pipexec", 1, "version", app_version); if (pid_file != NULL) { write_pid_file(pid_file); } install_signal_handler(); unsigned int const command_cnt = command_info_clp_count(optind, argc, argv); unsigned int const pipe_cnt = pipe_info_clp_count(optind, argc, argv); ITOCHAR(scommand_cnt, 16, command_cnt); logging(lid_internal, "command_line", "info", "Number of commands", 1, "command_cnt", scommand_cnt); ITOCHAR(spipe_cnt, 16, pipe_cnt); logging(lid_internal, "command_line", "info", "Number of pipes", 1, "command_cnt", scommand_cnt); unsigned int handled_args = 0; command_info_t icmd[command_cnt]; handled_args += command_info_array_constrcutor(icmd, optind, argc, argv); command_info_array_print(icmd, command_cnt); pipe_info_t ipipe[pipe_cnt]; pipe_info_parse(ipipe, optind, argc, argv, '>'); pipe_info_print(ipipe, pipe_cnt); pipe_info_check_for_duplicates(ipipe, pipe_cnt); ITOCHAR(shandled_args, 16, handled_args); logging(lid_internal, "command_line", "info", "Number of handled args", 1, "handled_args", shandled_args); unsigned int const not_processed_args = argc - optind - pipe_cnt - handled_args; ITOCHAR(snot_processed_args, 16, not_processed_args); logging(lid_internal, "command_line", "info", "Not processed args", 1, "not_processed_args", snot_processed_args); if(argc - optind - pipe_cnt - handled_args > 0) { logging(lid_internal, "command_line", "error", "Error: rubbish / unparsable parameters given", 0); usage(); } // Provide memory for child_pids and initialize. pid_t child_pids[command_cnt]; for (unsigned int i = 0; i < command_cnt; ++i) { child_pids[i] = 0; } g_child_pids = child_pids; g_child_cnt = command_cnt; bool child_failed = false; do { if (next_running_child() == command_cnt) { set_restart(0); ITOCHAR(schild_count, 16, command_cnt); logging(lid_internal, "exec", "info", "Start all children", 1, "child_count", schild_count); pipe_execv(icmd, command_cnt, ipipe, pipe_cnt, child_pids); } logging(lid_internal, "exec", "info", "Wait for termination of children", 0); while (next_running_child() != command_cnt) { // Still running children logging(lid_internal, "exec", "info", "Wait for next child to terminate", 0); child_pids_print(); int status; logging(lid_internal, "exec", "info", "Calling wait", 0); pid_t const cpid = wait(&status); if (cpid == -1) { ITOCHAR(swait, 16, cpid); ITOCHAR(serrno, 16, errno); logging(lid_internal, "tracing", "error", "Error waiting", 3, "pid", swait, "error", strerror(errno), "errno", serrno); } else { ITOCHAR(spid, 16, cpid); ITOCHAR(sstatus, 16, status); ITOCHAR(snormal_exit, 16, WIFEXITED(status)); ITOCHAR(schild_status, 16, WEXITSTATUS(status)); ITOCHAR(schild_signaled, 16, WIFSIGNALED(status)); logging(lid_child_exit, "exec", "info", "child exit", 5, "command_pid", spid, "status", schild_status, "normal_exit", snormal_exit, "child_status", schild_status, "child_signaled", schild_signaled); child_pids_unset(cpid); if( status!=0 ) { child_failed = true; } if (!WIFEXITED(status) || WIFSIGNALED(status)) { logging(lid_internal, "tracing", "warning", "Unnormal termination/signaling of child - restarting", 1, "pid", spid); set_restart(1); child_pids_kill_all_and_wait(); } } logging(lid_internal, "tracing", "debug", "Remaining children", 0); child_pids_print(); if (g_restart && sleep_timer != 0) { ITOCHAR(ssleep_timer, 16, sleep_timer); logging(lid_internal, "tracing", "info", "Waiting for before restart", 1, "sleep_timer", ssleep_timer); sleep(sleep_timer); logging(lid_internal, "tracing", "info", "Continue restarting", 0); } } } while (g_restart); if (pid_file != NULL) { remove_pid_file(pid_file); } logging(lid_internal, "tracing", "info", "exiting", 0); return child_failed ? 1 : 0; } pipexec-2.6.2/src/ptee.c000066400000000000000000000044141470374721700150650ustar00rootroot00000000000000/* * ptee * * Tee for pipes / fds. * This is like the 'normal' tee - except that it does not output to * different files but to differend fds. * * Copyright 2015,2022 by Andreas Florath * SPDX-License-Identifier: GPL-2.0-or-later */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "src/version.h" static void usage() { fprintf(stderr, "ptee from pipexec version %s\n", app_version); fprintf(stderr, "%s\n", desc_copyight); fprintf(stderr, "%s\n", desc_license); fprintf(stderr, "\n"); fprintf(stderr, "Usage: ptee [options] fd [fd ...]\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -h display this help\n"); fprintf(stderr, " -r fd fd to read from\n"); exit(1); } int main(int argc, char * argv[]) { int read_fd = 0; int opt; while ((opt = getopt(argc, argv, "hr:")) != -1) { switch (opt) { case 'h': usage(); break; case 'r': read_fd = atoi(optarg); break; default: /* '?' */ usage(); } } if(optind==argc) { fprintf(stderr, "Error: No fds given\n"); usage(); } // All parameters are fds. size_t const fd_cnt = argc - optind; int fds[fd_cnt]; size_t fdidx = 0; for(int aidx=optind; aidx"}; char const desc_license[] = {"License GPLv2+: GNU GPL version 2 or later " "."}; pipexec-2.6.2/src/version.h000066400000000000000000000002441470374721700156170ustar00rootroot00000000000000#ifndef PIPEXEC_VERSION_H #define PIPEXEC_VERSION_H extern char const app_version[]; extern char const desc_copyight[]; extern char const desc_license[]; #endif pipexec-2.6.2/test/000077500000000000000000000000001470374721700141515ustar00rootroot00000000000000pipexec-2.6.2/test/Makefile.inc000066400000000000000000000001671470374721700163650ustar00rootroot00000000000000# ptest bin_PROGRAMS += test/ptest test_ptest_SOURCES = \ test/ptest.c # Local Variables: # mode: makefile # End: pipexec-2.6.2/test/basic_tests.sh000066400000000000000000000013111470374721700170040ustar00rootroot00000000000000#!/bin/bash # # Copyright 2015,2022 by Andreas Florath # SPDX-License-Identifier: GPL-2.0-or-later # set -e PE=./bin/pipexec function fail() { echo "ERROR: test failed" exit 1 } echo "TEST: run without any arguments" if ${PE} 2>/dev/null; then fail fi echo "TEST: check return code when all childs succeed" if ! ${PE} -- [ A /bin/true ]; then fail fi echo "TEST: check return code when one child fails" if ${PE} -- [ A /bin/true ] [ B /bin/false ] [ C /bin/true ]; then fail fi echo "TEST: simple pipe" RES=$(./bin/pipexec -- [ ECHO /bin/echo Hello World ] [ CAT /bin/cat ] [ GREP /bin/grep Hello ] '{ECHO:1>CAT:0}' '{CAT:1>GREP:0}') if test "${RES}" != "Hello World"; then fail fi pipexec-2.6.2/test/ptest.c000066400000000000000000000003331470374721700154530ustar00rootroot00000000000000#include int main() { size_t r; while(1) { r=write(1, "ptest out\n", 10); char buf[10]; r=read(0, buf, 10); r=write(3, buf, 10); sleep(1); } (void)r; return 0; } pipexec-2.6.2/version.sh000077500000000000000000000003561470374721700152220ustar00rootroot00000000000000#!/bin/sh set -e TOPSRCDIR="" if test $# -eq 1; then TOPSRCDIR="$1" cd "${TOPSRCDIR}" fi if test -f version.txt; then cat version.txt exit 0 fi VERSION=$(git describe --tags --abbrev=8 HEAD 2>/dev/null) echo ${VERSION}