pax_global_header00006660000000000000000000000064142476076740014533gustar00rootroot0000000000000052 comment=c8bdbc0a2ed822fc7c67c5c3e102d89fe27fb2d0 inotify-tools-3.22.6.0/000077500000000000000000000000001424760767400146025ustar00rootroot00000000000000inotify-tools-3.22.6.0/.cirrus.yml000066400000000000000000000004451424760767400167150ustar00rootroot00000000000000task: name: FreeBSD freebsd_instance: matrix: image_family: freebsd-12-2 image_family: freebsd-11-4 pkginstall_script: - pkg install -y autoconf automake libtool bash git gcc - ln -s /usr/local/bin/bash /bin/ compile_script: - ./build_and_test.sh clean inotify-tools-3.22.6.0/.clang-format000066400000000000000000000000661424760767400171570ustar00rootroot00000000000000BasedOnStyle: chromium IndentWidth: 8 UseTab: Always inotify-tools-3.22.6.0/.github/000077500000000000000000000000001424760767400161425ustar00rootroot00000000000000inotify-tools-3.22.6.0/.github/FUNDING.yml000066400000000000000000000000371424760767400177570ustar00rootroot00000000000000open_collective: inotify-tools inotify-tools-3.22.6.0/.github/workflows/000077500000000000000000000000001424760767400201775ustar00rootroot00000000000000inotify-tools-3.22.6.0/.github/workflows/build.yml000066400000000000000000000007111424760767400220200ustar00rootroot00000000000000name: build on: [push, pull_request] jobs: build-1804: runs-on: ubuntu-18.04 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v1 - name: build_and_test run: ./build_and_test.sh clean build-2004: runs-on: ubuntu-20.04 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v1 - name: build_and_test run: ./build_and_test.sh clean inotify-tools-3.22.6.0/.github/workflows/issues.yml000066400000000000000000000007401424760767400222360ustar00rootroot00000000000000name: 'Automatically close stale issues and PRs' on: schedule: - cron: '0 0 * * *' jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v3 with: stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 2 years with no activity. Remove stale label or comment or this will be closed in 30 days.' days-before-stale: 730 days-before-close: 30 operations-per-run: 500 inotify-tools-3.22.6.0/.gitignore000066400000000000000000000007731424760767400166010ustar00rootroot00000000000000*~ *.la *.lo *.o *.diff *.patch *.orig *.rej .deps .libs Makefile !t/Makefile Makefile.in config/ /aclocal.m4 /autom4te.cache /configure /libinotifytools/src/doc /libinotifytools/src/inotifytools/inotify.h /libinotifytools/src/inotifytools/fanotify.h /libinotifytools/src/inotifytools/stamp-h2 /src/inotifywait /src/inotifywatch /src/fsnotifywait /src/fsnotifywatch config.h config.h.in config.log config.status libtool man/inotifywait.1 man/inotifywatch.1 stamp-h1 README libinotifytools/src/test inotify-tools-3.22.6.0/.travis.yml000066400000000000000000000002201424760767400167050ustar00rootroot00000000000000language: c arch: - amd64 - arm64 - s390x compiler: - gcc - clang dist: focal install: skip script: - ./build_and_test.sh clean inotify-tools-3.22.6.0/AUTHORS000066400000000000000000000013031424760767400156470ustar00rootroot00000000000000Andreas Schwab angerangel Bernhard M. Wiedemann Carl Bordum Hansen Edward Betts Enrico M. Crisostomo Eric Curtin Ilya Shipitsin Jackie Huang Jan Kratochvil Johannes Weißl jonathan00 liuguangzhao Mike Frysinger Radu Voicilas Ramkumar Ramachandra Rohan McGovern Sjon Thomas Kho inotify-tools-3.22.6.0/COPYING000066400000000000000000000431171424760767400156430ustar00rootroot00000000000000 GNU 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 Library 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. Copyright (C) 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. , 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 Library General Public License instead of this License. inotify-tools-3.22.6.0/ChangeLog000066400000000000000000000001631424760767400163540ustar00rootroot00000000000000This file is NOT updated manually. It is automatically generated from subversion commit messages by 'make dist'. inotify-tools-3.22.6.0/INSTALL000066400000000000000000000373671424760767400156530ustar00rootroot00000000000000Installation Instructions ************************* Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without warranty of any kind. Pre-requisites ============== Install autoconf, on Ubuntu: sudo apt-get install autoconf then install automake, on Ubuntu: sudo apt-get install autotools-dev sudo apt-get install automake then install libtool, on Ubuntu: sudo apt-get install libtool Now you can proceed with basic intallation: ./autogen.sh ./configure --prefix=/usr make sudo make install Basic Installation ================== Briefly, the shell command `./configure && make && make install' should configure, build, and install this package. The following more-detailed instructions are generic; see the `README' file for instructions specific to this package. Some packages provide this `INSTALL' file but do not implement all of the features documented below. The lack of an optional feature in a given package is not necessarily a bug. More recommendations for GNU packages can be found in *note Makefile Conventions: (standards)Makefile Conventions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). It can also use an optional file (typically called `config.cache' and enabled with `--cache-file=config.cache' or simply `-C') that saves the results of its tests to speed up reconfiguring. Caching is disabled by default to prevent problems with accidental use of stale cache files. If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If you are using the cache, and at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.ac' (or `configure.in') is used to create `configure' by a program called `autoconf'. You need `configure.ac' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. Running `configure' might take a while. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package, generally using the just-built uninstalled binaries. 4. Type `make install' to install the programs and any data files and documentation. When installing into a prefix owned by root, it is recommended that the package be configured and built as a regular user, and only the `make install' phase executed with root privileges. 5. Optionally, type `make installcheck' to repeat any self-tests, but this time using the binaries in their final installed location. This target does not install anything. Running this target as a regular user, particularly if the prior `make install' required root privileges, verifies that the installation completed correctly. 6. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. 7. Often, you can also type `make uninstall' to remove the installed files again. In practice, not all packages have tested that uninstallation works correctly, even though it is required by the GNU Coding Standards. 8. Some packages, particularly those that use Automake, provide `make distcheck', which can by used by developers to test that all other targets like `make install' and `make uninstall' work correctly. This target is generally not run by end users. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. Run `./configure --help' for details on some of the pertinent environment variables. You can give `configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c99 CFLAGS=-g LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you can use GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. This is known as a "VPATH" build. With a non-GNU `make', it is safer to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. On MacOS X 10.5 and later systems, you can create libraries and executables that work on multiple system types--known as "fat" or "universal" binaries--by specifying multiple `-arch' options to the compiler but only a single `-arch' option to the preprocessor. Like this: ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CPP="gcc -E" CXXCPP="g++ -E" This is not guaranteed to produce working output in all cases, you may have to build one architecture at a time and combine the results using the `lipo' tool if you have problems. Installation Names ================== By default, `make install' installs the package's commands under `/usr/local/bin', include files under `/usr/local/include', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PREFIX', where PREFIX must be an absolute file name. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you pass the option `--exec-prefix=PREFIX' to `configure', the package uses PREFIX as the prefix for installing programs and libraries. Documentation and other data files still use the regular prefix. In addition, if you use an unusual directory layout you can give options like `--bindir=DIR' to specify different values for particular kinds of files. Run `configure --help' for a list of the directories you can set and what kinds of files go in them. In general, the default for these options is expressed in terms of `${prefix}', so that specifying just `--prefix' will affect all of the other directory specifications that were not explicitly provided. The most portable way to affect installation locations is to pass the correct locations to `configure'; however, many packages provide one or both of the following shortcuts of passing variable assignments to the `make install' command line to change installation locations without having to reconfigure or recompile. The first method involves providing an override variable for each affected directory. For example, `make install prefix=/alternate/directory' will choose an alternate location for all directory configuration variables that were expressed in terms of `${prefix}'. Any directories that were specified during `configure', but not in terms of `${prefix}', must each be overridden at install time for the entire installation to be relocated. The approach of makefile variable overrides for each directory variable is required by the GNU Coding Standards, and ideally causes no recompilation. However, some platforms have known limitations with the semantics of shared libraries that end up requiring recompilation when using this method, particularly noticeable in packages that use GNU Libtool. The second method involves providing the `DESTDIR' variable. For example, `make install DESTDIR=/alternate/directory' will prepend `/alternate/directory' before all installation names. The approach of `DESTDIR' overrides is not required by the GNU Coding Standards, and does not work on platforms that have drive letters. On the other hand, it does better at avoiding recompilation issues, and works well even when some directory options were not specified in terms of `${prefix}' at `configure' time. Optional Features ================= If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Some packages offer the ability to configure how verbose the execution of `make' will be. For these packages, running `./configure --enable-silent-rules' sets the default to minimal output, which can be overridden with `make V=1'; while running `./configure --disable-silent-rules' sets the default to verbose, which can be overridden with `make V=0'. Particular systems ================== On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC is not installed, it is recommended to use the following options in order to use an ANSI C compiler: ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" and if that doesn't work, install pre-built binaries of GCC for HP-UX. HP-UX `make' updates targets which have the same time stamps as their prerequisites, which makes it generally unusable when shipped generated files such as `configure' are involved. Use GNU `make' instead. On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot parse its `' header file. The option `-nodtk' can be used as a workaround. If GNU CC is not installed, it is therefore recommended to try ./configure CC="cc" and if that doesn't work, try ./configure CC="cc -nodtk" On Solaris, don't put `/usr/ucb' early in your `PATH'. This directory contains several dysfunctional programs; working variants of these programs are available in `/usr/bin'. So, if you need `/usr/ucb' in your `PATH', put it _after_ `/usr/bin'. On Haiku, software installed for all users goes in `/boot/common', not `/usr/local'. It is recommended to use the following options: ./configure --prefix=/boot/common Specifying the System Type ========================== There may be some features `configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, `configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the `--build=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the option `--target=TYPE' to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with `--host=TYPE'. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to `configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the `configure' command line, using `VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified `gcc' to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for `CONFIG_SHELL' due to an Autoconf limitation. Until the limitation is lifted, you can use this workaround: CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash `configure' Invocation ====================== `configure' recognizes the following options to control how it operates. `--help' `-h' Print a summary of all of the options to `configure', and exit. `--help=short' `--help=recursive' Print a summary of the options unique to this package's `configure', and exit. The `short' variant lists options used only in the top level, while the `recursive' variant lists options also present in any nested packages. `--version' `-V' Print the version of Autoconf used to generate the `configure' script, and exit. `--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally `config.cache'. FILE defaults to `/dev/null' to disable caching. `--config-cache' `-C' Alias for `--cache-file=config.cache'. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to `/dev/null' (any error messages will still be shown). `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `--prefix=DIR' Use DIR as the installation prefix. *note Installation Names:: for more details, including other options available for fine-tuning the installation locations. `--no-create' `-n' Run the configure checks, but stop before creating any output files. `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. inotify-tools-3.22.6.0/Makefile.am000066400000000000000000000004171424760767400166400ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 SUBDIRS = libinotifytools src man README: README.md dist-hook: # Automake official documentation states that dist-hook should assume files # are not writable. chmod u+w $(distdir)/ChangeLog git log --pretty=format:'%s' > $(distdir)/ChangeLog inotify-tools-3.22.6.0/NEWS000066400000000000000000000000601424760767400152750ustar00rootroot00000000000000 * 19 October 2005 inotify-tools begun. inotify-tools-3.22.6.0/README.md000066400000000000000000000022511424760767400160610ustar00rootroot00000000000000[![GitHub Build Status](https://github.com/inotify-tools/inotify-tools/workflows/build/badge.svg)](https://github.com/inotify-tools/inotify-tools/actions) [![Travis Build Status](https://travis-ci.com/inotify-tools/inotify-tools.svg?branch=master)](https://travis-ci.com/inotify-tools/inotify-tools) [![Cirrus Build Status](https://api.cirrus-ci.com/github/inotify-tools/inotify-tools.svg?branch=master)](https://cirrus-ci.com/github/inotify-tools/inotify-tools) [![Coverity Build Status](https://scan.coverity.com/projects/23295/badge.svg)](https://scan.coverity.com/projects/inotifytools) [![Language Grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/inotify-tools/inotify-tools.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/inotify-tools/inotify-tools/context:cpp) [![Coverage Status](https://codecov.io/gh/inotify-tools/inotify-tools/coverage.svg?branch=master)](https://codecov.io/gh/inotify-tools/inotify-tools?branch=master) inotify-tools ============= This is a package of some commandline utilities relating to inotify. The general purpose of this package is to allow inotify's features to be used from within shell scripts. Read the man pages for further details. inotify-tools-3.22.6.0/autogen.sh000077500000000000000000000000571424760767400166050ustar00rootroot00000000000000#!/bin/sh autoreconf --install "$@" || exit 1 inotify-tools-3.22.6.0/build_and_test.sh000077500000000000000000000147521424760767400201320ustar00rootroot00000000000000#!/bin/bash set -e j=16 unit_test() { if [ "$os" != "freebsd" ]; then printf "\nunit test\n" cd libinotifytools/src/ make -j$j test ./test cd - fi } integration_test() { printf "\nintegration test\n" for i in {1..128}; do cd t make -j$j cd - done } tests() { unit_test integration_test } clean() { make distclean || true if [ "$arg1" == "clean" ]; then git clean -fdx > /dev/null 2>&1 fi } build() { ./autogen.sh ./configure --prefix=/usr $@ make -j$j unset CFLAGS unset LDFLAGS } arg1="$1" os=$(uname -o | sed "s#GNU/##g" | tr '[:upper:]' '[:lower:]') uname_m=$(uname -m) printf "gcc build\n" clean export CC="gcc" build tests if [ -n "$TRAVIS" ] || [ -n "$CI" ]; then if [ "$os" != "freebsd" ]; then sudo apt update || true sudo apt install -y gcc-arm-linux-gnueabihf || true sudo apt install -y cppcheck || true sudo apt install -y clang || true sudo apt install -y gcc || true sudo apt install -y clang-tidy || true sudo apt install -y clang-format || true sudo apt install -y clang-tools || true sudo apt install -y clang-format-11 || true sudo apt install -y doxygen || true fi for i in {64..11}; do if command -v "git-clang-format-$i" > /dev/null; then CLANG_FMT_VER="clang-format-$i" break fi done if [ -n "$CLANG_FMT_VER" ]; then printf "\nclang-format build\n" if ! git $CLANG_FMT_VER HEAD^ | grep -q "modif"; then echo -e "\nPlease change style to the format defined in the" \ ".clang-format file:\n" git diff --name-only exit 1 fi fi fi usr_inc="/usr/include" inotifytools_inc="libinotifytools/src" inotifytools_inc2="$inotifytools_inc/inotifytools" inc="-I$usr_inc -I$inotifytools_inc -I$inotifytools_inc2" if command -v clang-tidy > /dev/null; then printf "\nclang-tidy build\n" s_c_t="-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling" s_c_t="$s_c_t,-clang-analyzer-valist.Uninitialized" s_c_t="$s_c_t,-clang-analyzer-unix.Malloc" s_c_t="$s_c_t,-clang-analyzer-security.insecureAPI.strcpy" s_c_t="$s_c_t,-clang-diagnostic-incompatible-pointer-types-discards-qualifiers" c_t="clang-tidy" q="--quiet" w="--warnings-as-errors" $c_t $q $w=* --checks=$s_c_t $(find . -name "*.[c|h]") -- $inc fi if command -v doxygen > /dev/null; then printf "rh build\n" clean export CC="gcc" ./rh_build.sh tests fi printf "gcc static build\n" clean export CC="gcc" build --enable-static --disable-shared tests if [ "$os" != "freebsd" ] && ldconfig -p | grep -q libasan; then printf "\ngcc address sanitizer build\n" clean export CC="gcc" export CFLAGS="-fsanitize=address -O0 -ggdb" export LDFLAGS="-fsanitize=address -O0 -ggdb" build tests fi if command -v arm-linux-gnueabihf-gcc > /dev/null; then printf "\ngcc arm32 build\n" clean export CC="arm-linux-gnueabihf-gcc" build --host=arm-linux-gnueabihf if [ "$uname_m" == "aarch64" ]; then tests fi fi printf "\nclang build\n" clean export CC="clang" build tests if command -v scan-build > /dev/null; then printf "\ngcc scan-build\n" clean export CC="gcc" scan-build ./autogen.sh scan-build ./configure scan_build_args="-disable-checker unix.Malloc" scan_build_args="$scan_build_args -disable-checker core.AttributeNonNull" scan_build_args="$scan_build_args -disable-checker core.NonNullParamChecker" scan_build="$(scan-build $scan_build_args make -j$j)" echo "$scan_build" if ! echo "$scan_build" | grep -qi "no bugs found\|0 bugs found"; then false fi printf "\nclang scan-build\n" clean export CC="clang" scan-build ./autogen.sh scan-build ./configure scan_build=$(scan-build $scan_build_args make -j$j) echo "$scan_build" if ! echo "$scan_build" | grep -qi "no bugs found\|0 bugs found"; then false fi fi tests printf "\nclang static build\n" clean export CC="clang" build --enable-static --disable-shared tests if command -v cppcheck > /dev/null; then u="-U restrict -U __REDIRECT -U __restrict_arr -U __restrict" u="$u -U __REDIRECT_NTH -U _BSD_RUNE_T_ -U _TYPE_size_t -U __LDBL_REDIR1_DECL" supp="--suppress=missingInclude --suppress=unusedFunction" arg="-q --force $u --enable=all $inc $supp --error-exitcode=1" cppcheck="xargs cppcheck $arg" suppf="redblack.c" if find . -name "*.[c|h]" | grep -v "$suppf" | $cppcheck 2>&1 | grep ^; then false fi fi printf "\ngcc coverage build\n" clean export CC="gcc" export CFLAGS="--coverage" export LDFLAGS="--coverage" build --enable-static --disable-shared tests bash <(curl -s https://codecov.io/bash) if [ "$os" != "freebsd" ] && [ "$(uname -m)" == "x86_64" ]; then printf "\ncov-build build\n" clean file="/tmp/cov-analysis-${os}64.tar.gz" project="inotifytools" token="Dy7fkaSpHHjTg8JMFHKgOw" curl -o "$file" "https://scan.coverity.com/download/${os}64" \ --form project="$project" --form token="$token" tar xf "$file" export CC="gcc" ./autogen.sh ./configure cov-analysis-${os}64-*/bin/cov-build --dir cov-int make -j$j tar cfz cov-int.tar.gz cov-int version="$(git rev-parse HEAD)" description="$(git show --no-patch --oneline)" curl --form token="$token" \ --form email=ericcurtin17@gmail.com \ --form file=@cov-int.tar.gz \ --form version="$version" \ --form description="$description" \ https://scan.coverity.com/builds?project=$project # sonarcloud export SONAR_TOKEN="0bc5d48614caa711d6b908f80c039464aff99611" mkdir -p $HOME/.sonar SONAR_SCANNER_VERSION="4.4.0.2170" SONAR_SCANNER_DOWNLOAD_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip" curl -sSLo $HOME/.sonar/sonar-scanner.zip $SONAR_SCANNER_DOWNLOAD_URL unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ PATH="$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION-linux/bin:$PATH" SONAR_SERVER_URL="https://sonarcloud.io" BUILD_WRAPPER_DOWNLOAD_URL="$SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip" curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip $BUILD_WRAPPER_DOWNLOAD_URL unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ PATH="$HOME/.sonar/build-wrapper-linux-x86:$PATH" BUILD_WRAPPER_OUT_DIR="build_wrapper_output_directory" clean export CC="gcc" unset CFLAGS unset LDFLAGS ./autogen.sh ./configure build-wrapper-linux-x86-64 --out-dir $BUILD_WRAPPER_OUT_DIR make -j$j sonar-scanner --define sonar.host.url="$SONAR_SERVER_URL" --define sonar.cfamily.build-wrapper-output="$BUILD_WRAPPER_OUT_DIR" fi inotify-tools-3.22.6.0/configure.ac000066400000000000000000000060531424760767400170740ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) AC_INIT([inotify-tools], [3.22.6.0]) AC_CONFIG_AUX_DIR([config]) AC_CONFIG_SRCDIR([src/inotifywait.c]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([libinotifytools/src/inotifytools/inotify.h]) AC_CONFIG_HEADERS([libinotifytools/src/inotifytools/fanotify.h]) AC_CONFIG_MACRO_DIR([m4]) AC_DEFINE([_GNU_SOURCE], [], [For a few GNU-specific functions]) AC_PROG_MAKE_SET # Checks for programs. AC_PROG_CC AM_INIT_AUTOMAKE LT_INIT AC_PATH_PROG(DOXYGEN, doxygen, NO_DOXYGEN) AC_ARG_ENABLE(doxygen, AS_HELP_STRING([--enable-doxygen],[enable libinotifytools API docs generation]), DOXYGEN_ENABLE=$enableval, [if test "$DOXYGEN" != NO_DOXYGEN; then DOXYGEN_ENABLE=yes else DOXYGEN_ENABLE=no fi] ) if test "$DOXYGEN_ENABLE" = "yes" && test "$DOXYGEN" = "NO_DOXYGEN"; then AC_MSG_ERROR([Cannot find doxygen! Make sure it is in PATH.]) fi if test "$DOXYGEN_ENABLE" = "no"; then DOXYGEN=NO_DOXYGEN fi AM_CONDITIONAL([DOXYGEN_ENABLE], test x$DOXYGEN != xNO_DOXYGEN ) AC_ARG_ENABLE(static-binary, AS_HELP_STRING([--enable-static-binary],[enable static linking of binaries]), STATIC_BINARY_ENABLE=$enableval, STATIC_BINARY_ENABLE=no) AM_CONDITIONAL([STATIC_BINARY_ENABLE], test "$STATIC_BINARY_ENABLE" = "yes") # Checks for libraries. # Checks for header files. AC_CHECK_HEADERS([sys/inotify.h sys/fanotify.h mcheck.h]) AC_LANG(C) AC_MSG_CHECKING([whether sys/inotify.h actually works]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[#include ]], [[return (-1 == inotify_init());]] )], [AC_MSG_RESULT([yup]); AC_DEFINE([SYS_INOTIFY_H_EXISTS_AND_WORKS],[1],[sys/inotify.h exists and works correctly])], [AC_MSG_RESULT([nope, using own inotify headers])] ) # Checks for fanotify support. AC_MSG_CHECKING([whether sys/fanotify.h actually works]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[#include ]], [[return (-1 == fanotify_init(FAN_REPORT_DFID_NAME, 0));]] )], [AC_MSG_RESULT([yup]); DEFAULT_FANOTIFY=yes; AC_DEFINE([SYS_FANOTIFY_H_EXISTS_AND_WORKS],[1],[sys/fanotify.h exists and works correctly])], [AC_MSG_RESULT([nope, using own fanotify headers]); DEFAULT_FANOTIFY=no] ) AC_ARG_ENABLE(fanotify, AS_HELP_STRING([--enable-fanotify],[enable fanotify support]), ENABLE_FANOTIFY=$enableval, DOXYGEN_ENABLE="$DEFAULT_FANOTIFY" ) AM_CONDITIONAL([ENABLE_FANOTIFY], test "$ENABLE_FANOTIFY" = "yes") # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_C_INLINE # Checks for library functions. AC_CHECK_FUNCS([daemon]) # Set variables used in man page templates MAN_DATE=$(date -u -r ChangeLog +'%Y-%m-%d') MAN_PACKAGE_VERSION=$PACKAGE_VERSION AC_SUBST([MAN_DATE]) AC_SUBST([MAN_PACKAGE_VERSION]) # Output... AC_CONFIG_FILES([ Makefile src/Makefile man/Makefile man/inotifywait.1 man/inotifywatch.1 man/fsnotifywait.1 man/fsnotifywatch.1 libinotifytools/Makefile libinotifytools/src/Makefile libinotifytools/src/inotifytools/Makefile ]) AC_OUTPUT inotify-tools-3.22.6.0/libinotifytools/000077500000000000000000000000001424760767400200335ustar00rootroot00000000000000inotify-tools-3.22.6.0/libinotifytools/Makefile.am000066400000000000000000000000161424760767400220640ustar00rootroot00000000000000SUBDIRS = src inotify-tools-3.22.6.0/libinotifytools/src/000077500000000000000000000000001424760767400206225ustar00rootroot00000000000000inotify-tools-3.22.6.0/libinotifytools/src/Doxyfile000066400000000000000000001452221424760767400223360ustar00rootroot00000000000000# Doxyfile 1.4.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = libinotifytools # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = doc # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, # Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, # Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, # Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, # Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # This tag can be used to specify the encoding used in the generated output. # The encoding is not always determined by the language that is chosen, # but also whether or not the output is meant for Windows or non-Windows users. # In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES # forces the Windows encoding (this is the default for the Windows binary), # whereas setting the tag to NO uses a Unix-style encoding (the default for # all platforms other than Windows). USE_WINDOWS_ENCODING = NO # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like the Qt-style comments (thus requiring an # explicit @brief command for a brief description. JAVADOC_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the DETAILS_AT_TOP tag is set to YES then Doxygen # will output the detailed description near the top, like JavaDoc. # If set to NO, the detailed description appears after the member # documentation. DETAILS_AT_TOP = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for Java. # For instance, namespaces will be presented as packages, qualified scopes # will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to # include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = YES # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = YES # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from the # version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = inotifytools/inotifytools.h inotifytools.c # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py FILE_PATTERNS = # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = . # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES (the default) # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES (the default) # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compressed HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # If the GENERATE_TREEVIEW tag is set to YES, a side panel will be # generated containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a caller dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected # functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width # (in pixels) of the graphs generated by dot. If a graph becomes larger than # this value, doxygen will try to truncate the graph, so that it fits within # the specified constraint. Beware that most browsers cannot cope with very # large images. MAX_DOT_GRAPH_WIDTH = 1024 # The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height # (in pixels) of the graphs generated by dot. If a graph becomes larger than # this value, doxygen will try to truncate the graph, so that it fits within # the specified constraint. Beware that most browsers cannot cope with very # large images. MAX_DOT_GRAPH_HEIGHT = 1024 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that a graph may be further truncated if the graph's # image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH # and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), # the graph is not depth-constrained. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, which results in a white background. # Warning: Depending on the platform used, enabling this option may lead to # badly anti-aliased labels on the edges of a graph (i.e. they become hard to # read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO inotify-tools-3.22.6.0/libinotifytools/src/Makefile.am000066400000000000000000000016531424760767400226630ustar00rootroot00000000000000SUBDIRS = inotifytools lib_LTLIBRARIES = libinotifytools.la libinotifytools_la_SOURCES = inotifytools.c inotifytools_p.h redblack.c redblack.h stats.c stats.h libinotifytools_la_LDFLAGS = -version-info 4:1:4 check_PROGRAMS = test test_SOURCES = test.c test_LDADD = libinotifytools.la TESTS = test EXTRA_DIST = example.c Doxyfile nobase_include_HEADERS = inotifytools/inotifytools.h inotifytools/inotify-nosys.h inotifytools/inotify.h nobase_include_HEADERS += inotifytools/fanotify-dfid-name.h inotifytools/fanotify.h AM_CFLAGS = -std=c99 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wno-sign-compare -Wno-missing-field-initializers -Werror if DOXYGEN_ENABLE doc/html/*: inotifytools.c Doxyfile $(DOXYGEN) shareddocdir = $(datadir)/doc doc_DATA = doc/html/* clean-local: rm -rf 'doc' uninstall-hook: rm -rf '$(DESTDIR)/$(shareddocdir)/libinotifytools' endif DOXYGEN_ENABLE dist-hook: rm $(distdir)/inotifytools/inotify.h inotify-tools-3.22.6.0/libinotifytools/src/example.c000066400000000000000000000014721424760767400224250ustar00rootroot00000000000000#include #include #include #include /* * libinotifytools example program. * Compile with gcc -linotifytools example.c */ int main() { // initialize and watch the entire directory tree from the current working // directory downwards for all events if ( !inotifytools_initialize() || !inotifytools_watch_recursively( ".", IN_ALL_EVENTS ) ) { fprintf(stderr, "%s\n", strerror( inotifytools_error() ) ); return -1; } // set time format to 24 hour time, HH:MM:SS inotifytools_set_printf_timefmt( "%T" ); // Output all events as " " struct inotify_event * event = inotifytools_next_event( -1 ); while ( event ) { inotifytools_printf( event, "%T %w%f %e\n" ); event = inotifytools_next_event( -1 ); } } inotify-tools-3.22.6.0/libinotifytools/src/inotifytools.c000066400000000000000000002124671424760767400235440ustar00rootroot00000000000000// kate: replace-tabs off; space-indent off; /** * @mainpage libinotifytools * * libinotifytools is a small C library to simplify the use of Linux's inotify * interface. * * @link inotifytools/inotifytools.h Documentation for the library's public * interface.@endlink * * @link todo.html TODO list.@endlink */ #include "../../config.h" #include "inotifytools/inotifytools.h" #include "inotifytools_p.h" #include "stats.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "inotifytools/inotify.h" #ifdef __FreeBSD__ struct fanotify_event_fid; #define FAN_EVENT_INFO_TYPE_FID 1 #define FAN_EVENT_INFO_TYPE_DFID_NAME 2 #define FAN_EVENT_INFO_TYPE_DFID 3 #else // Linux only #define LINUX_FANOTIFY #include #include #include "inotifytools/fanotify.h" struct fanotify_event_fid { struct fanotify_event_info_fid info; struct file_handle handle; }; #endif /** * @file inotifytools/inotifytools.h * @brief inotifytools library public interface. * @author Rohan McGovern, \ * * This library provides a thin layer on top of the basic inotify interface. * The primary use is to easily set up watches on files, potentially many files * at once, and read events without having to deal with low-level I/O. There * are also several utility functions for inotify-related string formatting. * * To use this library, you must \c \#include the following headers accordingly: * \li \c \ - to use any functions declared in * this file. * \li \c \ - to have the \c inotify_event type defined * and the numeric IN_* event constants defined. If \c \ * was present on your system at compile time, this header simply includes * that. Otherwise it includes \c \. * * @section example Example * This very simple program recursively watches the entire directory tree * under its working directory for events, then prints them out with a * timestamp. * @include example.c * * @section events Events * * @note This section comes almost verbatim from the inotify(7) man page. * * @warning The information here applies to inotify in Linux 2.6.17. Older * versions of Linux may not support all the events described here. * * The following numeric events can be specified to functions in inotifytools, * and may be present in events returned through inotifytools: * * \li \c IN_ACCESS - File was accessed (read) \a * * \li \c IN_ATTRIB - Metadata changed (permissions, timestamps, * extended attributes, etc.) \a * * \li \c IN_CLOSE_WRITE - File opened for writing was closed \a * * \li \c IN_CLOSE_NOWRITE - File not opened for writing was closed \a * * \li \c IN_CREATE - File/directory created in watched directory \a * * \li \c IN_DELETE - File/directory deleted from watched directory \a * * \li \c IN_DELETE_SELF - Watched file/directory was itself deleted * \li \c IN_MODIFY - File was modified \a * * \li \c IN_MOVE_SELF - Watched file/directory was itself moved * \li \c IN_MOVED_FROM - File moved out of watched directory \a * * \li \c IN_MOVED_TO - File moved into watched directory \a * * \li \c IN_OPEN - File was opened \a * * * When monitoring a directory, the events marked with an asterisk \a * above * can occur for files in the directory, in which case the name field in the * returned inotify_event structure identifies the name of the file within the * directory. * * The IN_ALL_EVENTS macro is defined as a bit mask of all of the above events. * * Two additional convenience macros are IN_MOVE, which equates to * IN_MOVED_FROM|IN_MOVED_TO, and IN_CLOSE which equates to * IN_CLOSE_WRITE|IN_CLOSE_NOWRITE. * * The following bitmasks can also be provided when creating a new watch: * * \li \c IN_DONT_FOLLOW - Don't dereference pathname if it is a symbolic link * \li \c IN_MASK_ADD - Add (OR) events to watch mask for this pathname if * it already exists (instead of replacing mask) * \li \c IN_ONESHOT - Monitor pathname for one event, then remove from * watch list * \li \c IN_ONLYDIR - Only watch pathname if it is a directory * * The following bitmasks may occur in events generated by a watch: * * \li \c IN_IGNORED - Watch was removed explicitly * (inotifytools_remove_watch_*) or automatically (file * was deleted, or file system was unmounted) * \li \c IN_ISDIR - Subject of this event is a directory * \li \c IN_Q_OVERFLOW - Event queue overflowed (wd is -1 for this event) * \li \c IN_UNMOUNT - File system containing watched object was unmounted * * @section TODO TODO list * * @todo Improve wd/filename mapping. Currently there is no explicit code for * handling different filenames mapping to the same inode (and hence, wd). * gamin's approach sounds good: let the user watch an inode using several * different filenames, and when an event occurs on the inode, generate an * event for each filename. */ #define MAX_EVENTS 4096 #define INOTIFY_PROCDIR "/proc/sys/fs/inotify/" #define WATCHES_SIZE_PATH INOTIFY_PROCDIR "max_user_watches" #define QUEUE_SIZE_PATH INOTIFY_PROCDIR "max_queued_watches" #define INSTANCES_PATH INOTIFY_PROCDIR "max_user_instances" static int inotify_fd = -1; int collect_stats = 0; struct rbtree *tree_wd = 0; struct rbtree* tree_fid = 0; struct rbtree *tree_filename = 0; static int error = 0; int init = 0; int verbosity = 0; int fanotify_mode = 0; int fanotify_mark_type = 0; static pid_t self_pid = 0; static char* timefmt = 0; static regex_t* regex = 0; /* 0: --exclude[i], 1: --include[i] */ static int invert_regexp = 0; static int isdir( char const * path ); void record_stats( struct inotify_event const * event ); int onestr_to_event(char const * event); #define nasprintf(...) niceassert( -1 != asprintf(__VA_ARGS__), "out of memory") /** * @internal * Assert that a condition evaluates to true, and optionally output a message * if the assertion fails. * * You should use the niceassert() preprocessor macro instead. * * @param cond If 0, assertion fails, otherwise assertion succeeds. * * @param line Line number of source code where assertion is made. * * @param file Name of source file where assertion is made. * * @param condstr Stringified assertion expression. * * @param mesg A human-readable error message shown if assertion fails. */ void _niceassert( long cond, int line, char const * file, char const * condstr, char const * mesg ) { if ( cond ) return; if ( mesg ) { fprintf(stderr, "%s:%d assertion ( %s ) failed: %s\n", file, line, condstr, mesg ); } else { fprintf(stderr, "%s:%d assertion ( %s ) failed.\n", file, line, condstr); } } static void charcat(char* s, const char c) { size_t l = strlen(s); s[l] = c; s[++l] = 0; } /** * @internal */ static int read_num_from_file(char* filename, int* num) { FILE * file = fopen( filename, "r" ); if ( !file ) { error = errno; return 0; } if ( EOF == fscanf( file, "%d", num ) ) { error = errno; const int fclose_ret = fclose(file); niceassert(!fclose_ret, 0); return 0; } const int fclose_ret = fclose(file); niceassert(!fclose_ret, 0); return 1; } static int wd_compare(const void* d1, const void* d2, const void* config) { if (!d1 || !d2) return d1 - d2; return ((watch*)d1)->wd - ((watch*)d2)->wd; } static int fid_compare(const void* d1, const void* d2, const void* config) { #ifdef LINUX_FANOTIFY if (!d1 || !d2) return d1 - d2; watch* w1 = (watch*)d1; watch* w2 = (watch*)d2; int n1, n2; n1 = w1->fid->info.hdr.len; n2 = w2->fid->info.hdr.len; if (n1 != n2) return n1 - n2; return memcmp(w1->fid, w2->fid, n1); #else return d1 - d2; #endif } static int filename_compare(const void* d1, const void* d2, const void* config) { if (!d1 || !d2) return d1 - d2; return strcmp(((watch*)d1)->filename, ((watch*)d2)->filename); } /** * @internal */ watch *watch_from_wd( int wd ) { watch w; w.wd = wd; return (watch*)rbfind(&w, tree_wd); } /** * @internal */ watch* watch_from_fid(struct fanotify_event_fid* fid) { watch w; w.fid = fid; return (watch*)rbfind(&w, tree_fid); } /** * @internal */ watch *watch_from_filename( char const *filename ) { watch w; w.filename = (char*)filename; return (watch*)rbfind(&w, tree_filename); } /** * Initialise inotify. * With @fanotify non-zero, initialize fanotify filesystem watch. * * You must call this function before using any function which adds or removes * watches or attempts to access any information about watches. * * @return 1 on success, 0 on failure. On failure, the error can be * obtained from inotifytools_error(). */ int inotifytools_init(int fanotify, int watch_filesystem, int verbose) { if (init) return 1; error = 0; verbosity = verbose; // Try to initialise inotify/fanotify if (fanotify) { #ifdef LINUX_FANOTIFY self_pid = getpid(); fanotify_mode = 1; fanotify_mark_type = watch_filesystem ? FAN_MARK_FILESYSTEM : FAN_MARK_INODE; inotify_fd = fanotify_init(FAN_REPORT_FID | FAN_REPORT_DFID_NAME, 0); #endif } else { fanotify_mode = 0; inotify_fd = inotify_init(); } if (inotify_fd < 0) { error = errno; return 0; } collect_stats = 0; init = 1; tree_wd = rbinit(wd_compare, 0); tree_fid = rbinit(fid_compare, 0); tree_filename = rbinit(filename_compare, 0); timefmt = 0; return 1; } int inotifytools_initialize() { return inotifytools_init(0, 0, 0); } /** * @internal */ void destroy_watch(watch *w) { if (w->filename) free(w->filename); if (w->fid) free(w->fid); if (w->dirf) close(w->dirf); free(w); } /** * @internal */ void cleanup_tree(const void *nodep, const VISIT which, const int depth, void* arg) { if (which != endorder && which != leaf) return; watch *w = (watch*)nodep; destroy_watch(w); } /** * Close inotify and free the memory used by inotifytools. * * If you call this function, you must call inotifytools_initialize() * again before any other functions can be used. */ void inotifytools_cleanup() { if (!init) return; init = 0; close(inotify_fd); collect_stats = 0; error = 0; timefmt = 0; if (regex) { regfree(regex); free(regex); regex = 0; } rbwalk(tree_wd, cleanup_tree, 0); rbdestroy(tree_wd); rbdestroy(tree_fid); rbdestroy(tree_filename); tree_wd = 0; tree_fid = 0; tree_filename = 0; } /** * @internal */ struct replace_filename_data { char const *old_name; char const *new_name; size_t old_len; }; /** * @internal */ static void replace_filename(const void* nodep, const VISIT which, const int depth, const struct replace_filename_data* data) { if (which != endorder && which != leaf) return; watch *w = (watch*)nodep; char *name; if ( 0 == strncmp( data->old_name, w->filename, data->old_len ) ) { nasprintf( &name, "%s%s", data->new_name, &(w->filename[data->old_len]) ); if (!strcmp( w->filename, data->new_name )) { free(name); } else { rbdelete(w, tree_filename); free( w->filename ); w->filename = name; rbsearch(w, tree_filename); } } } /** * @internal */ static void get_num(const void* nodep, const VISIT which, const int depth, void* arg) { if (which != endorder && which != leaf) return; ++(*((int*)arg)); } /** * Convert character separated events from string form to integer form * (as in inotify.h). * * @param event a sequence of events in string form as defined in * inotify.h without leading IN_ prefix (e.g., MODIFY, * ATTRIB), separated by the @a sep character. Case * insensitive. Can be a single event. * Can be empty or NULL. See section \ref events. * * @param sep Character used to separate events. @a sep must not be * a character in a-z, A-Z, or _. * * @return integer representing the mask specified by @a event, or 0 * if any string in @a event is empty or NULL, or -1 if * any string in @a event does not match any event or * @a sep is invalid. * * @section example Example * @code * char * eventstr = "MODIFY:CLOSE:CREATE"; * int eventnum = inotifytools_str_to_event_sep( eventstr, ':' ); * if ( eventnum == IN_MODIFY | IN_CLOSE | IN_CREATE ) { * printf( "This code always gets executed!\n" ); * } * @endcode */ int inotifytools_str_to_event_sep(char const * event, char sep) { if ( strchr( "_" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ", sep ) ) { return -1; } int ret, len; char * event1, * event2; static const size_t eventstr_size = 4096; char eventstr[eventstr_size]; ret = 0; if ( !event || !event[0] ) return 0; event1 = (char *)event; event2 = strchr( event1, sep ); while ( event1 && event1[0] ) { if ( event2 ) { len = event2 - event1; niceassert(len < eventstr_size, "malformed event string (very long)"); } else { len = strlen(event1); } if (len > eventstr_size - 1) len = eventstr_size - 1; strncpy(eventstr, event1, len); eventstr[len] = 0; int ret1 = onestr_to_event(eventstr); if ( 0 == ret1 || -1 == ret1 ) { ret = ret1; break; } ret |= ret1; event1 = event2; if ( event1 && event1[0] ) { // jump over 'sep' character ++event1; // if last character was 'sep'... if ( !event1[0] ) return 0; event2 = strchr( event1, sep ); } } return ret; } /** * Convert comma-separated events from string form to integer form * (as in inotify.h). * * @param event a sequence of events in string form as defined in * inotify.h without leading IN_ prefix (e.g., MODIFY, * ATTRIB), comma-separated. Case * insensitive. Can be a single event. * Can be empty or NULL. See section \ref events. * * @return integer representing the mask specified by @a event, or 0 * if any string in @a event is empty or NULL, or -1 if * any string in @a event does not match any event. * * @section example Example * @code * char * eventstr = "MODIFY,CLOSE,CREATE"; * int eventnum = inotifytools_str_to_event( eventstr ); * if ( eventnum == IN_MODIFY | IN_CLOSE | IN_CREATE ) { * printf( "This code always gets executed!\n" ); * } * @endcode */ int inotifytools_str_to_event(char const * event) { return inotifytools_str_to_event_sep( event, ',' ); } /** * @internal * Convert a single event from string form to integer form (as in inotify.h). * * @param event event in string form as defined in inotify.h without * leading IN_ prefix (e.g., MODIFY, ATTRIB). Case * insensitive. Can be empty or NULL. * @return integer representing the mask specified by 'event', or 0 * if @a event is empty or NULL, or -1 if string does not * match any event. */ int onestr_to_event(char const * event) { static int ret; ret = -1; if ( !event || !event[0] ) ret = 0; else if ( 0 == strcasecmp(event, "ACCESS") ) ret = IN_ACCESS; else if ( 0 == strcasecmp(event, "MODIFY") ) ret = IN_MODIFY; else if ( 0 == strcasecmp(event, "ATTRIB") ) ret = IN_ATTRIB; else if ( 0 == strcasecmp(event, "CLOSE_WRITE") ) ret = IN_CLOSE_WRITE; else if ( 0 == strcasecmp(event, "CLOSE_NOWRITE") ) ret = IN_CLOSE_NOWRITE; else if ( 0 == strcasecmp(event, "OPEN") ) ret = IN_OPEN; else if ( 0 == strcasecmp(event, "MOVED_FROM") ) ret = IN_MOVED_FROM; else if ( 0 == strcasecmp(event, "MOVED_TO") ) ret = IN_MOVED_TO; else if ( 0 == strcasecmp(event, "CREATE") ) ret = IN_CREATE; else if ( 0 == strcasecmp(event, "DELETE") ) ret = IN_DELETE; else if ( 0 == strcasecmp(event, "DELETE_SELF") ) ret = IN_DELETE_SELF; else if ( 0 == strcasecmp(event, "UNMOUNT") ) ret = IN_UNMOUNT; else if ( 0 == strcasecmp(event, "Q_OVERFLOW") ) ret = IN_Q_OVERFLOW; else if ( 0 == strcasecmp(event, "IGNORED") ) ret = IN_IGNORED; else if ( 0 == strcasecmp(event, "CLOSE") ) ret = IN_CLOSE; else if ( 0 == strcasecmp(event, "MOVE_SELF") ) ret = IN_MOVE_SELF; else if ( 0 == strcasecmp(event, "MOVE") ) ret = IN_MOVE; else if ( 0 == strcasecmp(event, "ISDIR") ) ret = IN_ISDIR; else if ( 0 == strcasecmp(event, "ONESHOT") ) ret = IN_ONESHOT; else if ( 0 == strcasecmp(event, "ALL_EVENTS") ) ret = IN_ALL_EVENTS; return ret; } /** * Convert event from integer form to string form (as in inotify.h). * * The returned string is from static storage; subsequent calls to this function * or inotifytools_event_to_str_sep() will overwrite it. Don't free() it and * make a copy if you want to keep it. * * @param events OR'd event(s) in integer form as defined in inotify.h. * See section \ref events. * * @return comma-separated string representing the event(s), in no * particular order * * @section example Example * @code * int eventnum == IN_MODIFY | IN_CLOSE | IN_CREATE; * char * eventstr = inotifytools_event_to_str( eventnum ); * printf( "%s\n", eventstr ); * // outputs something like MODIFY,CLOSE,CREATE but order not guaranteed. * @endcode */ char * inotifytools_event_to_str(int events) { return inotifytools_event_to_str_sep(events, ','); } /** * Convert event from integer form to string form (as in inotify.h). * * The returned string is from static storage; subsequent calls to this function * or inotifytools_event_to_str() will overwrite it. Don't free() it and * make a copy if you want to keep it. * * @param events OR'd event(s) in integer form as defined in inotify.h * * @param sep character used to separate events * * @return @a sep separated string representing the event(s), in no * particular order. If the integer is not made of OR'ed * inotify events, the string returned will be a hexadecimal * representation of the integer. * * @section example Example * @code * int eventnum == IN_MODIFY | IN_CLOSE | IN_CREATE; * char * eventstr = inotifytools_event_to_str_sep( eventnum, '-' ); * printf( "%s\n", eventstr ); * // outputs something like MODIFY-CLOSE-CREATE but order not guaranteed. * @endcode */ char * inotifytools_event_to_str_sep(int events, char sep) { static char ret[1024]; ret[0] = '\0'; ret[1] = '\0'; if ( IN_ACCESS & events ) { charcat(ret, sep); strncat(ret, "ACCESS", 7); } if ( IN_MODIFY & events ) { charcat(ret, sep); strncat(ret, "MODIFY", 7); } if ( IN_ATTRIB & events ) { charcat(ret, sep); strncat(ret, "ATTRIB", 7); } if ( IN_CLOSE_WRITE & events ) { charcat(ret, sep); strncat(ret, "CLOSE_WRITE", 12); } if ( IN_CLOSE_NOWRITE & events ) { charcat(ret, sep); strncat(ret, "CLOSE_NOWRITE", 14); } if ( IN_OPEN & events ) { charcat(ret, sep); strncat(ret, "OPEN", 5); } if ( IN_MOVED_FROM & events ) { charcat(ret, sep); strncat(ret, "MOVED_FROM", 11); } if ( IN_MOVED_TO & events ) { charcat(ret, sep); strncat(ret, "MOVED_TO", 9); } if ( IN_CREATE & events ) { charcat(ret, sep); strncat(ret, "CREATE", 7); } if ( IN_DELETE & events ) { charcat(ret, sep); strncat(ret, "DELETE", 7); } if ( IN_DELETE_SELF & events ) { charcat(ret, sep); strncat(ret, "DELETE_SELF", 12); } if ( IN_UNMOUNT & events ) { charcat(ret, sep); strncat(ret, "UNMOUNT", 8); } if ( IN_Q_OVERFLOW & events ) { charcat(ret, sep); strncat(ret, "Q_OVERFLOW", 11); } if ( IN_IGNORED & events ) { charcat(ret, sep); strncat(ret, "IGNORED", 8); } if ( IN_CLOSE & events ) { charcat(ret, sep); strncat(ret, "CLOSE", 6); } if ( IN_MOVE_SELF & events ) { charcat(ret, sep); strncat(ret, "MOVE_SELF", 10); } if ( IN_ISDIR & events ) { charcat(ret, sep); strncat(ret, "ISDIR", 6); } if ( IN_ONESHOT & events ) { charcat(ret, sep); strncat(ret, "ONESHOT", 8); } // Maybe we didn't match any... ? if (ret[0] == '\0') { niceassert( -1 != sprintf( ret, "%c0x%08x", sep, events ), 0 ); } return &ret[1]; } /** * Get the filename from fid. * * Resolve filename from fid + name and return * static filename string. */ static const char* inotifytools_filename_from_fid( struct fanotify_event_fid* fid) { #ifdef LINUX_FANOTIFY static char filename[PATH_MAX]; struct fanotify_event_fid fsid = {}; int dirf = 0, mount_fd = AT_FDCWD; int len = 0, name_len = 0; // Match mount_fd from fid->fsid (and null fhandle) fsid.info.fsid.val[0] = fid->info.fsid.val[0]; fsid.info.fsid.val[1] = fid->info.fsid.val[1]; fsid.info.hdr.info_type = FAN_EVENT_INFO_TYPE_FID; fsid.info.hdr.len = sizeof(fsid); watch* mnt = watch_from_fid(&fsid); if (mnt) mount_fd = mnt->dirf; if (fid->info.hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) { int fid_len = sizeof(*fid) + fid->handle.handle_bytes; name_len = fid->info.hdr.len - fid_len; if (name_len && !fid->handle.f_handle[fid->handle.handle_bytes]) name_len = 0; // empty name?? } // Try to get path from file handle dirf = open_by_handle_at(mount_fd, &fid->handle, 0); if (dirf > 0) { // Got path by handle } else if (fanotify_mark_type == FAN_MARK_FILESYSTEM) { fprintf(stderr, "Failed to decode directory fid.\n"); return NULL; } else if (name_len) { // For recursive watch look for watch by fid without the name fid->info.hdr.info_type = FAN_EVENT_INFO_TYPE_DFID; fid->info.hdr.len -= name_len; watch* w = watch_from_fid(fid); fid->info.hdr.info_type = FAN_EVENT_INFO_TYPE_DFID_NAME; fid->info.hdr.len += name_len; if (!w) { fprintf(stderr, "Failed to lookup path by directory fid.\n"); return NULL; } dirf = w->dirf ? dup(w->dirf) : -1; if (dirf < 0) { fprintf(stderr, "Failed to get directory fd.\n"); return NULL; } } else { // Fallthrough to stored filename return NULL; } char sym[30]; sprintf(sym, "/proc/self/fd/%d", dirf); // PATH_MAX - 2 because we have to append two characters to this path, // '/' and 0 len = readlink(sym, filename, PATH_MAX - 2); if (len < 0) { close(dirf); fprintf(stderr, "Failed to resolve path from directory fd.\n"); return NULL; } filename[len++] = '/'; filename[len] = 0; if (name_len > 0) { const char* name = (const char*)fid->handle.f_handle + fid->handle.handle_bytes; int deleted = faccessat(dirf, name, F_OK, AT_SYMLINK_NOFOLLOW); if (deleted && errno != ENOENT) { fprintf(stderr, "Failed to access file %s (%s).\n", name, strerror(errno)); close(dirf); return NULL; } memcpy(filename + len, name, name_len); if (deleted) strncat(filename, " (deleted)", 11); } close(dirf); return filename; #else return NULL; #endif } /** * Get the filename from a watch. * * If not stored in watch, resolve filename from fid + name and return * static filename string. */ const char* inotifytools_filename_from_watch(watch* w) { if (!w) return ""; if (!w->fid || !fanotify_mark_type) return w->filename; return inotifytools_filename_from_fid(w->fid) ?: w->filename; } /** * Get the filename used to establish a watch. * * inotifytools_initialize() must be called before this function can * be used. * * @param wd watch descriptor. * * @return filename associated with watch descriptor @a wd, or NULL if @a wd * is not associated with any filename. * * @note This always returns the filename which was used to establish a watch. * This means the filename may be a relative path. If this isn't desired, * then always use absolute paths when watching files. * Also, this is not necessarily the filename which might have been used * to cause an event on the file, since inotify is inode based and there * can be many filenames mapping to a single inode. * Finally, if a file is moved or renamed while being watched, the * filename returned will still be the original name. */ const char* inotifytools_filename_from_wd(int wd) { niceassert( init, "inotifytools_initialize not called yet" ); if (!wd) return ""; watch *w = watch_from_wd(wd); if (!w) return ""; return inotifytools_filename_from_watch(w); } /** * Get the directory path used to establish a watch. * * Returns the filename recorded for event->wd and the dirname * prefix length. * * The caller should NOT free() the returned string. */ const char* inotifytools_dirname_from_event(struct inotify_event* event, size_t* dirnamelen) { const char* filename = inotifytools_filename_from_wd(event->wd); const char* dirsep = NULL; if (!filename) { return NULL; } /* Split dirname from filename for fanotify event */ if (fanotify_mode) dirsep = strrchr(filename, '/'); if (!dirsep) { *dirnamelen = strlen(filename); return filename; } *dirnamelen = dirsep - filename + 1; return filename; } /** * Get the watched path and filename from an event. * * Returns the filename either recorded for event->wd or * from event->name and the watched filename for event->wd. * * The caller should NOT free() the returned strings. */ const char* inotifytools_filename_from_event(struct inotify_event* event, char const** eventname, size_t* dirnamelen) { if (event->len > 0) *eventname = event->name; else *eventname = ""; const char* filename = inotifytools_dirname_from_event(event, dirnamelen); /* On fanotify watch, filename includes event->name */ if (filename && filename[*dirnamelen]) *eventname = filename + *dirnamelen; return filename; } /** * Get the directory path from an event. * * Returns the filename recorded for event->wd or NULL. * For an event on non-directory also returns NULL. * * The caller is responsible to free() the returned string. */ char* inotifytools_dirpath_from_event(struct inotify_event* event) { const char* filename = inotifytools_filename_from_wd(event->wd); if (!filename || !*filename || !(event->mask & IN_ISDIR)) { return NULL; } /* * fanotify watch->filename includes the name, so no need to add the * event->name again. */ char* path; nasprintf(&path, "%s%s/", filename, fanotify_mode ? "" : event->name); return path; } /** * Get the watch descriptor for a particular filename. * * inotifytools_initialize() must be called before this function can * be used. * * @param filename file name to find watch descriptor for. * * @return watch descriptor associated with filename, or -1 if @a filename is * not associated with any watch descriptor. * * @note The filename specified must always be the original name used to * establish the watch. */ int inotifytools_wd_from_filename( char const * filename ) { niceassert( init, "inotifytools_initialize not called yet" ); if (!filename || !*filename) return -1; watch *w = watch_from_filename(filename); if (!w) return -1; return w->wd; } /** * Set the filename for a particular watch descriptor. * * This function should be used to update a filename when a file is known to * have been moved or renamed. At the moment, libinotifytools does not * automatically handle this situation. * * inotifytools_initialize() must be called before this function can * be used. * * @param wd Watch descriptor. * * @param filename New filename. */ void inotifytools_set_filename_by_wd( int wd, char const * filename ) { niceassert( init, "inotifytools_initialize not called yet" ); watch *w = watch_from_wd(wd); if (!w) return; if (w->filename) free(w->filename); w->filename = strdup(filename); } /** * Set the filename for one or more watches with a particular existing filename. * * This function should be used to update a filename when a file is known to * have been moved or renamed. At the moment, libinotifytools does not * automatically handle this situation. * * inotifytools_initialize() must be called before this function can * be used. * * @param oldname Current filename. * * @param newname New filename. */ void inotifytools_set_filename_by_filename( char const * oldname, char const * newname ) { watch *w = watch_from_filename(oldname); if (!w) return; if (w->filename) free(w->filename); w->filename = strdup(newname); } /** * Replace a certain filename prefix on all watches. * * This function should be used to update filenames for an entire directory tree * when a directory is known to have been moved or renamed. At the moment, * libinotifytools does not automatically handle this situation. * * inotifytools_initialize() must be called before this function can * be used. * * @param oldname Current filename prefix. * * @param newname New filename prefix. * * @section example Example * @code * // if /home/user1/original_dir is moved to /home/user2/new_dir, then to * // update all watches: * inotifytools_replace_filename( "/home/user1/original_dir", * "/home/user2/new_dir" ); * @endcode */ void inotifytools_replace_filename( char const * oldname, char const * newname ) { if (!oldname || !newname) return; if (!*oldname || !*newname) return; struct replace_filename_data data; data.old_name = oldname; data.new_name = newname; data.old_len = strlen(oldname); rbwalk(tree_filename, (void *)replace_filename, (void *)&data); } /** * @internal */ int remove_inotify_watch(watch *w) { error = 0; // There is no kernel object representing the watch with fanotify if (w->fid) return 0; int status = inotify_rm_watch( inotify_fd, w->wd ); if ( status < 0 ) { fprintf(stderr, "Failed to remove watch on %s: %s\n", w->filename, strerror(status) ); error = status; return 0; } return 1; } /** * @internal */ watch* create_watch(int wd, struct fanotify_event_fid* fid, const char* filename, int dirf) { if (wd < 0 || !filename) return 0; watch *w = (watch*)calloc(1, sizeof(watch)); if (!w) { fprintf(stderr, "Failed to allocate watch.\n"); return NULL; } w->wd = wd ?: (unsigned long)fid; w->fid = fid; w->dirf = dirf; w->filename = strdup(filename); rbsearch(w, tree_wd); if (fid) rbsearch(w, tree_fid); rbsearch(w, tree_filename); return w; } /** * Remove a watch on a file specified by watch descriptor. * * inotifytools_initialize() must be called before this function can * be used. * * @param wd Watch descriptor of watch to be removed. * * @return 1 on success, 0 on failure. If the given watch doesn't exist, * returns 1. On failure, the error can be * obtained from inotifytools_error(). */ int inotifytools_remove_watch_by_wd( int wd ) { niceassert( init, "inotifytools_initialize not called yet" ); watch *w = watch_from_wd(wd); if (!w) return 1; if (!remove_inotify_watch(w)) return 0; rbdelete(w, tree_wd); if (w->fid) rbdelete(w, tree_fid); rbdelete(w, tree_filename); destroy_watch(w); return 1; } /** * Remove a watch on a file specified by filename. * * @param filename Name of file on which watch should be removed. * * @return 1 on success, 0 on failure. On failure, the error can be * obtained from inotifytools_error(). * * @note The filename specified must always be the original name used to * establish the watch. */ int inotifytools_remove_watch_by_filename( char const * filename ) { niceassert( init, "inotifytools_initialize not called yet" ); watch *w = watch_from_filename(filename); if (!w) return 1; if (!remove_inotify_watch(w)) return 0; rbdelete(w, tree_wd); if (w->fid) rbdelete(w, tree_fid); rbdelete(w, tree_filename); destroy_watch(w); return 1; } /** * Set up a watch on a file. * * @param filename Absolute or relative path of file to watch. * * @param events bitwise ORed inotify events to watch for. See section * \ref events. * * @return 1 on success, 0 on failure. On failure, the error can be * obtained from inotifytools_error(). */ int inotifytools_watch_file(char const* filename, int events) { static char const* filenames[2]; filenames[0] = filename; filenames[1] = NULL; return inotifytools_watch_files( filenames, events ); } /** * Set up a watch on a list of files. * * inotifytools_initialize() must be called before this function can * be used. * * @param filenames null-terminated array of absolute or relative paths of * files to watch. * * @param events bitwise OR'ed inotify events to watch for. See section * \ref events. * * @return 1 on success, 0 on failure. On failure, the error can be * obtained from inotifytools_error(). */ int inotifytools_watch_files(char const* filenames[], int events) { niceassert( init, "inotifytools_initialize not called yet" ); error = 0; static int i; for ( i = 0; filenames[i]; ++i ) { int wd = -1; if (fanotify_mode) { #ifdef LINUX_FANOTIFY unsigned int flags = FAN_MARK_ADD | fanotify_mark_type; if (events & IN_DONT_FOLLOW) { events &= ~IN_DONT_FOLLOW; flags |= FAN_MARK_DONT_FOLLOW; } wd = fanotify_mark(inotify_fd, flags, events | FAN_EVENT_ON_CHILD, AT_FDCWD, filenames[i]); #endif } else { wd = inotify_add_watch(inotify_fd, filenames[i], events); } if ( wd < 0 ) { if ( wd == -1 ) { error = errno; return 0; } // if ( wd == -1 ) else { fprintf( stderr, "Failed to watch %s: returned wd was %d " "(expected -1 or >0 )", filenames[i], wd ); // no appropriate value for error return 0; } // else } // if ( wd < 0 ) const char* filename = filenames[i]; size_t filenamelen = strlen(filename); char* dirname; int dirf = 0; // Always end filename with / if it is a directory if (!isdir(filename)) { dirname = NULL; } else if (filename[filenamelen - 1] == '/') { dirname = strdup(filename); } else { nasprintf(&dirname, "%s/", filename); filename = dirname; filenamelen++; } struct fanotify_event_fid* fid = NULL; #ifdef LINUX_FANOTIFY if (!wd) { fid = calloc(1, sizeof(*fid) + MAX_FID_LEN); if (!fid) { fprintf(stderr, "Failed to allocate fid"); free(dirname); return 0; } struct statfs buf; if (statfs(filenames[i], &buf)) { free(fid); fprintf(stderr, "Statfs failed on %s: %s\n", filenames[i], strerror(errno)); free(dirname); return 0; } memcpy(&fid->info.fsid, &buf.f_fsid, sizeof(__kernel_fsid_t)); // Hash mount_fd with fid->fsid (and null fhandle) int ret, mntid; watch* mnt = dirname ? watch_from_fid(fid) : NULL; if (dirname && !mnt) { struct fanotify_event_fid* fsid; fsid = calloc(1, sizeof(*fsid)); if (!fsid) { free(fid); fprintf(stderr, "Failed to allocate fsid"); free(dirname); return 0; } fsid->info.fsid.val[0] = fid->info.fsid.val[0]; fsid->info.fsid.val[1] = fid->info.fsid.val[1]; fsid->info.hdr.info_type = FAN_EVENT_INFO_TYPE_FID; fsid->info.hdr.len = sizeof(*fsid); mntid = open(dirname, O_RDONLY); if (mntid < 0) { free(fid); free(fsid); fprintf(stderr, "Failed to open %s: %s\n", dirname, strerror(errno)); free(dirname); return 0; } // Hash mount_fd without terminating / dirname[filenamelen - 1] = 0; create_watch(0, fsid, dirname, mntid); dirname[filenamelen - 1] = '/'; } fid->handle.handle_bytes = MAX_FID_LEN; ret = name_to_handle_at(AT_FDCWD, filenames[i], (void*)&fid->handle, &mntid, 0); if (ret || fid->handle.handle_bytes > MAX_FID_LEN) { free(fid); fprintf(stderr, "Encode fid failed on %s: %s\n", filenames[i], strerror(errno)); free(dirname); return 0; } fid->info.hdr.info_type = dirname ? FAN_EVENT_INFO_TYPE_DFID : FAN_EVENT_INFO_TYPE_FID; fid->info.hdr.len = sizeof(*fid) + fid->handle.handle_bytes; if (dirname) { dirf = open(dirname, O_PATH); if (dirf < 0) { free(fid); fprintf(stderr, "Failed to open %s: %s\n", dirname, strerror(errno)); free(dirname); return 0; } } } #endif create_watch(wd, fid, filename, dirf); free(dirname); } // for return 1; } /** * Get the next inotify event to occur. * * inotifytools_initialize() must be called before this function can * be used. * * @param timeout maximum amount of time, in seconds, to wait for an event. * If @a timeout is non-negative, the function is non-blocking. * If @a timeout is negative, the function will block until an * event occurs. * * @return pointer to an inotify event, or NULL if function timed out before * an event occurred. The event is located in static storage and it * may be overwritten in subsequent calls; do not call free() on it, * and make a copy if you want to keep it. * * @note Your program should call this function or * inotifytools_next_events() frequently; between calls to this function, * inotify events will be queued in the kernel, and eventually the queue * will overflow and you will miss some events. * * @note If the function inotifytools_ignore_events_by_regex() has been called * with a non-NULL parameter, this function will not return on events * which match the regular expression passed to that function. However, * the @a timeout period begins again each time a matching event occurs. */ struct inotify_event * inotifytools_next_event( long int timeout ) { if (!timeout) { timeout = -1; } return inotifytools_next_events( timeout, 1 ); } /** * Get the next inotify events to occur. * * inotifytools_initialize() must be called before this function can * be used. * * @param timeout maximum amount of time, in seconds, to wait for an event. * If @a timeout is non-negative, the function is non-blocking. * If @a timeout is negative, the function will block until an * event occurs. * * @param num_events approximate number of inotify events to wait for until * this function returns. Use this for buffering reads to * inotify if you expect to receive large amounts of events. * You are NOT guaranteed that this number of events will * actually be read; instead, you are guaranteed that the * number of bytes read from inotify is * @a num_events * sizeof(struct inotify_event). Obviously * the larger this number is, the greater the latency between * when an event occurs and when you'll know about it. * May not be larger than 4096. * * @return pointer to an inotify event, or NULL if function timed out before * an event occurred or @a num_events < 1. The event is located in * static storage and it may be overwritten in subsequent calls; do not * call free() on it, and make a copy if you want to keep it. * When @a num_events is greater than 1, this will return a pointer to * the first event only, and you MUST call this function again to * get pointers to subsequent events; don't try to add to the pointer * to find the next events or you will run into trouble. * * @note You may actually get different events with different values of * @a num_events. This is because inotify does some in-kernel filtering * of duplicate events, meaning some duplicate events will not be * reported if @a num_events > 1. For some purposes this is fine, but * for others (such as gathering accurate statistics on numbers of event * occurrences) you must call this function with @a num_events = 1, or * simply use inotifytools_next_event(). * * @note Your program should call this function frequently; between calls to this * function, inotify events will be queued in the kernel, and eventually * the queue will overflow and you will miss some events. * * @note If the function inotifytools_ignore_events_by_regex() has been called * with a non-NULL parameter, this function will not return on events * which match the regular expression passed to that function. However, * the @a timeout period begins again each time a matching event occurs. */ struct inotify_event * inotifytools_next_events( long int timeout, int num_events ) { niceassert( init, "inotifytools_initialize not called yet" ); niceassert( num_events <= MAX_EVENTS, "too many events requested" ); if ( num_events < 1 ) return NULL; // second half of event[] buffer is for fanotify->inotify conversion static struct inotify_event event[2 * MAX_EVENTS]; static struct inotify_event * ret; static int first_byte = 0; static ssize_t bytes; static ssize_t this_bytes; static jmp_buf jmp; static struct nstring match_name; static char match_name_string[MAX_STRLEN+1]; setjmp(jmp); pid_t event_pid = 0; error = 0; // first_byte is index into event buffer if ( first_byte != 0 && first_byte <= (int)(bytes - sizeof(struct inotify_event)) ) { ret = (struct inotify_event *)((char *)&event[0] + first_byte); if (!fanotify_mode && first_byte + sizeof(*ret) + ret->len > bytes) { // oh... no. this can't be happening. An incomplete event. // Copy what we currently have into first element, call self to // read remainder. // oh, and they BETTER NOT overlap. // Boy I hope this code works. // But I think this can never happen due to how inotify is written. niceassert( (long)((char *)&event[0] + sizeof(struct inotify_event) + event[0].len) <= (long)ret, "extremely unlucky user, death imminent" ); // how much of the event do we have? bytes = (char *)&event[0] + bytes - (char *)ret; memcpy( &event[0], ret, bytes ); return inotifytools_next_events( timeout, num_events ); } this_bytes = 0; goto more_events; } else if ( first_byte == 0 ) { bytes = 0; } static unsigned int bytes_to_read; static int rc; static fd_set read_fds; static struct timeval read_timeout; read_timeout.tv_sec = timeout; read_timeout.tv_usec = 0; static struct timeval * read_timeout_ptr; read_timeout_ptr = ( timeout < 0 ? NULL : &read_timeout ); FD_ZERO(&read_fds); FD_SET(inotify_fd, &read_fds); rc = select(inotify_fd + 1, &read_fds, NULL, NULL, read_timeout_ptr); if ( rc < 0 ) { // error error = errno; return NULL; } else if ( rc == 0 ) { // timeout return NULL; } // wait until we have enough bytes to read do { rc = ioctl( inotify_fd, FIONREAD, &bytes_to_read ); } while ( !rc && bytes_to_read < sizeof(struct inotify_event)*num_events ); if ( rc == -1 ) { error = errno; return NULL; } this_bytes = read(inotify_fd, (char*)&event[0] + bytes, sizeof(struct inotify_event) * MAX_EVENTS - bytes); if ( this_bytes < 0 ) { error = errno; return NULL; } if ( this_bytes == 0 ) { fprintf(stderr, "Inotify reported end-of-file. Possibly too many " "events occurred at once.\n"); return NULL; } more_events: ret = (struct inotify_event*)((char*)&event[0] + first_byte); #ifdef LINUX_FANOTIFY // convert fanotify events to inotify events if (fanotify_mode) { struct fanotify_event_metadata* meta = (void*)ret; struct fanotify_event_info_fid* info = (void*)(meta + 1); struct fanotify_event_fid* fid = NULL; const char* name = ""; int fid_len = 0; int name_len = 0; first_byte += meta->event_len; if (meta->event_len > sizeof(*meta)) { switch (info->hdr.info_type) { case FAN_EVENT_INFO_TYPE_FID: case FAN_EVENT_INFO_TYPE_DFID: case FAN_EVENT_INFO_TYPE_DFID_NAME: fid = (void*)info; fid_len = sizeof(*fid) + fid->handle.handle_bytes; if (info->hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) { name_len = info->hdr.len - fid_len; } if (name_len > 0) { name = (const char*) fid->handle.f_handle + fid->handle.handle_bytes; } // Convert zero padding to zero // name_len. For some events on // directories, the fid is that of the // dir and name is ".". Do not include // "." name in fid hash, but keep it for // debug print. if (name_len && (!*name || !strcmp(name, "."))) { info->hdr.len -= name_len; name_len = 0; } break; } } if (!fid) { fprintf(stderr, "No fid in fanotify event.\n"); return NULL; } if (verbosity > 1) { printf( "fanotify_event: bytes=%zd, first_byte=%d, " "this_bytes=%zd, event_len=%u, fid_len=%d, " "name_len=%d, name=%s\n", bytes, first_byte, this_bytes, meta->event_len, fid_len, name_len, name); } ret = &event[MAX_EVENTS]; watch* w = watch_from_fid(fid); if (!w) { struct fanotify_event_fid* newfid = calloc(1, info->hdr.len); if (!newfid) { fprintf(stderr, "Failed to allocate fid.\n"); return NULL; } memcpy(newfid, fid, info->hdr.len); const char* filename = inotifytools_filename_from_fid(fid); if (filename) { w = create_watch(0, newfid, filename, 0); if (!w) { free(newfid); return NULL; } } if (verbosity) { unsigned long id; memcpy((void*)&id, fid->handle.f_handle, sizeof(id)); printf("[fid=%x.%x.%lx;name='%s'] %s\n", fid->info.fsid.val[0], fid->info.fsid.val[1], id, name, filename ?: ""); } } ret->wd = w ? w->wd : 0; ret->mask = (uint32_t)meta->mask; ret->len = name_len; if (name_len > 0) memcpy(ret->name, name, name_len); event_pid = meta->pid; } else { first_byte += sizeof(struct inotify_event) + ret->len; } #endif bytes += this_bytes; niceassert( first_byte <= bytes, "ridiculously long filename, things will " "almost certainly screw up." ); if ( first_byte == bytes ) { first_byte = 0; } /* Skip events from self due to open_by_handle_at() */ if (self_pid && self_pid == event_pid) { longjmp(jmp, 0); } if (regex) { inotifytools_snprintf(&match_name, MAX_STRLEN, ret, "%w%f"); memcpy(&match_name_string, &match_name.buf, match_name.len); match_name_string[match_name.len] = '\0'; if (0 == regexec(regex, match_name_string, 0, 0, 0)) { if (!invert_regexp) longjmp(jmp, 0); } else { if (invert_regexp) longjmp(jmp, 0); } } if (collect_stats) { record_stats(ret); } return ret; } /** * Set up recursive watches on an entire directory tree. * * inotifytools_initialize() must be called before this function can * be used. * * @param path path of directory or file to watch. If the path is a directory, * every subdirectory will also be watched for the same events up * to the maximum readable depth. If the path is a file, the file * is watched exactly as if inotifytools_watch_file() were used. * * @param events Inotify events to watch for. See section \ref events. * * @return 1 on success, 0 on failure. On failure, the error can be * obtained from inotifytools_error(). Note that some errors on * subdirectories will be ignored; for example, if you watch a directory * tree which contains some directories which you do not have access to, * those directories will not be watched, but this function will still * return 1 if no other errors occur. * * @note This function does not attempt to work atomically. If you use this * function to watch a directory tree and files or directories are being * created or removed within that directory tree, there are no guarantees * as to whether or not those files will be watched. */ int inotifytools_watch_recursively(char const* path, int events) { return inotifytools_watch_recursively_with_exclude( path, events, 0 ); } /** * Set up recursive watches on an entire directory tree, optionally excluding * some directories. * * inotifytools_initialize() must be called before this function can * be used. * * @author UH * * @param path path of directory or file to watch. If the path is a directory, * every subdirectory will also be watched for the same events up * to the maximum readable depth. If the path is a file, the file * is watched exactly as if inotifytools_watch_file() were used. * * @param exclude_list NULL terminated path list of directories not to watch. * Can be NULL if no paths are to be excluded. * Directories may or may not include a trailing '/'. * * @param events Inotify events to watch for. See section \ref events. * * @return 1 on success, 0 on failure. On failure, the error can be * obtained from inotifytools_error(). Note that some errors on * subdirectories will be ignored; for example, if you watch a directory * tree which contains some directories which you do not have access to, * those directories will not be watched, but this function will still * return 1 if no other errors occur. * * @note This function does not attempt to work atomically. If you use this * function to watch a directory tree and files or directories are being * created or removed within that directory tree, there are no guarantees * as to whether or not those files will be watched. */ int inotifytools_watch_recursively_with_exclude(char const* path, int events, char const** exclude_list) { niceassert( init, "inotifytools_initialize not called yet" ); DIR * dir; char * my_path; error = 0; dir = opendir( path ); if ( !dir ) { // If not a directory, don't need to do anything special if ( errno == ENOTDIR ) { return inotifytools_watch_file( path, events ); } else { error = errno; return 0; } } if ( path[strlen(path)-1] != '/' ) { nasprintf( &my_path, "%s/", path ); } else { my_path = (char *)path; } static struct dirent * ent; char * next_file; static struct stat64 my_stat; ent = readdir( dir ); // Watch each directory within this directory while ( ent ) { if ( (0 != strcmp( ent->d_name, "." )) && (0 != strcmp( ent->d_name, ".." )) ) { nasprintf(&next_file,"%s%s", my_path, ent->d_name); if ( -1 == lstat64( next_file, &my_stat ) ) { error = errno; free( next_file ); if ( errno != EACCES ) { error = errno; if ( my_path != path ) free( my_path ); closedir( dir ); return 0; } } else if ( S_ISDIR( my_stat.st_mode ) && !S_ISLNK( my_stat.st_mode )) { free( next_file ); nasprintf(&next_file,"%s%s/", my_path, ent->d_name); static unsigned int no_watch; static char const** exclude_entry; no_watch = 0; for (exclude_entry = exclude_list; exclude_entry && *exclude_entry && !no_watch; ++exclude_entry) { static int exclude_length; exclude_length = strlen(*exclude_entry); if ((*exclude_entry)[exclude_length-1] == '/') { --exclude_length; } if ( strlen(next_file) == (unsigned)(exclude_length + 1) && !strncmp(*exclude_entry, next_file, exclude_length)) { // directory found in exclude list no_watch = 1; } } if (!no_watch) { static int status; status = inotifytools_watch_recursively_with_exclude( next_file, events, exclude_list ); // For some errors, we will continue. if ( !status && (EACCES != error) && (ENOENT != error) && (ELOOP != error) ) { free( next_file ); if ( my_path != path ) free( my_path ); closedir( dir ); return 0; } } // if !no_watch free( next_file ); } // if isdir and not islnk else { free( next_file ); } } ent = readdir( dir ); error = 0; } closedir( dir ); int ret = inotifytools_watch_file( my_path, events ); if ( my_path != path ) free( my_path ); return ret; } /** * Get the last error which occurred. * * When a function fails, call this to find out why. The returned value is * a typical @a errno value, the meaning of which depends on context. For * example, if inotifytools_watch_file() fails because you attempt to watch * a file which doesn't exist, this function will return @a ENOENT. * * @return an error code. */ int inotifytools_error() { return error; } /** * @internal */ static int isdir( char const * path ) { static struct stat64 my_stat; if ( -1 == lstat64( path, &my_stat ) ) { if (errno == ENOENT) return 0; fprintf(stderr, "Stat failed on %s: %s\n", path, strerror(errno)); return 0; } return S_ISDIR( my_stat.st_mode ) && !S_ISLNK( my_stat.st_mode ); } /** * Get the number of watches set up through libinotifytools. * * @return number of watches set up by inotifytools_watch_file(), * inotifytools_watch_files() and inotifytools_watch_recursively(). */ int inotifytools_get_num_watches() { int ret = 0; rbwalk(tree_filename, get_num, (void*)&ret); return ret; } /** * Print a string to standard out using an inotify_event and a printf-like * syntax. * The string written will only ever be up to 4096 characters in length. * * @param event the event to use to construct a string. * * @param fmt the format string used to construct a string. * * @return number of characters written, or -1 if an error occurs. * * @section syntax Format string syntax * The following tokens will be replaced with the specified string: * \li \c \%w - This will be replaced with the name of the Watched file on * which an event occurred. * \li \c \%c - This will be replaced with the cookie of the Watched file on * which an event occurred. * \li \c \%f - When an event occurs within a directory, this will be replaced * with the name of the File which caused the event to occur. * Otherwise, this will be replaced with an empty string. * \li \c \%e - Replaced with the Event(s) which occurred, comma-separated. * \li \c \%Xe - Replaced with the Event(s) which occurred, separated by * whichever character is in the place of `X'. * \li \c \%T - Replaced by the current Time in the format specified by the * string previously passed to inotifytools_set_printf_timefmt(), * or replaced with an empty string if that function has never * been called. * \li \c \%0 - Replaced with the 'NUL' character * \li \c \%n - Replaced with the 'Line Feed' character * * @section example Example * @code * // suppose this is the only file watched. * inotifytools_watch_file( "mydir/", IN_CLOSE ); * * // wait until an event occurs * struct inotify_event * event = inotifytools_next_event( -1 ); * * inotifytools_printf(stderr, event, "in %w, file %f had event(s): %.e\n"); * // suppose the file 'myfile' in mydir was read from and closed. Then, * // this prints to standard out something like: * // "in mydir/, file myfile had event(s): CLOSE_NOWRITE.CLOSE.ISDIR\n" * @endcode */ int inotifytools_printf( struct inotify_event* event, char* fmt ) { return inotifytools_fprintf( stdout, event, fmt ); } /** * Print a string to a file using an inotify_event and a printf-like syntax. * The string written will only ever be up to 4096 characters in length. * * @param file file to print to * * @param event the event to use to construct a string. * * @param fmt the format string used to construct a string. * * @return number of characters written, or -1 if an error occurs. * * @section syntax Format string syntax * The following tokens will be replaced with the specified string: * \li \c \%w - This will be replaced with the name of the Watched file on * which an event occurred. * \li \c \%c - This will be replaced with the cookie of the Watched file on * which an event occurred. * \li \c \%f - When an event occurs within a directory, this will be replaced * with the name of the File which caused the event to occur. * Otherwise, this will be replaced with an empty string. * \li \c \%e - Replaced with the Event(s) which occurred, comma-separated. * \li \c \%Xe - Replaced with the Event(s) which occurred, separated by * whichever character is in the place of `X'. * \li \c \%T - Replaced by the current Time in the format specified by the * string previously passed to inotifytools_set_printf_timefmt(), * or replaced with an empty string if that function has never * been called. * \li \c \%0 - Replaced with the 'NUL' character * \li \c \%n - Replaced with the 'Line Feed' character * * @section example Example * @code * // suppose this is the only file watched. * inotifytools_watch_file( "mydir/", IN_CLOSE ); * * // wait until an event occurs * struct inotify_event * event = inotifytools_next_event( -1 ); * * inotifytools_fprintf(stderr, event, "in %w, file %f had event(s): %.e\n"); * // suppose the file 'myfile' in mydir was read from and closed. Then, * // this prints to standard error something like: * // "in mydir/, file myfile had event(s): CLOSE_NOWRITE.CLOSE.ISDIR\n" * @endcode */ int inotifytools_fprintf( FILE* file, struct inotify_event* event, char* fmt ) { static struct nstring out; static int ret; ret = inotifytools_sprintf( &out, event, fmt ); if ( -1 != ret ) fwrite( out.buf, sizeof(char), out.len, file ); return ret; } /** * Construct a string using an inotify_event and a printf-like syntax. * The string can only ever be up to 4096 characters in length. * * This function will keep writing until it reaches 4096 characters. If your * allocated array is not large enough to hold the entire string, your program * may crash. * inotifytools_snprintf() is safer and you should use it where possible. * * @param out location in which to store nstring. * * @param event the event to use to construct a nstring. * * @param fmt the format string used to construct a nstring. * * @return number of characters written, or -1 if an error occurs. * * @section syntax Format string syntax * The following tokens will be replaced with the specified string: * \li \c \%w - This will be replaced with the name of the Watched file on * which an event occurred. * \li \c \%c - This will be replaced with the cookie of the Watched file on * which an event occurred. * \li \c \%f - When an event occurs within a directory, this will be replaced * with the name of the File which caused the event to occur. * Otherwise, this will be replaced with an empty string. * \li \c \%e - Replaced with the Event(s) which occurred, comma-separated. * \li \c \%Xe - Replaced with the Event(s) which occurred, separated by * whichever character is in the place of `X'. * \li \c \%T - Replaced by the current Time in the format specified by the * string previously passed to inotifytools_set_printf_timefmt(), * or replaced with an empty string if that function has never * been called. * \li \c \%0 - Replaced with the 'NUL' character * \li \c \%n - Replaced with the 'Line Feed' character * * @section example Example * @code * // suppose this is the only file watched. * inotifytools_watch_file( "mydir/", IN_CLOSE ); * * // wait until an event occurs * struct inotify_event * event = inotifytools_next_event( -1 ); * * nstring mynstring; * inotifytools_sprintf(mynstring, event, "in %w, file %f had event(s): %.e\n"); * fwrite( mynstring.buf, sizeof(char), mynstring.len, stdout ); * // suppose the file 'myfile' in mydir was written to and closed. Then, * // this prints something like: * // "in mydir/, file myfile had event(s): CLOSE_WRITE.CLOSE.ISDIR\n" * @endcode */ int inotifytools_sprintf( struct nstring * out, struct inotify_event* event, char* fmt ) { return inotifytools_snprintf( out, MAX_STRLEN, event, fmt ); } /** * Construct a string using an inotify_event and a printf-like syntax. * The string can only ever be up to 4096 characters in length. * * @param out location in which to store nstring. * * @param size maximum amount of characters to write. * * @param event the event to use to construct a nstring. * * @param fmt the format string used to construct a nstring. * * @return number of characters written, or -1 if an error occurs. * * @section syntax Format string syntax * The following tokens will be replaced with the specified string: * \li \c \%w - This will be replaced with the name of the Watched file on * which an event occurred. * \li \c \%c - This will be replaced with cookie of the Watched file on * which an event occurred. * \li \c \%f - When an event occurs within a directory, this will be replaced * with the name of the File which caused the event to occur. * Otherwise, this will be replaced with an empty string. * \li \c \%e - Replaced with the Event(s) which occurred, comma-separated. * \li \c \%Xe - Replaced with the Event(s) which occurred, separated by * whichever character is in the place of `X'. * \li \c \%T - Replaced by the current Time in the format specified by the * string previously passed to inotifytools_set_printf_timefmt(), * or replaced with an empty string if that function has never * been called. * \li \c \%0 - Replaced with the 'NUL' character * \li \c \%n - Replaced with the 'Line Feed' character * * @section example Example * @code * // suppose this is the only file watched. * inotifytools_watch_file( "mydir/", IN_CLOSE ); * * // wait until an event occurs * struct inotify_event * event = inotifytools_next_event( -1 ); * * struct nstring mynstring; * inotifytools_snprintf( mynstring, MAX_STRLEN, event, * "in %w, file %f had event(s): %.e\n" ); * fwrite( mynstring.buf, sizeof(char), mynstring.len, stdout ); * // suppose the file 'myfile' in mydir was written to and closed. Then, * // this prints something like: * // "in mydir/, file myfile had event(s): CLOSE_WRITE.CLOSE.ISDIR\n" * @endcode */ int inotifytools_snprintf( struct nstring * out, int size, struct inotify_event* event, char* fmt ) { const char* eventstr; static unsigned int i, ind; static char ch1; static char timestr[MAX_STRLEN]; static time_t now; size_t dirnamelen = 0; const char* eventname; const char* filename = inotifytools_filename_from_event(event, &eventname, &dirnamelen); if ( !fmt || 0 == strlen(fmt) ) { error = EINVAL; return -1; } if ( strlen(fmt) > MAX_STRLEN || size > MAX_STRLEN) { error = EMSGSIZE; return -1; } ind = 0; for ( i = 0; i < strlen(fmt) && (int)ind < size - 1; ++i ) { if ( fmt[i] != '%' ) { out->buf[ind++] = fmt[i]; continue; } if ( i == strlen(fmt) - 1 ) { // last character is %, invalid error = EINVAL; return ind; } ch1 = fmt[i+1]; if ( ch1 == '%' ) { out->buf[ind++] = '%'; ++i; continue; } if ( ch1 == '0' ) { out->buf[ind++] = '\0'; ++i; continue; } if ( ch1 == 'n' ) { out->buf[ind++] = '\n'; ++i; continue; } if ( ch1 == 'w' ) { if (filename && dirnamelen <= size - ind) { strncpy(&out->buf[ind], filename, dirnamelen); ind += dirnamelen; } ++i; continue; } if ( ch1 == 'f' ) { if ( eventname ) { strncpy( &out->buf[ind], eventname, size - ind ); ind += strlen(eventname); } ++i; continue; } if ( ch1 == 'c' ) { ind += snprintf( &out->buf[ind], size-ind, "%x", event->cookie); ++i; continue; } if ( ch1 == 'e' ) { eventstr = inotifytools_event_to_str( event->mask ); strncpy( &out->buf[ind], eventstr, size - ind ); ind += strlen(eventstr); ++i; continue; } if ( ch1 == 'T' ) { if ( timefmt ) { now = time(0); struct tm now_tm; if (!strftime(timestr, MAX_STRLEN - 1, timefmt, localtime_r(&now, &now_tm))) { // time format probably invalid error = EINVAL; return ind; } } else { timestr[0] = 0; } strncpy( &out->buf[ind], timestr, size - ind ); ind += strlen(timestr); ++i; continue; } // Check if next char in fmt is e if ( i < strlen(fmt) - 2 && fmt[i+2] == 'e' ) { eventstr = inotifytools_event_to_str_sep( event->mask, ch1 ); strncpy( &out->buf[ind], eventstr, size - ind ); ind += strlen(eventstr); i += 2; continue; } // OK, this wasn't a special format character, just output it as normal if ( ind < MAX_STRLEN ) out->buf[ind++] = '%'; if ( ind < MAX_STRLEN ) out->buf[ind++] = ch1; ++i; } out->len = ind; return ind - 1; } /** * Set time format for printf functions. * * @param fmt A format string valid for use with strftime, or NULL. If NULL, * time substitutions will no longer be made in printf functions. * Note that this format string is not validated at all; using an * incorrect format string will cause the printf functions to give * incorrect results. */ void inotifytools_set_printf_timefmt( char * fmt ) { timefmt = fmt; } /** * Get the event queue size. * * This setting can also be read or modified by accessing the file * \a /proc/sys/fs/inotify/max_queued_events. * * @return the maximum number of events which will be queued in the kernel. */ int inotifytools_get_max_queued_events() { int ret; if ( !read_num_from_file( QUEUE_SIZE_PATH, &ret ) ) return -1; return ret; } /** * Get the maximum number of user instances of inotify. * * This setting can also be read or modified by accessing the file * \a /proc/sys/fs/inotify/max_user_instances. * * @return the maximum number of inotify file descriptors a single user can * obtain. */ int inotifytools_get_max_user_instances() { int ret; if ( !read_num_from_file( INSTANCES_PATH, &ret ) ) return -1; return ret; } /** * Get the maximum number of user watches. * * This setting can also be read or modified by accessing the file * \a /proc/sys/fs/inotify/max_user_watches. * * @return the maximum number of inotify watches a single user can obtain per * inotify instance. */ int inotifytools_get_max_user_watches() { int ret; if ( !read_num_from_file( WATCHES_SIZE_PATH, &ret ) ) return -1; return ret; } /** * Ignore inotify events matching a particular regular expression. * * @a pattern is a regular expression and @a flags is a bitwise combination of * POSIX regular expression flags. @a invert determines the type of filtering: * 0 (--exclude[i]): exclude all files matching @a pattern * 1 (--include[i]): exclude all files except those matching @a pattern * * On future calls to inotifytools_next_events() or inotifytools_next_event(), * the regular expression is executed on the filename of files on which * events occur. If the regular expression matches, the matched event will be * ignored. */ static int do_ignore_events_by_regex( char const *pattern, int flags, int invert ) { if (!pattern) { if (regex) { regfree(regex); free(regex); regex = 0; } return 1; } if (regex) { regfree(regex); } else { regex = (regex_t *)malloc(sizeof(regex_t)); } invert_regexp = invert; int ret = regcomp(regex, pattern, flags | REG_NOSUB); if (0 == ret) return 1; regfree(regex); free(regex); regex = 0; error = EINVAL; return 0; } /** * Ignore inotify events matching a particular regular expression. * * @a pattern is a regular expression and @a flags is a bitwise combination of * POSIX regular expression flags. * * On future calls to inotifytools_next_events() or inotifytools_next_event(), * the regular expression is executed on the filename of files on which * events occur. If the regular expression matches, the matched event will be * ignored. */ int inotifytools_ignore_events_by_regex( char const *pattern, int flags ) { return do_ignore_events_by_regex(pattern, flags, 0); } /** * Ignore inotify events NOT matching a particular regular expression. * * @a pattern is a regular expression and @a flags is a bitwise combination of * POSIX regular expression flags. * * On future calls to inotifytools_next_events() or inotifytools_next_event(), * the regular expression is executed on the filename of files on which * events occur. If the regular expression matches, the matched event will be * ignored. */ int inotifytools_ignore_events_by_inverted_regex( char const *pattern, int flags ) { return do_ignore_events_by_regex(pattern, flags, 1); } int event_compare(const void *p1, const void *p2, const void *config) { if (!p1 || !p2) return p1 - p2; char asc = 1; long sort_event = (long)config; if (sort_event == -1) { sort_event = 0; asc = 0; } else if (sort_event < 0) { sort_event = -sort_event; asc = 0; } unsigned int *i1 = stat_ptr((watch*)p1, sort_event); unsigned int *i2 = stat_ptr((watch*)p2, sort_event); if (0 == *i1 - *i2) { return ((watch*)p1)->wd - ((watch*)p2)->wd; } if (asc) return *i1 - *i2; else return *i2 - *i1; } struct rbtree *inotifytools_wd_sorted_by_event(int sort_event) { struct rbtree *ret = rbinit(event_compare, (void*)(uintptr_t)sort_event); RBLIST *all = rbopenlist(tree_wd); void const *p = rbreadlist(all); while (p) { void const *r = rbsearch(p, ret); niceassert((int)(r == p), "Couldn't insert watch into new tree"); p = rbreadlist(all); } rbcloselist(all); return ret; } inotify-tools-3.22.6.0/libinotifytools/src/inotifytools/000077500000000000000000000000001424760767400233645ustar00rootroot00000000000000inotify-tools-3.22.6.0/libinotifytools/src/inotifytools/Makefile.am000066400000000000000000000000541424760767400254170ustar00rootroot00000000000000EXTRA_DIST = inotify-nosys.h inotifytools.h inotify-tools-3.22.6.0/libinotifytools/src/inotifytools/fanotify-dfid-name.h000066400000000000000000000132551424760767400272040ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ #define _LINUX_FANOTIFY_H #include /* the following events that user-space can register for */ #define FAN_ACCESS 0x00000001 /* File was accessed */ #define FAN_MODIFY 0x00000002 /* File was modified */ #define FAN_ATTRIB 0x00000004 /* Metadata changed */ #define FAN_CLOSE_WRITE 0x00000008 /* Writtable file closed */ #define FAN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ #define FAN_OPEN 0x00000020 /* File was opened */ #define FAN_MOVED_FROM 0x00000040 /* File was moved from X */ #define FAN_MOVED_TO 0x00000080 /* File was moved to Y */ #define FAN_CREATE 0x00000100 /* Subfile was created */ #define FAN_DELETE 0x00000200 /* Subfile was deleted */ #define FAN_DELETE_SELF 0x00000400 /* Self was deleted */ #define FAN_MOVE_SELF 0x00000800 /* Self was moved */ #define FAN_OPEN_EXEC 0x00001000 /* File was opened for exec */ #define FAN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ #define FAN_OPEN_PERM 0x00010000 /* File open in perm check */ #define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */ #define FAN_OPEN_EXEC_PERM 0x00040000 /* File open/exec in perm check */ #define FAN_ONDIR 0x40000000 /* event occurred against dir */ #define FAN_EVENT_ON_CHILD 0x08000000 /* interested in child events */ /* helper events */ #define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */ #define FAN_MOVE (FAN_MOVED_FROM | FAN_MOVED_TO) /* moves */ /* flags used for fanotify_init() */ #define FAN_CLOEXEC 0x00000001 #define FAN_NONBLOCK 0x00000002 /* These are NOT bitwise flags. Both bits are used together. */ #define FAN_CLASS_NOTIF 0x00000000 #define FAN_CLASS_CONTENT 0x00000004 #define FAN_CLASS_PRE_CONTENT 0x00000008 /* Deprecated - do not use this in programs and do not add new flags here! */ #define FAN_ALL_CLASS_BITS \ (FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | FAN_CLASS_PRE_CONTENT) #define FAN_UNLIMITED_QUEUE 0x00000010 #define FAN_UNLIMITED_MARKS 0x00000020 #define FAN_ENABLE_AUDIT 0x00000040 /* Flags to determine fanotify event format */ #define FAN_REPORT_TID 0x00000100 /* event->pid is thread id */ #define FAN_REPORT_FID 0x00000200 /* Report unique file id */ #define FAN_REPORT_DIR_FID 0x00000400 /* Report unique directory id */ #define FAN_REPORT_NAME 0x00000800 /* Report events with name */ /* Convenience macro - FAN_REPORT_NAME requires FAN_REPORT_DIR_FID */ #define FAN_REPORT_DFID_NAME (FAN_REPORT_DIR_FID | FAN_REPORT_NAME) /* Deprecated - do not use this in programs and do not add new flags here! */ #define FAN_ALL_INIT_FLAGS \ (FAN_CLOEXEC | FAN_NONBLOCK | FAN_ALL_CLASS_BITS | \ FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS) /* flags used for fanotify_modify_mark() */ #define FAN_MARK_ADD 0x00000001 #define FAN_MARK_REMOVE 0x00000002 #define FAN_MARK_DONT_FOLLOW 0x00000004 #define FAN_MARK_ONLYDIR 0x00000008 /* FAN_MARK_MOUNT is 0x00000010 */ #define FAN_MARK_IGNORED_MASK 0x00000020 #define FAN_MARK_IGNORED_SURV_MODIFY 0x00000040 #define FAN_MARK_FLUSH 0x00000080 /* FAN_MARK_FILESYSTEM is 0x00000100 */ /* These are NOT bitwise flags. Both bits can be used togther. */ #define FAN_MARK_INODE 0x00000000 #define FAN_MARK_MOUNT 0x00000010 #define FAN_MARK_FILESYSTEM 0x00000100 /* Deprecated - do not use this in programs and do not add new flags here! */ #define FAN_ALL_MARK_FLAGS \ (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_DONT_FOLLOW | \ FAN_MARK_ONLYDIR | FAN_MARK_MOUNT | FAN_MARK_IGNORED_MASK | \ FAN_MARK_IGNORED_SURV_MODIFY | FAN_MARK_FLUSH) /* Deprecated - do not use this in programs and do not add new flags here! */ #define FAN_ALL_EVENTS (FAN_ACCESS | FAN_MODIFY | FAN_CLOSE | FAN_OPEN) /* * All events which require a permission response from userspace */ /* Deprecated - do not use this in programs and do not add new flags here! */ #define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM | FAN_ACCESS_PERM) /* Deprecated - do not use this in programs and do not add new flags here! */ #define FAN_ALL_OUTGOING_EVENTS \ (FAN_ALL_EVENTS | FAN_ALL_PERM_EVENTS | FAN_Q_OVERFLOW) #define FANOTIFY_METADATA_VERSION 3 struct fanotify_event_metadata { __u32 event_len; __u8 vers; __u8 reserved; __u16 metadata_len; __aligned_u64 mask; __s32 fd; __s32 pid; }; #define FAN_EVENT_INFO_TYPE_FID 1 #define FAN_EVENT_INFO_TYPE_DFID_NAME 2 #define FAN_EVENT_INFO_TYPE_DFID 3 /* Variable length info record following event metadata */ struct fanotify_event_info_header { __u8 info_type; __u8 pad; __u16 len; }; /* Unique file identifier info record */ struct fanotify_event_info_fid { struct fanotify_event_info_header hdr; __kernel_fsid_t fsid; /* * Following is an opaque struct file_handle that can be passed as * an argument to open_by_handle_at(2). */ unsigned char handle[0]; }; struct fanotify_response { __s32 fd; __u32 response; }; /* Legit userspace responses to a _PERM event */ #define FAN_ALLOW 0x01 #define FAN_DENY 0x02 #define FAN_AUDIT 0x10 /* Bit mask to create audit record for result */ /* No fd set in event */ #define FAN_NOFD -1 /* Helper functions to deal with fanotify_event_metadata buffers */ #define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata)) #define FAN_EVENT_NEXT(meta, len) \ ((len) -= (meta)->event_len, \ (struct fanotify_event_metadata*)(((char*)(meta)) + \ (meta)->event_len)) #define FAN_EVENT_OK(meta, len) \ ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \ (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \ (long)(meta)->event_len <= (long)(len)) inotify-tools-3.22.6.0/libinotifytools/src/inotifytools/fanotify.h.in000066400000000000000000000010221424760767400257540ustar00rootroot00000000000000#ifndef _INOTIFYTOOLS_FANOTIFY_H #define _INOTIFYTOOLS_FANOTIFY_H 1 /* this is defined to 1 if works. */ #undef SYS_FANOTIFY_H_EXISTS_AND_WORKS #ifdef SYS_FANOTIFY_H_EXISTS_AND_WORKS #include #else /* * Assuming sys/fanotify.h does exist, but linux/fanotify.h doesn't have * FAN_REPORT_DFID_NAME, so include our own copy of linux/fanotify.h * before including sys/fanotify.h. */ #include "fanotify-dfid-name.h" #include #endif // SYS_FANOTIFY_H_EXISTS_AND_WORKS #endif inotify-tools-3.22.6.0/libinotifytools/src/inotifytools/inotify-nosys.h000066400000000000000000000127471424760767400264020ustar00rootroot00000000000000/* * This header is used if cannot be found. * * Inode based directory notification for Linux * * Copyright (C) 2005 John McCutchan */ #ifndef _LINUX_INOTIFY_H #define _LINUX_INOTIFY_H #include #include #include #ifdef __FreeBSD__ #define stat64 stat #define lstat64 lstat #endif /* * struct inotify_event - structure read from the inotify device for each event * * When you are watching a directory, you will receive the filename for events * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd. */ struct inotify_event { int wd; /* watch descriptor */ uint32_t mask; /* watch mask */ uint32_t cookie; /* cookie to synchronize two events */ uint32_t len; /* length (including nulls) of name */ char name[]; /* stub for possible name */ }; /* the following are legal, implemented events that user-space can watch for */ #define IN_ACCESS 0x00000001 /* File was accessed */ #define IN_MODIFY 0x00000002 /* File was modified */ #define IN_ATTRIB 0x00000004 /* Metadata changed */ #define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed */ #define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ #define IN_OPEN 0x00000020 /* File was opened */ #define IN_MOVED_FROM 0x00000040 /* File was moved from X */ #define IN_MOVED_TO 0x00000080 /* File was moved to Y */ #define IN_CREATE 0x00000100 /* Subfile was created */ #define IN_DELETE 0x00000200 /* Subfile was deleted */ #define IN_DELETE_SELF 0x00000400 /* Self was deleted */ #define IN_MOVE_SELF 0x00000800 /* Self was moved */ /* the following are legal events. they are sent as needed to any watch */ #define IN_UNMOUNT 0x00002000 /* Backing fs was unmounted */ #define IN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ #define IN_IGNORED 0x00008000 /* File was ignored */ /* helper events */ #define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* close */ #define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* moves */ /* special flags */ #define IN_ONLYDIR 0x01000000 /* only watch the path if it is a directory */ #define IN_DONT_FOLLOW 0x02000000 /* don't follow a sym link */ #define IN_MASK_ADD 0x20000000 /* add to the mask of an already existing watch */ #define IN_ISDIR 0x40000000 /* event occurred against dir */ #define IN_ONESHOT 0x80000000 /* only send event once */ /* * All of the events - we build the list by hand so that we can add flags in * the future and not break backward compatibility. Apps will get only the * events that they originally wanted. Be sure to add new events here! */ #define IN_ALL_EVENTS (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \ IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \ IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF | \ IN_MOVE_SELF) #if defined (__alpha__) # define __NR_inotify_init 444 # define __NR_inotify_add_watch 445 # define __NR_inotify_rm_watch 446 #elif defined(__arm__) || defined(__aarch64__) #ifndef __NR_inotify_init #define __NR_inotify_init 316 #endif #ifndef __NR_inotify_add_watch #define __NR_inotify_add_watch 317 #endif #ifndef __NR_inotify_rm_watch #define __NR_inotify_rm_watch 318 #endif #elif defined (__frv__) # define __NR_inotify_init 291 # define __NR_inotify_add_watch 292 # define __NR_inotify_rm_watch 293 #elif defined(__i386__) # define __NR_inotify_init 291 # define __NR_inotify_add_watch 292 # define __NR_inotify_rm_watch 293 #elif defined (__ia64__) # define __NR_inotify_init 1277 # define __NR_inotify_add_watch 1278 # define __NR_inotify_rm_watch 1279 #elif defined (__mips__) # if _MIPS_SIM == _MIPS_SIM_ABI32 # define __NR_inotify_init (__NR_Linux + 284) # define __NR_inotify_add_watch (__NR_Linux + 285) # define __NR_inotify_rm_watch (__NR_Linux + 286) # endif # if _MIPS_SIM == _MIPS_SIM_ABI64 # define __NR_inotify_init (__NR_Linux + 243) # define __NR_inotify_add_watch (__NR_Linux + 244) # define __NR_inotify_rm_watch (__NR_Linux + 245) # endif # if _MIPS_SIM == _MIPS_SIM_NABI32 # define __NR_inotify_init (__NR_Linux + 247) # define __NR_inotify_add_watch (__NR_Linux + 248) # define __NR_inotify_rm_watch (__NR_Linux + 249) # endif #elif defined(__parisc__) # define __NR_inotify_init (__NR_Linux + 269) # define __NR_inotify_add_watch (__NR_Linux + 270) # define __NR_inotify_rm_watch (__NR_Linux + 271) #elif defined(__powerpc__) || defined(__powerpc64__) # define __NR_inotify_init 275 # define __NR_inotify_add_watch 276 # define __NR_inotify_rm_watch 277 #elif defined (__s390__) # define __NR_inotify_init 284 # define __NR_inotify_add_watch 285 # define __NR_inotify_rm_watch 286 #elif defined (__sh__) # define __NR_inotify_init 290 # define __NR_inotify_add_watch 291 # define __NR_inotify_rm_watch 292 #elif defined (__sh64__) # define __NR_inotify_init 318 # define __NR_inotify_add_watch 319 # define __NR_inotify_rm_watch 320 #elif defined (__sparc__) || defined (__sparc64__) # define __NR_inotify_init 151 # define __NR_inotify_add_watch 152 # define __NR_inotify_rm_watch 156 #elif defined(__x86_64__) # define __NR_inotify_init 253 # define __NR_inotify_add_watch 254 # define __NR_inotify_rm_watch 255 #else # error "Unsupported architecture!" #endif static inline int inotify_init (void) { return syscall (__NR_inotify_init); } static inline int inotify_add_watch (int fd, const char *name, uint32_t mask) { return syscall (__NR_inotify_add_watch, fd, name, mask); } static inline int inotify_rm_watch (int fd, uint32_t wd) { return syscall (__NR_inotify_rm_watch, fd, wd); } #endif /* _LINUX_INOTIFY_H */ inotify-tools-3.22.6.0/libinotifytools/src/inotifytools/inotify.h.in000066400000000000000000000004631424760767400256260ustar00rootroot00000000000000#ifndef _INOTIFYTOOLS_INOTIFY_H #define _INOTIFYTOOLS_INOTIFY_H 1 /* this is defined to 1 if works. */ #undef SYS_INOTIFY_H_EXISTS_AND_WORKS #ifdef SYS_INOTIFY_H_EXISTS_AND_WORKS #include #else #include "inotify-nosys.h" #endif // SYS_INOTIFY_H_EXISTS_AND_WORKS #endif inotify-tools-3.22.6.0/libinotifytools/src/inotifytools/inotifytools.h000066400000000000000000000065501424760767400263050ustar00rootroot00000000000000#ifndef _inotifytools_H #define _inotifytools_H #ifdef __FreeBSD__ #define stat64 stat #define lstat64 lstat #endif #ifdef __cplusplus extern "C" { #endif #include #define MAX_STRLEN 4096 /** @struct nstring * @brief This structure holds string that can contain any character including NULL. * @var nstring::buf * Member 'buf' contains character buffer. It can hold up to 4096 characters. * @var nstring::len * Member 'len' contains number of characters in buffer. */ struct nstring { char buf[MAX_STRLEN]; unsigned int len; }; int inotifytools_str_to_event(char const * event); int inotifytools_str_to_event_sep(char const * event, char sep); char * inotifytools_event_to_str(int events); char * inotifytools_event_to_str_sep(int events, char sep); void inotifytools_set_filename_by_wd( int wd, char const * filename ); void inotifytools_set_filename_by_filename( char const * oldname, char const * newname ); void inotifytools_replace_filename( char const * oldname, char const * newname ); struct inotify_event; const char* inotifytools_dirname_from_event(struct inotify_event* event, size_t* dirnamelen); const char* inotifytools_filename_from_event(struct inotify_event* event, char const** eventname, size_t* dirnamelen); char* inotifytools_dirpath_from_event(struct inotify_event* event); struct watch; const char* inotifytools_filename_from_watch(struct watch* w); const char* inotifytools_filename_from_wd(int wd); int inotifytools_wd_from_filename( char const * filename ); int inotifytools_remove_watch_by_filename( char const * filename ); int inotifytools_remove_watch_by_wd( int wd ); int inotifytools_watch_file(char const* filename, int events); int inotifytools_watch_files(char const* filenames[], int events); int inotifytools_watch_recursively(char const* path, int events); int inotifytools_watch_recursively_with_exclude(char const* path, int events, char const** exclude_list); // [UH] int inotifytools_ignore_events_by_regex( char const *pattern, int flags ); int inotifytools_ignore_events_by_inverted_regex( char const *pattern, int flags ); struct inotify_event * inotifytools_next_event( long int timeout ); struct inotify_event * inotifytools_next_events( long int timeout, int num_events ); int inotifytools_error(); int inotifytools_get_stat_by_wd( int wd, int event ); int inotifytools_get_stat_total( int event ); int inotifytools_get_stat_by_filename( char const * filename, int event ); void inotifytools_initialize_stats(); int inotifytools_initialize(); int inotifytools_init(int fanotify, int watch_filesystem, int verbose); void inotifytools_cleanup(); int inotifytools_get_num_watches(); int inotifytools_printf( struct inotify_event* event, char* fmt ); int inotifytools_fprintf( FILE* file, struct inotify_event* event, char* fmt ); int inotifytools_sprintf( struct nstring * out, struct inotify_event* event, char* fmt ); int inotifytools_snprintf( struct nstring * out, int size, struct inotify_event* event, char* fmt ); void inotifytools_set_printf_timefmt( char * fmt ); int inotifytools_get_max_user_watches(); int inotifytools_get_max_user_instances(); int inotifytools_get_max_queued_events(); #ifdef __cplusplus } #endif #endif // _inotifytools_H inotify-tools-3.22.6.0/libinotifytools/src/inotifytools_p.h000066400000000000000000000036661424760767400240670ustar00rootroot00000000000000#ifndef INOTIFYTOOLS_P_H #define INOTIFYTOOLS_P_H #include "redblack.h" /** * @internal * Assert that a condition evaluates to true, and optionally output a message * if the assertion fails. * * @param cond Integer; if 0, assertion fails, otherwise assertion succeeds. * * @param mesg A human-readable error message shown if assertion fails. * * @section example Example * @code * int upper = 100, lower = 50; * int input = get_user_input(); * niceassert( input <= upper && input >= lower, * "input not in required range!"); * @endcode */ #define niceassert(cond,mesg) _niceassert((long)cond, __LINE__, __FILE__, \ #cond, mesg) /** * @internal * Assert that a condition evaluates to true, and optionally output a message * if the assertion fails. * * You should use the niceassert() preprocessor macro instead. * * @param cond If 0, assertion fails, otherwise assertion succeeds. * * @param line Line number of source code where assertion is made. * * @param file Name of source file where assertion is made. * * @param condstr Stringified assertion expression. * * @param mesg A human-readable error message shown if assertion fails. */ void _niceassert( long cond, int line, char const * file, char const * condstr, char const * mesg ); struct rbtree *inotifytools_wd_sorted_by_event(int sort_event); extern int init; struct fanotify_event_fid; #define MAX_FID_LEN 20 typedef struct watch { struct fanotify_event_fid* fid; char *filename; unsigned long wd; int dirf; unsigned hit_access; unsigned hit_modify; unsigned hit_attrib; unsigned hit_close_write; unsigned hit_close_nowrite; unsigned hit_open; unsigned hit_moved_from; unsigned hit_moved_to; unsigned hit_create; unsigned hit_delete; unsigned hit_delete_self; unsigned hit_unmount; unsigned hit_move_self; unsigned hit_total; } watch; extern struct rbtree *tree_wd; #endif inotify-tools-3.22.6.0/libinotifytools/src/redblack.c000066400000000000000000000576751424760767400225610ustar00rootroot00000000000000/* Redblack balanced tree algorithm Copyright (C) Damian Ivereigh 2000 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for details. 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 Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Implement the red/black tree structure. It is designed to emulate ** the standard tsearch() stuff. i.e. the calling conventions are ** exactly the same */ #include #include #include #include "redblack.h" #define assert(expr) /* Uncomment this if you would rather use a raw sbrk to get memory ** (however the memory is never released again (only re-used). Can't ** see any point in using this these days. */ /* #define USE_SBRK */ enum nodecolour { BLACK, RED }; struct RB_ENTRY(node) { struct RB_ENTRY(node) *left; /* Left down */ struct RB_ENTRY(node) *right; /* Right down */ struct RB_ENTRY(node) *up; /* Up */ enum nodecolour colour; /* Node colour */ #ifdef RB_INLINE RB_ENTRY(data_t) key; /* User's key (and data) */ #define RB_GET(x,y) &x->y #define RB_SET(x,y,v) x->y = *(v) #else const RB_ENTRY(data_t) *key; /* Pointer to user's key (and data) */ #define RB_GET(x,y) x->y #define RB_SET(x,y,v) x->y = v #endif /* RB_INLINE */ }; /* Dummy (sentinel) node, so that we can make X->left->up = X ** We then use this instead of NULL to mean the top or bottom ** end of the rb tree. It is a black node. ** ** Initialization of the last field in this initializer is left implicit ** because it could be of any type. We count on the compiler to zero it. */ struct RB_ENTRY(node) RB_ENTRY(_null)={&RB_ENTRY(_null), &RB_ENTRY(_null), &RB_ENTRY(_null), BLACK}; #define RBNULL (&RB_ENTRY(_null)) #if defined(USE_SBRK) static struct RB_ENTRY(node) *RB_ENTRY(_alloc)(); static void RB_ENTRY(_free)(struct RB_ENTRY(node) *); #else static struct RB_ENTRY(node) *RB_ENTRY(_alloc)() {return (struct RB_ENTRY(node) *) malloc(sizeof(struct RB_ENTRY(node)));} static void RB_ENTRY(_free)(struct RB_ENTRY(node) *x) {free(x);} #endif /* These functions are always needed */ static void RB_ENTRY(_left_rotate)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); static void RB_ENTRY(_right_rotate)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); static struct RB_ENTRY(node) *RB_ENTRY(_successor)(const struct RB_ENTRY(node) *); static struct RB_ENTRY(node) *RB_ENTRY(_predecessor)(const struct RB_ENTRY(node) *); static struct RB_ENTRY(node) *RB_ENTRY(_traverse)(int, const RB_ENTRY(data_t) * , struct RB_ENTRY(tree) *); /* These functions may not be needed */ #ifndef no_lookup static struct RB_ENTRY(node) *RB_ENTRY(_lookup)(int, const RB_ENTRY(data_t) * , struct RB_ENTRY(tree) *); #endif #ifndef no_destroy static void RB_ENTRY(_destroy)(struct RB_ENTRY(node) *); #endif #ifndef no_delete static void RB_ENTRY(_delete)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); static void RB_ENTRY(_delete_fix)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); #endif #ifndef no_walk static void RB_ENTRY(_walk)(const struct RB_ENTRY(node) *, void (*)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *, int); #endif #ifndef no_readlist static RBLIST *RB_ENTRY(_openlist)(const struct RB_ENTRY(node) *); static const RB_ENTRY(data_t) * RB_ENTRY(_readlist)(RBLIST *); static void RB_ENTRY(_closelist)(RBLIST *); #endif /* ** OK here we go, the balanced tree stuff. The algorithm is the ** fairly standard red/black taken from "Introduction to Algorithms" ** by Cormen, Leiserson & Rivest. Maybe one of these days I will ** fully understand all this stuff. ** ** Basically a red/black balanced tree has the following properties:- ** 1) Every node is either red or black (colour is RED or BLACK) ** 2) A leaf (RBNULL pointer) is considered black ** 3) If a node is red then its children are black ** 4) Every path from a node to a leaf contains the same no ** of black nodes ** ** 3) & 4) above guarantee that the longest path (alternating ** red and black nodes) is only twice as long as the shortest ** path (all black nodes). Thus the tree remains fairly balanced. */ /* * Initialise a tree. Identifies the comparison routine and any config * data that must be sent to it when called. * Returns a pointer to the top of the tree. */ #ifndef RB_CUSTOMIZE RB_STATIC struct RB_ENTRY(tree) * rbinit(int (*cmp)(const void *, const void *, const void *), const void *config) #else RB_STATIC struct RB_ENTRY(tree) *RB_ENTRY(init)(void) #endif /* RB_CUSTOMIZE */ { struct RB_ENTRY(tree) *retval; if ((retval=(struct RB_ENTRY(tree) *) malloc(sizeof(struct RB_ENTRY(tree))))==NULL) return(NULL); #ifndef RB_CUSTOMIZE retval->rb_cmp=cmp; retval->rb_config=config; #endif /* RB_CUSTOMIZE */ retval->rb_root=RBNULL; return(retval); } #ifndef no_destroy RB_STATIC void RB_ENTRY(destroy)(struct RB_ENTRY(tree) *rbinfo) { if (rbinfo==NULL) return; if (rbinfo->rb_root!=RBNULL) RB_ENTRY(_destroy)(rbinfo->rb_root); free(rbinfo); } #endif /* no_destroy */ #ifndef no_search RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(search)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x; if (rbinfo==NULL) return(NULL); x=RB_ENTRY(_traverse)(1, key, rbinfo); return((x==RBNULL) ? NULL : RB_GET(x, key)); } #endif /* no_search */ #ifndef no_find RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(find)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x; if (rbinfo==NULL) return(NULL); /* If we have a NULL root (empty tree) then just return */ if (rbinfo->rb_root==RBNULL) return(NULL); x=RB_ENTRY(_traverse)(0, key, rbinfo); return((x==RBNULL) ? NULL : RB_GET(x, key)); } #endif /* no_find */ #ifndef no_delete RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(delete)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x; const RB_ENTRY(data_t) * y; if (rbinfo==NULL) return(NULL); x=RB_ENTRY(_traverse)(0, key, rbinfo); if (x==RBNULL) { return(NULL); } else { y=RB_GET(x, key); RB_ENTRY(_delete)(&rbinfo->rb_root, x); return(y); } } #endif /* no_delete */ #ifndef no_walk RB_STATIC void RB_ENTRY(walk)(const struct RB_ENTRY(tree) *rbinfo, void (*action)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *arg) { if (rbinfo==NULL) return; RB_ENTRY(_walk)(rbinfo->rb_root, action, arg, 0); } #endif /* no_walk */ #ifndef no_readlist RB_STATIC RBLIST * RB_ENTRY(openlist)(const struct RB_ENTRY(tree) *rbinfo) { if (rbinfo==NULL) return(NULL); return(RB_ENTRY(_openlist)(rbinfo->rb_root)); } RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(readlist)(RBLIST *rblistp) { if (rblistp==NULL) return(NULL); return(RB_ENTRY(_readlist)(rblistp)); } RB_STATIC void RB_ENTRY(closelist)(RBLIST *rblistp) { if (rblistp==NULL) return; RB_ENTRY(_closelist)(rblistp); } #endif /* no_readlist */ #ifndef no_lookup RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(lookup)(int mode, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x; /* If we have a NULL root (empty tree) then just return NULL */ if (rbinfo==NULL || rbinfo->rb_root==NULL) return(NULL); x=RB_ENTRY(_lookup)(mode, key, rbinfo); return((x==RBNULL) ? NULL : RB_GET(x, key)); } #endif /* no_lookup */ /* --------------------------------------------------------------------- */ /* Search for and if not found and insert is true, will add a new ** node in. Returns a pointer to the new node, or the node found */ static struct RB_ENTRY(node) * RB_ENTRY(_traverse)(int insert, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x,*y,*z; int cmp; int found=0; y=RBNULL; /* points to the parent of x */ x=rbinfo->rb_root; /* walk x down the tree */ while(x!=RBNULL && found==0) { y=x; /* printf("key=%s, RB_GET(x, key)=%s\n", key, RB_GET(x, key)); */ #ifndef RB_CUSTOMIZE cmp=RB_CMP(key, RB_GET(x, key), rbinfo->rb_config); #else cmp=RB_CMP(key, RB_GET(x, key)); #endif /* RB_CUSTOMIZE */ if (cmp<0) x=x->left; else if (cmp>0) x=x->right; else found=1; } if (found || !insert) return(x); if ((z=RB_ENTRY(_alloc)())==NULL) { /* Whoops, no memory */ return(RBNULL); } RB_SET(z, key, key); z->up=y; if (y==RBNULL) { rbinfo->rb_root=z; } else { #ifndef RB_CUSTOMIZE cmp=RB_CMP(RB_GET(z, key), RB_GET(y, key), rbinfo->rb_config); #else cmp=RB_CMP(RB_GET(z, key), RB_GET(y, key)); #endif /* RB_CUSTOMIZE */ if (cmp<0) y->left=z; else y->right=z; } z->left=RBNULL; z->right=RBNULL; /* colour this new node red */ z->colour=RED; /* Having added a red node, we must now walk back up the tree balancing ** it, by a series of rotations and changing of colours */ x=z; /* While we are not at the top and our parent node is red ** N.B. Since the root node is guaranteed black, then we ** are also going to stop if we are the child of the root */ while(x != rbinfo->rb_root && (x->up->colour == RED)) { /* if our parent is on the left side of our grandparent */ if (x->up == x->up->up->left) { /* get the right side of our grandparent (uncle?) */ y=x->up->up->right; if (y->colour == RED) { /* make our parent black */ x->up->colour = BLACK; /* make our uncle black */ y->colour = BLACK; /* make our grandparent red */ x->up->up->colour = RED; /* now consider our grandparent */ x=x->up->up; } else { /* if we are on the right side of our parent */ if (x == x->up->right) { /* Move up to our parent */ x=x->up; RB_ENTRY(_left_rotate)(&rbinfo->rb_root, x); } /* make our parent black */ x->up->colour = BLACK; /* make our grandparent red */ x->up->up->colour = RED; /* right rotate our grandparent */ RB_ENTRY(_right_rotate)(&rbinfo->rb_root, x->up->up); } } else { /* everything here is the same as above, but ** exchanging left for right */ y=x->up->up->left; if (y->colour == RED) { x->up->colour = BLACK; y->colour = BLACK; x->up->up->colour = RED; x=x->up->up; } else { if (x == x->up->left) { x=x->up; RB_ENTRY(_right_rotate)(&rbinfo->rb_root, x); } x->up->colour = BLACK; x->up->up->colour = RED; RB_ENTRY(_left_rotate)(&rbinfo->rb_root, x->up->up); } } } /* Set the root node black */ (rbinfo->rb_root)->colour = BLACK; return(z); } #ifndef no_lookup /* Search for a key according to mode (see redblack.h) */ static struct RB_ENTRY(node) * RB_ENTRY(_lookup)(int mode, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x,*y; int cmp=0; int found=0; y=RBNULL; /* points to the parent of x */ x=rbinfo->rb_root; if (mode==RB_LUFIRST) { /* Keep going left until we hit a NULL */ while(x!=RBNULL) { y=x; x=x->left; } return(y); } else if (mode==RB_LULAST) { /* Keep going right until we hit a NULL */ while(x!=RBNULL) { y=x; x=x->right; } return(y); } /* walk x down the tree */ while(x!=RBNULL && found==0) { y=x; /* printf("key=%s, RB_GET(x, key)=%s\n", key, RB_GET(x, key)); */ #ifndef RB_CUSTOMIZE cmp=RB_CMP(key, RB_GET(x, key), rbinfo->rb_config); #else cmp=RB_CMP(key, RB_GET(x, key)); #endif /* RB_CUSTOMIZE */ if (cmp<0) x=x->left; else if (cmp>0) x=x->right; else found=1; } if (found && (mode==RB_LUEQUAL || mode==RB_LUGTEQ || mode==RB_LULTEQ)) return(x); if (!found && (mode==RB_LUEQUAL || mode==RB_LUNEXT || mode==RB_LUPREV)) return(RBNULL); if (mode==RB_LUGTEQ || (!found && mode==RB_LUGREAT)) { if (cmp>0) return(RB_ENTRY(_successor)(y)); else return(y); } if (mode==RB_LULTEQ || (!found && mode==RB_LULESS)) { if (cmp<0) return(RB_ENTRY(_predecessor)(y)); else return(y); } if (mode==RB_LUNEXT || (found && mode==RB_LUGREAT)) return(RB_ENTRY(_successor)(x)); if (mode==RB_LUPREV || (found && mode==RB_LULESS)) return(RB_ENTRY(_predecessor)(x)); /* Shouldn't get here */ return(RBNULL); } #endif /* no_lookup */ #ifndef no_destroy /* * Destroy all the elements blow us in the tree * only useful as part of a complete tree destroy. */ static void RB_ENTRY(_destroy)(struct RB_ENTRY(node) *x) { if (x!=RBNULL) { if (x->left!=RBNULL) RB_ENTRY(_destroy)(x->left); if (x->right!=RBNULL) RB_ENTRY(_destroy)(x->right); RB_ENTRY(_free)(x); } } #endif /* no_destroy */ /* ** Rotate our tree thus:- ** ** X rb_left_rotate(X)---> Y ** / \ / \ ** A Y <---rb_right_rotate(Y) X C ** / \ / \ ** B C A B ** ** N.B. This does not change the ordering. ** ** We assume that neither X or Y is NULL */ static void RB_ENTRY(_left_rotate)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *x) { struct RB_ENTRY(node) *y; assert(x!=RBNULL); assert(x->right!=RBNULL); y=x->right; /* set Y */ /* Turn Y's left subtree into X's right subtree (move B)*/ x->right = y->left; /* If B is not null, set it's parent to be X */ if (y->left != RBNULL) y->left->up = x; /* Set Y's parent to be what X's parent was */ y->up = x->up; /* if X was the root */ if (x->up == RBNULL) { *rootp=y; } else { /* Set X's parent's left or right pointer to be Y */ if (x == x->up->left) { x->up->left=y; } else { x->up->right=y; } } /* Put X on Y's left */ y->left=x; /* Set X's parent to be Y */ x->up = y; } static void RB_ENTRY(_right_rotate)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *y) { struct RB_ENTRY(node) *x; assert(y!=RBNULL); assert(y->left!=RBNULL); x=y->left; /* set X */ /* Turn X's right subtree into Y's left subtree (move B) */ y->left = x->right; /* If B is not null, set it's parent to be Y */ if (x->right != RBNULL) x->right->up = y; /* Set X's parent to be what Y's parent was */ x->up = y->up; /* if Y was the root */ if (y->up == RBNULL) { *rootp=x; } else { /* Set Y's parent's left or right pointer to be X */ if (y == y->up->left) { y->up->left=x; } else { y->up->right=x; } } /* Put Y on X's right */ x->right=y; /* Set Y's parent to be X */ y->up = x; } /* Return a pointer to the smallest key greater than x */ static struct RB_ENTRY(node) * RB_ENTRY(_successor)(const struct RB_ENTRY(node) *x) { struct RB_ENTRY(node) *y; if (x->right!=RBNULL) { /* If right is not NULL then go right one and ** then keep going left until we find a node with ** no left pointer. */ for (y=x->right; y->left!=RBNULL; y=y->left); } else { /* Go up the tree until we get to a node that is on the ** left of its parent (or the root) and then return the ** parent. */ y=x->up; while(y!=RBNULL && x==y->right) { x=y; y=y->up; } } return(y); } /* Return a pointer to the largest key smaller than x */ static struct RB_ENTRY(node) * RB_ENTRY(_predecessor)(const struct RB_ENTRY(node) *x) { struct RB_ENTRY(node) *y; if (x->left!=RBNULL) { /* If left is not NULL then go left one and ** then keep going right until we find a node with ** no right pointer. */ for (y=x->left; y->right!=RBNULL; y=y->right); } else { /* Go up the tree until we get to a node that is on the ** right of its parent (or the root) and then return the ** parent. */ y=x->up; while(y!=RBNULL && x==y->left) { x=y; y=y->up; } } return(y); } #ifndef no_delete /* Delete the node z, and free up the space */ static void RB_ENTRY(_delete)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *z) { struct RB_ENTRY(node) *x, *y; if (z->left == RBNULL || z->right == RBNULL) y=z; else y=RB_ENTRY(_successor)(z); if (y->left != RBNULL) x=y->left; else x=y->right; x->up = y->up; if (y->up == RBNULL) { *rootp=x; } else { if (y==y->up->left) y->up->left = x; else y->up->right = x; } if (y!=z) { RB_SET(z, key, RB_GET(y, key)); } if (y->colour == BLACK) RB_ENTRY(_delete_fix)(rootp, x); RB_ENTRY(_free)(y); } /* Restore the reb-black properties after a delete */ static void RB_ENTRY(_delete_fix)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *x) { struct RB_ENTRY(node) *w; while (x!=*rootp && x->colour==BLACK) { if (x==x->up->left) { w=x->up->right; if (w->colour==RED) { w->colour=BLACK; x->up->colour=RED; rb_left_rotate(rootp, x->up); w=x->up->right; } if (w->left->colour==BLACK && w->right->colour==BLACK) { w->colour=RED; x=x->up; } else { if (w->right->colour == BLACK) { w->left->colour=BLACK; w->colour=RED; RB_ENTRY(_right_rotate)(rootp, w); w=x->up->right; } w->colour=x->up->colour; x->up->colour = BLACK; w->right->colour = BLACK; RB_ENTRY(_left_rotate)(rootp, x->up); x=*rootp; } } else { w=x->up->left; if (w->colour==RED) { w->colour=BLACK; x->up->colour=RED; RB_ENTRY(_right_rotate)(rootp, x->up); w=x->up->left; } if (w->right->colour==BLACK && w->left->colour==BLACK) { w->colour=RED; x=x->up; } else { if (w->left->colour == BLACK) { w->right->colour=BLACK; w->colour=RED; RB_ENTRY(_left_rotate)(rootp, w); w=x->up->left; } w->colour=x->up->colour; x->up->colour = BLACK; w->left->colour = BLACK; RB_ENTRY(_right_rotate)(rootp, x->up); x=*rootp; } } } x->colour=BLACK; } #endif /* no_delete */ #ifndef no_walk static void RB_ENTRY(_walk)(const struct RB_ENTRY(node) *x, void (*action)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *arg, int level) { if (x==RBNULL) return; if (x->left==RBNULL && x->right==RBNULL) { /* leaf */ (*action)(RB_GET(x, key), leaf, level, arg); } else { (*action)(RB_GET(x, key), preorder, level, arg); RB_ENTRY(_walk)(x->left, action, arg, level+1); (*action)(RB_GET(x, key), postorder, level, arg); RB_ENTRY(_walk)(x->right, action, arg, level+1); (*action)(RB_GET(x, key), endorder, level, arg); } } #endif /* no_walk */ #ifndef no_readlist static RBLIST * RB_ENTRY(_openlist)(const struct RB_ENTRY(node) *rootp) { RBLIST *rblistp; rblistp=(RBLIST *) malloc(sizeof(RBLIST)); if (!rblistp) return(NULL); rblistp->rootp=rootp; rblistp->nextp=rootp; if (rootp!=RBNULL) { while(rblistp->nextp->left!=RBNULL) { rblistp->nextp=rblistp->nextp->left; } } return(rblistp); } static const RB_ENTRY(data_t) * RB_ENTRY(_readlist)(RBLIST *rblistp) { const RB_ENTRY(data_t) *key=NULL; if (rblistp!=NULL && rblistp->nextp!=RBNULL) { key=RB_GET(rblistp->nextp, key); rblistp->nextp=RB_ENTRY(_successor)(rblistp->nextp); } return(key); } static void rb_closelist(RBLIST *rblistp) { if (rblistp) free(rblistp); } #endif /* no_readlist */ #if defined(RB_USE_SBRK) /* Allocate space for our nodes, allowing us to get space from ** sbrk in larger chucks. */ static struct RB_ENTRY(node) *rbfreep=NULL; #define RB_ENTRY(NODE)ALLOC_CHUNK_SIZE 1000 static struct RB_ENTRY(node) * RB_ENTRY(_alloc)() { struct RB_ENTRY(node) *x; int i; if (rbfreep==NULL) { /* must grab some more space */ rbfreep=(struct RB_ENTRY(node) *) sbrk(sizeof(struct RB_ENTRY(node)) * RB_ENTRY(NODE)ALLOC_CHUNK_SIZE); if (rbfreep==(struct RB_ENTRY(node) *) -1) { return(NULL); } /* tie them together in a linked list (use the up pointer) */ for (i=0, x=rbfreep; iup = (x+1); } x->up=NULL; } x=rbfreep; rbfreep = rbfreep->up; #ifdef RB_ALLOC RB_ALLOC(ACCESS(x, key)); #endif /* RB_ALLOC */ return(x); } /* free (dealloc) an RB_ENTRY(node) structure - add it onto the front of the list ** N.B. RB_ENTRY(node) need not have been allocated through rb_alloc() */ static void RB_ENTRY(_free)(struct RB_ENTRY(node) *x) { #ifdef RB_FREE RB_FREE(ACCESS(x, key)); #endif /* RB_FREE */ x->up=rbfreep; rbfreep=x; } #endif #if 0 int RB_ENTRY(_check)(struct RB_ENTRY(node) *rootp) { if (rootp==NULL || rootp==RBNULL) return(0); if (rootp->up!=RBNULL) { fprintf(stderr, "Root up pointer not RBNULL"); dumptree(rootp, 0); return(1); } if (RB_ENTRY(_check)1(rootp)) { RB_ENTRY(dumptree)(rootp, 0); return(1); } if (RB_ENTRY(count_black)(rootp)==-1) { RB_ENTRY(dumptree)(rootp, 0); return(-1); } return(0); } int RB_ENTRY(_check1)(struct RB_ENTRY(node) *x) { if (x->left==NULL || x->right==NULL) { fprintf(stderr, "Left or right is NULL"); return(1); } if (x->colour==RED) { if (x->left->colour!=BLACK && x->right->colour!=BLACK) { fprintf(stderr, "Children of red node not both black, x=%ld", x); return(1); } } if (x->left != RBNULL) { if (x->left->up != x) { fprintf(stderr, "x->left->up != x, x=%ld", x); return(1); } if (rb_check1(x->left)) return(1); } if (x->right != RBNULL) { if (x->right->up != x) { fprintf(stderr, "x->right->up != x, x=%ld", x); return(1); } if (rb_check1(x->right)) return(1); } return(0); } RB_ENTRY(count_black)(struct RB_ENTRY(node) *x) { int nleft, nright; if (x==RBNULL) return(1); nleft=RB_ENTRY(count_black)(x->left); nright=RB_ENTRY(count_black)(x->right); if (nleft==-1 || nright==-1) return(-1); if (nleft != nright) { fprintf(stderr, "Black count not equal on left & right, x=%ld", x); return(-1); } if (x->colour == BLACK) { nleft++; } return(nleft); } RB_ENTRY(dumptree)(struct RB_ENTRY(node) *x, int n) { char *prkey(); if (x!=NULL && x!=RBNULL) { n++; fprintf(stderr, "Tree: %*s %ld: left=%ld, right=%ld, colour=%s, key=%s", n, "", x, x->left, x->right, (x->colour==BLACK) ? "BLACK" : "RED", prkey(RB_GET(x, key))); RB_ENTRY(dumptree)(x->left, n); RB_ENTRY(dumptree)(x->right, n); } } #endif /* * $Log: redblack.c,v $ * Revision 1.9 2003/10/24 01:31:21 damo * Patches from Eric Raymond: %prefix is implemented.  Various other small * changes avoid stepping on global namespaces and improve the documentation. * * Revision 1.8 2002/08/26 05:33:47 damo * Some minor fixes:- * Stopped ./configure warning about stuff being in the wrong order * Fixed compiler warning about const (not sure about this) * Changed directory of redblack.c in documentation * * Revision 1.7 2002/08/26 03:11:40 damo * Fixed up a bunch of compiler warnings when compiling example4 * * Tidies up the Makefile.am & Specfile. * * Renamed redblack to rbgen * * Revision 1.6 2002/08/26 01:03:35 damo * Patch from Eric Raymond to change the way the library is used:- * * Eric's idea is to convert libredblack into a piece of in-line code * generated by another program. This should be faster, smaller and easier * to use. * * This is the first check-in of his code before I start futzing with it! * * Revision 1.5 2002/01/30 07:54:53 damo * Fixed up the libtool versioning stuff (finally) * Fixed bug 500600 (not detecting a NULL return from malloc) * Fixed bug 509485 (no longer needs search.h) * Cleaned up debugging section * Allow multiple inclusions of redblack.h * Thanks to Matthias Andree for reporting (and fixing) these * * Revision 1.4 2000/06/06 14:43:43 damo * Added all the rbwalk & rbopenlist stuff. Fixed up malloc instead of sbrk. * Added two new examples * * Revision 1.3 2000/05/24 06:45:27 damo * Converted everything over to using const * Added a new example1.c file to demonstrate the worst case scenario * Minor fixups of the spec file * * Revision 1.2 2000/05/24 06:17:10 damo * Fixed up the License (now the LGPL) * * Revision 1.1 2000/05/24 04:15:53 damo * Initial import of files. Versions are now all over the place. Oh well * */ inotify-tools-3.22.6.0/libinotifytools/src/redblack.h000066400000000000000000000136441424760767400225520ustar00rootroot00000000000000/* * RCS $Id: redblack.h,v 1.9 2003/10/24 01:31:21 damo Exp $ */ /* Redblack balanced tree algorithm Copyright (C) Damian Ivereigh 2000 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for details. 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 Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Header file for redblack.c, should be included by any code that ** uses redblack.c since it defines the functions */ /* Stop multiple includes */ #ifndef _REDBLACK_H #define _REDBLACK_H #ifndef RB_CUSTOMIZE /* * Without customization, the data member in the tree nodes is a void * pointer, and you need to pass in a comparison function to be * applied at runtime. With customization, you specify the data type * as the macro RB_ENTRY(data_t) (has to be a macro because compilers * gag on typdef void) and the name of the compare function as the * value of the macro RB_CMP. Because the comparison function is * compiled in, RB_CMP only needs to take two arguments. If your * content type is not a pointer, define INLINE to get direct access. */ #define rbdata_t void #define RB_CMP(s, t, e) (*rbinfo->rb_cmp)(s, t, e) #undef RB_INLINE #define RB_ENTRY(name) rb##name #endif /* RB_CUSTOMIZE */ #ifndef RB_STATIC #define RB_STATIC #endif /* Modes for rblookup */ #define RB_NONE -1 /* None of those below */ #define RB_LUEQUAL 0 /* Only exact match */ #define RB_LUGTEQ 1 /* Exact match or greater */ #define RB_LULTEQ 2 /* Exact match or less */ #define RB_LULESS 3 /* Less than key (not equal to) */ #define RB_LUGREAT 4 /* Greater than key (not equal to) */ #define RB_LUNEXT 5 /* Next key after current */ #define RB_LUPREV 6 /* Prev key before current */ #define RB_LUFIRST 7 /* First key in index */ #define RB_LULAST 8 /* Last key in index */ /* For rbwalk - pinched from search.h */ typedef enum { preorder, postorder, endorder, leaf } VISIT; struct RB_ENTRY(lists) { const struct RB_ENTRY(node) *rootp; const struct RB_ENTRY(node) *nextp; }; #define RBLIST struct RB_ENTRY(lists) struct RB_ENTRY(tree) { #ifndef RB_CUSTOMIZE /* comparison routine */ int (*rb_cmp)(const void *, const void *, const void *); /* config data to be passed to rb_cmp */ const void *rb_config; /* root of tree */ #endif /* RB_CUSTOMIZE */ struct RB_ENTRY(node) *rb_root; }; #ifndef RB_CUSTOMIZE RB_STATIC struct RB_ENTRY(tree) *rbinit(int (*)(const void *, const void *, const void *), const void *); #else RB_STATIC struct RB_ENTRY(tree) *RB_ENTRY(init)(void); #endif /* RB_CUSTOMIZE */ #ifndef no_delete RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(delete)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_find RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(find)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_lookup RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(lookup)(int, const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_search RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(search)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_destroy RB_STATIC void RB_ENTRY(destroy)(struct RB_ENTRY(tree) *); #endif #ifndef no_walk RB_STATIC void RB_ENTRY(walk)(const struct RB_ENTRY(tree) *, void (*)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *); #endif #ifndef no_readlist RB_STATIC RBLIST *RB_ENTRY(openlist)(const struct RB_ENTRY(tree) *); RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(readlist)(RBLIST *); RB_STATIC void RB_ENTRY(closelist)(RBLIST *); #endif /* Some useful macros */ #define rbmin(rbinfo) RB_ENTRY(lookup)(RB_LUFIRST, NULL, (rbinfo)) #define rbmax(rbinfo) RB_ENTRY(lookup)(RB_LULAST, NULL, (rbinfo)) #endif /* _REDBLACK_H */ /* * * $Log: redblack.h,v $ * Revision 1.9 2003/10/24 01:31:21 damo * Patches from Eric Raymond: %prefix is implemented.  Various other small * changes avoid stepping on global namespaces and improve the documentation. * * Revision 1.8 2003/10/23 04:18:47 damo * Fixed up the rbgen stuff ready for the 1.3 release * * Revision 1.7 2002/08/26 03:11:40 damo * Fixed up a bunch of compiler warnings when compiling example4 * * Tidies up the Makefile.am & Specfile. * * Renamed redblack to rbgen * * Revision 1.6 2002/08/26 01:03:35 damo * Patch from Eric Raymond to change the way the library is used:- * * Eric's idea is to convert libredblack into a piece of in-line code * generated by another program. This should be faster, smaller and easier * to use. * * This is the first check-in of his code before I start futzing with it! * * Revision 1.5 2002/01/30 07:54:53 damo * Fixed up the libtool versioning stuff (finally) * Fixed bug 500600 (not detecting a NULL return from malloc) * Fixed bug 509485 (no longer needs search.h) * Cleaned up debugging section * Allow multiple inclusions of redblack.h * Thanks to Matthias Andree for reporting (and fixing) these * * Revision 1.4 2000/06/06 14:43:43 damo * Added all the rbwalk & rbopenlist stuff. Fixed up malloc instead of sbrk. * Added two new examples * * Revision 1.3 2000/05/24 06:45:27 damo * Converted everything over to using const * Added a new example1.c file to demonstrate the worst case scenario * Minor fixups of the spec file * * Revision 1.2 2000/05/24 06:17:10 damo * Fixed up the License (now the LGPL) * * Revision 1.1 2000/05/24 04:15:53 damo * Initial import of files. Versions are now all over the place. Oh well * */ inotify-tools-3.22.6.0/libinotifytools/src/stats.c000066400000000000000000000152601424760767400221300ustar00rootroot00000000000000#include "stats.h" static unsigned num_access; static unsigned num_modify; static unsigned num_attrib; static unsigned num_close_nowrite; static unsigned num_close_write; static unsigned num_open; static unsigned num_move_self; static unsigned num_moved_to; static unsigned num_moved_from; static unsigned num_create; static unsigned num_delete; static unsigned num_delete_self; static unsigned num_unmount; static unsigned num_total; /** * @internal */ void empty_stats(const void *nodep, const VISIT which, const int depth, void *arg) { if (which != endorder && which != leaf) return; watch *w = (watch*)nodep; w->hit_access = 0; w->hit_modify = 0; w->hit_attrib = 0; w->hit_close_nowrite = 0; w->hit_close_write = 0; w->hit_open = 0; w->hit_move_self = 0; w->hit_moved_from = 0; w->hit_moved_to = 0; w->hit_create = 0; w->hit_delete = 0; w->hit_delete_self = 0; w->hit_unmount = 0; w->hit_total = 0; } /** * @internal */ void record_stats( struct inotify_event const * event ) { if (!event) return; watch *w = watch_from_wd(event->wd); if (!w) return; if ( IN_ACCESS & event->mask ) { ++w->hit_access; ++num_access; } if ( IN_MODIFY & event->mask ) { ++w->hit_modify; ++num_modify; } if ( IN_ATTRIB & event->mask ) { ++w->hit_attrib; ++num_attrib; } if ( IN_CLOSE_WRITE & event->mask ) { ++w->hit_close_write; ++num_close_write; } if ( IN_CLOSE_NOWRITE & event->mask ) { ++w->hit_close_nowrite; ++num_close_nowrite; } if ( IN_OPEN & event->mask ) { ++w->hit_open; ++num_open; } if ( IN_MOVED_FROM & event->mask ) { ++w->hit_moved_from; ++num_moved_from; } if ( IN_MOVED_TO & event->mask ) { ++w->hit_moved_to; ++num_moved_to; } if ( IN_CREATE & event->mask ) { ++w->hit_create; ++num_create; } if ( IN_DELETE & event->mask ) { ++w->hit_delete; ++num_delete; } if ( IN_DELETE_SELF & event->mask ) { ++w->hit_delete_self; ++num_delete_self; } if ( IN_UNMOUNT & event->mask ) { ++w->hit_unmount; ++num_unmount; } if ( IN_MOVE_SELF & event->mask ) { ++w->hit_move_self; ++num_move_self; } ++w->hit_total; ++num_total; } unsigned int *stat_ptr(watch *w, int event) { if ( IN_ACCESS == event ) return &w->hit_access; if ( IN_MODIFY == event ) return &w->hit_modify; if ( IN_ATTRIB == event ) return &w->hit_attrib; if ( IN_CLOSE_WRITE == event ) return &w->hit_close_write; if ( IN_CLOSE_NOWRITE == event ) return &w->hit_close_nowrite; if ( IN_OPEN == event ) return &w->hit_open; if ( IN_MOVED_FROM == event ) return &w->hit_moved_from; if ( IN_MOVED_TO == event ) return &w->hit_moved_to; if ( IN_CREATE == event ) return &w->hit_create; if ( IN_DELETE == event ) return &w->hit_delete; if ( IN_DELETE_SELF == event ) return &w->hit_delete_self; if ( IN_UNMOUNT == event ) return &w->hit_unmount; if ( IN_MOVE_SELF == event ) return &w->hit_move_self; if ( 0 == event ) return &w->hit_total; return 0; } /** * Get statistics by a particular watch descriptor. * * inotifytools_initialize_stats() must be called before this function can * be used. * * @param wd watch descriptor to get stats for. * * @param event a single inotify event to get statistics for, or 0 for event * total. See section \ref events. * * @return the number of times the event specified by @a event has occurred on * the watch descriptor specified by @a wd since stats collection was * enabled, or -1 if @a event or @a wd are invalid. */ int inotifytools_get_stat_by_wd( int wd, int event ) { if (!collect_stats) return -1; watch *w = watch_from_wd(wd); if (!w) return -1; unsigned int *i = stat_ptr(w, event); if (!i) return -1; return *i; } /** * Get statistics aggregated across all watches. * * inotifytools_initialize_stats() must be called before this function can * be used. * * @param event a single inotify event to get statistics for, or 0 for event * total. See section \ref events. * * @return the number of times the event specified by @a event has occurred over * all watches since stats collection was enabled, or -1 if @a event * is not a valid event. */ int inotifytools_get_stat_total( int event ) { if (!collect_stats) return -1; if ( IN_ACCESS == event ) return num_access; if ( IN_MODIFY == event ) return num_modify; if ( IN_ATTRIB == event ) return num_attrib; if ( IN_CLOSE_WRITE == event ) return num_close_write; if ( IN_CLOSE_NOWRITE == event ) return num_close_nowrite; if ( IN_OPEN == event ) return num_open; if ( IN_MOVED_FROM == event ) return num_moved_from; if ( IN_MOVED_TO == event ) return num_moved_to; if ( IN_CREATE == event ) return num_create; if ( IN_DELETE == event ) return num_delete; if ( IN_DELETE_SELF == event ) return num_delete_self; if ( IN_UNMOUNT == event ) return num_unmount; if ( IN_MOVE_SELF == event ) return num_move_self; if ( 0 == event ) return num_total; return -1; } /** * Get statistics by a particular filename. * * inotifytools_initialize_stats() must be called before this function can * be used. * * @param filename name of file to get stats for. * * @param event a single inotify event to get statistics for, or 0 for event * total. See section \ref events. * * @return the number of times the event specified by @a event has occurred on * the file specified by @a filename since stats collection was * enabled, or -1 if the file is not being watched or @a event is * invalid. * * @note The filename specified must always be the original name used to * establish the watch. */ int inotifytools_get_stat_by_filename( char const * filename, int event ) { return inotifytools_get_stat_by_wd( inotifytools_wd_from_filename( filename ), event ); } /** * Initialize or reset statistics. * * inotifytools_initialize() must be called before this function can * be used. * * When this function is called, all subsequent events will be tallied. * Statistics can then be obtained via the @a inotifytools_get_stat_* functions. * * After the first call, subsequent calls to this function will reset the * event tallies to 0. */ void inotifytools_initialize_stats() { niceassert( init, "inotifytools_initialize not called yet" ); // if already collecting stats, reset stats if (collect_stats) { rbwalk(tree_wd, empty_stats, 0); } num_access = 0; num_modify = 0; num_attrib = 0; num_close_nowrite = 0; num_close_write = 0; num_open = 0; num_move_self = 0; num_moved_from = 0; num_moved_to = 0; num_create = 0; num_delete = 0; num_delete_self = 0; num_unmount = 0; num_total = 0; collect_stats = 1; } inotify-tools-3.22.6.0/libinotifytools/src/stats.h000066400000000000000000000004661424760767400221370ustar00rootroot00000000000000#ifndef STATS_H #define STATS_H #include "inotifytools/inotify.h" #include "inotifytools/inotifytools.h" #include "inotifytools_p.h" extern int collect_stats; void record_stats( struct inotify_event const * event ); unsigned int *stat_ptr(watch *w, int event); watch *watch_from_wd(int wd); #endif // STATS_H inotify-tools-3.22.6.0/libinotifytools/src/test.c000066400000000000000000000312111424760767400217430ustar00rootroot00000000000000#include "inotifytools/inotify.h" #include "inotifytools/inotifytools.h" #include "../../config.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MCHECK_H #include #endif #define verify(A) \ if (!_verify((int)(A), #A, __FILE__, __LINE__, __PRETTY_FUNCTION__)) \ return; #define verify2(A, B) \ if (!_verify((int)(A), B, __FILE__, __LINE__, __PRETTY_FUNCTION__)) \ return; #define compare(A, B) \ if (!_compare((uintptr_t)(A), (int)(B), #A, #B, __FILE__, __LINE__, \ __PRETTY_FUNCTION__)) \ return; #define succeed() \ do { \ ++tests_succeeded; \ /*fprintf(stderr, "%s:%d Test '%s' passed", file, line, func);*/ \ /*fprintf(stderr, "\n");*/ \ } while (0) #define fail(...) \ do { \ ++tests_failed; \ fprintf(stderr, "%s:%d Test '%s' failed: ", file, line, func); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n"); \ if (inotifytools_error() != 0) { \ fprintf(stderr, "inotifytools_error() returns %d (%s)\n", \ inotifytools_error(), strerror(inotifytools_error())); \ } \ } while (0) #define TEST_DIR "/tmp/inotifytools_test" #define INFO(...) \ do { \ printf("%s: ", __PRETTY_FUNCTION__); \ printf(__VA_ARGS__); \ } while (0) #define ENTER INFO("test begin\n"); #define EXIT INFO("test end\n"); static int tests_failed; static int tests_succeeded; int _verify(int cond, char const *teststr, char const *file, int line, char const *func) { if (cond) { succeed(); return cond; } fail("Verification failed (%s)", teststr); return cond; } int _compare(int value, int expval, char const *act_str, char const *exp_str, char const *file, int line, char const *func) { if (value == expval) { succeed(); return 1; } fail("Actual (%s): %d, Expected (%s): %d", act_str, value, exp_str, expval); return 0; } void event_to_str() { ENTER verify2( !strcmp(inotifytools_event_to_str(IN_OPEN | IN_MODIFY | IN_ACCESS), "OPEN,MODIFY,ACCESS") || !strcmp(inotifytools_event_to_str(IN_OPEN | IN_MODIFY | IN_ACCESS), "OPEN,ACCESS,MODIFY") || !strcmp(inotifytools_event_to_str(IN_OPEN | IN_MODIFY | IN_ACCESS), "ACCESS,OPEN,MODIFY") || !strcmp(inotifytools_event_to_str(IN_OPEN | IN_MODIFY | IN_ACCESS), "MODIFY,OPEN,ACCESS") || !strcmp(inotifytools_event_to_str(IN_OPEN | IN_MODIFY | IN_ACCESS), "ACCESS,MODIFY,OPEN") || !strcmp(inotifytools_event_to_str(IN_OPEN | IN_MODIFY | IN_ACCESS), "MODIFY,ACCESS,OPEN"), inotifytools_event_to_str(IN_OPEN | IN_MODIFY | IN_ACCESS)); EXIT } void event_to_str_sep() { ENTER verify2( !strcmp( inotifytools_event_to_str_sep(IN_OPEN | IN_MODIFY | IN_ACCESS, ':'), "OPEN:MODIFY:ACCESS") || !strcmp(inotifytools_event_to_str_sep( IN_OPEN | IN_MODIFY | IN_ACCESS, ':'), "OPEN:ACCESS:MODIFY") || !strcmp(inotifytools_event_to_str_sep( IN_OPEN | IN_MODIFY | IN_ACCESS, ':'), "ACCESS:OPEN:MODIFY") || !strcmp(inotifytools_event_to_str_sep( IN_OPEN | IN_MODIFY | IN_ACCESS, ':'), "MODIFY:OPEN:ACCESS") || !strcmp(inotifytools_event_to_str_sep( IN_OPEN | IN_MODIFY | IN_ACCESS, ':'), "ACCESS:MODIFY:OPEN") || !strcmp(inotifytools_event_to_str_sep( IN_OPEN | IN_MODIFY | IN_ACCESS, ':'), "MODIFY:ACCESS:OPEN"), inotifytools_event_to_str_sep(IN_OPEN | IN_MODIFY | IN_ACCESS, ':')); EXIT } void str_to_event() { ENTER compare(inotifytools_str_to_event("open,modify,access"), IN_OPEN | IN_MODIFY | IN_ACCESS); compare(inotifytools_str_to_event(",open,modify,access"), 0); compare(inotifytools_str_to_event("open,modify,access,"), 0); compare(inotifytools_str_to_event("open,modify,,access,close"), 0); compare(inotifytools_str_to_event("open,mod,access,close"), -1); compare(inotifytools_str_to_event("mod"), -1); compare(inotifytools_str_to_event(","), 0); compare(inotifytools_str_to_event(",,"), 0); compare(inotifytools_str_to_event("open"), IN_OPEN); compare(inotifytools_str_to_event("close"), IN_CLOSE); compare(inotifytools_str_to_event(",close"), 0); compare(inotifytools_str_to_event(",,close"), 0); compare(inotifytools_str_to_event("close,"), 0); compare(inotifytools_str_to_event("close,,"), 0); EXIT } void str_to_event_sep() { ENTER compare(inotifytools_str_to_event_sep("open:modify:access", ':'), IN_OPEN | IN_MODIFY | IN_ACCESS); compare(inotifytools_str_to_event_sep("open,modify,access", ','), IN_OPEN | IN_MODIFY | IN_ACCESS); compare(inotifytools_str_to_event_sep(":open:modify:access", ':'), 0); compare(inotifytools_str_to_event_sep("open:modify:access:", ':'), 0); compare(inotifytools_str_to_event_sep("open:modify::access:close", ':'), 0); compare(inotifytools_str_to_event_sep("open:mod:access:close", ':'), -1); compare(inotifytools_str_to_event_sep("mod", ':'), -1); compare(inotifytools_str_to_event_sep(":", ':'), 0); compare(inotifytools_str_to_event_sep("::", ':'), 0); compare(inotifytools_str_to_event_sep("open", ':'), IN_OPEN); compare(inotifytools_str_to_event_sep("close", ':'), IN_CLOSE); compare(inotifytools_str_to_event_sep(":close", ':'), 0); compare(inotifytools_str_to_event_sep("::close", ':'), 0); compare(inotifytools_str_to_event_sep("close:", ':'), 0); compare(inotifytools_str_to_event_sep("close::", ':'), 0); compare(inotifytools_str_to_event_sep("open:modify:access", ','), -1); compare(inotifytools_str_to_event_sep("open:modify:access", 'o'), -1); EXIT } void basic_watch_info() { ENTER verify(inotifytools_initialize()); verify(inotifytools_watch_file("/", IN_CLOSE)); compare(inotifytools_wd_from_filename("/"), 1); compare(inotifytools_wd_from_filename("foobar"), -1); verify(!strcmp(inotifytools_filename_from_wd(1), "/")); verify(inotifytools_remove_watch_by_filename("/")); compare(inotifytools_wd_from_filename("/"), -1); compare(*inotifytools_filename_from_wd(1), 0); verify(inotifytools_watch_file("/", IN_CLOSE)); compare(inotifytools_wd_from_filename("/"), 2); compare(inotifytools_wd_from_filename("foobar"), -1); verify(!strcmp(inotifytools_filename_from_wd(2), "/")); verify(inotifytools_remove_watch_by_wd(2)); compare(inotifytools_wd_from_filename("/"), -1); compare(*inotifytools_filename_from_wd(2), 0); EXIT } void tst_inotifytools_snprintf() { ENTER verify((0 == mkdir(TEST_DIR, 0700)) || (EEXIST == errno)); verify(inotifytools_initialize()); verify(inotifytools_watch_file(TEST_DIR, IN_CLOSE)); #define RESET \ do { \ memset(out.buf, 0, MAX_STRLEN); \ memset(test_event, 0, sizeof(struct inotify_event)); \ test_event->wd = inotifytools_wd_from_filename(TEST_DIR "/"); \ verify(test_event->wd >= 0); \ inotifytools_set_printf_timefmt(0); \ } while (0) struct nstring out; char event_buf[4096]; struct inotify_event *test_event = (struct inotify_event *)event_buf; RESET; test_event->mask = IN_ACCESS; inotifytools_snprintf(&out, MAX_STRLEN, test_event, "Event %e %.e on %w %f %T"); verify2(!strcmp(out.buf, "Event ACCESS ACCESS on " TEST_DIR "/ "), out.buf); RESET; test_event->mask = IN_ACCESS | IN_DELETE; inotifytools_snprintf(&out, MAX_STRLEN, test_event, "Event %e %.e on %w %f %T"); verify2( !strcmp(out.buf, "Event ACCESS,DELETE ACCESS.DELETE on " TEST_DIR "/ ") || !strcmp(out.buf, "Event DELETE,ACCESS DELETE.ACCESS on " TEST_DIR "/ "), out.buf); RESET; test_event->mask = IN_MODIFY; inotifytools_snprintf(&out, 10, test_event, "Event %e %.e on %w %f %T"); verify2(!strcmp(out.buf, "Event MODI"), out.buf); RESET; test_event->mask = IN_ACCESS; strncpy(test_event->name, "my_great_file", 14); test_event->len = strlen(test_event->name) + 1; inotifytools_snprintf(&out, MAX_STRLEN, test_event, "Event %e %.e on %w %f %T"); verify2(!strcmp(out.buf, "Event ACCESS ACCESS on " TEST_DIR "/ my_great_file "), out.buf); RESET; test_event->mask = IN_ACCESS; inotifytools_set_printf_timefmt("%D%% %H:%M"); { char expected[MAX_STRLEN]; char timestr[512]; time_t now = time(0); strftime(timestr, 512, "%D%% %H:%M", localtime(&now)); snprintf(expected, MAX_STRLEN, "Event ACCESS ACCESS on %s/ %s", TEST_DIR, timestr); inotifytools_snprintf(&out, MAX_STRLEN, test_event, "Event %e %.e on %w %f %T"); verify2(!strcmp(out.buf, expected), out.buf); } EXIT } void watch_limit() { ENTER verify((0 == mkdir(TEST_DIR, 0700)) || (EEXIST == errno)); INFO("Warning, this test may take a while\n"); #define INNER_LIMIT 16000 #define OUTER_LIMIT 5 verify(inotifytools_initialize()); inotifytools_initialize_stats(); for (int j = 0; j < OUTER_LIMIT; ++j) { char fn[1024]; int max = 0; for (int i = 0; i < INNER_LIMIT; ++i) { snprintf(fn, 1023, "%s/%d", TEST_DIR, i); int fd = creat(fn, 0700); verify(-1 != fd); verify(0 == close(fd)); int ret = inotifytools_watch_file(fn, IN_ALL_EVENTS); verify(ret || inotifytools_error() == ENOSPC); if (ret) { max = i + 1; int wd = inotifytools_wd_from_filename(fn); verify(wd > 0); verify(!strcmp(fn, inotifytools_filename_from_wd(wd))); } } compare(inotifytools_get_num_watches(), max); for (int i = 0; i < max; ++i) { snprintf(fn, 1023, "%s/%d", TEST_DIR, i); verify(inotifytools_remove_watch_by_filename(fn)); } } EXIT } void cleanup() { compare(system("rm -rf " TEST_DIR), 0); inotifytools_cleanup(); } int main() { tests_failed = 0; tests_succeeded = 0; #ifdef HAVE_MCHECK_H char *mtrace_name = getenv("MALLOC_TRACE"); if (mtrace_name) { printf("malloc trace might be written to %s\n", mtrace_name); } else { printf("If you want to do a malloc trace, set MALLOC_TRACE to " "a path for logging.\n"); } mtrace(); #endif cleanup(); event_to_str(); cleanup(); event_to_str_sep(); cleanup(); str_to_event(); cleanup(); str_to_event_sep(); cleanup(); basic_watch_info(); cleanup(); watch_limit(); cleanup(); tst_inotifytools_snprintf(); cleanup(); printf("Out of %d tests, %d succeeded and %d failed.\n", tests_failed + tests_succeeded, tests_succeeded, tests_failed); if (tests_failed) return -tests_failed; } inotify-tools-3.22.6.0/m4/000077500000000000000000000000001424760767400151225ustar00rootroot00000000000000inotify-tools-3.22.6.0/m4/.gitignore000066400000000000000000000000051424760767400171050ustar00rootroot00000000000000*.m4 inotify-tools-3.22.6.0/man/000077500000000000000000000000001424760767400153555ustar00rootroot00000000000000inotify-tools-3.22.6.0/man/Makefile.am000066400000000000000000000001141424760767400174050ustar00rootroot00000000000000dist_man_MANS = inotifywait.1 inotifywatch.1 fsnotifywait.1 fsnotifywatch.1 inotify-tools-3.22.6.0/man/fsnotifywait.1.in000066400000000000000000000000271424760767400205710ustar00rootroot00000000000000.so man1/inotifywait.1 inotify-tools-3.22.6.0/man/fsnotifywatch.1.in000066400000000000000000000000301424760767400207250ustar00rootroot00000000000000.so man1/inotifywatch.1 inotify-tools-3.22.6.0/man/inotifywait.1.in000066400000000000000000000303101424760767400204070ustar00rootroot00000000000000.TH inotifywait 1 "@MAN_DATE@" "inotifywait @MAN_PACKAGE_VERSION@" .SH NAME inotifywait, fsnotifywait \- wait for changes to files using inotify or fanotify .SH SYNOPSIS .B inotifywait .RB [ \-hcmrPq ] .RB [ \-e ] .RB [ \-t ] .RB [ \-\-format ] .RB [ \-\-timefmt ] [ ... ] .B fsnotifywait .RB [ \-hcmrPqIFS ] .RB [ \-e ] .RB [ \-t ] .RB [ \-\-format ] .RB [ \-\-timefmt ] [ ... ] .SH DESCRIPTION .B inotifywait efficiently waits for changes to files using Linux's .BR inotify(7) interface. It is suitable for waiting for changes to files from shell scripts. It can either exit once an event occurs, or continually execute and output events as they occur. .B fsnotifywait is similar to .B inotifywait but it is using Linux's .BR fanotify(7) interface by default. If explicitly specified, it uses the .BR inotify(7) interface. .SH OUTPUT .B inotifywait and .B fsnotifywait will output diagnostic information on standard error and event information on standard output. The event output can be configured, but by default it consists of lines of the following form: .B watched_filename EVENT_NAMES event_filename .TP .B watched_filename is the name of the file on which the event occurred. If the file is a directory, a trailing slash is output. .TP .B EVENT_NAMES are the names of the inotify events which occurred, separated by commas. .TP .B event_filename is output only when the event occurred on a directory, and in this case the name of the file within the directory which caused this event is output. By default, any special characters in filenames are not escaped in any way. This can make the output of inotifywait difficult to parse in awk scripts or similar. The .B \-\-csv and .B \-\-format options will be helpful in this case. .SH OPTIONS .TP .B \-h, \-\-help Output some helpful usage information. .TP .B @ When watching a directory tree recursively, exclude the specified file from being watched. The file must be specified with a relative or absolute path according to whether a relative or absolute path is given for watched directories. If a specific path is explicitly both included and excluded, it will always be watched. .B Note: If you need to watch a directory or file whose name starts with @, give the absolute path. .TP .B \-\-fromfile Read filenames to watch or exclude from a file, one filename per line. If filenames begin with @ they are excluded as described above. If is `-', filenames are read from standard input. Use this option if you need to watch too many files to pass in as command line arguments. .TP .B \-m, \-\-monitor Instead of exiting after receiving a single event, execute indefinitely. The default behaviour is to exit after the first event occurs. .TP .B \-d, \-\-daemon Same as \-\-monitor, except run in the background logging events to a file that must be specified by \-\-outfile. Implies \-\-syslog. .TP .B \-o, \-\-outfile Output events to rather than stdout. .TP .B \-s, \-\-syslog Output errors to .BR syslog(3) system log module rather than stderr. .TP .B \-P, \-\-no\-dereference Do not follow symlinks. .TP .B \-r, \-\-recursive Watch all subdirectories of any directories passed as arguments. Watches will be set up recursively to an unlimited depth. Symbolic links are not traversed. Newly created subdirectories will also be watched. .B Warning: If you use this option while watching the root directory of a large tree, it may take quite a while until all inotify watches are established, and events will not be received in this time. Also, since one inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user will be reached. The default maximum is 8192; it can be increased by writing to .BR /proc/sys/fs/inotify/max_user_watches . .TP .B \-q, \-\-quiet If specified once, the program will be less verbose. Specifically, it will not state when it has completed establishing all inotify watches. If specified twice, the program will output nothing at all, except in the case of fatal errors. .TP .B \-\-exclude Do not process any events for the subset of files whose filenames match the specified POSIX regular expression, case sensitive. .TP .B \-\-excludei Do not process any events for the subset of files whose filenames match the specified POSIX regular expression, case insensitive. .TP .B \-\-include Process events only for the subset of files whose filenames match the specified POSIX regular expression, case sensitive. .TP .B \-\-includei Process events only for the subset of files whose filenames match the specified POSIX regular expression, case insensitive. .TP .B \-t , \-\-timeout Exit if an appropriate event has not occurred within seconds. If is zero (the default), wait indefinitely for an event. .TP .B \-e , \-\-event Listen for specific event(s) only. The events which can be listened for are listed in the .B EVENTS section. This option can be specified more than once. If omitted, all events are listened for. .TP .B \-c, \-\-csv Output in CSV (comma-separated values) format. This is useful when filenames may contain spaces, since in this case it is not safe to simply split the output at each space character. .TP .B \-\-timefmt Set a time format string as accepted by strftime(3) for use with the `%T' conversion in the \-\-format option. .TP .B \-\-no-newline Don't print newline symbol after user-specified format in the \-\-format option. .TP .B \-\-format Output in a user-specified format, using printf-like syntax. The event strings output are limited to around 4000 characters and will be truncated to this length. The following conversions are supported: .TP %w This will be replaced with the name of the Watched file on which an event occurred. When an event occurs within a Watched directory or when watching with fanotify, this will be replaced with the name of the Directory in which the event occurred. .TP %f When an event occurs within a Watched directory or when watching with fanotify, this will be replaced with the name of the File which caused the event to occur. Otherwise, this will be replaced with an empty string. .TP %e Replaced with the Event(s) which occurred, comma-separated. .TP %Xe Replaced with the Event(s) which occurred, separated by whichever character is in the place of `X'. .TP %T Replaced with the current Time in the format specified by the \-\-timefmt option, which should be a format string suitable for passing to strftime(3). .TP %0 Replaced with NUL. .TP %n Replaced with Line Feed. .\" .SS fsnotifywait .PP The following additional options are available: .TP .B \-I, \-\-inotify Watch using inotify. .TP .B \-F, \-\-fanotify Watch using fanotify (default). fanotify support for reporting events with inotify compatible information was added in kernel v5.9. With older kernels the command will fail. As of kernel v5.12, fanotify requires admin privileges. .TP .B \-S, \-\-filesystem Watch entire filesystem of any directories passed as arguments using fanotify. .SH "EXIT STATUS" .TP .B 0 The program executed successfully, and an event occurred which was being listened for. .TP .B 1 An error occurred in execution of the program, or an event occurred which was not being listened for. The latter generally occurs if something happens which forcibly removes the inotify watch, such as a watched file being deleted or the filesystem containing a watched file being unmounted. .TP .B 2 The .B \-t option was used and an event did not occur in the specified interval of time. .SH EVENTS The following events are valid for use with the .B \-e option: .TP .B access A watched file or a file within a watched directory was read from. .TP .B modify A watched file or a file within a watched directory was written to. .TP .B attrib The metadata of a watched file or a file within a watched directory was modified. This includes timestamps, file permissions, extended attributes etc. .TP .B close_write A watched file or a file within a watched directory was closed, after being opened in writable mode. This does not necessarily imply the file was written to. .TP .B close_nowrite A watched file or a file within a watched directory was closed, after being opened in read-only mode. .TP .B close A watched file or a file within a watched directory was closed, regardless of how it was opened. Note that this is actually implemented simply by listening for both .B close_write and .B close_nowrite, hence all close events received will be output as one of these, not .B CLOSE. .TP .B open A watched file or a file within a watched directory was opened. .TP .B moved_to A file or directory was moved into a watched directory. This event occurs even if the file is simply moved from and to the same directory. .TP .B moved_from A file or directory was moved from a watched directory. This event occurs even if the file is simply moved from and to the same directory. .TP .B move A file or directory was moved from or to a watched directory. Note that this is actually implemented simply by listening for both .B moved_to and .B moved_from, hence all move events received will be output as one or both of these, not .B MOVE. .TP .B move_self A watched file or directory was moved. After this event, the file or directory is no longer being watched. .TP .B create A file or directory was created within a watched directory. .TP .B delete A file or directory within a watched directory was deleted. .TP .B delete_self A watched file or directory was deleted. After this event the file or directory is no longer being watched. Note that this event can occur even if it is not explicitly being listened for. .TP .B unmount The filesystem on which a watched file or directory resides was unmounted. After this event the file or directory is no longer being watched. Note that this event can occur even if it is not explicitly being listened to. .SH EXAMPLES .SS Example 1 Running inotifywait at the command-line to wait for any file in the `test' directory to be accessed. After running inotifywait, `cat test/foo' is run in a separate console. .nf % inotifywait test Setting up watches. Watches established. test/ ACCESS foo .fi .SS Example 2 A short shell script to efficiently wait for httpd-related log messages and do something appropriate. .nf #!/bin/sh while ! inotifywait -e modify /var/log/messages; do if tail -n1 /var/log/messages | grep httpd; then kdialog --msgbox "Apache needs love!" fi done .fi .SS Example 3 A custom output format is used to watch `~/test'. Meanwhile, someone runs `touch ~/test/badfile; touch ~/test/goodfile; rm ~/test/badfile' in another console. .nf % inotifywait -m -r --format '%:e %f' ~/test Setting up watches. Beware: since -r was given, this may take a while! Watches established. CREATE badfile OPEN badfile ATTRIB badfile CLOSE_WRITE:CLOSE badfile CREATE goodfile OPEN goodfile ATTRIB goodfile CLOSE_WRITE:CLOSE goodfile DELETE badfile .fi .SS Example 4 Enforce file permissions in directory `~/test' .nf inotifywait -qmr -e 'moved_to,create' --format '%w%f%0' --no-newline ~/test |\\ while IFS= read -r -d '' file do chmod -v a+rX "$file" done .fi .SH CAVEATS When using inotifywait, the filename that is outputted is not guaranteed to be up to date after a move because it is the inode that is being monitored. Additionally, none of the observed operations are guaranteed to have been performed on the filename inotifywait was instructed to monitor in cases when the file is known by several names in the filesystem. .SH BUGS There are race conditions in the recursive directory watching code which can cause events to be missed if they occur in a directory immediately after that directory is created. This is probably not fixable. It is assumed the inotify event queue will never overflow. .SH AUTHORS inotifywait was started by Rohan McGovern, and is currently maintained by Eric Curtin and Radu Voicilas. https://www.openhub.net/p/inotify-tools/contributors/summary gives you a more complete list of contributors. inotifywait is part of inotify-tools. The inotify-tools website is located at: .I https://github.com/inotify-tools/inotify-tools/wiki .SH "SEE ALSO" inotifywatch(1), strftime(3), inotify(7) inotify-tools-3.22.6.0/man/inotifywatch.1.in000066400000000000000000000240771424760767400205660ustar00rootroot00000000000000.TH inotifywatch 1 "@MAN_DATE@" "inotifywatch @MAN_PACKAGE_VERSION@" .SH NAME inotifywatch, fsnotifywatch \- gather filesystem access statistics using inotify or fanotify .SH SYNOPSIS .B inotifywatch .RB [ \-hvzrPqf ] .RB [ \-e ] .RB [ \-t ] .RB [ \-a ] .RB [ \-d ] [ ... ] .B fsnotifywatch .RB [ \-hvzrPqfIFS ] .RB [ \-e ] .RB [ \-t ] .RB [ \-a ] .RB [ \-d ] [ ... ] .SH DESCRIPTION .B inotifywatch listens for filesystem events using Linux's .BR inotify(7) interface, then outputs a summary count of the events received on each file or directory. .B fsnotifywatch is similar to .B inotifywatch but it is using Linux's .BR fanotify(7) interface by default. If explicitly specified, it uses the .BR inotify(7) interface. .SH OUTPUT .B inotifywatch and .B fsnotifywatch will output a table on standard out with one column for each type of event and one row for each watched file or directory. The table will show the amount of times each event occurred for each watched file or directory. Output can be sorted by a particular event using the .B \-a or .B \-d options. Some diagnostic information will be output on standard error. .SH OPTIONS .TP .B \-h, \-\-help Output some helpful usage information. .TP .B \-v, \-\-verbose Output some extra information on standard error during execution. .TP .B @ When watching a directory tree recursively, exclude the specified file from being watched. The file must be specified with a relative or absolute path according to whether a relative or absolute path is given for watched directories. If a specific path is explicitly both included and excluded, it will always be watched. .B Note: If you need to watch a directory or file whose name starts with @, give the absolute path. .TP .B \-\-fromfile Read filenames to watch or exclude from a file, one filename per line. If filenames begin with @ they are excluded as described above. If is `-', filenames are read from standard input. Use this option if you need to watch too many files to pass in as command line arguments. .TP .B \-z, \-\-zero Output table rows and columns even if all elements are zero. By default, rows and columns are only output if they contain non-zero elements. Using this option when watching for every event on a lot of files can result in a .I lot of output! .TP .B \-\-exclude Do not process any events for the subset of files whose filenames match the specified POSIX regular expression, case sensitive. .TP .B \-\-excludei Do not process any events for the subset of files whose filenames match the specified POSIX regular expression, case insensitive. .TP .B \-\-include Process events only for the subset of files whose filenames match the specified POSIX regular expression, case sensitive. .TP .B \-\-includei Process events only for the subset of files whose filenames match the specified POSIX regular expression, case insensitive. .TP .B \-r, \-\-recursive Watch all subdirectories of any directories passed as arguments. Watches will be set up recursively to an unlimited depth. Symbolic links are not traversed. If new directories are created within watched directories they will automatically be watched. .B Warning: If you use this option while watching the root directory of a large tree, it may take quite a while until all inotify watches are established, and events will not be received in this time. Also, since one inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user will be reached. The default maximum is 8192; it can be increased by writing to .BR /proc/sys/fs/inotify/max_user_watches . .TP .B \-P, \-\-no\-dereference Do not follow symlinks. .TP .B \-t , \-\-timeout Listen only for the specified amount of seconds. If not specified, inotifywatch will gather statistics until receiving an interrupt signal by (for example) pressing CONTROL-C at the console. .TP .B \-e , \-\-event Listen for specific event(s) only. The events which can be listened for are listed in the .B EVENTS section. This option can be specified more than once. If omitted, all events are listened for. .TP .B \-a , \-\-ascending Sort output ascending by event counts for the specified event. Sortable events include `total' and all the events listed in the .B EVENTS section except `move' and `close' (you must use `moved_to', `moved_from', `close_write' or `close_nowrite' instead). The default is to sort descending by `total'. .TP .B \-d , \-\-descending Sort output descending by event counts for the specified event. Sortable events include `total' and all the events listed in the .B EVENTS section except `move' and `close' (you must use `moved_to', `moved_from', `close_write' or `close_nowrite' instead). The default is to sort descending by `total'. .SH "EXIT STATUS" .TP .B 0 The program executed successfully. .TP .B 1 An error occurred in execution of the program. .SH EVENTS The following events are valid for use with the .B \-e option: .TP .B access A watched file or a file within a watched directory was read from. .TP .B modify A watched file or a file within a watched directory was written to. .TP .B attrib The metadata of a watched file or a file within a watched directory was modified. This includes timestamps, file permissions, extended attributes etc. .TP .B close_write A watched file or a file within a watched directory was closed, after being opened in writable mode. This does not necessarily imply the file was written to. .TP .B close_nowrite A watched file or a file within a watched directory was closed, after being opened in read-only mode. .TP .B close A watched file or a file within a watched directory was closed, regardless of how it was opened. Note that this is actually implemented simply by listening for both .B close_write and .B close_nowrite, hence all close events received will be output as one of these, not .B CLOSE. .TP .B open A watched file or a file within a watched directory was opened. .TP .B moved_to A file or directory was moved into a watched directory. This event occurs even if the file is simply moved from and to the same directory. .TP .B moved_from A file or directory was moved from a watched directory. This event occurs even if the file is simply moved from and to the same directory. .TP .B move A file or directory was moved from or to a watched directory. Note that this is actually implemented simply by listening for both .B moved_to and .B moved_from, hence all close events received will be output as one or both of these, not .B MOVE. .TP .B move_self A watched file or directory was moved. After this event, the file or directory is no longer being watched. .TP .B create A file or directory was created within a watched directory. .TP .B delete A file or directory within a watched directory was deleted. .TP .B delete_self A watched file or directory was deleted. After this event the file or directory is no longer being watched. Note that this event can occur even if it is not explicitly being listened for. .TP .B unmount The filesystem on which a watched file or directory resides was unmounted. After this event the file or directory is no longer being watched. Note that this event can occur even if it is not explicitly being listened to. .\" .SS fsnotifywatch .PP The following additional options are available: .TP .TP .B \-I, \-\-inotify Watch using inotify. .TP .B \-F, \-\-fanotify Watch using fanotify (default). fanotify support for reporting events with inotify compatible information was added in kernel v5.9. With older kernels the command will fail. As of kernel v5.12, fanotify requires admin privileges. .TP .B \-S, \-\-filesystem Watch entire filesystem of any directories passed as arguments using fanotify. .SH EXAMPLE Watching the `~/.beagle' directory for 60 seconds: .nf % inotifywatch -v -e access -e modify -t 60 -r ~/.beagle Establishing watches... Setting up watch(es) on /home/rohan/.beagle OK, /home/rohan/.beagle is now being watched. Total of 302 watches. Finished establishing watches, now collecting statistics. Will listen for events for 60 seconds. total access modify filename 1436 1074 362 /home/rohan/.beagle/Indexes/FileSystemIndex/PrimaryIndex/ 1323 1053 270 /home/rohan/.beagle/Indexes/FileSystemIndex/SecondaryIndex/ 303 116 187 /home/rohan/.beagle/Indexes/KMailIndex/PrimaryIndex/ 261 74 187 /home/rohan/.beagle/TextCache/ 206 0 206 /home/rohan/.beagle/Log/ 42 0 42 /home/rohan/.beagle/Indexes/FileSystemIndex/Locks/ 18 6 12 /home/rohan/.beagle/Indexes/FileSystemIndex/ 12 0 12 /home/rohan/.beagle/Indexes/KMailIndex/Locks/ 3 0 3 /home/rohan/.beagle/TextCache/54/ 3 0 3 /home/rohan/.beagle/TextCache/bc/ 3 0 3 /home/rohan/.beagle/TextCache/20/ 3 0 3 /home/rohan/.beagle/TextCache/62/ 2 2 0 /home/rohan/.beagle/Indexes/KMailIndex/SecondaryIndex/ .fi .SH CAVEATS When using inotifywatch, the filename that is outputted is not guaranteed to be up to date after a move because it is the inode that is being monitored. Additionally, none of the observed operations are guaranteed to have been performed on the filename inotifywatch was instructed to monitor in cases when the file is known by several names in the filesystem. .SH BUGS There are race conditions in the recursive directory watching code which can cause events to be missed if they occur in a directory immediately after that directory is created. This is probably not fixable. It is assumed the inotify event queue will never overflow. .SH AUTHORS inotifywatch was started by Rohan McGovern, and is currently maintained by Eric Curtin and Radu Voicilas. https://www.openhub.net/p/inotify-tools/contributors/summary gives you a more complete list of contributors. inotifywatch is part of inotify-tools. The inotify-tools website is located at: .I https://github.com/inotify-tools/inotify-tools/wiki .SH "SEE ALSO" inotifywait(1), inotify(7) inotify-tools-3.22.6.0/rh_build.sh000077500000000000000000000006221424760767400167310ustar00rootroot00000000000000#!/bin/bash set -e if [ -n "$1" ]; then j="$1" else ./autogen.sh ./configure --disable-dependency-tracking --disable-static --enable-doxygen j="-j16" fi # https://docs.fedoraproject.org/en-US/packaging-guidelines/#_removing_rpath sed -i 's|^hardcode_libdir_flag_spec=.*|hardcode_libdir_flag_spec=""|g' libtool sed -i 's|^runpath_var=LD_RUN_PATH|runpath_var=DIE_RPATH_DIE|g' libtool make $j inotify-tools-3.22.6.0/sonar-project.properties000066400000000000000000000006231424760767400215070ustar00rootroot00000000000000sonar.projectKey=inotify-tools_inotify-tools sonar.organization=inotify-tools # This is the name and version displayed in the SonarCloud UI. #sonar.projectName=inotify-tools #sonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. #sonar.sources=. # Encoding of the source code. Default is default system encoding #sonar.sourceEncoding=UTF-8 inotify-tools-3.22.6.0/src/000077500000000000000000000000001424760767400153715ustar00rootroot00000000000000inotify-tools-3.22.6.0/src/Makefile.am000066400000000000000000000013241424760767400174250ustar00rootroot00000000000000bin_PROGRAMS = inotifywait inotifywatch inotifywait_SOURCES = inotifywait.c common.c common.h inotifywatch_SOURCES = inotifywatch.c common.c common.h if ENABLE_FANOTIFY # Build the fsnotify* tools with fanotify as the default backend bin_PROGRAMS += fsnotifywait fsnotifywatch fsnotifywait_SOURCES = inotifywait.c common.c common.h fsnotifywait_CPPFLAGS = -DENABLE_FANOTIFY fsnotifywatch_SOURCES = inotifywatch.c common.c common.h fsnotifywatch_CPPFLAGS = -DENABLE_FANOTIFY endif AM_CFLAGS = -Wall -Wextra -Wshadow -Werror -std=c99 -I../libinotifytools/src AM_CPPFLAGS = -I$(top_srcdir)/libinotifytools/src LDADD = ../libinotifytools/src/libinotifytools.la if STATIC_BINARY_ENABLE AM_LDFLAGS = -static-libtool-libs endif inotify-tools-3.22.6.0/src/common.c000066400000000000000000000134761424760767400170400ustar00rootroot00000000000000#include "common.h" #include "../config.h" #include #include #include #include #include #include #include #include #include #define MAXLEN 4096 #define LIST_CHUNK 1024 static void resize_if_necessary(const int count, int* len, const char*** ptr) { if (count >= *len - 1) { *len += LIST_CHUNK; *ptr = (const char**)realloc(*ptr, sizeof(char*) * *len); } } void print_event_descriptions() { printf("\taccess\t\tfile or directory contents were read\n"); printf("\tmodify\t\tfile or directory contents were written\n"); printf("\tattrib\t\tfile or directory attributes changed\n"); printf("\tclose_write\tfile or directory closed, after being opened in\n" "\t \twritable mode\n"); printf("\tclose_nowrite\tfile or directory closed, after being opened in\n" "\t \tread-only mode\n"); printf("\tclose\t\tfile or directory closed, regardless of read/write " "mode\n"); printf("\topen\t\tfile or directory opened\n"); printf("\tmoved_to\tfile or directory moved to watched directory\n"); printf("\tmoved_from\tfile or directory moved from watched directory\n"); printf("\tmove\t\tfile or directory moved to or from watched directory\n"); printf("\tmove_self\t\tA watched file or directory was moved.\n"); printf("\tcreate\t\tfile or directory created within watched directory\n"); printf("\tdelete\t\tfile or directory deleted within watched directory\n"); printf("\tdelete_self\tfile or directory was deleted\n"); printf("\tunmount\t\tfile system containing file or directory unmounted\n"); } int isdir(char const *path) { static struct stat64 my_stat; if (-1 == lstat64(path, &my_stat)) { if (errno == ENOENT) return 0; fprintf(stderr, "Stat failed on %s: %s\n", path, strerror(errno)); return 0; } return S_ISDIR(my_stat.st_mode) && !S_ISLNK(my_stat.st_mode); } void free_list(int argc, char** argv, FileList* list) { char* start_of_stack = argv[0]; char* end_of_stack = argv[argc - 1]; for (int i = 0; argv[i]; ++i) { if (argv[i] < start_of_stack) { start_of_stack = argv[i]; } else if (argv[i] > end_of_stack) { end_of_stack = argv[i]; } } while (*end_of_stack) { ++end_of_stack; } for (int i = 0; list->watch_files[i]; ++i) { if (list->watch_files[i] < start_of_stack || list->watch_files[i] > end_of_stack) { free((void*)list->watch_files[i]); } } free(list->watch_files); for (int i = 0; list->exclude_files[i]; ++i) { if (list->exclude_files[i] < start_of_stack || list->exclude_files[i] > end_of_stack) { free((void*)list->exclude_files[i]); } } free(list->exclude_files); } void construct_path_list(int argc, char** argv, char const* filename, FileList* list) { list->watch_files = 0; list->exclude_files = 0; FILE* file = 0; if (filename) { if (!strcmp(filename, "-")) { file = stdin; } else { file = fopen(filename, "r"); if (!file) { fprintf(stderr, "Couldn't open %s: %s\n", filename, strerror(errno)); } } } int watch_len = LIST_CHUNK; int exclude_len = LIST_CHUNK; int watch_count = 0; int exclude_count = 0; list->watch_files = (char const**)malloc(sizeof(char*) * LIST_CHUNK); list->exclude_files = (char const**)malloc(sizeof(char*) * LIST_CHUNK); char name[MAXLEN]; while (file && fgets(name, MAXLEN, file)) { if (name[strlen(name) - 1] == '\n') name[strlen(name) - 1] = 0; if (strlen(name) == 0) continue; if ('@' == name[0] && strlen(name) == 1) continue; if ('@' == name[0]) { resize_if_necessary(exclude_count, &exclude_len, &list->exclude_files); list->exclude_files[exclude_count++] = strdup(&name[1]); } else { resize_if_necessary(watch_count, &watch_len, &list->watch_files); list->watch_files[watch_count++] = strdup(name); } } if (file && file != stdin) fclose(file); for (int i = 0; i < argc; ++i) { if (strlen(argv[i]) == 0) continue; if ('@' == argv[i][0] && strlen(argv[i]) == 1) continue; if ('@' == argv[i][0]) { resize_if_necessary(exclude_count, &exclude_len, &list->exclude_files); list->exclude_files[exclude_count++] = &argv[i][1]; } else { resize_if_necessary(watch_count, &watch_len, &list->watch_files); list->watch_files[watch_count++] = argv[i]; } } list->exclude_files[exclude_count] = 0; list->watch_files[watch_count] = 0; } void warn_inotify_init_error(int fanotify) { const char* backend = fanotify ? "fanotify" : "inotify"; const char* resource = fanotify ? "groups" : "instances"; int error = inotifytools_error(); fprintf(stderr, "Couldn't initialize %s: %s\n", backend, strerror(error)); if (error == EMFILE) { fprintf(stderr, "Try increasing the value of " "/proc/sys/fs/%s/max_user_%s\n", backend, resource); } if (fanotify && error == EINVAL) { fprintf(stderr, "fanotify support for reporting the events with " "file names was added in kernel v5.9.\n"); } if (fanotify && error == EPERM) { fprintf(stderr, "fanotify watch requires admin privileges\n"); } } bool is_timeout_option_valid(unsigned int* timeout, char* o) { if ((o == NULL) || (*o == '\0')) { fprintf(stderr, "The provided value is not a valid timeout value.\n" "Please specify a long int value.\n"); return false; } char* timeout_end = NULL; errno = 0; *timeout = strtol(o, &timeout_end, 10); const int err = errno; if (err != 0) { fprintf(stderr, "Something went wrong with the timeout " "value you provided.\n"); fprintf(stderr, "%s\n", strerror(err)); return false; } if (*timeout_end != '\0') { fprintf(stderr, "'%s' is not a valid timeout value.\n" "Please specify a long int value.\n", o); return false; } return true; } inotify-tools-3.22.6.0/src/common.h000066400000000000000000000017601424760767400170360ustar00rootroot00000000000000#ifndef COMMON_H #define COMMON_H #ifdef __FreeBSD__ #define stat64 stat #define lstat64 lstat #ifdef ENABLE_FANOTIFY #error "FreeBSD does not support fanotify" #endif #endif #include #define BLOCKING_TIMEOUT 0 #ifndef EXIT_SUCCESS #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1 #endif #define EXIT_TIMEOUT 2 #ifdef ENABLE_FANOTIFY // fsnotifywait/fsnotifywatch defaults to fanotify #define TOOLS_PREFIX "fsnotify" #define DEFAULT_FANOTIFY_MODE 1 #else // inotifywait/inotifywatch defaults to inotify #define TOOLS_PREFIX "inotify" #define DEFAULT_FANOTIFY_MODE 0 #endif void print_event_descriptions(); int isdir(char const *path); typedef struct { char const** watch_files; char const** exclude_files; } FileList; void free_list(int argc, char** argv, FileList* list); void construct_path_list(int argc, char** argv, char const* filename, FileList* list); void warn_inotify_init_error(int fanotify); bool is_timeout_option_valid(unsigned int* timeout, char* o); #endif inotify-tools-3.22.6.0/src/inotifywait.c000066400000000000000000000543301424760767400201100ustar00rootroot00000000000000#include "../config.h" #include "../libinotifytools/src/inotifytools_p.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char *optarg; extern int optind, opterr, optopt; #define MAX_STRLEN 4096 // METHODS static bool parse_opts(int* argc, char*** argv, int* events, bool* monitor, int* quiet, unsigned int* timeout, int* recursive, bool* csv, bool* daemon, bool* syslog, bool* no_dereference, char** format, char** timefmt, char** fromfile, char** outfile, char** exc_regex, char** exc_iregex, char** inc_regex, char** inc_iregex, bool* no_newline, int* fanotify, bool* filesystem); void print_help(); static const char* csv_escape_len(const char* string, size_t len) { static char csv[MAX_STRLEN + 1]; static unsigned int i, ind; if (string == NULL) { return ""; } if (len == 0 || len > MAX_STRLEN) { return ""; } // May not need escaping if (!strchr(string, '"') && !strchr(string, ',') && !strchr(string, '\n') && string[0] != ' ' && string[len - 1] != ' ') { strncpy(csv, string, len); csv[len] = '\0'; return csv; } // OK, so now we _do_ need escaping. csv[0] = '"'; ind = 1; for (i = 0; i < len; ++i) { if (string[i] == '"') { csv[ind++] = '"'; } csv[ind++] = string[i]; } csv[ind++] = '"'; csv[ind] = '\0'; return csv; } static const char* csv_escape(const char* string) { if (string == NULL) { return ""; } return csv_escape_len(string, strlen(string)); } void validate_format(char *fmt) { // Make a fake event struct inotify_event *event = (struct inotify_event *)malloc(sizeof(struct inotify_event) + 4); if (!event) { fprintf(stderr, "Seem to be out of memory... yikes!\n"); exit(EXIT_FAILURE); } event->wd = 0; event->mask = IN_ALL_EVENTS; event->len = 3; event->name[0] = 0; FILE *devnull = fopen("/dev/null", "a"); if (!devnull) { fprintf(stderr, "Couldn't open /dev/null: %s\n", strerror(errno)); free(event); return; } if (-1 == inotifytools_fprintf(devnull, event, fmt)) { fprintf(stderr, "Something is wrong with your format string.\n"); free(event); fclose(devnull); exit(EXIT_FAILURE); } free(event); fclose(devnull); } void output_event_csv(struct inotify_event *event) { size_t dirnamelen = 0; const char* eventname; const char* filename = inotifytools_filename_from_event(event, &eventname, &dirnamelen); filename = csv_escape_len(filename, dirnamelen); if (filename && *filename) printf("%s,", filename); // eventname may be pointing into snprintf buffer char* name = strdup(eventname); printf("%s,", csv_escape(inotifytools_event_to_str(event->mask))); if (name) { printf("%s", csv_escape(name)); free(name); } printf("\n"); } void output_error(bool syslog, char *fmt, ...) { va_list va; va_start(va, fmt); if (syslog) { vsyslog(LOG_INFO, fmt, va); } else { vfprintf(stderr, fmt, va); } va_end(va); } int main(int argc, char **argv) { int events = 0; int orig_events; bool monitor = false; int quiet = 0; unsigned int timeout = BLOCKING_TIMEOUT; int recursive = 0; int fanotify = DEFAULT_FANOTIFY_MODE; bool filesystem = false; bool csv = false; bool dodaemon = false; bool sysl = false; bool no_dereference = false; char *format = NULL; char *timefmt = NULL; char *fromfile = NULL; char *outfile = NULL; char *exc_regex = NULL; char *exc_iregex = NULL; char *inc_regex = NULL; char *inc_iregex = NULL; bool no_newline = false; int fd, rc; // Parse commandline options, aborting if something goes wrong if (!parse_opts(&argc, &argv, &events, &monitor, &quiet, &timeout, &recursive, &csv, &dodaemon, &sysl, &no_dereference, &format, &timefmt, &fromfile, &outfile, &exc_regex, &exc_iregex, &inc_regex, &inc_iregex, &no_newline, &fanotify, &filesystem)) { return EXIT_FAILURE; } rc = inotifytools_init(fanotify, filesystem, !quiet); if (!rc) { warn_inotify_init_error(fanotify); return EXIT_FAILURE; } if (timefmt) inotifytools_set_printf_timefmt(timefmt); if ((exc_regex && !inotifytools_ignore_events_by_regex(exc_regex, REG_EXTENDED)) || (exc_iregex && !inotifytools_ignore_events_by_regex(exc_iregex, REG_EXTENDED | REG_ICASE))) { fprintf(stderr, "Error in `exclude' regular expression.\n"); return EXIT_FAILURE; } if ((inc_regex && !inotifytools_ignore_events_by_inverted_regex(inc_regex, REG_EXTENDED)) || (inc_iregex && !inotifytools_ignore_events_by_inverted_regex( inc_iregex, REG_EXTENDED | REG_ICASE))) { fprintf(stderr, "Error in `include' regular expression.\n"); return EXIT_FAILURE; } if (format) validate_format(format); // Attempt to watch file // If events is still 0, make it all events. if (events == 0) events = IN_ALL_EVENTS; orig_events = events; if (monitor && recursive) { events = events | IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM; } if (no_dereference) { events = events | IN_DONT_FOLLOW; } if (fanotify) { events |= IN_ISDIR; } FileList list; construct_path_list(argc, argv, fromfile, &list); if (0 == list.watch_files[0]) { fprintf(stderr, "No files specified to watch!\n"); goto failure; } // Daemonize - BSD double-fork approach if (dodaemon) { // Absolute path for outfile before entering the child. char *logfile = calloc(PATH_MAX + 1, sizeof(char)); if (realpath(outfile, logfile) == NULL) { fprintf(stderr, "%s: %s\n", strerror(errno), outfile); free(logfile); goto failure; } if (daemon(0, 0)) { fprintf(stderr, "Failed to daemonize!\n"); free(logfile); goto failure; } // Redirect stdin from /dev/null fd = open("/dev/null", O_RDONLY); if (fd != fileno(stdin)) { dup2(fd, fileno(stdin)); close(fd); } // Redirect stdout to a file fd = open(logfile, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd < 0) { fprintf(stderr, "Failed to open output file %s\n", logfile); free(logfile); goto failure; } free(logfile); if (fd != fileno(stdout)) { dup2(fd, fileno(stdout)); close(fd); } // Redirect stderr to /dev/null fd = open("/dev/null", O_WRONLY); if (fd != fileno(stderr)) { dup2(fd, fileno(stderr)); close(fd); } } else if (outfile != NULL) { // Redirect stdout to a file if specified fd = open(outfile, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd < 0) { fprintf(stderr, "Failed to open output file %s\n", outfile); goto failure; } if (fd != fileno(stdout)) { dup2(fd, fileno(stdout)); close(fd); } } if (sysl) { openlog("inotifywait", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON); } if (!quiet) { if (filesystem) { output_error(sysl, "Setting up filesystem watches.\n"); } else if (recursive) { output_error(sysl, "Setting up watches. Beware: since -r " "was given, this may take a while!\n"); } else { output_error(sysl, "Setting up watches.\n"); } } // now watch files for (int i = 0; list.watch_files[i]; ++i) { char const* this_file = list.watch_files[i]; if (filesystem) { if (!inotifytools_watch_files(list.watch_files, events)) { output_error( sysl, "Couldn't add filesystem watch %s: %s\n", this_file, strerror(inotifytools_error())); goto failure; } break; } if ((recursive && !inotifytools_watch_recursively_with_exclude( this_file, events, list.exclude_files)) || (!recursive && !inotifytools_watch_file(this_file, events))) { if (inotifytools_error() == ENOSPC) { const char* backend = fanotify ? "fanotify" : "inotify"; const char* resource = fanotify ? "marks" : "watches"; output_error( sysl, "Failed to watch %s; upper limit on %s %s " "reached!\n", this_file, backend, resource); output_error( sysl, "Please increase the amount of %s %s " "allowed per user via `/proc/sys/fs/%s/" "max_user_%s'.\n", backend, resource, backend, resource); } else { output_error(sysl, "Couldn't watch %s: %s\n", this_file, strerror(inotifytools_error())); } goto failure; } } if (!quiet) { output_error(sysl, "Watches established.\n"); } // Now wait till we get event struct inotify_event *event; char *moved_from = 0; do { event = inotifytools_next_event(timeout); if (!event) { if (!inotifytools_error()) { goto timeout; } else { output_error(sysl, "%s\n", strerror(inotifytools_error())); goto failure; } } if (quiet < 2 && (event->mask & orig_events)) { if (csv) { output_event_csv(event); } else if (format) { inotifytools_printf(event, format); } else { inotifytools_printf(event, "%w %,e %f\n"); } } // TODO: replace filename of renamed filesystem watch entries if (filesystem) continue; // if we last had MOVED_FROM and don't currently have MOVED_TO, // moved_from file must have been moved outside of tree - so unwatch it. if (moved_from && !(event->mask & IN_MOVED_TO)) { if (!inotifytools_remove_watch_by_filename(moved_from)) { output_error(sysl, "Error removing watch on %s: %s\n", moved_from, strerror(inotifytools_error())); } free(moved_from); moved_from = 0; } if (monitor && recursive) { if ((event->mask & IN_CREATE) || (!moved_from && (event->mask & IN_MOVED_TO))) { // New file - if it is a directory, watch it char* new_file = inotifytools_dirpath_from_event(event); if (new_file && *new_file && isdir(new_file) && !inotifytools_watch_recursively(new_file, events)) { output_error( sysl, "Couldn't watch new directory %s: %s\n", new_file, strerror(inotifytools_error())); } free(new_file); } // IN_CREATE else if (event->mask & IN_MOVED_FROM) { moved_from = inotifytools_dirpath_from_event(event); // if not watched... if (inotifytools_wd_from_filename(moved_from) == -1) { free(moved_from); moved_from = 0; } } // IN_MOVED_FROM else if (event->mask & IN_MOVED_TO) { if (moved_from) { char* new_name = inotifytools_dirpath_from_event(event); inotifytools_replace_filename(moved_from, new_name); free(new_name); free(moved_from); moved_from = 0; } // moved_from } } fflush(NULL); } while (monitor); // If we weren't trying to listen for this event... if ((events & event->mask) == 0) { // ...then most likely something bad happened, like IGNORE etc. goto failure; } free_list(argc, argv, &list); return EXIT_SUCCESS; failure: free_list(argc, argv, &list); return EXIT_FAILURE; timeout: free_list(argc, argv, &list); return EXIT_TIMEOUT; } static bool parse_opts(int* argc, char*** argv, int* events, bool* monitor, int* quiet, unsigned int* timeout, int* recursive, bool* csv, bool* daemon, bool* syslog, bool* no_dereference, char** format, char** timefmt, char** fromfile, char** outfile, char** exc_regex, char** exc_iregex, char** inc_regex, char** inc_iregex, bool* no_newline, int* fanotify, bool* filesystem) { assert(argc); assert(argv); assert(events); assert(monitor); assert(quiet); assert(timeout); assert(recursive); assert(fanotify); assert(filesystem); assert(csv); assert(daemon); assert(syslog); assert(no_dereference); assert(format); assert(timefmt); assert(fromfile); assert(outfile); assert(exc_regex); assert(exc_iregex); assert(inc_regex); assert(inc_iregex); // Settings for options int new_event; // How many times --exclude has been specified unsigned int exclude_count = 0; // How many times --excludei has been specified unsigned int excludei_count = 0; const char* regex_warning = "only the last option will be taken into consideration.\n"; // Short options static const char opt_string[] = "mrhcdsPqt:fo:e:IFS"; // Long options static const struct option long_opts[] = { {"help", no_argument, NULL, 'h'}, {"event", required_argument, NULL, 'e'}, {"monitor", no_argument, NULL, 'm'}, {"quiet", no_argument, NULL, 'q'}, {"timeout", required_argument, NULL, 't'}, {"filename", no_argument, NULL, 'f'}, {"recursive", no_argument, NULL, 'r'}, {"inotify", no_argument, NULL, 'I'}, {"fanotify", no_argument, NULL, 'F'}, {"filesystem", no_argument, NULL, 'S'}, {"csv", no_argument, NULL, 'c'}, {"daemon", no_argument, NULL, 'd'}, {"syslog", no_argument, NULL, 's'}, {"no-dereference", no_argument, NULL, 'P'}, {"format", required_argument, NULL, 'n'}, {"no-newline", no_argument, NULL, '0'}, {"timefmt", required_argument, NULL, 'i'}, {"fromfile", required_argument, NULL, 'z'}, {"outfile", required_argument, NULL, 'o'}, {"exclude", required_argument, NULL, 'a'}, {"excludei", required_argument, NULL, 'b'}, {"include", required_argument, NULL, 'j'}, {"includei", required_argument, NULL, 'k'}, {NULL, 0, 0, 0}, }; // Get first option char curr_opt = getopt_long(*argc, *argv, opt_string, long_opts, NULL); // While more options exist... while ((curr_opt != '?') && (curr_opt != (char)-1)) { switch (curr_opt) { // --help or -h case 'h': print_help(); // Shouldn't process any further... return false; // --monitor or -m case 'm': *monitor = true; break; // --quiet or -q case 'q': (*quiet)++; break; // --recursive or -r case 'r': (*recursive)++; break; #ifdef ENABLE_FANOTIFY // --inotify or -I case 'I': (*fanotify) = 0; break; // --fanotify or -F case 'F': (*fanotify) = 1; break; // --filesystem or -S case 'S': (*filesystem) = true; (*fanotify) = 1; break; #endif // --csv or -c case 'c': (*csv) = true; break; // --daemon or -d case 'd': (*daemon) = true; (*monitor) = true; (*syslog) = true; break; // --syslog or -s case 's': (*syslog) = true; break; // --no-dereference or -P case 'P': (*no_dereference) = true; break; // --filename or -f case 'f': fprintf(stderr, "The '--filename' option no longer " "exists. " "The option it enabled in " "earlier\nversions of " "inotifywait is now turned on by " "default.\n"); return false; // --format case 'n': assert(optarg); if (!(*format)) { *format = (char*)malloc(strlen(optarg) + 2); } strcpy(*format, optarg); break; // --no-newline case '0': (*no_newline) = true; break; // --timefmt case 'i': (*timefmt) = optarg; break; // --exclude case 'a': (*exc_regex) = optarg; exclude_count++; break; // --excludei case 'b': (*exc_iregex) = optarg; excludei_count++; break; // --include case 'j': (*inc_regex) = optarg; break; // --includei case 'k': (*inc_iregex) = optarg; break; // --fromfile case 'z': if (*fromfile) { fprintf(stderr, "Multiple --fromfile options " "given.\n"); return false; } (*fromfile) = optarg; break; // --outfile case 'o': if (*outfile) { fprintf(stderr, "Multiple --outfile options " "given.\n"); return false; } (*outfile) = optarg; break; // --timeout or -t case 't': if (!is_timeout_option_valid(timeout, optarg)) { return false; } break; // --event or -e case 'e': // Get event mask from event string new_event = inotifytools_str_to_event(optarg); // If optarg was invalid, abort if (new_event == -1) { fprintf( stderr, "'%s' is not a valid event! Run " "with the " "'--help' option to see a list of " "events.\n", optarg); return false; } // Add the new event to the event mask (*events) = ((*events) | new_event); break; } curr_opt = getopt_long(*argc, *argv, opt_string, long_opts, NULL); } if (*format && !(*no_newline)) { strncat(*format, "\n", 2); } if (*exc_regex && *exc_iregex) { fprintf(stderr, "--exclude and --excludei cannot both be specified.\n"); return false; } if (*inc_regex && *inc_iregex) { fprintf(stderr, "--include and --includei cannot both be specified.\n"); return false; } if ((*inc_regex && *exc_regex) || (*inc_regex && *exc_iregex) || (*inc_iregex && *exc_regex) || (*inc_iregex && *exc_iregex)) { fprintf( stderr, "include and exclude regexp cannot both be specified.\n"); return false; } if (*format && *csv) { fprintf(stderr, "-c and --format cannot both be specified.\n"); return false; } if (!*format && *no_newline) { fprintf(stderr, "--no-newline cannot be specified without --format.\n"); return false; } if (!*format && *timefmt) { fprintf(stderr, "--timefmt cannot be specified without --format.\n"); return false; } if (*format && strstr(*format, "%T") && !*timefmt) { fprintf(stderr, "%%T is in --format string, but --timefmt was not " "specified.\n"); return false; } if (*daemon && *outfile == NULL) { fprintf(stderr, "-o must be specified with -d.\n"); return false; } if (exclude_count > 1) { fprintf(stderr, "--exclude: %s", regex_warning); } if (excludei_count > 1) { fprintf(stderr, "--excludei: %s", regex_warning); } (*argc) -= optind; *argv = &(*argv)[optind]; // If ? returned, invalid option return (curr_opt != '?'); } #define TOOL_NAME TOOLS_PREFIX "wait" void print_help() { printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); printf("Wait for a particular event on a file or set of files.\n"); printf("Usage: %s [ options ] file1 [ file2 ] [ file3 ] [ ... ]\n", TOOL_NAME); printf("Options:\n"); printf("\t-h|--help \tShow this help text.\n"); printf( "\t@ \tExclude the specified file from being " "watched.\n"); printf( "\t--exclude \n" "\t \tExclude all events on files matching the\n" "\t \textended regular expression .\n" "\t \tOnly the last --exclude option will be\n" "\t \ttaken into consideration.\n"); printf( "\t--excludei \n" "\t \tLike --exclude but case insensitive.\n"); printf( "\t--include \n" "\t \tExclude all events on files except the ones\n" "\t \tmatching the extended regular expression\n" "\t \t.\n"); printf( "\t--includei \n" "\t \tLike --include but case insensitive.\n"); printf( "\t-m|--monitor \tKeep listening for events forever or until " "--timeout expires.\n" "\t \tWithout this option, %s will exit after " "one event is received.\n", TOOL_NAME); printf( "\t-d|--daemon \tSame as --monitor, except run in the " "background\n" "\t \tlogging events to a file specified by " "--outfile.\n" "\t \tImplies --syslog.\n"); printf( "\t-P|--no-dereference\n" "\t \tDo not follow symlinks.\n"); printf("\t-r|--recursive\tWatch directories recursively.\n"); #ifdef ENABLE_FANOTIFY printf("\t-I|--inotify\tWatch with inotify.\n"); printf("\t-F|--fanotify\tWatch with fanotify.\n"); printf("\t-S|--filesystem\tWatch entire filesystem with fanotify.\n"); #endif printf( "\t--fromfile \n" "\t \tRead files to watch from or `-' for " "stdin.\n"); printf( "\t-o|--outfile \n" "\t \tPrint events to rather than stdout.\n"); printf("\t-s|--syslog \tSend errors to syslog rather than stderr.\n"); printf("\t-q|--quiet \tPrint less (only print events).\n"); printf("\t-qq \tPrint nothing (not even events).\n"); printf( "\t--format \tPrint using a specified printf-like format\n" "\t \tstring; read the man page for more details.\n"); printf( "\t--no-newline \tDon't print newline symbol after\n" "\t \t--format string.\n"); printf( "\t--timefmt \tstrftime-compatible format string for use " "with\n" "\t \t%%T in --format string.\n"); printf("\t-c|--csv \tPrint events in CSV format.\n"); printf( "\t-t|--timeout \n" "\t \tWhen listening for a single event, time out " "after\n" "\t \twaiting for an event for seconds.\n" "\t \tIf is zero, %s will never time " "out.\n", TOOL_NAME); printf( "\t-e|--event [ -e|--event ... ]\n" "\t\tListen for specific event(s). If omitted, all events are \n" "\t\tlistened for.\n\n"); printf("Exit status:\n"); printf("\t%d - An event you asked to watch for was received.\n", EXIT_SUCCESS); printf("\t%d - An event you did not ask to watch for was received\n", EXIT_FAILURE); printf( "\t (usually delete_self or unmount), or some error " "occurred.\n"); printf( "\t%d - The --timeout option was given and no events occurred\n", EXIT_TIMEOUT); printf("\t in the specified interval of time.\n\n"); printf("Events:\n"); print_event_descriptions(); } inotify-tools-3.22.6.0/src/inotifywatch.c000066400000000000000000000515211424760767400202510ustar00rootroot00000000000000#include "../config.h" #include "../libinotifytools/src/inotifytools_p.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char *optarg; extern int optind, opterr, optopt; // METHODS static bool parse_opts(int* argc, char*** argv, int* events, unsigned int* timeout, int* verbose, int* zero, int* sort, int* recursive, int* no_dereference, char** fromfile, char** exc_regex, char** exc_iregex, char** inc_regex, char** inc_iregex, int* fanotify, bool* filesystem); void print_help(); static bool done; void handle_impatient_user(int signal __attribute__((unused))) { static int times_called = 0; if (times_called) { fprintf(stderr, "No statistics collected, asked to abort before all " "watches could be established.\n"); exit(1); } fprintf(stderr, "No statistics have been collected because I haven't " "finished establishing\n" "inotify watches yet. If you are sure you want me to exit, " "interrupt me again.\n"); ++times_called; } void handle_signal(int signal __attribute__((unused))) { done = true; } int print_info(); void print_info_now(int signal __attribute__((unused))) { print_info(); printf("\n"); } int events; int sort; int zero; int main(int argc, char **argv) { events = 0; unsigned int timeout = BLOCKING_TIMEOUT; int verbose = 0; zero = 0; int recursive = 0; int fanotify = DEFAULT_FANOTIFY_MODE; bool filesystem = false; int no_dereference = 0; char *fromfile = 0; sort = -1; done = false; char *exc_regex = NULL; char *exc_iregex = NULL; char *inc_regex = NULL; char *inc_iregex = NULL; int rc; signal(SIGINT, handle_impatient_user); // Parse commandline options, aborting if something goes wrong if (!parse_opts(&argc, &argv, &events, &timeout, &verbose, &zero, &sort, &recursive, &no_dereference, &fromfile, &exc_regex, &exc_iregex, &inc_regex, &inc_iregex, &fanotify, &filesystem)) { return EXIT_FAILURE; } if ((exc_regex && !inotifytools_ignore_events_by_regex(exc_regex, REG_EXTENDED)) || (exc_iregex && !inotifytools_ignore_events_by_regex(exc_iregex, REG_EXTENDED | REG_ICASE))) { fprintf(stderr, "Error in `exclude' regular expression.\n"); return EXIT_FAILURE; } if ((inc_regex && !inotifytools_ignore_events_by_inverted_regex(inc_regex, REG_EXTENDED)) || (inc_iregex && !inotifytools_ignore_events_by_inverted_regex( inc_iregex, REG_EXTENDED | REG_ICASE))) { fprintf(stderr, "Error in `include' regular expression.\n"); return EXIT_FAILURE; } rc = inotifytools_init(fanotify, filesystem, verbose); if (!rc) { warn_inotify_init_error(fanotify); return EXIT_FAILURE; } // Attempt to watch file // If events is still 0, make it all events. if (!events) events = IN_ALL_EVENTS; if (no_dereference) events = events | IN_DONT_FOLLOW; if (fanotify) events |= IN_ISDIR; FileList list; construct_path_list(argc, argv, fromfile, &list); if (0 == list.watch_files[0]) { fprintf(stderr, "No files specified to watch!\n"); goto failure; } unsigned int num_watches = 0; unsigned int status; fprintf(stderr, "Establishing watches...\n"); for (int i = 0; list.watch_files[i]; ++i) { char const* this_file = list.watch_files[i]; if (filesystem) { fprintf(stderr, "Setting up filesystem watch on %s\n", this_file); if (!inotifytools_watch_files(list.watch_files, events)) { fprintf(stderr, "Couldn't add filesystem watch %s: %s\n", this_file, strerror(inotifytools_error())); goto failure; } break; } if (recursive && verbose) { fprintf(stderr, "Setting up watch(es) on %s\n", this_file); } if (recursive) { status = inotifytools_watch_recursively_with_exclude( this_file, events, list.exclude_files); } else { status = inotifytools_watch_file(this_file, events); } if (!status) { if (inotifytools_error() == ENOSPC) { const char* backend = fanotify ? "fanotify" : "inotify"; const char* resource = fanotify ? "marks" : "watches"; fprintf(stderr, "Failed to watch %s; upper limit on %s %s " "reached!\n", this_file, backend, resource); fprintf(stderr, "Please increase the amount of %s %s " "allowed per user via `/proc/sys/fs/%s/" "max_user_%s'.\n", backend, resource, backend, resource); } else { fprintf(stderr, "Failed to watch %s: %s\n", this_file, strerror(inotifytools_error())); } goto failure; } if (recursive && verbose) { fprintf(stderr, "OK, %s is now being watched.\n", this_file); } } num_watches = inotifytools_get_num_watches(); if (verbose) { fprintf(stderr, "Total of %u watches.\n", num_watches); } fprintf(stderr, "Finished establishing watches, now collecting statistics.\n"); if (timeout && verbose) { fprintf(stderr, "Will listen for events for %u seconds.\n", timeout); } signal(SIGINT, handle_signal); signal(SIGHUP, handle_signal); signal(SIGTERM, handle_signal); if (timeout) { signal(SIGALRM, handle_signal); alarm(timeout); } else { alarm(UINT_MAX); } signal(SIGUSR1, print_info_now); inotifytools_initialize_stats(); // Now wait till we get event struct inotify_event *event; char *moved_from = 0; do { event = inotifytools_next_event(BLOCKING_TIMEOUT); if (!event) { if (!inotifytools_error()) { goto timeout; } else if (inotifytools_error() != EINTR) { fprintf(stderr, "%s\n", strerror(inotifytools_error())); goto failure; } else { continue; } } // TODO: replace filename of renamed filesystem watch entries if (filesystem) continue; // if we last had MOVED_FROM and don't currently have MOVED_TO, // moved_from file must have been moved outside of tree - so unwatch it. if (moved_from && !(event->mask & IN_MOVED_TO)) { if (!inotifytools_remove_watch_by_filename(moved_from)) { fprintf(stderr, "Error removing watch on %s: %s\n", moved_from, strerror(inotifytools_error())); } free(moved_from); moved_from = 0; } if (recursive) { if ((event->mask & IN_CREATE) || (!moved_from && (event->mask & IN_MOVED_TO))) { // New file - if it is a directory, watch it char* new_file = inotifytools_dirpath_from_event(event); if (new_file && *new_file && isdir(new_file) && !inotifytools_watch_recursively(new_file, events)) { fprintf(stderr, "Couldn't watch new directory %s: %s\n", new_file, strerror(inotifytools_error())); } free(new_file); } // IN_CREATE else if (event->mask & IN_MOVED_FROM) { moved_from = inotifytools_dirpath_from_event(event); // if not watched... if (inotifytools_wd_from_filename(moved_from) == -1) { free(moved_from); moved_from = 0; } } // IN_MOVED_FROM else if (event->mask & IN_MOVED_TO) { if (moved_from) { char* new_name = inotifytools_dirpath_from_event(event); inotifytools_replace_filename(moved_from, new_name); free(new_name); free(moved_from); moved_from = 0; } // moved_from } } } while (!done); free_list(argc, argv, &list); return print_info(); failure: free_list(argc, argv, &list); return EXIT_FAILURE; timeout: free_list(argc, argv, &list); return EXIT_TIMEOUT; } int print_info() { if (!inotifytools_get_stat_total(0)) { fprintf(stderr, "No events occurred.\n"); return EXIT_SUCCESS; } // OK, go through the watches and print stats. printf("total "); if ((IN_ACCESS & events) && (zero || inotifytools_get_stat_total(IN_ACCESS))) printf("access "); if ((IN_MODIFY & events) && (zero || inotifytools_get_stat_total(IN_MODIFY))) printf("modify "); if ((IN_ATTRIB & events) && (zero || inotifytools_get_stat_total(IN_ATTRIB))) printf("attrib "); if ((IN_CLOSE_WRITE & events) && (zero || inotifytools_get_stat_total(IN_CLOSE_WRITE))) printf("close_write "); if ((IN_CLOSE_NOWRITE & events) && (zero || inotifytools_get_stat_total(IN_CLOSE_NOWRITE))) printf("close_nowrite "); if ((IN_OPEN & events) && (zero || inotifytools_get_stat_total(IN_OPEN))) printf("open "); if ((IN_MOVED_FROM & events) && (zero || inotifytools_get_stat_total(IN_MOVED_FROM))) printf("moved_from "); if ((IN_MOVED_TO & events) && (zero || inotifytools_get_stat_total(IN_MOVED_TO))) printf("moved_to "); if ((IN_MOVE_SELF & events) && (zero || inotifytools_get_stat_total(IN_MOVE_SELF))) printf("move_self "); if ((IN_CREATE & events) && (zero || inotifytools_get_stat_total(IN_CREATE))) printf("create "); if ((IN_DELETE & events) && (zero || inotifytools_get_stat_total(IN_DELETE))) printf("delete "); if ((IN_DELETE_SELF & events) && (zero || inotifytools_get_stat_total(IN_DELETE_SELF))) printf("delete_self "); if ((IN_UNMOUNT & events) && (zero || inotifytools_get_stat_total(IN_UNMOUNT))) printf("unmount "); printf("filename\n"); struct rbtree *tree = inotifytools_wd_sorted_by_event(sort); RBLIST *rblist = rbopenlist(tree); watch *w = (watch *)rbreadlist(rblist); while (w) { if (!zero && !w->hit_total) { w = (watch *)rbreadlist(rblist); continue; } printf("%-5u ", w->hit_total); if ((IN_ACCESS & events) && (zero || inotifytools_get_stat_total(IN_ACCESS))) printf("%-6u ", w->hit_access); if ((IN_MODIFY & events) && (zero || inotifytools_get_stat_total(IN_MODIFY))) printf("%-6u ", w->hit_modify); if ((IN_ATTRIB & events) && (zero || inotifytools_get_stat_total(IN_ATTRIB))) printf("%-6u ", w->hit_attrib); if ((IN_CLOSE_WRITE & events) && (zero || inotifytools_get_stat_total(IN_CLOSE_WRITE))) printf("%-11u ", w->hit_close_write); if ((IN_CLOSE_NOWRITE & events) && (zero || inotifytools_get_stat_total(IN_CLOSE_NOWRITE))) printf("%-13u ", w->hit_close_nowrite); if ((IN_OPEN & events) && (zero || inotifytools_get_stat_total(IN_OPEN))) printf("%-4u ", w->hit_open); if ((IN_MOVED_FROM & events) && (zero || inotifytools_get_stat_total(IN_MOVED_FROM))) printf("%-10u ", w->hit_moved_from); if ((IN_MOVED_TO & events) && (zero || inotifytools_get_stat_total(IN_MOVED_TO))) printf("%-8u ", w->hit_moved_to); if ((IN_MOVE_SELF & events) && (zero || inotifytools_get_stat_total(IN_MOVE_SELF))) printf("%-9u ", w->hit_move_self); if ((IN_CREATE & events) && (zero || inotifytools_get_stat_total(IN_CREATE))) printf("%-6u ", w->hit_create); if ((IN_DELETE & events) && (zero || inotifytools_get_stat_total(IN_DELETE))) printf("%-6u ", w->hit_delete); if ((IN_DELETE_SELF & events) && (zero || inotifytools_get_stat_total(IN_DELETE_SELF))) printf("%-11u ", w->hit_delete_self); if ((IN_UNMOUNT & events) && (zero || inotifytools_get_stat_total(IN_UNMOUNT))) printf("%-7u ", w->hit_unmount); printf("%s\n", inotifytools_filename_from_watch(w)); w = (watch *)rbreadlist(rblist); } rbcloselist(rblist); rbdestroy(tree); return EXIT_SUCCESS; } static bool parse_opts(int* argc, char*** argv, int* e, unsigned int* timeout, int* verbose, int* z, int* s, int* recursive, int* no_dereference, char** fromfile, char** exc_regex, char** exc_iregex, char** inc_regex, char** inc_iregex, int* fanotify, bool* filesystem) { assert(argc); assert(argv); assert(e); assert(timeout); assert(verbose); assert(z); assert(s); assert(recursive); assert(fanotify); assert(filesystem); assert(no_dereference); assert(fromfile); assert(exc_regex); assert(exc_iregex); assert(inc_regex); assert(inc_iregex); // Settings for options int new_event; bool sort_set = false; // Short options static const char opt_string[] = "hrPa:d:zve:t:IFS"; // Construct array static const struct option long_opts[] = { {"help", no_argument, NULL, 'h'}, {"event", required_argument, NULL, 'e'}, {"timeout", required_argument, NULL, 't'}, {"verbose", no_argument, NULL, 'v'}, {"zero", no_argument, NULL, 'z'}, {"ascending", required_argument, NULL, 'a'}, {"descending", required_argument, NULL, 'd'}, {"recursive", no_argument, NULL, 'r'}, {"inotify", no_argument, NULL, 'I'}, {"fanotify", no_argument, NULL, 'F'}, {"filesystem", no_argument, NULL, 'S'}, {"no-dereference", no_argument, NULL, 'P'}, {"fromfile", required_argument, NULL, 'o'}, {"exclude", required_argument, NULL, 'c'}, {"excludei", required_argument, NULL, 'b'}, {"include", required_argument, NULL, 'j'}, {"includei", required_argument, NULL, 'k'}, {NULL, 0, 0, 0}, }; // Get first option char curr_opt = getopt_long(*argc, *argv, opt_string, long_opts, NULL); // While more options exist... while ((curr_opt != '?') && (curr_opt != (char)-1)) { switch (curr_opt) { // --help or -h case 'h': print_help(); // Shouldn't process any further... return false; // --verbose or -v case 'v': ++(*verbose); break; // --recursive or -r case 'r': ++(*recursive); break; #ifdef ENABLE_FANOTIFY // --inotify or -I case 'I': (*fanotify) = 0; break; // --fanotify or -F case 'F': (*fanotify) = 1; break; // --filesystem or -S case 'S': (*filesystem) = true; (*fanotify) = 1; break; #endif case 'P': ++(*no_dereference); break; // --zero or -z case 'z': ++(*z); break; // --exclude case 'c': (*exc_regex) = optarg; break; // --excludei case 'b': (*exc_iregex) = optarg; break; // --include case 'j': (*inc_regex) = optarg; break; // --includei case 'k': (*inc_iregex) = optarg; break; // --fromfile case 'o': if (*fromfile) { fprintf(stderr, "Multiple --fromfile options " "given.\n"); return false; } (*fromfile) = optarg; break; // --timeout or -t case 't': if (!is_timeout_option_valid(timeout, optarg)) { return false; } break; // --event or -e case 'e': // Get event mask from event string new_event = inotifytools_str_to_event(optarg); // If optarg was invalid, abort if (new_event == -1) { fprintf( stderr, "'%s' is not a valid event! Run " "with the " "'--help' option to see a list of " "events.\n", optarg); return false; } // Add the new event to the event mask (*e) = ((*e) | new_event); break; // --ascending or -a case 'a': assert(optarg); if (sort_set) { fprintf(stderr, "Please specify -a or -d once " "only!\n"); return false; } if (0 == strcasecmp(optarg, "total")) { (*s) = 0; } else if (0 == strcasecmp(optarg, "move")) { fprintf( stderr, "Cannot sort by `move' event; " "please use " "`moved_from' or `moved_to'.\n"); return false; } else if (0 == strcasecmp(optarg, "close")) { fprintf(stderr, "Cannot sort by `close' event; " "please use " "`close_write' or " "`close_nowrite'.\n"); return false; } else { int event = inotifytools_str_to_event(optarg); // If optarg was invalid, abort if (event == -1) { fprintf(stderr, "'%s' is not a valid " "key for " "sorting!\n", optarg); return false; } (*s) = event; } sort_set = true; break; // --descending or -d case 'd': assert(optarg); if (sort_set) { fprintf(stderr, "Please specify -a or -d once " "only!\n"); return false; } if (0 == strcasecmp(optarg, "total")) { (*s) = -1; } else { int event = inotifytools_str_to_event(optarg); // If optarg was invalid, abort if (event == -1) { fprintf(stderr, "'%s' is not a valid " "key for " "sorting!\n", optarg); return false; } (*s) = -event; } break; } curr_opt = getopt_long(*argc, *argv, opt_string, long_opts, NULL); } (*argc) -= optind; *argv = &(*argv)[optind]; if ((*s) != 0 && (*s) != -1 && !(abs(*s) & ((*e) ? (*e) : IN_ALL_EVENTS))) { fprintf(stderr, "Can't sort by an event which isn't being watched " "for!\n"); return false; } if (*exc_regex && *exc_iregex) { fprintf(stderr, "--exclude and --excludei cannot both be specified.\n"); return false; } if (*inc_regex && *inc_iregex) { fprintf(stderr, "--include and --includei cannot both be specified.\n"); return false; } if ((*inc_regex && *exc_regex) || (*inc_regex && *exc_iregex) || (*inc_iregex && *exc_regex) || (*inc_iregex && *exc_iregex)) { fprintf( stderr, "include and exclude regexp cannot both be specified.\n"); return false; } // If ? returned, invalid option return (curr_opt != '?'); } #define TOOL_NAME TOOLS_PREFIX "watch" void print_help() { printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); printf("Gather filesystem usage statistics using %s.\n", TOOL_NAME); printf("Usage: %s [ options ] file1 [ file2 ] [ ... ]\n", TOOL_NAME); printf("Options:\n"); printf("\t-h|--help \tShow this help text.\n"); printf("\t-v|--verbose \tBe verbose.\n"); printf( "\t@ \tExclude the specified file from being " "watched.\n"); printf( "\t--fromfile \n" "\t\tRead files to watch from or `-' for stdin.\n"); printf( "\t--exclude \n" "\t\tExclude all events on files matching the extended regular\n" "\t\texpression .\n"); printf( "\t--excludei \n" "\t\tLike --exclude but case insensitive.\n"); printf( "\t--include \n" "\t\tExclude all events on files except the ones\n" "\t\tmatching the extended regular expression\n" "\t\t.\n"); printf( "\t--includei \n" "\t\tLike --include but case insensitive.\n"); printf( "\t-z|--zero\n" "\t\tIn the final table of results, output rows and columns even\n" "\t\tif they consist only of zeros (the default is to not output\n" "\t\tthese rows and columns).\n"); printf("\t-r|--recursive\tWatch directories recursively.\n"); #ifdef ENABLE_FANOTIFY printf("\t-I|--inotify\tWatch with inotify.\n"); printf("\t-F|--fanotify\tWatch with fanotify.\n"); printf("\t-S|--filesystem\tWatch entire filesystem with fanotify.\n"); #endif printf( "\t-P|--no-dereference\n" "\t\tDo not follow symlinks.\n"); printf( "\t-t|--timeout \n" "\t\tListen only for specified amount of time in seconds; if\n" "\t\tomitted or zero, %s will execute until receiving an\n" "\t\tinterrupt signal.\n", TOOL_NAME); printf( "\t-e|--event [ -e|--event ... ]\n" "\t\tListen for specific event(s). If omitted, all events are \n" "\t\tlistened for.\n"); printf( "\t-a|--ascending \n" "\t\tSort ascending by a particular event, or `total'.\n"); printf( "\t-d|--descending \n" "\t\tSort descending by a particular event, or `total'.\n\n"); printf("Exit status:\n"); printf("\t%d - Exited normally.\n", EXIT_SUCCESS); printf("\t%d - Some error occurred.\n\n", EXIT_FAILURE); printf("Events:\n"); print_event_descriptions(); } inotify-tools-3.22.6.0/t/000077500000000000000000000000001424760767400150455ustar00rootroot00000000000000inotify-tools-3.22.6.0/t/Makefile000066400000000000000000000031741424760767400165120ustar00rootroot00000000000000# Run tests # # Copyright (c) 2011-2012 Mathias Lafeldt # Copyright (c) 2005-2012 Git project # Copyright (c) 2005-2012 Junio C Hamano # # 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, see http://www.gnu.org/licenses/ . SHELL_PATH ?= /bin/bash RM ?= rm -f PROVE ?= prove AGGREGATE_SCRIPT ?= aggregate-results.sh DEFAULT_TEST_TARGET ?= test T = $(sort $(wildcard *.t)) all: $(DEFAULT_TEST_TARGET) test: pre-clean $(MAKE) aggregate-results-and-cleanup prove: pre-clean @echo "*** prove ***"; $(PROVE) --exec '$(SHELL_PATH)' $(PROVE_OPTS) $(T) :: $(TEST_OPTS) $(MAKE) clean-except-prove-cache $(T): @echo "*** $@ ***"; '$(SHELL_PATH)' $@ $(TEST_OPTS) pre-clean: $(RM) -r test-results clean-except-prove-cache: $(RM) -r 'trash.directory'.* test-results clean: clean-except-prove-cache $(RM) .prove aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results $(MAKE) clean aggregate-results: for f in test-results/*.counts; do \ echo "$$f"; \ done | '$(SHELL_PATH)' '$(AGGREGATE_SCRIPT)' .PHONY: all test prove $(T) pre-clean clean .PHONY: aggregate-results-and-cleanup aggregate-results inotify-tools-3.22.6.0/t/aggregate-results.sh000077500000000000000000000027041424760767400210340ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2008-2012 Git project # # 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, see http://www.gnu.org/licenses/ . failed_tests= fixed=0 success=0 failed=0 broken=0 total=0 while read file; do while read type value; do case $type in '') continue ;; fixed) fixed=$(($fixed + $value)) ;; success) success=$(($success + $value)) ;; failed) failed=$(($failed + $value)) if test $value != 0; then test_name=$(expr "$file" : 'test-results/\(.*\)\.[0-9]*\.counts') failed_tests="$failed_tests $test_name" fi ;; broken) broken=$(($broken + $value)) ;; total) total=$(($total + $value)) ;; esac done <"$file" done if test -n "$failed_tests"; then printf "\nfailed test(s):$failed_tests\n\n" fi printf "%-8s%d\n" fixed $fixed printf "%-8s%d\n" success $success printf "%-8s%d\n" failed $failed printf "%-8s%d\n" broken $broken printf "%-8s%d\n" total $total inotify-tools-3.22.6.0/t/fanotify-common.sh000066400000000000000000000006161424760767400205110ustar00rootroot00000000000000#!/bin/sh # Check for kernel support and privileges fanotify_supported() { ../../src/fsnotifywait --fanotify $* 2>&1 | grep -q 'No files specified' } # Create and mount a test filesystem mount_filesystem() { fstype=$1 size=$2 mnt=$3 rm -f img truncate -s $size img && mkfs.$fstype img && \ mkdir -p $mnt && mount -o loop img $mnt && \ df -t $fstype $mnt } inotify-tools-3.22.6.0/t/fsnotifywait-subtree.t000077500000000000000000000020631424760767400214330ustar00rootroot00000000000000#!/bin/sh test_description='Subtree watch Verify that fsnotifywait --recursive/--filesystem gets events on files created inside the watched subtree ' . ./fanotify-common.sh . ./sharness.sh logfile="log" run_() { export LD_LIBRARY_PATH="../../libinotifytools/src/" testdir=root/A/B/C/D rm -rf root/A && mkdir -p $testdir && {(sleep 1 && touch $testdir/test)&} && ../../src/$* \ --quiet \ --outfile $logfile \ --event CREATE \ --timeout 2 \ root } run_and_check_log() { rm -f $logfile run_ $* && grep 'CREATE.test$' $logfile } test_expect_success 'event logged' ' run_and_check_log inotifywait --recursive ' if fanotify_supported; then test_expect_success 'event logged' ' run_and_check_log fsnotifywait --fanotify --recursive ' fi if fanotify_supported --filesystem; then test_expect_success 'event logged' ' test_when_finished "umount -l root" && mount_filesystem ext2 10M root && run_and_check_log fsnotifywait --filesystem ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-csv-watched-dir.t000077500000000000000000000021211424760767400225610ustar00rootroot00000000000000#!/bin/sh test_description='Issue #157 Check --csv format of event on a file when watching the parent directory ' . ./fanotify-common.sh . ./sharness.sh logfile="log" watchpath="$(realpath .)" run_() { # Setup code, defer an ATTRIB event for after # inotifywait has been set up. timeout=2 && touch $logfile test-file && {(sleep 1 && chmod 777 test-file)&} && export LD_LIBRARY_PATH="../../libinotifytools/src/" ../../src/$* \ --csv \ --quiet \ --daemon \ --outfile $logfile \ --event ATTRIB \ --timeout $timeout \ "$watchpath" && # No way to use 'wait' for a process that is not a child of this one, # sleep instead until inotifywait's timeout is reached. sleep $timeout } run_and_check_log() { rm -f $logfile run_ $* && cp log /tmp/ && grep "^$watchpath/,ATTRIB,test-file\$" $logfile } test_expect_success 'event logged' ' run_and_check_log inotifywait ' if fanotify_supported; then test_expect_success 'event logged' ' run_and_check_log fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-csv-watched-file.t000077500000000000000000000016511424760767400227310ustar00rootroot00000000000000#!/bin/sh test_description='Issue #157 Check --csv format of event on a file when watching the file itself ' . ./sharness.sh logfile="log" watchpath="$(realpath test-file)" run_() { # Setup code, defer an ATTRIB event for after # inotifywait has been set up. timeout=2 && touch $logfile test-file && {(sleep 1 && chmod 777 test-file)&} && export LD_LIBRARY_PATH="../../libinotifytools/src/" ../../src/$* \ --csv \ --quiet \ --daemon \ --outfile $logfile \ --event ATTRIB \ --timeout $timeout \ "$watchpath" && # No way to use 'wait' for a process that is not a child of this one, # sleep instead until inotifywait's timeout is reached. sleep $timeout } run_and_check_log() { rm -f $logfile run_ $* && grep "^$watchpath,ATTRIB,\$" $logfile } test_expect_success 'event logged' ' run_and_check_log inotifywait ' test_done inotify-tools-3.22.6.0/t/inotifywait-daemon-logs-chown.t000077500000000000000000000020401424760767400231160ustar00rootroot00000000000000#!/bin/sh test_description='Issue #62 When --daemon is used, events are logged correctly to --outfile even if that is a relative path ' . ./fanotify-common.sh . ./sharness.sh logfile="log" run_() { # Setup code, defer an ATTRIB event for after # inotifywait has been set up. timeout=2 && touch $logfile test-file && {(sleep 1 && chown $(id -u) test-file)&} && export LD_LIBRARY_PATH="../../libinotifytools/src/" ../../src/$* \ --quiet \ --daemon \ --outfile $logfile \ --event ATTRIB \ --timeout $timeout \ $(realpath test-file) && # No way to use 'wait' for a process that is not a child of this one, # sleep instead until inotifywait's timeout is reached. sleep $timeout } run_and_check_log() { rm -f $logfile run_ $* && grep ATTRIB $logfile } test_expect_success 'event logged' ' run_and_check_log inotifywait ' if fanotify_supported; then test_expect_success 'event logged' ' run_and_check_log fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-daemon-logs-to-relative-paths.t000077500000000000000000000020331424760767400253520ustar00rootroot00000000000000#!/bin/sh test_description='Issue #62 When --daemon is used, events are logged correctly to --outfile even if that is a relative path ' . ./fanotify-common.sh . ./sharness.sh logfile="log" run_() { # Setup code, defer an ATTRIB event for after # inotifywait has been set up. timeout=2 && touch $logfile test-file && {(sleep 1 && chmod 777 test-file)&} && export LD_LIBRARY_PATH="../../libinotifytools/src/" ../../src/$* \ --quiet \ --daemon \ --outfile $logfile \ --event ATTRIB \ --timeout $timeout \ $(realpath test-file) && # No way to use 'wait' for a process that is not a child of this one, # sleep instead until inotifywait's timeout is reached. sleep $timeout } run_and_check_log() { rm -f $logfile run_ $* && grep ATTRIB $logfile } test_expect_success 'event logged' ' run_and_check_log inotifywait ' if fanotify_supported; then test_expect_success 'event logged' ' run_and_check_log fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-format-option-cookie.t000077500000000000000000000024741424760767400236550ustar00rootroot00000000000000#!/bin/bash test_description='Resolves Issue #72 Make transaction id (cookie) available as part of the format string using %c' . ./fanotify-common.sh . ./sharness.sh logfile="log" run_() { # Setup code, defer an ATTRIB event for after # inotifywait has been set up. touch $logfile export LD_LIBRARY_PATH="../../libinotifytools/src/" ../../src/$* \ --timeout 4 \ --monitor \ --daemon \ --quiet \ --outfile $logfile \ --format '%c %e %w%f' \ --event create \ --event moved_to \ --event moved_from \ $(realpath ./) sleep 1 touch test-file-src mv test-file-src test-file-dst sleep 1 } run_and_check_log() { set -e trap "set +e" RETURN rm -f "${logfile}" run_ $* local NONCOOKIE="$(cat "${logfile}" | sed -n 1p | grep -Eo "^[^ ]+")" #Make sure cookie is 0 for single events [[ "${NONCOOKIE}" == "0" ]] || return 1 local COOKIE_A="$(cat "${logfile}" | sed -n 2p | grep -Eo "^[^ ]+")" [[ -n "${COOKIE_A}" ]] || return 2 local COOKIE_B="$(cat "${logfile}" | sed -n 3p | grep -Eo "^[^ ]+")" [[ "${COOKIE_A}" == "${COOKIE_B}" ]] || return 3 cat "${logfile}" } test_expect_success 'event logged' ' run_and_check_log inotifywait ' if fanotify_supported; then test_expect_success 'event logged' ' run_and_check_log fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-format-option-null.t000077500000000000000000000017361424760767400233560ustar00rootroot00000000000000#!/bin/bash test_description='Check producing NUL-delimited output' . ./fanotify-common.sh . ./sharness.sh logfile="log" run_() { touch $logfile export LD_LIBRARY_PATH="../../libinotifytools/src/" ../../src/$* \ --monitor \ --quiet \ --outfile $logfile \ --format '%w%f%0' \ --no-newline \ --event create \ --event moved_to \ --event moved_from \ $(realpath ./) & PID="$!" sleep 1 touch test-file-src mv test-file-src test-file-dst sleep 1 kill ${PID} } run_and_check_log() { set -e trap "set +e" RETURN rm -f "${logfile}" run_ $* srcfile="${PWD}/test-file-src" dstfile="${PWD}/test-file-dst" return $(printf "${srcfile}\0${srcfile}\0${dstfile}\0" | cmp -s "${logfile}") } test_expect_success 'the output is delimited by NUL' ' run_and_check_log inotifywait ' if fanotify_supported; then test_expect_success 'the output is delimited by NUL' ' run_and_check_log fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-format-watched-dir.t000077500000000000000000000021031424760767400232560ustar00rootroot00000000000000#!/bin/sh test_description='Issue #157 Check printed format of event on a file when watching the parent directory ' . ./fanotify-common.sh . ./sharness.sh logfile="log" watchpath="$(realpath .)" run_() { # Setup code, defer an ATTRIB event for after # inotifywait has been set up. timeout=2 && touch $logfile test-file && {(sleep 1 && chmod 777 test-file)&} && export LD_LIBRARY_PATH="../../libinotifytools/src/" ../../src/$* \ --quiet \ --daemon \ --outfile $logfile \ --event ATTRIB \ --timeout $timeout \ "$watchpath" && # No way to use 'wait' for a process that is not a child of this one, # sleep instead until inotifywait's timeout is reached. sleep $timeout } run_and_check_log() { rm -f $logfile run_ $* && cp log /tmp/ && grep "^$watchpath/ ATTRIB test-file\$" $logfile } test_expect_success 'event logged' ' run_and_check_log inotifywait ' if fanotify_supported; then test_expect_success 'event logged' ' run_and_check_log fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-format-watched-file.t000077500000000000000000000016331424760767400234260ustar00rootroot00000000000000#!/bin/sh test_description='Issue #157 Check printed format of event on a file when watching the file itself ' . ./sharness.sh logfile="log" watchpath="$(realpath test-file)" run_() { # Setup code, defer an ATTRIB event for after # inotifywait has been set up. timeout=2 && touch $logfile test-file && {(sleep 1 && chmod 777 test-file)&} && export LD_LIBRARY_PATH="../../libinotifytools/src/" ../../src/$* \ --quiet \ --daemon \ --outfile $logfile \ --event ATTRIB \ --timeout $timeout \ "$watchpath" && # No way to use 'wait' for a process that is not a child of this one, # sleep instead until inotifywait's timeout is reached. sleep $timeout } run_and_check_log() { rm -f $logfile run_ $* && grep "^$watchpath ATTRIB \$" $logfile } test_expect_success 'event logged' ' run_and_check_log inotifywait ' test_done inotify-tools-3.22.6.0/t/inotifywait-no-dereference-ignore-symlinked-file.t000077500000000000000000000011601424760767400266530ustar00rootroot00000000000000#!/bin/sh test_description='--no-dereference causes inotifywait to ignore events on symlink target' . ./fanotify-common.sh . ./sharness.sh run_() { export LD_LIBRARY_PATH="../../libinotifytools/src/" rm -f test-symlink touch test && ln -s test test-symlink && {(sleep 1 && touch test)&} && ../../src/$* --quiet --no-dereference --timeout 2 test-symlink } test_expect_success 'Exit code 2 is returned' ' test_expect_code 2 run_ inotifywait ' if fanotify_supported; then test_expect_success 'Exit code 2 is returned' ' test_expect_code 2 run_ fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-no-dereference-respond-to-symlink-event.t000077500000000000000000000011221424760767400273510ustar00rootroot00000000000000#!/bin/sh test_description='--no-dereference causes inotifywait to respond to events on symlink' . ./fanotify-common.sh . ./sharness.sh run_() { export LD_LIBRARY_PATH="../../libinotifytools/src/" rm -f test-symlink touch test && ln -s test test-symlink && {(sleep 1 && touch -h test-symlink)&} && ../../src/$* --quiet --no-dereference --timeout 2 test-symlink } test_expect_success 'Exit code 0 is returned' ' run_ inotifywait ' if fanotify_supported; then test_expect_success 'Exit code 0 is returned' ' run_ fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/inotifywait-no-event-occured.t000077500000000000000000000007371424760767400227650ustar00rootroot00000000000000#!/bin/sh test_description='No event occured for inotifywait' . ./fanotify-common.sh . ./sharness.sh run_() { export LD_LIBRARY_PATH="../../libinotifytools/src/" touch test && ../../src/$* --quiet --timeout 1 test } test_expect_success 'Exit code 2 is returned' ' test_expect_code 2 run_ inotifywait ' if fanotify_supported; then test_expect_success 'Exit code 2 is returned' ' test_expect_code 2 run_ fsnotifywait --fanotify ' fi test_done inotify-tools-3.22.6.0/t/sharness.sh000066400000000000000000000554121424760767400172360ustar00rootroot00000000000000#!/bin/sh # # Copyright (c) 2011-2012 Mathias Lafeldt # Copyright (c) 2005-2012 Git project # Copyright (c) 2005-2012 Junio C Hamano # # 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, see http://www.gnu.org/licenses/ . # Public: Current version of Sharness. SHARNESS_VERSION="1.0.0" export SHARNESS_VERSION # Public: The file extension for tests. By default, it is set to "t". : ${SHARNESS_TEST_EXTENSION:=t} export SHARNESS_TEST_EXTENSION # Reset TERM to original terminal if found, otherwise save orignal TERM [ "x" = "x$SHARNESS_ORIG_TERM" ] && SHARNESS_ORIG_TERM="$TERM" || TERM="$SHARNESS_ORIG_TERM" # Public: The unsanitized TERM under which sharness is originally run export SHARNESS_ORIG_TERM # Export SHELL_PATH : ${SHELL_PATH:=$SHELL} export SHELL_PATH # For repeatability, reset the environment to a known state. # TERM is sanitized below, after saving color control sequences. LANG=C LC_ALL=C PAGER=cat TZ=UTC EDITOR=: export LANG LC_ALL PAGER TZ EDITOR unset VISUAL CDPATH GREP_OPTIONS # Line feed LF=' ' [ "x$TERM" != "xdumb" ] && ( [ -t 1 ] && tput bold >/dev/null 2>&1 && tput setaf 1 >/dev/null 2>&1 && tput sgr0 >/dev/null 2>&1 ) && color=t while test "$#" -ne 0; do case "$1" in -d|--d|--de|--deb|--debu|--debug) debug=t; shift ;; -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) immediate=t; shift ;; -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) TEST_LONG=t; export TEST_LONG; shift ;; --in|--int|--inte|--inter|--intera|--interac|--interact|--interacti|--interactiv|--interactive|--interactive-|--interactive-t|--interactive-te|--interactive-tes|--interactive-test|--interactive-tests): TEST_INTERACTIVE=t; export TEST_INTERACTIVE; verbose=t; shift ;; -h|--h|--he|--hel|--help) help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; -q|--q|--qu|--qui|--quie|--quiet) # Ignore --quiet under a TAP::Harness. Saying how many tests # passed without the ok/not ok details is always an error. test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; --chain-lint) chain_lint=t; shift ;; --no-chain-lint) chain_lint=; shift ;; --no-color) color=; shift ;; --root=*) root=$(expr "z$1" : 'z[^=]*=\(.*\)') shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac done if test -n "$color"; then # Save the color control sequences now rather than run tput # each time say_color() is called. This is done for two # reasons: # * TERM will be changed to dumb # * HOME will be changed to a temporary directory and tput # might need to read ~/.terminfo from the original HOME # directory to get the control sequences # Note: This approach assumes the control sequences don't end # in a newline for any terminal of interest (command # substitutions strip trailing newlines). Given that most # (all?) terminals in common use are related to ECMA-48, this # shouldn't be a problem. say_color_error=$(tput bold; tput setaf 1) # bold red say_color_skip=$(tput setaf 4) # blue say_color_warn=$(tput setaf 3) # brown/yellow say_color_pass=$(tput setaf 2) # green say_color_info=$(tput setaf 6) # cyan say_color_reset=$(tput sgr0) say_color_="" # no formatting for normal text say_color() { test -z "$1" && test -n "$quiet" && return eval "say_color_color=\$say_color_$1" shift printf "%s\\n" "$say_color_color$*$say_color_reset" } else say_color() { test -z "$1" && test -n "$quiet" && return shift printf "%s\n" "$*" } fi TERM=dumb export TERM error() { say_color error "error: $*" EXIT_OK=t exit 1 } say() { say_color info "$*" } test -n "$test_description" || error "Test script did not set test_description." if test "$help" = "t"; then echo "$test_description" exit 0 fi exec 5>&1 exec 6<&0 if test "$verbose" = "t"; then exec 4>&2 3>&1 else exec 4>/dev/null 3>/dev/null fi test_failure=0 test_count=0 test_fixed=0 test_broken=0 test_success=0 die() { code=$? if test -n "$EXIT_OK"; then exit $code else echo >&5 "FATAL: Unexpected exit with code $code" exit 1 fi } EXIT_OK= trap 'die' EXIT # Public: Define that a test prerequisite is available. # # The prerequisite can later be checked explicitly using test_have_prereq or # implicitly by specifying the prerequisite name in calls to test_expect_success # or test_expect_failure. # # $1 - Name of prerequiste (a simple word, in all capital letters by convention) # # Examples # # # Set PYTHON prerequisite if interpreter is available. # command -v python >/dev/null && test_set_prereq PYTHON # # # Set prerequisite depending on some variable. # test -z "$NO_GETTEXT" && test_set_prereq GETTEXT # # Returns nothing. test_set_prereq() { satisfied_prereq="$satisfied_prereq$1 " } satisfied_prereq=" " # Public: Check if one or more test prerequisites are defined. # # The prerequisites must have previously been set with test_set_prereq. # The most common use of this is to skip all the tests if some essential # prerequisite is missing. # # $1 - Comma-separated list of test prerequisites. # # Examples # # # Skip all remaining tests if prerequisite is not set. # if ! test_have_prereq PERL; then # skip_all='skipping perl interface tests, perl not available' # test_done # fi # # Returns 0 if all prerequisites are defined or 1 otherwise. test_have_prereq() { # prerequisites can be concatenated with ',' save_IFS=$IFS IFS=, set -- $* IFS=$save_IFS total_prereq=0 ok_prereq=0 missing_prereq= for prerequisite; do case "$prerequisite" in !*) negative_prereq=t prerequisite=${prerequisite#!} ;; *) negative_prereq= esac total_prereq=$(($total_prereq + 1)) case "$satisfied_prereq" in *" $prerequisite "*) satisfied_this_prereq=t ;; *) satisfied_this_prereq= esac case "$satisfied_this_prereq,$negative_prereq" in t,|,t) ok_prereq=$(($ok_prereq + 1)) ;; *) # Keep a list of missing prerequisites; restore # the negative marker if necessary. prerequisite=${negative_prereq:+!}$prerequisite if test -z "$missing_prereq"; then missing_prereq=$prerequisite else missing_prereq="$prerequisite,$missing_prereq" fi esac done test $total_prereq = $ok_prereq } # You are not expected to call test_ok_ and test_failure_ directly, use # the text_expect_* functions instead. test_ok_() { test_success=$(($test_success + 1)) say_color "" "ok $test_count - $@" } test_failure_() { test_failure=$(($test_failure + 1)) say_color error "not ok $test_count - $1" shift echo "$@" | sed -e 's/^/# /' test "$immediate" = "" || { EXIT_OK=t; exit 1; } } test_known_broken_ok_() { test_fixed=$(($test_fixed + 1)) say_color error "ok $test_count - $@ # TODO known breakage vanished" } test_known_broken_failure_() { test_broken=$(($test_broken + 1)) say_color warn "not ok $test_count - $@ # TODO known breakage" } # Public: Execute commands in debug mode. # # Takes a single argument and evaluates it only when the test script is started # with --debug. This is primarily meant for use during the development of test # scripts. # # $1 - Commands to be executed. # # Examples # # test_debug "cat some_log_file" # # Returns the exit code of the last command executed in debug mode or 0 # otherwise. test_debug() { test "$debug" = "" || eval "$1" } # Public: Stop execution and start a shell. # # This is useful for debugging tests and only makes sense together with "-v". # Be sure to remove all invocations of this command before submitting. test_pause() { if test "$verbose" = t; then "$SHELL_PATH" <&6 >&3 2>&4 else error >&5 "test_pause requires --verbose" fi } test_eval_() { # This is a separate function because some tests use # "return" to end a test_expect_success block early. case ",$test_prereq," in *,INTERACTIVE,*) eval "$*" ;; *) eval &3 2>&4 "$*" ;; esac } test_run_() { test_cleanup=: expecting_failure=$2 test_eval_ "$1" eval_ret=$? if test "$chain_lint" = "t"; then test_eval_ "(exit 117) && $1" if test "$?" != 117; then error "bug in the test script: broken &&-chain: $1" fi fi if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"; then test_eval_ "$test_cleanup" fi if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then echo "" fi return "$eval_ret" } test_skip_() { test_count=$(($test_count + 1)) to_skip= for skp in $SKIP_TESTS; do case $this_test.$test_count in $skp) to_skip=t break esac done if test -z "$to_skip" && test -n "$test_prereq" && ! test_have_prereq "$test_prereq"; then to_skip=t fi case "$to_skip" in t) of_prereq= if test "$missing_prereq" != "$test_prereq"; then of_prereq=" of $test_prereq" fi say_color skip >&3 "skipping test: $@" say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})" : true ;; *) false ;; esac } # Public: Run test commands and expect them to succeed. # # When the test passed, an "ok" message is printed and the number of successful # tests is incremented. When it failed, a "not ok" message is printed and the # number of failed tests is incremented. # # With --immediate, exit test immediately upon the first failed test. # # Usually takes two arguments: # $1 - Test description # $2 - Commands to be executed. # # With three arguments, the first will be taken to be a prerequisite: # $1 - Comma-separated list of test prerequisites. The test will be skipped if # not all of the given prerequisites are set. To negate a prerequisite, # put a "!" in front of it. # $2 - Test description # $3 - Commands to be executed. # # Examples # # test_expect_success \ # 'git-write-tree should be able to write an empty tree.' \ # 'tree=$(git-write-tree)' # # # Test depending on one prerequisite. # test_expect_success TTY 'git --paginate rev-list uses a pager' \ # ' ... ' # # # Multiple prerequisites are separated by a comma. # test_expect_success PERL,PYTHON 'yo dawg' \ # ' test $(perl -E 'print eval "1 +" . qx[python -c "print 2"]') == "4" ' # # Returns nothing. test_expect_success() { test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_success" export test_prereq if ! test_skip_ "$@"; then say >&3 "expecting success: $2" if test_run_ "$2"; then test_ok_ "$1" else test_failure_ "$@" fi fi echo >&3 "" } # Public: Run test commands and expect them to fail. Used to demonstrate a known # breakage. # # This is NOT the opposite of test_expect_success, but rather used to mark a # test that demonstrates a known breakage. # # When the test passed, an "ok" message is printed and the number of fixed tests # is incremented. When it failed, a "not ok" message is printed and the number # of tests still broken is incremented. # # Failures from these tests won't cause --immediate to stop. # # Usually takes two arguments: # $1 - Test description # $2 - Commands to be executed. # # With three arguments, the first will be taken to be a prerequisite: # $1 - Comma-separated list of test prerequisites. The test will be skipped if # not all of the given prerequisites are set. To negate a prerequisite, # put a "!" in front of it. # $2 - Test description # $3 - Commands to be executed. # # Returns nothing. test_expect_failure() { test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_failure" export test_prereq if ! test_skip_ "$@"; then say >&3 "checking known breakage: $2" if test_run_ "$2" expecting_failure; then test_known_broken_ok_ "$1" else test_known_broken_failure_ "$1" fi fi echo >&3 "" } # Public: Run command and ensure that it fails in a controlled way. # # Use it instead of "! ". For example, when dies due to a # segfault, test_must_fail diagnoses it as an error, while "! " would # mistakenly be treated as just another expected failure. # # This is one of the prefix functions to be used inside test_expect_success or # test_expect_failure. # # $1.. - Command to be executed. # # Examples # # test_expect_success 'complain and die' ' # do something && # do something else && # test_must_fail git checkout ../outerspace # ' # # Returns 1 if the command succeeded (exit code 0). # Returns 1 if the command died by signal (exit codes 130-192) # Returns 1 if the command could not be found (exit code 127). # Returns 0 otherwise. test_must_fail() { "$@" exit_code=$? if test $exit_code = 0; then echo >&2 "test_must_fail: command succeeded: $*" return 1 elif test $exit_code -gt 129 -a $exit_code -le 192; then echo >&2 "test_must_fail: died by signal: $*" return 1 elif test $exit_code = 127; then echo >&2 "test_must_fail: command not found: $*" return 1 fi return 0 } # Public: Run command and ensure that it succeeds or fails in a controlled way. # # Similar to test_must_fail, but tolerates success too. Use it instead of # " || :" to catch failures caused by a segfault, for instance. # # This is one of the prefix functions to be used inside test_expect_success or # test_expect_failure. # # $1.. - Command to be executed. # # Examples # # test_expect_success 'some command works without configuration' ' # test_might_fail git config --unset all.configuration && # do something # ' # # Returns 1 if the command died by signal (exit codes 130-192) # Returns 1 if the command could not be found (exit code 127). # Returns 0 otherwise. test_might_fail() { "$@" exit_code=$? if test $exit_code -gt 129 -a $exit_code -le 192; then echo >&2 "test_might_fail: died by signal: $*" return 1 elif test $exit_code = 127; then echo >&2 "test_might_fail: command not found: $*" return 1 fi return 0 } # Public: Run command and ensure it exits with a given exit code. # # This is one of the prefix functions to be used inside test_expect_success or # test_expect_failure. # # $1 - Expected exit code. # $2.. - Command to be executed. # # Examples # # test_expect_success 'Merge with d/f conflicts' ' # test_expect_code 1 git merge "merge msg" B master # ' # # Returns 0 if the expected exit code is returned or 1 otherwise. test_expect_code() { want_code=$1 shift "$@" exit_code=$? if test $exit_code = $want_code; then return 0 fi echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" return 1 } # Public: Compare two files to see if expected output matches actual output. # # The TEST_CMP variable defines the command used for the comparision; it # defaults to "diff -u". Only when the test script was started with --verbose, # will the command's output, the diff, be printed to the standard output. # # This is one of the prefix functions to be used inside test_expect_success or # test_expect_failure. # # $1 - Path to file with expected output. # $2 - Path to file with actual output. # # Examples # # test_expect_success 'foo works' ' # echo expected >expected && # foo >actual && # test_cmp expected actual # ' # # Returns the exit code of the command set by TEST_CMP. test_cmp() { ${TEST_CMP:-diff -u} "$@" } # Public: portably print a sequence of numbers. # # seq is not in POSIX and GNU seq might not be available everywhere, # so it is nice to have a seq implementation, even a very simple one. # # $1 - Starting number. # $2 - Ending number. # # Examples # # test_expect_success 'foo works 10 times' ' # for i in $(test_seq 1 10) # do # foo || return # done # ' # # Returns 0 if all the specified numbers can be displayed. test_seq() { i="$1" j="$2" while test "$i" -le "$j" do echo "$i" || return i=$(expr "$i" + 1) done } # Public: Check if the file expected to be empty is indeed empty, and barfs # otherwise. # # $1 - File to check for emptyness. # # Returns 0 if file is empty, 1 otherwise. test_must_be_empty() { if test -s "$1" then echo "'$1' is not empty, it contains:" cat "$1" return 1 fi } # debugging-friendly alternatives to "test [-f|-d|-e]" # The commands test the existence or non-existence of $1. $2 can be # given to provide a more precise diagnosis. test_path_is_file () { if ! test -f "$1" then echo "File $1 doesn't exist. $2" false fi } test_path_is_dir () { if ! test -d "$1" then echo "Directory $1 doesn't exist. $2" false fi } # Check if the directory exists and is empty as expected, barf otherwise. test_dir_is_empty () { test_path_is_dir "$1" && if test -n "$(ls -a1 "$1" | egrep -v '^\.\.?$')" then echo "Directory '$1' is not empty, it contains:" ls -la "$1" return 1 fi } # Public: Schedule cleanup commands to be run unconditionally at the end of a # test. # # If some cleanup command fails, the test will not pass. With --immediate, no # cleanup is done to help diagnose what went wrong. # # This is one of the prefix functions to be used inside test_expect_success or # test_expect_failure. # # $1.. - Commands to prepend to the list of cleanup commands. # # Examples # # test_expect_success 'test core.capslock' ' # git config core.capslock true && # test_when_finished "git config --unset core.capslock" && # do_something # ' # # Returns the exit code of the last cleanup command executed. test_when_finished() { test_cleanup="{ $* } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" } # Public: Schedule cleanup commands to be run unconditionally when all tests # have run. # # This can be used to clean up things like test databases. It is not needed to # clean up temporary files, as test_done already does that. # # Examples: # # cleanup mysql -e "DROP DATABASE mytest" # # Returns the exit code of the last cleanup command executed. final_cleanup= cleanup() { final_cleanup="{ $* } && (exit \"\$eval_ret\"); eval_ret=\$?; $final_cleanup" } # Public: Summarize test results and exit with an appropriate error code. # # Must be called at the end of each test script. # # Can also be used to stop tests early and skip all remaining tests. For this, # set skip_all to a string explaining why the tests were skipped before calling # test_done. # # Examples # # # Each test script must call test_done at the end. # test_done # # # Skip all remaining tests if prerequisite is not set. # if ! test_have_prereq PERL; then # skip_all='skipping perl interface tests, perl not available' # test_done # fi # # Returns 0 if all tests passed or 1 if there was a failure. test_done() { EXIT_OK=t if test -z "$HARNESS_ACTIVE"; then test_results_dir="$SHARNESS_TEST_DIRECTORY/test-results" mkdir -p "$test_results_dir" test_results_path="$test_results_dir/$this_test.$$.counts" cat >>"$test_results_path" <<-EOF total $test_count success $test_success fixed $test_fixed broken $test_broken failed $test_failure EOF fi if test "$test_fixed" != 0; then say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" fi if test "$test_broken" != 0; then say_color warn "# still have $test_broken known breakage(s)" fi if test "$test_broken" != 0 || test "$test_fixed" != 0; then test_remaining=$(( $test_count - $test_broken - $test_fixed )) msg="remaining $test_remaining test(s)" else test_remaining=$test_count msg="$test_count test(s)" fi case "$test_failure" in 0) # Maybe print SKIP message if test -n "$skip_all" && test $test_count -gt 0; then error "Can't use skip_all after running some tests" fi [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all" if test $test_remaining -gt 0; then say_color pass "# passed all $msg" fi say "1..$test_count$skip_all" test_eval_ "$final_cleanup" test -d "$remove_trash" && cd "$(dirname "$remove_trash")" && rm -rf "$(basename "$remove_trash")" exit 0 ;; *) say_color error "# failed $test_failure among $msg" say "1..$test_count" exit 1 ;; esac } # Public: Root directory containing tests. Tests can override this variable, # e.g. for testing Sharness itself. : ${SHARNESS_TEST_DIRECTORY:=$(pwd)} export SHARNESS_TEST_DIRECTORY # Public: Source directory of test code and sharness library. # This directory may be different from the directory in which tests are # being run. : ${SHARNESS_TEST_SRCDIR:=$(cd $(dirname $0) && pwd)} export SHARNESS_TEST_SRCDIR # Public: Build directory that will be added to PATH. By default, it is set to # the parent directory of SHARNESS_TEST_DIRECTORY. : ${SHARNESS_BUILD_DIRECTORY:="$SHARNESS_TEST_DIRECTORY/.."} PATH="$SHARNESS_BUILD_DIRECTORY:$PATH" export PATH SHARNESS_BUILD_DIRECTORY # Public: Path to test script currently executed. SHARNESS_TEST_FILE="$0" export SHARNESS_TEST_FILE # Prepare test area. SHARNESS_TRASH_DIRECTORY="trash.directory.$(basename "$SHARNESS_TEST_FILE" ".$SHARNESS_TEST_EXTENSION")" test -n "$root" && SHARNESS_TRASH_DIRECTORY="$root/$SHARNESS_TRASH_DIRECTORY" case "$SHARNESS_TRASH_DIRECTORY" in /*) ;; # absolute path is good *) SHARNESS_TRASH_DIRECTORY="$SHARNESS_TEST_DIRECTORY/$SHARNESS_TRASH_DIRECTORY" ;; esac test "$debug" = "t" || remove_trash="$SHARNESS_TRASH_DIRECTORY" rm -rf "$SHARNESS_TRASH_DIRECTORY" || { EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" exit 1 } # # Load any extensions in $srcdir/sharness.d/*.sh # if test -d "${SHARNESS_TEST_SRCDIR}/sharness.d" then for file in "${SHARNESS_TEST_SRCDIR}"/sharness.d/*.sh do # Ensure glob was not an empty match: test -e "${file}" || break if test -n "$debug" then echo >&5 "sharness: loading extensions from ${file}" fi . "${file}" if test $? != 0 then echo >&5 "sharness: Error loading ${file}. Aborting." exit 1 fi done fi # Public: Empty trash directory, the test area, provided for each test. The HOME # variable is set to that directory too. export SHARNESS_TRASH_DIRECTORY HOME="$SHARNESS_TRASH_DIRECTORY" export HOME mkdir -p "$SHARNESS_TRASH_DIRECTORY" || exit 1 # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). cd -P "$SHARNESS_TRASH_DIRECTORY" || exit 1 this_test=${SHARNESS_TEST_FILE##*/} this_test=${this_test%.$SHARNESS_TEST_EXTENSION} for skp in $SKIP_TESTS; do case "$this_test" in $skp) say_color info >&3 "skipping test $this_test altogether" skip_all="skip all tests in $this_test" test_done esac done test -n "$TEST_LONG" && test_set_prereq EXPENSIVE test -n "$TEST_INTERACTIVE" && test_set_prereq INTERACTIVE # Make sure this script ends with code 0 : # vi: set ts=4 sw=4 noet : inotify-tools-3.22.6.0/upload_api_docs000077500000000000000000000007221424760767400176560ustar00rootroot00000000000000#!/bin/sh cd libinotifytools/src || exit 1 doxygen || exit 1 for file in doc/html/*.html; do sed -r -i -e 's||\ \ \ \ |' "$file" || exit 1 done scp doc/html/* ro_han@shell.sourceforge.net:/home/users/r/ro/ro_han/inotify-tools/htdocs/api || exit 1