зеркало из https://github.com/github/libprojfs.git
initial
A dump of the initial state of the project. The testing infrastructure (and indeed, most of this in general) is Chris' work. Co-authored-by: Chris Darroch <chrisd8088@github.com>
This commit is contained in:
Коммит
a2b8541645
|
@ -0,0 +1,37 @@
|
|||
/aclocal.m4
|
||||
/autom4te.cache/
|
||||
/compile
|
||||
/config.cache
|
||||
/config.guess
|
||||
/config.log
|
||||
/config.sh
|
||||
/config.status
|
||||
/config.sub
|
||||
/configure
|
||||
/depcomp
|
||||
/include/config.h
|
||||
/include/config.h.in
|
||||
/include/stamp-h*
|
||||
/install-sh
|
||||
/libtool
|
||||
/ltmain.sh
|
||||
/m4/
|
||||
/missing
|
||||
/projfs.pc
|
||||
/t/.prove
|
||||
/t/test_*
|
||||
/t/*.trs
|
||||
!/t/test_*.c
|
||||
!/t/test_*.h
|
||||
/t/test-mounts/
|
||||
/t/test-output/
|
||||
/t/get_strerror
|
||||
/t/wait_mount
|
||||
.deps/
|
||||
.libs/
|
||||
*.la
|
||||
*.lo
|
||||
*.log
|
||||
*.o
|
||||
Makefile
|
||||
Makefile.in
|
|
@ -0,0 +1,502 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
|
@ -0,0 +1,339 @@
|
|||
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 Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
|
@ -0,0 +1,35 @@
|
|||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
SUBDIRS = include lib
|
||||
DIST_SUBDIRS = $(SUBDIRS) t
|
||||
|
||||
EXTRA_DIST = COPYING.LIB COPYING.TESTLIB NOTICE README.md \
|
||||
config.sh.in projfs.pc.in tap-driver.sh
|
||||
|
||||
if ENABLE_VFSAPI
|
||||
EXTRA_DIST += COPYING.VFSAPI
|
||||
endif ENABLE_VFSAPI
|
||||
|
||||
pkgconfigdir = @pkgconfigdir@
|
||||
pkgconfig_DATA = projfs.pc
|
||||
|
||||
$(pkgconfig_DATA): config.status
|
||||
|
||||
check-local:
|
||||
$(MAKE) -C t/ check
|
||||
|
||||
.PHONY: prove
|
||||
prove: all
|
||||
$(MAKE) -C t/ prove
|
||||
|
||||
.PHONY: test
|
||||
test: check
|
||||
|
||||
.PHONY: recheck
|
||||
recheck:
|
||||
$(MAKE) -C t/ recheck
|
||||
|
||||
.PHONY: gitclean
|
||||
gitclean:
|
||||
@-git clean -xdf
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
This library contains code derived from other open source projects.
|
||||
You can find the source code of these projects along with their
|
||||
license information below.
|
||||
|
||||
Project: libfuse (Linux Filesystem in USErspace reference implementation)
|
||||
Copyright (C) Miklos Szeredi <miklos@szeredi.hu> and FUSE contributors.
|
||||
https://github.com/libfuse/libfuse
|
||||
License: LGPLv2
|
||||
|
||||
Project: VFSforGit (Virtual Filesystem for Git)
|
||||
Copyright (C) Microsoft Corporation.
|
||||
https://github.com/Microsoft/VFSforGit
|
||||
License: MIT
|
||||
|
||||
Project: Git (stupid content tracker)
|
||||
Copyright (C) Junio C Hamano <gitster@pobox.com> and Git contributors.
|
||||
https://git-scm.com/
|
||||
License: GPLv2
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# libprojfs
|
||||
|
||||
A Linux projected filesystem library, similar in concept to the Windows
|
||||
[Projected File System][winprojfs] and developed in conjunction with the
|
||||
[VFSforGit][vfs4git] project.
|
||||
|
||||
## Design
|
||||
|
||||
*TBD*
|
||||
|
||||
## Getting Started
|
||||
|
||||
*TBD*
|
||||
|
||||
## Contributing
|
||||
|
||||
*TBD with Code of Conduct*
|
||||
|
||||
## Licensing
|
||||
|
||||
The primary libprojfs source code, including header files, is licensed
|
||||
under the [GNU Lesser General Public License, version 2.1][lgpl-v2];
|
||||
see the [COPYING.LIB](COPYING.LIB) file for details.
|
||||
|
||||
The VFSforGit API is licensed under the [MIT License][mit]; see the
|
||||
[COPYING.VFSAPI](COPYING.VFSAPI) file for details. This specifically
|
||||
means the [projfs_vfsapi.h](include/projfs_vfsapi.h) header file is
|
||||
cross-licensed under both the LGPLv2.1 and MIT licenses.
|
||||
|
||||
Some other portions of this project, such as the test scripts and
|
||||
test script libraries, and files from the [GNU Build System][gnu-build],
|
||||
are licensed under the [GNU General Public License, version 2][gpl-v2];
|
||||
see the [COPYING.TESTLIB](COPYING.TESTLIB) file for details. These
|
||||
files are not compiled into the libprojfs library but are used for
|
||||
build and test functionality only.
|
||||
|
||||
## Development Roadmap
|
||||
|
||||
We are developing the libprojfs library first, using FUSE to prototype
|
||||
and test its performance, before migrating functionality into a
|
||||
Linux kernel module (assuming that proves to be necessary to meet
|
||||
our performance criteria).
|
||||
|
||||
The VFSforGit API, which is currently supported through the use of
|
||||
the `--enable-vfs-api` configuration option to libprojfs, may at some
|
||||
point refactored out of this library entirely and handled exclusively
|
||||
within Linux-specific code in the VFSforGit project. However, for
|
||||
the moment it has proven efficient to keep it within this library
|
||||
while libprojfs undergoes rapid early development.
|
||||
|
||||
## Authors
|
||||
|
||||
The libprojfs library is currently maintained and developed by
|
||||
several members of GitHub's Engineering organization, including:
|
||||
|
||||
* [@chrisd8088](https://github.com/chrisd8088)
|
||||
* [@kivikakk](https://github.com/kivikakk)
|
||||
|
||||
[gnu-build]: https://www.gnu.org/software/automake/manual/html_node/GNU-Build-System.html
|
||||
[gpl-v2]: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
|
||||
[lgpl-v2]: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
|
||||
[mit]: https://github.com/Microsoft/VFSForGit/blob/master/License.md
|
||||
[winprojfs]: https://docs.microsoft.com/en-us/windows/desktop/api/_projfs/
|
||||
[vfs4git]: https://github.com/Microsoft/VFSForGit
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
aclocal -Wall --install -I m4 &&
|
||||
libtoolize -Wall --copy &&
|
||||
autoheader -Wall &&
|
||||
autoconf -Wall &&
|
||||
automake -Wall --add-missing --copy &&
|
||||
./configure "$@"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
AWK='@AWK@'
|
||||
DIFF='@DIFF@'
|
||||
MKDIR_P='@MKDIR_P@'
|
||||
SED='@SED@'
|
||||
SHELL='@SHELL@'
|
||||
|
||||
SHELL_PATH="$SHELL"
|
|
@ -0,0 +1,90 @@
|
|||
AC_INIT([libprojfs], [0.1])
|
||||
|
||||
AC_PREREQ([2.59])
|
||||
AM_INIT_AUTOMAKE([foreign no-dist-gzip dist-xz subdir-objects])
|
||||
LT_INIT
|
||||
|
||||
AC_CONFIG_HEADERS([include/config.h])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
|
||||
AC_PROG_AWK
|
||||
# AC_PROG_CC adds -g -O2 to CFLAGS by default for gcc
|
||||
AC_PROG_CC
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_MKDIR_P
|
||||
AC_PROG_SED
|
||||
|
||||
AC_SYS_LARGEFILE
|
||||
|
||||
libprojfs_cflags='-fno-strict-aliasing'
|
||||
libprojfs_cflags+=" -Wall -Wextra"
|
||||
libprojfs_cflags+=" -Wmissing-declarations"
|
||||
libprojfs_cflags+=" -Wno-sign-compare"
|
||||
libprojfs_cflags+=" -Wstrict-prototypes"
|
||||
libprojfs_cflags+=" -Wwrite-strings"dnl
|
||||
|
||||
AC_ARG_ENABLE([debug],
|
||||
[AS_HELP_STRING([--enable-debug],
|
||||
[Enable debug output])],
|
||||
[AC_DEFINE(PROJFS_DEBUG, 1, [Enable debugging output])]dnl
|
||||
)dnl
|
||||
|
||||
AC_SUBST([libprojfs_cflags])
|
||||
|
||||
enable_vfsapi=false
|
||||
libprojfs_export_regex='projfs'
|
||||
|
||||
AC_ARG_ENABLE([vfs_api],
|
||||
[AS_HELP_STRING([--enable-vfs-api],
|
||||
[Enable .NET VFSforGit API])],
|
||||
[AC_DEFINE(PROJFS_VFSAPI, 1, [Enable .NET VFSforGit API])
|
||||
enable_vfsapi=true
|
||||
libprojfs_export_regex='(projfs|PrjFS)'
|
||||
]dnl
|
||||
)dnl
|
||||
|
||||
AM_CONDITIONAL([ENABLE_VFSAPI], [test :$enable_vfsapi = :true])
|
||||
AC_SUBST([libprojfs_export_regex])
|
||||
|
||||
AC_ARG_WITH([pkgconfigdir],
|
||||
[AS_HELP_STRING([--with-pkgconfigdir=DIR],
|
||||
[Install pkgconfig file in DIR @<:@LIBDIR/pkgconfig@:>@])],
|
||||
[pkgconfigdir=$withval],
|
||||
[pkgconfigdir='${libdir}/pkgconfig']dnl
|
||||
)dnl
|
||||
|
||||
AC_SUBST([pkgconfigdir])
|
||||
|
||||
AC_TYPE_PID_T
|
||||
AC_TYPE_SIZE_T
|
||||
AC_TYPE_UINT64_T
|
||||
|
||||
# TODO: for VFS API but can't be conditional; remove if no longer needed
|
||||
AC_HEADER_STDBOOL
|
||||
AC_TYPE_UINT16_T
|
||||
|
||||
AC_CHECK_HEADER([pthread.h], [],
|
||||
[AC_MSG_ERROR(POSIX threads header file not found)]dnl
|
||||
)dnl
|
||||
|
||||
AC_CHECK_HEADERS([sys/fanotify.h])
|
||||
AC_CHECK_HEADERS([sys/inotify.h])
|
||||
|
||||
LIBS=''
|
||||
# TODO: remove when FUSE no longer used (also Libs.private in projfs.pc)
|
||||
AC_SEARCH_LIBS([fuse_new_30], [fuse3])
|
||||
AC_SEARCH_LIBS([pthread_create], [pthread])
|
||||
|
||||
libprojfs_libs="$LIBS"
|
||||
AC_SUBST([libprojfs_libs])
|
||||
|
||||
AC_CHECK_PROGS([DIFF], [diff])
|
||||
AC_ARG_VAR([DIFF], [File comparison tool])
|
||||
|
||||
AC_CHECK_PROGS([PROVE], [prove])
|
||||
AC_ARG_VAR([PROVE], [TAP harness command, e.g., 'prove -v'])
|
||||
|
||||
AC_CONFIG_FILES([Makefile include/Makefile lib/Makefile t/Makefile
|
||||
config.sh projfs.pc])
|
||||
AC_OUTPUT
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
projfsincludedir=@includedir@/projfs
|
||||
|
||||
projfsinclude_HEADERS = projfs.h projfs_notify.h
|
||||
|
||||
if ENABLE_VFSAPI
|
||||
projfsinclude_HEADERS += projfs_vfsapi.h
|
||||
endif ENABLE_VFSAPI
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PROJFS_H
|
||||
#define PROJFS_H
|
||||
|
||||
/** @file
|
||||
*
|
||||
* This file defines the library interface of ProjFS
|
||||
*/
|
||||
|
||||
#include <stdint.h> /* for uint64_t */
|
||||
|
||||
#include "projfs_notify.h"
|
||||
|
||||
// TODO: remove when not needed
|
||||
#define FUSE_USE_VERSION 32
|
||||
#include <fuse3/fuse_lowlevel.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Handle for a projfs filesystem */
|
||||
struct projfs;
|
||||
|
||||
/** Filesystem event */
|
||||
struct projfs_event {
|
||||
uint64_t mask;
|
||||
pid_t pid;
|
||||
void *user_data;
|
||||
const char *path;
|
||||
const char *target_path; /* move destination or link target */
|
||||
};
|
||||
|
||||
/**
|
||||
* Filesystem event handlers
|
||||
*
|
||||
* Error codes should be returned as negated errno values.
|
||||
*/
|
||||
struct projfs_handlers {
|
||||
/**
|
||||
* Handle projection request for a file or directory.
|
||||
*
|
||||
* TODO: doxygen
|
||||
* @param path ...
|
||||
* @return ...
|
||||
*/
|
||||
int (*handle_proj_event) (struct projfs_event *event, int fd);
|
||||
|
||||
/**
|
||||
* Handle notification of a file or directory event.
|
||||
*
|
||||
* TODO: doxygen
|
||||
* @param path ...
|
||||
* @return ...
|
||||
*/
|
||||
int (*handle_notify_event) (struct projfs_event *event);
|
||||
|
||||
/**
|
||||
* Handle permission request for file or directory event.
|
||||
*
|
||||
* TODO: doxygen
|
||||
* @param path ...
|
||||
* @return ...
|
||||
*/
|
||||
int (*handle_perm_event) (struct projfs_event *event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a FUSE session in a projfs filesystem.
|
||||
* TODO: remove when not needed
|
||||
*/
|
||||
void projfs_set_session(struct projfs *fs, struct fuse_session *se);
|
||||
|
||||
/**
|
||||
* Create a new projfs filesystem.
|
||||
* TODO: doxygen
|
||||
*/
|
||||
struct projfs *projfs_new(const char *lowerdir, const char *mountdir,
|
||||
const struct projfs_handlers *handlers,
|
||||
size_t handlers_size, void *user_data);
|
||||
|
||||
/**
|
||||
* Start a projfs filesystem.
|
||||
* TODO: doxygen
|
||||
*/
|
||||
int projfs_start(struct projfs *fs);
|
||||
|
||||
/**
|
||||
* Stop a projfs filesystem.
|
||||
* TODO: doxygen
|
||||
*/
|
||||
void *projfs_stop(struct projfs *fs);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* This interface uses 64 bit off_t.
|
||||
*
|
||||
* On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
|
||||
*/
|
||||
|
||||
// from https://github.com/libfuse/libfuse/commit/d8f3ab7
|
||||
#if defined(__GNUC__) && \
|
||||
(__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && \
|
||||
!defined __cplusplus
|
||||
_Static_assert(sizeof(off_t) == 8, "projfs: off_t must be 64bit");
|
||||
#else
|
||||
struct _projfs_off_t_must_be_64bit_dummy_struct {
|
||||
unsigned _projfs_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1);
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /* PROJFS_H */
|
|
@ -0,0 +1,84 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PROJFS_NOTIFY_H
|
||||
#define PROJFS_NOTIFY_H
|
||||
|
||||
/** @file
|
||||
*
|
||||
* This file defines the event notification interface of ProjFS
|
||||
*/
|
||||
|
||||
#include <sys/types.h> /* for pid_t, etc. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define himask(x) (((uint64_t)x) << 32)
|
||||
|
||||
/** Filesystem events which may be reported */
|
||||
#define PROJFS_CLOSE_WRITE 0x00000008 /* Writable file was closed */
|
||||
#define PROJFS_OPEN 0x00000020 /* File was opened */
|
||||
#define PROJFS_DELETE_SELF 0x00000400 /* Delete permission */
|
||||
#define PROJFS_CREATE_SELF himask(0x0001) /* File was created */
|
||||
|
||||
/** Filesystem event flags */
|
||||
#define PROJFS_ONDIR 0x40000000 /* Event occurred on dir */
|
||||
|
||||
/** Event permission handler responses */
|
||||
#define PROJFS_ALLOW 0x01
|
||||
#define PROJFS_DENY 0x02
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* This interface should match that defined by the fanotify/inotify APIs
|
||||
* and underlying fsnotify interface.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_SYS_FANOTIFY_H
|
||||
#include <sys/fanotify.h>
|
||||
|
||||
#if (PROJFS_CLOSE_WRITE != FAN_CLOSE_WRITE || \
|
||||
PROJFS_OPEN != FAN_OPEN || \
|
||||
PROJFS_ONDIR != FAN_ONDIR || \
|
||||
PROJFS_ALLOW != FAN_ALLOW || \
|
||||
PROJFS_DENY != FAN_DENY)
|
||||
#error "Projfs notification API out of sync with sys/fanotify.h API"
|
||||
#endif
|
||||
#endif /* HAVE_SYS_FANOTIFY_H */
|
||||
|
||||
#ifdef HAVE_SYS_INOTIFY_H
|
||||
#include <sys/inotify.h>
|
||||
|
||||
#if (PROJFS_CLOSE_WRITE != IN_CLOSE_WRITE || \
|
||||
PROJFS_OPEN != IN_OPEN || \
|
||||
PROJFS_DELETE_SELF != IN_DELETE_SELF || \
|
||||
PROJFS_ONDIR != IN_ISDIR)
|
||||
#error "Projfs notification API out of sync with sys/inotify.h API"
|
||||
#endif
|
||||
#endif /* HAVE_SYS_INOTIFY_H */
|
||||
|
||||
#endif /* PROJFS_NOTIFY_H */
|
|
@ -0,0 +1,284 @@
|
|||
/* VFSforGit ProjFS API
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
This -- and only this -- header file may also be distributed under
|
||||
the terms of the MIT License as follows:
|
||||
|
||||
Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE
|
||||
|
||||
You should have received a copy of the MIT License along with this
|
||||
library, in the file COPYING.VFSAPI; if not, see
|
||||
<https://opensource.org/licenses/MIT>
|
||||
*/
|
||||
|
||||
#ifndef PROJFS_VFSAPI_H
|
||||
#define PROJFS_VFSAPI_H
|
||||
|
||||
/** @file
|
||||
*
|
||||
* This file defines the VFSforGit interface of ProjFS
|
||||
*/
|
||||
|
||||
#ifdef HAVE_STDINT_H
|
||||
#include <stdint.h> /* for uint16_t */
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STDBOOL_H
|
||||
#include <stdbool.h> /* for bool */
|
||||
#else
|
||||
#ifndef __cplusplus
|
||||
#ifdef HAVE__BOOL
|
||||
#define bool _Bool
|
||||
#else
|
||||
#define bool unsigned char
|
||||
#endif /* HAVE__BOOL */
|
||||
#endif /* __cplusplus */
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define _In_
|
||||
#define _Out_
|
||||
|
||||
#define PrjFS_PlaceholderIdLength 128
|
||||
|
||||
typedef struct _PrjFS_MountHandle PrjFS_MountHandle;
|
||||
|
||||
typedef struct _PrjFS_FileHandle PrjFS_FileHandle;
|
||||
|
||||
typedef struct _PrjFS_Callbacks PrjFS_Callbacks;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PrjFS_Result_Invalid = 0x00000000,
|
||||
|
||||
PrjFS_Result_Success = 0x00000001,
|
||||
PrjFS_Result_Pending = 0x00000002,
|
||||
|
||||
// Bugs in the caller
|
||||
PrjFS_Result_EInvalidArgs = 0x10000001,
|
||||
PrjFS_Result_EInvalidOperation = 0x10000002,
|
||||
PrjFS_Result_ENotSupported = 0x10000004,
|
||||
|
||||
// Runtime errors
|
||||
PrjFS_Result_EDriverNotLoaded = 0x20000001,
|
||||
PrjFS_Result_EOutOfMemory = 0x20000002,
|
||||
PrjFS_Result_EFileNotFound = 0x20000004,
|
||||
PrjFS_Result_EPathNotFound = 0x20000008,
|
||||
PrjFS_Result_EAccessDenied = 0x20000010,
|
||||
PrjFS_Result_EInvalidHandle = 0x20000020,
|
||||
PrjFS_Result_EIOError = 0x20000040,
|
||||
PrjFS_Result_ENotAVirtualizationRoot = 0x20000080,
|
||||
PrjFS_Result_EVirtualizationRootAlreadyExists = 0x20000100,
|
||||
|
||||
PrjFS_Result_ENotYetImplemented = 0xFFFFFFFF
|
||||
} PrjFS_Result;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PrjFS_NotificationType_Invalid = 0x00000000,
|
||||
|
||||
PrjFS_NotificationType_None = 0x00000001,
|
||||
PrjFS_NotificationType_NewFileCreated = 0x00000004,
|
||||
PrjFS_NotificationType_PreDelete = 0x00000010,
|
||||
PrjFS_NotificationType_FileRenamed = 0x00000080,
|
||||
PrjFS_NotificationType_HardLinkCreated = 0x00000100,
|
||||
PrjFS_NotificationType_PreConvertToFull = 0x00001000,
|
||||
|
||||
PrjFS_NotificationType_PreModify = 0x10000001,
|
||||
PrjFS_NotificationType_FileModified = 0x10000002,
|
||||
PrjFS_NotificationType_FileDeleted = 0x10000004
|
||||
} PrjFS_NotificationType;
|
||||
|
||||
#if 0
|
||||
typedef struct
|
||||
{
|
||||
_In_ PrjFS_NotificationType NotificationBitMask;
|
||||
_In_ const char* NotificationRelativeRoot;
|
||||
} PrjFS_NotificationMapping;
|
||||
#endif
|
||||
|
||||
PrjFS_Result PrjFS_StartVirtualizationInstance(
|
||||
_In_ const char* storageRootFullPath,
|
||||
_In_ const char* virtualizationRootFullPath,
|
||||
_In_ PrjFS_Callbacks callbacks,
|
||||
_In_ unsigned int poolThreadCount,
|
||||
_Out_ PrjFS_MountHandle** mountHandle
|
||||
);
|
||||
|
||||
void PrjFS_StopVirtualizationInstance(
|
||||
_In_ const PrjFS_MountHandle* mountHandle
|
||||
);
|
||||
|
||||
PrjFS_Result PrjFS_ConvertDirectoryToVirtualizationRoot(
|
||||
_In_ const char* virtualizationRootFullPath
|
||||
);
|
||||
|
||||
#if 0
|
||||
PrjFS_Result PrjFS_ConvertDirectoryToPlaceholder(
|
||||
_In_ const char* relativePath
|
||||
);
|
||||
|
||||
PrjFS_Result PrjFS_WritePlaceholderDirectory(
|
||||
_In_ const char* relativePath
|
||||
);
|
||||
|
||||
PrjFS_Result PrjFS_WritePlaceholderFile(
|
||||
_In_ const char* relativePath,
|
||||
_In_ unsigned char providerId[PrjFS_PlaceholderIdLength],
|
||||
_In_ unsigned char contentId[PrjFS_PlaceholderIdLength],
|
||||
_In_ unsigned long fileSize,
|
||||
_In_ uint16_t fileMode
|
||||
);
|
||||
|
||||
PrjFS_Result PrjFS_WriteSymLink(
|
||||
_In_ const char* relativePath,
|
||||
_In_ const char* symLinkTarget
|
||||
);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PrjFS_UpdateType_Invalid = 0x00000000,
|
||||
|
||||
PrjFS_UpdateType_AllowReadOnly = 0x00000020
|
||||
} PrjFS_UpdateType;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PrjFS_UpdateFailureCause_Invalid = 0x00000000,
|
||||
|
||||
PrjFS_UpdateFailureCause_FullFile = 0x00000002,
|
||||
PrjFS_UpdateFailureCause_ReadOnly = 0x00000008
|
||||
} PrjFS_UpdateFailureCause;
|
||||
|
||||
PrjFS_Result PrjFS_UpdatePlaceholderFileIfNeeded(
|
||||
_In_ const char* relativePath,
|
||||
_In_ unsigned char providerId[PrjFS_PlaceholderIdLength],
|
||||
_In_ unsigned char contentId[PrjFS_PlaceholderIdLength],
|
||||
_In_ unsigned long fileSize,
|
||||
_In_ uint16_t fileMode,
|
||||
_In_ PrjFS_UpdateType updateFlags,
|
||||
_Out_ PrjFS_UpdateFailureCause* failureCause
|
||||
);
|
||||
|
||||
PrjFS_Result PrjFS_ReplacePlaceholderFileWithSymLink(
|
||||
_In_ const char* relativePath,
|
||||
_In_ const char* symLinkTarget,
|
||||
_In_ PrjFS_UpdateType updateFlags,
|
||||
_Out_ PrjFS_UpdateFailureCause* failureCause
|
||||
);
|
||||
|
||||
PrjFS_Result PrjFS_DeleteFile(
|
||||
_In_ const char* relativePath,
|
||||
_In_ PrjFS_UpdateType updateFlags,
|
||||
_Out_ PrjFS_UpdateFailureCause* failureCause
|
||||
);
|
||||
|
||||
PrjFS_Result PrjFS_WriteFileContents(
|
||||
_In_ const PrjFS_FileHandle* fileHandle,
|
||||
_In_ const void* bytes,
|
||||
_In_ unsigned int byteCount
|
||||
);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PrjFS_FileState_Invalid = 0x00000000,
|
||||
|
||||
PrjFS_FileState_Placeholder = 0x00000001,
|
||||
PrjFS_FileState_HydratedPlaceholder = 0x00000002,
|
||||
PrjFS_FileState_Full = 0x00000008
|
||||
} PrjFS_FileState;
|
||||
|
||||
PrjFS_Result PrjFS_GetOnDiskFileState(
|
||||
_In_ const char* fullPath,
|
||||
_Out_ unsigned int* fileState
|
||||
);
|
||||
#endif
|
||||
|
||||
typedef PrjFS_Result (PrjFS_EnumerateDirectoryCallback)(
|
||||
_In_ unsigned long commandId,
|
||||
_In_ const char* relativePath,
|
||||
_In_ int triggeringProcessId,
|
||||
_In_ const char* triggeringProcessName
|
||||
);
|
||||
|
||||
typedef PrjFS_Result (PrjFS_GetFileStreamCallback)(
|
||||
_In_ unsigned long commandId,
|
||||
_In_ const char* relativePath,
|
||||
_In_ unsigned char providerId[PrjFS_PlaceholderIdLength],
|
||||
_In_ unsigned char contentId[PrjFS_PlaceholderIdLength],
|
||||
_In_ int triggeringProcessId,
|
||||
_In_ const char* triggeringProcessName,
|
||||
_In_ const PrjFS_FileHandle* fileHandle
|
||||
);
|
||||
|
||||
typedef PrjFS_Result (PrjFS_NotifyOperationCallback)(
|
||||
_In_ unsigned long commandId,
|
||||
_In_ const char* relativePath,
|
||||
_In_ unsigned char providerId[PrjFS_PlaceholderIdLength],
|
||||
_In_ unsigned char contentId[PrjFS_PlaceholderIdLength],
|
||||
_In_ int triggeringProcessId,
|
||||
_In_ const char* triggeringProcessName,
|
||||
_In_ bool isDirectory,
|
||||
_In_ PrjFS_NotificationType notificationType,
|
||||
_In_ const char* destinationRelativePath
|
||||
);
|
||||
|
||||
typedef struct _PrjFS_Callbacks
|
||||
{
|
||||
_In_ PrjFS_EnumerateDirectoryCallback* EnumerateDirectory;
|
||||
_In_ PrjFS_GetFileStreamCallback* GetFileStream;
|
||||
_In_ PrjFS_NotifyOperationCallback* NotifyOperation;
|
||||
} PrjFS_Callbacks;
|
||||
|
||||
#if 0
|
||||
PrjFS_Result PrjFS_CompleteCommand(
|
||||
_In_ unsigned long commandId,
|
||||
_In_ PrjFS_Result result
|
||||
);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PROJFS_VFSAPI_H */
|
|
@ -0,0 +1,19 @@
|
|||
AM_CFLAGS = $(libprojfs_cflags)
|
||||
AM_CPPFLAGS = -I@top_srcdir@/include
|
||||
|
||||
lib_LTLIBRARIES = libprojfs.la
|
||||
|
||||
libprojfs_la_SOURCES = projfs.c projfs_i.h \
|
||||
$(top_srcdir)/include/projfs.h \
|
||||
$(top_srcdir)/include/projfs_notify.h
|
||||
|
||||
libprojfs_la_LIBADD = @libprojfs_libs@
|
||||
|
||||
if ENABLE_VFSAPI
|
||||
libprojfs_la_SOURCES += projfs_vfsapi.c \
|
||||
$(top_srcdir)/include/projfs_vfsapi.h
|
||||
endif ENABLE_VFSAPI
|
||||
|
||||
libprojfs_la_LDFLAGS = -version-number 0:0:0 \
|
||||
-export-symbols-regex "^@libprojfs_export_regex@"
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h> // TODO: remove unless using strsignal()
|
||||
|
||||
#include <fuse3/fuse_opt.h>
|
||||
|
||||
#include "projfs.h"
|
||||
#include "projfs_i.h"
|
||||
|
||||
#ifdef PROJFS_VFSAPI
|
||||
#include "projfs_vfsapi.h"
|
||||
#endif
|
||||
|
||||
// TODO: remove if not using
|
||||
static void projfs_ll_lookup(fuse_req_t req, fuse_ino_t parent,
|
||||
const char *name)
|
||||
{
|
||||
(void) req;
|
||||
(void) parent;
|
||||
(void) name;
|
||||
}
|
||||
|
||||
static struct fuse_lowlevel_ops ll_ops = {
|
||||
.lookup = projfs_ll_lookup
|
||||
};
|
||||
|
||||
#ifdef PROJFS_DEBUG
|
||||
#define DEBUG_ARGV "--debug",
|
||||
#define DEBUG_ARGC 1
|
||||
#else
|
||||
#define DEBUG_ARGV
|
||||
#define DEBUG_ARGC 0
|
||||
#endif
|
||||
|
||||
void projfs_set_session(struct projfs *fs, struct fuse_session *se)
|
||||
{
|
||||
if (fs == NULL)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&fs->mutex);
|
||||
fs->session = se;
|
||||
pthread_mutex_unlock(&fs->mutex);
|
||||
}
|
||||
|
||||
struct projfs *projfs_new(const char *lowerdir, const char *mountdir,
|
||||
const struct projfs_handlers *handlers,
|
||||
size_t handlers_size, void *user_data)
|
||||
{
|
||||
struct projfs *fs;
|
||||
|
||||
// TODO: prevent failure with relative lowerdir
|
||||
if (lowerdir == NULL) {
|
||||
fprintf(stderr, "projfs: no lowerdir specified\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
// TODO: debug failure to exit when given a relative mountdir
|
||||
if (mountdir == NULL) {
|
||||
fprintf(stderr, "projfs: no mountdir specified\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (sizeof(struct projfs_handlers) < handlers_size) {
|
||||
fprintf(stderr, "projfs: warning: library too old, "
|
||||
"some handlers may be ignored\n");
|
||||
handlers_size = sizeof(struct projfs_handlers);
|
||||
}
|
||||
|
||||
fs = (struct projfs *) calloc(1, sizeof(struct projfs));
|
||||
if (fs == NULL) {
|
||||
fprintf(stderr, "projfs: failed to allocate projfs object\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
fs->lowerdir = strdup(lowerdir);
|
||||
if (fs->lowerdir == NULL) {
|
||||
fprintf(stderr, "projfs: failed to allocate lower path\n");
|
||||
goto out_handle;
|
||||
}
|
||||
|
||||
fs->mountdir = strdup(mountdir);
|
||||
if (fs->mountdir == NULL) {
|
||||
fprintf(stderr, "projfs: failed to allocate mount path\n");
|
||||
goto out_lower;
|
||||
}
|
||||
|
||||
if (handlers != NULL)
|
||||
memcpy(&fs->handlers, handlers, handlers_size);
|
||||
|
||||
fs->user_data = user_data;
|
||||
|
||||
pthread_mutex_init(&fs->mutex, NULL);
|
||||
|
||||
return fs;
|
||||
|
||||
out_lower:
|
||||
free(fs->lowerdir);
|
||||
out_handle:
|
||||
free(fs);
|
||||
out:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *projfs_loop(void *data)
|
||||
{
|
||||
struct projfs *fs = (struct projfs *) data;
|
||||
const char *argv[] = { "projfs", DEBUG_ARGV NULL };
|
||||
int argc = 1 + DEBUG_ARGC;
|
||||
struct fuse_args args = FUSE_ARGS_INIT(argc, (char **) argv);
|
||||
struct fuse_session *se;
|
||||
struct fuse_loop_config loop;
|
||||
int err;
|
||||
int res = 0;
|
||||
|
||||
// TODO: pass a real userdata structure here, not fs
|
||||
se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), fs);
|
||||
if (se == NULL) {
|
||||
res = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
projfs_set_session(fs, se);
|
||||
|
||||
// TODO: defer all signal handling to user, once we remove FUSE
|
||||
if (fuse_set_signal_handlers(se) != 0) {
|
||||
res = 2;
|
||||
goto out_session;
|
||||
}
|
||||
|
||||
// TODO: mount with x-gvfs-hide option and maybe others for KDE, etc.
|
||||
if (fuse_session_mount(se, fs->mountdir) != 0) {
|
||||
res = 3;
|
||||
goto out_signal;
|
||||
}
|
||||
|
||||
// since we're running in a thread, don't daemonize, just chdir()
|
||||
if (fuse_daemonize(1)) {
|
||||
res = 4;
|
||||
goto out_unmount;
|
||||
}
|
||||
|
||||
// TODO: support configs; ideally libfuse's full suite
|
||||
loop.clone_fd = 0;
|
||||
loop.max_idle_threads = 10;
|
||||
|
||||
// TODO: output strsignal() only for dev purposes
|
||||
if ((err = fuse_session_loop_mt(se, &loop)) != 0)
|
||||
{
|
||||
if (err > 0)
|
||||
fprintf(stderr, "projfs: %s signal\n", strsignal(err));
|
||||
res = 5;
|
||||
}
|
||||
|
||||
out_unmount:
|
||||
fuse_session_unmount(se);
|
||||
out_signal:
|
||||
fuse_remove_signal_handlers(se);
|
||||
out_session:
|
||||
projfs_set_session(fs, NULL);
|
||||
fuse_session_destroy(se);
|
||||
out:
|
||||
fs->error = res;
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
int projfs_start(struct projfs *fs)
|
||||
{
|
||||
sigset_t oldset;
|
||||
sigset_t newset;
|
||||
pthread_t thread_id;
|
||||
int res;
|
||||
|
||||
// TODO: override stack size per fuse_start_thread()?
|
||||
|
||||
// TODO: defer all signal handling to user, once we remove FUSE
|
||||
sigemptyset(&newset);
|
||||
sigaddset(&newset, SIGTERM);
|
||||
sigaddset(&newset, SIGINT);
|
||||
sigaddset(&newset, SIGHUP);
|
||||
sigaddset(&newset, SIGQUIT);
|
||||
// TODO: handle error from pthread_sigmask()
|
||||
pthread_sigmask(SIG_BLOCK, &newset, &oldset);
|
||||
|
||||
res = pthread_create(&thread_id, NULL, projfs_loop, fs);
|
||||
|
||||
// TODO: report error from pthread_sigmask() but don't return -1
|
||||
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
|
||||
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "projfs: error creating thread: %s\n",
|
||||
strerror(res));
|
||||
return -1;
|
||||
}
|
||||
|
||||
fs->thread_id = thread_id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *projfs_stop(struct projfs *fs)
|
||||
{
|
||||
struct stat buf;
|
||||
void *user_data;
|
||||
|
||||
pthread_mutex_lock(&fs->mutex);
|
||||
if (fs->session != NULL)
|
||||
fuse_session_exit(fs->session);
|
||||
pthread_mutex_unlock(&fs->mutex);
|
||||
// TODO: barrier/fence to ensure all CPUs see exit flag?
|
||||
|
||||
/* could send a USR1 signal and have a no-op handler installed by
|
||||
* projfs_loop(), but this is a simpler way to trigger fuse_do_work()
|
||||
* to exit, which semaphores fuse_session_loop_mt() to exit as well;
|
||||
* we can ignore any errors
|
||||
*/
|
||||
stat(fs->mountdir, &buf);
|
||||
|
||||
// TODO: use pthread_tryjoin_np() in a loop if avail (AC_CHECK_FUNCS)
|
||||
if (fs->thread_id) {
|
||||
pthread_join(fs->thread_id, NULL);
|
||||
}
|
||||
|
||||
if (fs->error > 0) {
|
||||
// TODO: translate projfs_loop() codes into messages
|
||||
fprintf(stderr, "projfs: error from event loop: %d\n",
|
||||
fs->error);
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&fs->mutex);
|
||||
|
||||
free(fs->mountdir);
|
||||
free(fs->lowerdir);
|
||||
|
||||
user_data = fs->user_data;
|
||||
free(fs);
|
||||
return user_data;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PROJFS_I_H
|
||||
#define PROJFS_I_H
|
||||
|
||||
/** Private projfs filesystem handle */
|
||||
struct projfs {
|
||||
char *lowerdir;
|
||||
char *mountdir;
|
||||
struct projfs_handlers handlers;
|
||||
void *user_data;
|
||||
pthread_mutex_t mutex;
|
||||
struct fuse_session *session;
|
||||
pthread_t thread_id;
|
||||
int error;
|
||||
};
|
||||
|
||||
/** Private event handler type */
|
||||
typedef int (*projfs_handler_t)(struct projfs_event *);
|
||||
|
||||
#endif /* PROJFS_I_H */
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
/* VFSforGit ProjFS API
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "projfs.h"
|
||||
#include "projfs_i.h"
|
||||
#include "projfs_vfsapi.h"
|
||||
|
||||
static char *get_proc_cmdline(pid_t pid)
|
||||
{
|
||||
char proc_cmdline_path[14 + 3*sizeof(pid_t) + 1]; // /proc/%d/cmdline
|
||||
int fd;
|
||||
char *cmdline = NULL;
|
||||
int len;
|
||||
|
||||
sprintf(proc_cmdline_path, "/proc/%d/cmdline", pid);
|
||||
|
||||
fd = open(proc_cmdline_path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
goto out;
|
||||
|
||||
/* While PATH_MAX may not be sufficent for pathological cases, we
|
||||
* assume it's adaquate for all normal processes which might be
|
||||
* accessing a git working directory.
|
||||
*
|
||||
* Also note that the contents of /proc/<pid>/cmdline will contain
|
||||
* nul-separated command-line arguments, so in the usual case
|
||||
* our buffer will have "extra" data after the actual command
|
||||
* path, which will be ignored by our callers. But we add a nul
|
||||
* just in case we hit a pathologically long command path.
|
||||
*/
|
||||
cmdline = malloc(PATH_MAX + 1);
|
||||
if (cmdline == NULL)
|
||||
goto out_fd;
|
||||
|
||||
len = read(fd, cmdline, PATH_MAX);
|
||||
if (len > 0)
|
||||
cmdline[len] = '\0';
|
||||
else {
|
||||
free(cmdline);
|
||||
cmdline = NULL;
|
||||
}
|
||||
|
||||
out_fd:
|
||||
close(fd);
|
||||
out:
|
||||
return cmdline;
|
||||
}
|
||||
|
||||
static int convert_result_to_errno(PrjFS_Result result)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (result) {
|
||||
case PrjFS_Result_Success:
|
||||
break;
|
||||
case PrjFS_Result_Pending:
|
||||
ret = EINPROGRESS;
|
||||
break;
|
||||
|
||||
case PrjFS_Result_EInvalidArgs:
|
||||
ret = EINVAL;
|
||||
break;
|
||||
case PrjFS_Result_EInvalidOperation:
|
||||
ret = EPERM;
|
||||
break;
|
||||
case PrjFS_Result_ENotSupported:
|
||||
ret = ENOTSUP;
|
||||
break;
|
||||
|
||||
case PrjFS_Result_EDriverNotLoaded:
|
||||
ret = ENODEV;
|
||||
break;
|
||||
case PrjFS_Result_EOutOfMemory:
|
||||
ret = ENOMEM;
|
||||
break;
|
||||
case PrjFS_Result_EFileNotFound:
|
||||
case PrjFS_Result_EPathNotFound:
|
||||
ret = ENOENT;
|
||||
break;
|
||||
case PrjFS_Result_EAccessDenied:
|
||||
ret = EPERM;
|
||||
break;
|
||||
case PrjFS_Result_EInvalidHandle:
|
||||
ret = EBADF;
|
||||
break;
|
||||
case PrjFS_Result_EIOError:
|
||||
ret = EIO;
|
||||
break;
|
||||
case PrjFS_Result_ENotYetImplemented:
|
||||
ret = ENOSYS;
|
||||
break;
|
||||
|
||||
case PrjFS_Result_Invalid:
|
||||
default:
|
||||
ret = EINVAL; // should imply an internal error, not client's
|
||||
}
|
||||
|
||||
return -ret; // return negated value for convenience
|
||||
}
|
||||
|
||||
static int handle_nonproj_event(struct projfs_event *event, int perm)
|
||||
{
|
||||
PrjFS_NotifyOperationCallback *callback =
|
||||
((PrjFS_Callbacks *) (event->user_data))->NotifyOperation;
|
||||
PrjFS_NotificationType notificationType = PrjFS_NotificationType_None;
|
||||
unsigned char providerId[PrjFS_PlaceholderIdLength];
|
||||
unsigned char contentId[PrjFS_PlaceholderIdLength];
|
||||
PrjFS_Result result;
|
||||
uint64_t mask = event->mask;
|
||||
char *cmdline = NULL;
|
||||
const char *triggeringProcessName = "";
|
||||
int ret = 0;
|
||||
|
||||
if (callback == NULL)
|
||||
goto out;
|
||||
|
||||
if (mask & PROJFS_DELETE_SELF)
|
||||
notificationType = PrjFS_NotificationType_PreDelete;
|
||||
else if (mask & PROJFS_CREATE_SELF)
|
||||
notificationType = PrjFS_NotificationType_NewFileCreated;
|
||||
// TODO: dispatch for additional event types
|
||||
|
||||
if (notificationType == PrjFS_NotificationType_None)
|
||||
goto out;
|
||||
|
||||
cmdline = get_proc_cmdline(event->pid);
|
||||
if (cmdline != NULL)
|
||||
triggeringProcessName = (const char *) cmdline;
|
||||
|
||||
result = callback(0, event->path, providerId, contentId,
|
||||
event->pid, triggeringProcessName,
|
||||
(mask & PROJFS_ONDIR) ? 1 : 0,
|
||||
notificationType, event->target_path);
|
||||
|
||||
ret = convert_result_to_errno(result);
|
||||
|
||||
out:
|
||||
if (perm) {
|
||||
if (ret == 0)
|
||||
ret = PROJFS_ALLOW;
|
||||
else if (ret == -EPERM)
|
||||
ret = PROJFS_DENY;
|
||||
}
|
||||
|
||||
if (cmdline != NULL)
|
||||
free(cmdline);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int handle_notify_event(struct projfs_event *event)
|
||||
{
|
||||
return handle_nonproj_event(event, 0);
|
||||
}
|
||||
|
||||
static int handle_perm_event(struct projfs_event *event)
|
||||
{
|
||||
return handle_nonproj_event(event, 1);
|
||||
}
|
||||
|
||||
PrjFS_Result PrjFS_StartVirtualizationInstance(
|
||||
_In_ const char* storageRootFullPath,
|
||||
_In_ const char* virtualizationRootFullPath,
|
||||
_In_ PrjFS_Callbacks callbacks,
|
||||
_In_ unsigned int poolThreadCount,
|
||||
_Out_ PrjFS_MountHandle** mountHandle
|
||||
)
|
||||
{
|
||||
struct projfs *fs;
|
||||
struct projfs_handlers handlers;
|
||||
PrjFS_Callbacks *user_data;
|
||||
PrjFS_Result result = PrjFS_Result_Success;
|
||||
|
||||
user_data = malloc(sizeof(callbacks));
|
||||
if (user_data == NULL) {
|
||||
result = PrjFS_Result_EOutOfMemory;
|
||||
goto out;
|
||||
}
|
||||
memcpy(user_data, &callbacks, sizeof(callbacks));
|
||||
|
||||
handlers.handle_notify_event = handle_notify_event;
|
||||
handlers.handle_perm_event = handle_perm_event;
|
||||
|
||||
// TODO: better error responses, where possible
|
||||
fs = projfs_new(storageRootFullPath, virtualizationRootFullPath,
|
||||
&handlers, sizeof(handlers), user_data);
|
||||
if (fs == NULL) {
|
||||
result = PrjFS_Result_Invalid;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// TODO: respect poolThreadCount
|
||||
(void) poolThreadCount;
|
||||
|
||||
if (projfs_start(fs) != 0) {
|
||||
result = PrjFS_Result_Invalid;
|
||||
goto out_fs;
|
||||
}
|
||||
|
||||
*mountHandle = (PrjFS_MountHandle *) fs;
|
||||
return result;
|
||||
|
||||
out_fs:
|
||||
projfs_stop(fs);
|
||||
out:
|
||||
free(user_data);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PrjFS_StopVirtualizationInstance(
|
||||
_In_ const PrjFS_MountHandle* mountHandle
|
||||
)
|
||||
{
|
||||
struct projfs *fs = (struct projfs *) mountHandle;
|
||||
void *user_data;
|
||||
|
||||
user_data = projfs_stop(fs);
|
||||
free(user_data);
|
||||
}
|
||||
|
||||
// TODO: likely unneeded; remove from VFS API and LinuxFileSystemVirtualizer
|
||||
// - OR -
|
||||
// use as a place to check for pre-existing mounts and clean them up
|
||||
PrjFS_Result PrjFS_ConvertDirectoryToVirtualizationRoot(
|
||||
_In_ const char* virtualizationRootFullPath
|
||||
)
|
||||
{
|
||||
// TODO: remove from function signature if not used
|
||||
(void) virtualizationRootFullPath;
|
||||
|
||||
return PrjFS_Result_Success;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
prefix=@prefix@
|
||||
exec_prefix=@exec_prefix@
|
||||
libdir=@libdir@
|
||||
includedir=@includedir@
|
||||
|
||||
Name: projfs
|
||||
Description: Projected Filesystem
|
||||
Version: @VERSION@
|
||||
Libs: -L${libdir} -lprojfs -pthread
|
||||
Libs.private: @libprojfs_libs@
|
||||
Cflags: -I${includedir}/projfs
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
cat <<EOF
|
||||
noop
|
||||
|
||||
(this exists because we need to have a projfs-linux-private project in Janky in
|
||||
order for projfs-docker to depend on it.)
|
||||
EOF
|
|
@ -0,0 +1,96 @@
|
|||
AUTOMAKE_OPTIONS = 1.13 # for default parallel test harness
|
||||
|
||||
AM_CFLAGS = $(libprojfs_cflags)
|
||||
AM_CPPFLAGS = -I@top_srcdir@/include
|
||||
|
||||
LDADD = ../lib/libprojfs.la
|
||||
|
||||
test_common = test_common.c \
|
||||
test_common.h \
|
||||
$(top_srcdir)/include/projfs.h \
|
||||
$(top_srcdir)/include/projfs_notify.h
|
||||
|
||||
check_PROGRAMS = get_strerror \
|
||||
test_projfs_handlers \
|
||||
test_projfs_simple \
|
||||
wait_mount
|
||||
|
||||
get_strerror_SOURCES = get_strerror.c $(test_common)
|
||||
test_projfs_handlers_SOURCES = test_projfs_handlers.c $(test_common)
|
||||
test_projfs_simple_SOURCES = test_projfs_simple.c $(test_common)
|
||||
wait_mount_SOURCES = wait_mount.c
|
||||
|
||||
if ENABLE_VFSAPI
|
||||
test_vfsapi_common = $(test_common) $(top_srcdir)/include/projfs_vfsapi.h
|
||||
|
||||
check_PROGRAMS += test_vfsapi_handlers \
|
||||
test_vfsapi_simple
|
||||
|
||||
test_vfsapi_handlers_SOURCES = test_vfsapi_handlers.c $(test_vfsapi_common)
|
||||
test_vfsapi_simple_SOURCES = test_vfsapi_simple.c $(test_vfsapi_common)
|
||||
endif ENABLE_VFSAPI
|
||||
|
||||
TESTS = t000-mirror-read.t \
|
||||
t001-mirror-mkdir.t \
|
||||
t002-mirror-write.t \
|
||||
t003-mirror-remove.t \
|
||||
t200-event-ok.t \
|
||||
t201-event-err.t \
|
||||
t202-event-deny.t \
|
||||
t203-event-null.t \
|
||||
t204-event-allow.t
|
||||
|
||||
if ENABLE_VFSAPI
|
||||
TESTS += t500-vfs-mirror-read.t \
|
||||
t501-vfs-mirror-mkdir.t \
|
||||
t502-vfs-mirror-write.t \
|
||||
t503-vfs-mirror-remove.t \
|
||||
t700-vfs-event-ok.t \
|
||||
t701-vfs-event-err.t \
|
||||
t702-vfs-event-deny.t \
|
||||
t703-vfs-event-null.t \
|
||||
t704-vfs-event-allow.t
|
||||
|
||||
endif ENABLE_VFSAPI
|
||||
|
||||
EXTRA_DIST = test-lib.sh test-lib-functions.sh $(TESTS)
|
||||
|
||||
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' HARNESS_ACTIVE=true \
|
||||
$(SHELL) ../tap-driver.sh
|
||||
|
||||
TEST_MOUNTS_DIR = test-mounts
|
||||
TEST_OUTPUT_DIR = test-output
|
||||
PROVE_FILE = .prove
|
||||
|
||||
check-local: clean-mounts
|
||||
|
||||
.PHONY: test
|
||||
test: check
|
||||
|
||||
.PHONY: prove
|
||||
prove: clean-mounts clean-output $(check_PROGRAMS)
|
||||
@if test ":$(PROVE)" != ":"; \
|
||||
then \
|
||||
prove_dir="."; \
|
||||
state_opt=`echo "$(PROJFS_PROVE_OPTS)" | $(GREP) -- --state=`; \
|
||||
if test ":$$state_opt" != ":" && test -f "$(PROVE_FILE)"; \
|
||||
then \
|
||||
prove_dir=""; \
|
||||
fi && \
|
||||
$(PROVE) --exec $(SHELL) $(PROJFS_PROVE_OPTS) $$prove_dir; \
|
||||
$(RM) -r "$(TEST_MOUNTS_DIR)"; \
|
||||
else \
|
||||
echo "No 'prove' TAP harness available." && exit 1; \
|
||||
fi
|
||||
|
||||
clean-mounts:
|
||||
@$(RM) -r "$(TEST_MOUNTS_DIR)"
|
||||
|
||||
clean-output:
|
||||
@$(RM) -r "$(TEST_OUTPUT_DIR)"
|
||||
|
||||
clean-prove:
|
||||
@$(RM) $(PROVE_FILE)
|
||||
|
||||
clean-local: clean-mounts clean-output clean-prove
|
||||
|
|
@ -0,0 +1,922 @@
|
|||
# libprojfs Test Suite
|
||||
|
||||
Both this [README](README.md) and the shell test suite libraries used in this
|
||||
project are derived in large part from those written for the
|
||||
[git](https://git-scm.com/) version control system. Please see the
|
||||
[NOTICE](../NOTICE) file distributed with this library for additional
|
||||
information regarding copyright ownership, and the
|
||||
[git/t source](https://github.com/git/git/tree/master/t) for
|
||||
further reading.
|
||||
|
||||
## Running Tests
|
||||
|
||||
The easiest way to run tests is to say `make test` (or `make check`, which
|
||||
is equivalent). This runs all the tests, using the
|
||||
[Automake](https://www.gnu.org/software/automake/manual/html_node/Tests.html)
|
||||
TAP test harness:
|
||||
```
|
||||
PASS: t000-mirror-read.t 1 - create source tree
|
||||
PASS: t000-mirror-read.t 2 - check target tree structure
|
||||
PASS: t000-mirror-read.t 3 - check target tree content
|
||||
...
|
||||
============================================================================
|
||||
Testsuite summary for libprojfs 0.1
|
||||
============================================================================
|
||||
# TOTAL: 42
|
||||
# PASS: 27
|
||||
# SKIP: 5
|
||||
# XFAIL: 10
|
||||
# FAIL: 0
|
||||
# XPASS: 0
|
||||
# ERROR: 0
|
||||
============================================================================
|
||||
```
|
||||
|
||||
Since the tests all output [TAP](http://testanything.org/) they can
|
||||
be run with any TAP harness. If [prove(1)](https://metacpan.org/pod/prove)
|
||||
is available, it can be run using `make prove`:
|
||||
```
|
||||
./t000-mirror-read.t ...... ok
|
||||
./t001-mirror-mkdir.t ..... ok
|
||||
./t002-mirror-write.t ..... ok
|
||||
...
|
||||
All tests successful.
|
||||
Files=10, Tests=42, 1 wallclock secs ( ... )
|
||||
Result: PASS
|
||||
```
|
||||
|
||||
### Test Harness Options
|
||||
|
||||
Both TAP harnesses come with a variety of options, although those
|
||||
offered by the Automake harness do not have many obvious uses.
|
||||
|
||||
The (somewhat limited) set of options available for the default
|
||||
Automake TAP harness are as follows:
|
||||
```
|
||||
[--color-tests {yes|no}]
|
||||
[--comments|--no-comments]
|
||||
[--diagnostic-string <string>]
|
||||
[--expect-failure {yes|no}]
|
||||
[--ignore-exit]
|
||||
[--merge|--no-merge]
|
||||
```
|
||||
|
||||
These Automake TAP options may be passed using `LOG_DRIVER_FLAGS`;
|
||||
for example:
|
||||
```
|
||||
$ LOG_DRIVER_FLAGS='--comments --merge' make test
|
||||
```
|
||||
See [Command-line arguments for test drivers][automake-tests]
|
||||
and [Use TAP with the Automake test harness][automake-tap] for more details.
|
||||
|
||||
By contrast, the options available to `prove` are much richer and
|
||||
potentially more useful, and may be passed using `PROJFS_PROVE_OPTS`;
|
||||
for example:
|
||||
```
|
||||
$ PROJFS_PROVE_OPTS='--timer --jobs 4' make prove
|
||||
```
|
||||
See [man 1 prove](https://perldoc.perl.org/prove.html) for more details.
|
||||
|
||||
### Re-Running Failed Tests
|
||||
|
||||
Both TAP harnesses (Automake's and `prove`) offer the ability to
|
||||
re-run only selected tests, particularly those tests which failed
|
||||
in a prior run and which may now pass.
|
||||
|
||||
The default Automake harness can be executed in this mode by running
|
||||
`make recheck`, which will examine the metadata in any per-script `t/t*.trs`
|
||||
files left from a previous `make check` (or `make test`) to determine
|
||||
which tests need to be run again.
|
||||
|
||||
The `prove` harness can achieve the same result when using its `--state`
|
||||
option. For example, to run only those tests which failed (assuming
|
||||
the same command was used at least once before so `prove` has saved
|
||||
its state):
|
||||
```
|
||||
$ PROJFS_PROVE_OPTS='--state=failed,save' make prove
|
||||
```
|
||||
|
||||
### Running Individual Tests
|
||||
|
||||
You can also run each test individually from command line (without using
|
||||
either TAP harness), like this:
|
||||
```
|
||||
$ ./t000-mirror-read.t
|
||||
ok 1 - create source tree
|
||||
ok 2 - check target tree structure
|
||||
ok 3 - check target tree content
|
||||
# passed all 3 test(s)
|
||||
1..3
|
||||
```
|
||||
|
||||
### Test Options
|
||||
|
||||
In addition to the options available for the TAP harnesses, the tests
|
||||
themselves accept a number of options, which may be supplied directly
|
||||
as command-line arguments when running tests individually, or by setting
|
||||
the `PROJFS_TEST_OPTS` environment variable before running `make test`
|
||||
or `make check`.
|
||||
|
||||
To check whether all test scripts have correctly chained commands using `&&`,
|
||||
use `--chain-lint`:
|
||||
```
|
||||
$ PROJFS_TEST_OPTS=--chain-lint make test
|
||||
```
|
||||
|
||||
To log per-script exit codes and verbose output, including shell commands,
|
||||
into `*.exit` and `*.out` files under `t/test-output/`:
|
||||
```
|
||||
$ PROJFS_TEST_OPTS='-V -x' make test
|
||||
```
|
||||
|
||||
To run tests individually with verbose output, including shell commands:
|
||||
```
|
||||
$ ./t000-mirror-read.t -x
|
||||
```
|
||||
|
||||
The full set of available options is described below.
|
||||
|
||||
* `-v` or `--verbose`:
|
||||
|
||||
This makes the test more verbose. Specifically, the `test_*()` commands
|
||||
being invoked and their results are output to stdout. Note that this
|
||||
option is *not* available when using a TAP harness (e.g., when running
|
||||
`make test` or `make prove`); see `-V` for the alternative.
|
||||
|
||||
* `--verbose-only=<test-selector>`:
|
||||
|
||||
Like `--verbose` but the effect is limited to tests whose numbers
|
||||
match `<test-selector>`. Note that this option is *not* available when
|
||||
using a TAP harness.
|
||||
|
||||
Test numbers are matched against the running count of `test_*()`
|
||||
commands within each script. The syntax of the `<test-selector>`
|
||||
pattern is the same as for the `--run` option, and is described in
|
||||
more detail in the [Skipping Tests](#skipping-tests) section below.
|
||||
|
||||
* `--tee`:
|
||||
|
||||
In addition to printing the test output to the terminal,
|
||||
write it to files named `t/test-results/<test-name>.t.out`.
|
||||
As the output filenames depend on the test scripts' filenames,
|
||||
it is safe to run the tests in parallel when using this option.
|
||||
|
||||
The exit codes from each test script are also written into files
|
||||
named `t/test-results/<test-name>.t.exit`.
|
||||
|
||||
* `-V` or `--verbose-log`:
|
||||
|
||||
Write verbose output to the same logfile as `--tee`, but do
|
||||
*not* write it to stdout. Unlike `--tee --verbose`, this option
|
||||
is safe to use when stdout is being consumed by a TAP parser
|
||||
like `prove`. Implies both `--tee` and `--verbose`.
|
||||
|
||||
* `-x`:
|
||||
|
||||
Turn on shell tracing (i.e., `set -x`) during the tests
|
||||
themselves. Implies `--verbose` unless `--verbose-log` is set.
|
||||
Note that use with `--verbose-log` is supported but short options
|
||||
cannot be combined; that is, use `-V -x`, not `-Vx`.
|
||||
|
||||
Will be ignored in test scripts which set the variable `test_untraceable`
|
||||
to a non-empty value, unless run with a Bash version supporting
|
||||
`BASH_XTRACEFD`, i.e., v4.1 or later.
|
||||
|
||||
* `-q` or `--quiet`:
|
||||
|
||||
Do not print individual `ok`/`not ok` per-test output to stdout;
|
||||
only print the summary of test results from within a test script.
|
||||
Ignored when running under a TAP harness.
|
||||
|
||||
* `--no-color`:
|
||||
|
||||
Do not use color when printing the summary of test results from
|
||||
within a test script. Not applicable when running under a TAP
|
||||
harness.
|
||||
|
||||
* `-d` or `--debug`:
|
||||
|
||||
This may help the person who is developing a new test.
|
||||
It causes any command defined with `test_debug` to run.
|
||||
The "trash" directory (used to store all temporary data
|
||||
during testing under `t/test-mounts/<test-name>`) is not deleted even if
|
||||
there are no failed tests so that you can inspect its contents after
|
||||
the test finished.
|
||||
|
||||
* `-i` or `--immediate`:
|
||||
|
||||
This causes test scripts to immediately exit upon the first
|
||||
failed test. Cleanup commands requested with `test_when_finished` are
|
||||
not executed if the test failed, in order to keep the state for
|
||||
inspection by the tester to diagnose the bug.
|
||||
|
||||
In particular, use of this option may leave the projected filesystem
|
||||
still mounted, and so is not recommended for use when running multiple
|
||||
tests under a TAP harness, as manual `umount` operations may be
|
||||
required to remove "leftover" filesystem mounts.
|
||||
|
||||
* `-r <test-selector>` or `--run=<test-selector>`:
|
||||
|
||||
Run only the subset of tests indicated by `<test-selector>` from within
|
||||
each test script. See the section [Skipping Tests](#skipping-tests)
|
||||
below for the `<test-selector>` syntax.
|
||||
|
||||
* `--root=<directory>`:
|
||||
|
||||
Create the "trash" directories used to store all temporary data,
|
||||
including any temporary filesystem mounts, under `<directory>`
|
||||
instead of the `t/` directory.
|
||||
|
||||
Using this option with a RAM-based filesystem (such as tmpfs)
|
||||
can massively speed up the test suite, but note that even after
|
||||
a successful test run, an empty `<directory>/test-mounts/` subdirectory
|
||||
may be left behind.
|
||||
|
||||
* `--chain-lint` (and `--no-chain-lint`):
|
||||
|
||||
If `--chain-lint` is enabled, check each test script to make sure that
|
||||
it properly "`&&`-chains" all commands (so that a failure in the middle
|
||||
does not go unnoticed by the final exit code of the script).
|
||||
This check is performed in addition to running the tests themselves.
|
||||
|
||||
You may also enable or disable this feature by setting the
|
||||
`PROJFS_TEST_CHAIN_LINT` environment variable to `1` or `0`,
|
||||
respectively.
|
||||
|
||||
* `--valgrind=<tool>` and `--valgrind-only=<test-selector>`:
|
||||
|
||||
Not available yet.
|
||||
|
||||
## Skipping Tests
|
||||
|
||||
As described above, the `--run` and `--verbose-only` test options
|
||||
accept a `<test-selector>` which identifies the specific tests to be
|
||||
executed within one or more test scripts.
|
||||
|
||||
The `<test-selector>` syntax is a list of individual test numbers or
|
||||
ranges, with an optional negation prefix, that defines what tests in
|
||||
a test suite to include in the run.
|
||||
|
||||
A range is two numbers separated with a dash and matches a range of
|
||||
tests with both ends been included. You may omit the first or the
|
||||
second number to mean "from the first test" or "up to the very last test"
|
||||
respectively.
|
||||
|
||||
Optional prefix of '!' means that the test or a range of tests
|
||||
should be excluded from the run.
|
||||
|
||||
If `--run` or `--verbose-only` starts with an unprefixed number or range,
|
||||
then the initial set of tests to run is empty. If the first item starts
|
||||
with a `!` prefix, then all the tests are added to the initial set.
|
||||
|
||||
After the initial set is determined, each test number or range in the
|
||||
`<test-selector>` list is added to the set of tests to run (or excluded
|
||||
from the set, in the case of `!`), parsing from left to right through the
|
||||
list. Individual numbers or ranges within the list may be separated
|
||||
either by a space or a comma.
|
||||
|
||||
For example, to run only tests up to a specific test (8), one
|
||||
could do this:
|
||||
```
|
||||
$ ./t200-event-ok.t -r -8
|
||||
```
|
||||
|
||||
A common case is to run several setup tests (1, 2, 3) and then a
|
||||
specific test (9) that relies on that setup:
|
||||
```
|
||||
$ ./t200-event-ok.t --run='-3,9'
|
||||
```
|
||||
|
||||
As noted above, the test set is built by going through the items
|
||||
from left to right, so this:
|
||||
```
|
||||
$ ./t200-event-ok.t --run='1-4 !3'
|
||||
```
|
||||
will run tests 1, 2, and 4 only, not 3. Items that come later have
|
||||
higher precedence. It means that this:
|
||||
```
|
||||
$ ./t200-event-ok.t --run='!3 2-4'
|
||||
```
|
||||
would just run all tests starting from 1, including 3, because
|
||||
the leading negated item (`!3`) causes the initial set to be defined as
|
||||
all the tests, from which test 3 is then removed, and then test 3 is
|
||||
added back again as part of the range 2-4.
|
||||
|
||||
You may use negation with ranges. The following will run all
|
||||
tests in the test script except from 3 up to 6:
|
||||
```
|
||||
$ ./t200-event-ok.t --run='!3-6'
|
||||
```
|
||||
|
||||
Some tests in a test script rely on the previous tests performing
|
||||
certain actions; specifically some tests are designated as a
|
||||
"setup" test, so you cannot _arbitrarily_ disable one test and
|
||||
then expect the rest to function correctly.
|
||||
|
||||
The `--run` option is typically most useful when you want to focus on
|
||||
a specific test and know what setup is needed for it, or when you want
|
||||
to run everything up to a certain test. And while `--run` can be passed
|
||||
via the `PROJFS_TEST_OPTS` environment variable, note that when combined
|
||||
with a TAP harness, the `<test-selector>` list will apply to _all_ test
|
||||
scripts, which is rarely what is desired.
|
||||
|
||||
Alternatively, the `PROJFS_SKIP_TESTS` environment variable may be
|
||||
used with a TAP harness (or individual tests), and it has the
|
||||
advantage that test scripts may also be specifically identified.
|
||||
|
||||
The syntax of `PROJFS_SKIP_TESTS` is a space-separated
|
||||
list of patterns which identify the tests to skip,
|
||||
and either can match the `t[0-9]{3}` part to skip the whole
|
||||
test script, or `t[0-9]{3}` followed by `.$number` to identify
|
||||
a test within a test script. For example:
|
||||
```
|
||||
$ PROJFS_SKIP_TESTS=t200.8 make test
|
||||
```
|
||||
or:
|
||||
```
|
||||
$ PROJFS_SKIP_TESTS='t[1-4]?? t000.[1-3]' make test
|
||||
```
|
||||
|
||||
## Writing Tests
|
||||
|
||||
Each test script is written as a shell script, and should start
|
||||
with the standard `#!/bin/sh`, and an
|
||||
assignment to variable `test_description`, like this:
|
||||
```
|
||||
#!/bin/sh
|
||||
|
||||
test_description='xxx test (option --frotz)
|
||||
|
||||
This test tries the --frotz option on a projfs mount.
|
||||
```
|
||||
|
||||
### Starting Tests
|
||||
|
||||
After assigning `test_description`, the test script should source
|
||||
[test-lib.sh](test-lib.sh) like this:
|
||||
```
|
||||
. ./test-lib.sh
|
||||
```
|
||||
|
||||
This test harness library does the following things:
|
||||
|
||||
* If the script is invoked with command line argument `--help`
|
||||
(or `-h`), it prints the `test_description` and exits.
|
||||
|
||||
* Defines standard test helper functions for your scripts to
|
||||
use. These functions are designed to make all scripts behave
|
||||
consistently when command line arguments like `--verbose` (or `-v`),
|
||||
`--debug` (or `-d`), and `--immediate` (or `-i`) are given.
|
||||
|
||||
* Creates an empty trash directory under `t/test-mounts` and
|
||||
and [chdir(2)](http://man7.org/linux/man-pages/man2/chdir.2.html) into it.
|
||||
This directory is `t/test-mounts/<test-name>`,
|
||||
with `t/` subject to change by the `--root` option documented above.
|
||||
|
||||
In most cases, tests should then call the `projfs_start` function
|
||||
to execute a test mount helper program such as
|
||||
[test_projfs_handlers](test_projfs_handlers.c).
|
||||
|
||||
The mount helper normally takes at least two arguments; these should
|
||||
be directory names which will be used to create a temporary source
|
||||
(lower) directory and a target (projected) mount of that directory,
|
||||
both within the test script's trash directory. For example, given
|
||||
`source` and `target` as arguments, the temporary directories and
|
||||
mount point created would be
|
||||
`t/test-mounts/<test-name>/source` and `t/test-mounts/<test-name>/target`.
|
||||
|
||||
The `projfs_start` function should be called after the test library is
|
||||
loaded:
|
||||
```
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_projfs_handlers source target || exit 1
|
||||
```
|
||||
|
||||
The mount helper program's ID will be recorded so it can be stopped
|
||||
by the `projfs_stop` function after all tests are complete; see the
|
||||
next section for details.
|
||||
|
||||
### Ending Tests
|
||||
|
||||
Your script will be a sequence of tests, using helper functions
|
||||
from the test harness library. At the end of the script, call
|
||||
`test_done` to signal to the harness that there will be no further
|
||||
test output.
|
||||
|
||||
Assuming your test script called the `projfs_start` function, it must
|
||||
also call `projfs_stop` just prior to calling `test_done`, like this:
|
||||
```
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
```
|
||||
|
||||
The `projfs_stop` function will signal the test mount helper program
|
||||
to exit and then wait for its mount to be un-mounted.
|
||||
|
||||
### Do's & Don'ts
|
||||
|
||||
Here are a few examples of things you probably should and shouldn't do
|
||||
when writing tests.
|
||||
|
||||
Here are the "do's":
|
||||
|
||||
* Put all code inside `test_expect_success` and other assertions.
|
||||
|
||||
Even code that isn't a test per se, but merely some setup code
|
||||
should be inside a test assertion.
|
||||
|
||||
* Chain your test assertions.
|
||||
|
||||
Write test code like this:
|
||||
```
|
||||
echo foo > source/bar &&
|
||||
test_cmp target/bar ... &&
|
||||
test ...
|
||||
```
|
||||
Instead of:
|
||||
```
|
||||
echo foo > source/bar
|
||||
test_cmp target/bar ...
|
||||
test ...
|
||||
```
|
||||
|
||||
That way all of the commands in your tests will succeed or fail.
|
||||
If you must ignore the return value of something, consider prepending
|
||||
the command with `test_might_fail` or `test_must_fail`, or using a
|
||||
helper function (e.g., use `sane_unset` instead of `unset`, in order
|
||||
to avoid issues caused by non-portable return values in the case when
|
||||
a variable was already unset).
|
||||
|
||||
* When a test checks for an absolute path, construct the expected value
|
||||
using `$(pwd)` rather than `$PWD`, `$TEST_DIRECTORY`, or
|
||||
`$TRASH_DIRECTORY`. While not strictly necessary on Linux, this
|
||||
aids readability and consistency across scripts.
|
||||
|
||||
* Remember that inside the `<script>` part, the standard output and
|
||||
standard error streams are discarded, and the test harness only
|
||||
reports `ok` or `not ok` to the end user running the tests. However,
|
||||
when using `--verbose` or `--verbose-log`, the standard output and error
|
||||
streams are captured to help debug the tests.
|
||||
|
||||
See the [Test Harness Library](#test-harness-library) section below
|
||||
for a description of the `<script>` part of a test helper function.
|
||||
|
||||
And here are the "don'ts":
|
||||
|
||||
* Don't `exit` within the `<script>` part.
|
||||
|
||||
The harness will catch this as a programming error of the test.
|
||||
Use `test_done` instead if you need to stop the tests early (see
|
||||
[Programmatically Skipping Tests](#programmatically-skipping-tests)
|
||||
section below).
|
||||
|
||||
* Don't use `! cmd ...` when you want to make sure a command
|
||||
exits with failure in a controlled way. Instead,
|
||||
use `test_must_fail cmd ...`. This will signal a failure if the
|
||||
commands dies in an unexpected way (e.g., a segfault).
|
||||
|
||||
On the other hand, don't use `test_must_fail` for running regular
|
||||
platform OS commands; just use `! cmd ...`. We are not in the business
|
||||
of verifying that the world given to us works sanely.
|
||||
|
||||
* Don't feed the output of a command to a pipe, as in:
|
||||
```
|
||||
cmd ... |
|
||||
xargs -n 1 basename |
|
||||
grep foo
|
||||
```
|
||||
which will discard the command's exit code and may mask a crash.
|
||||
In the above example, all exit codes are ignored except those from
|
||||
`grep`.
|
||||
|
||||
Instead, write the output of the command to a temporary
|
||||
file with `>` or assign it to a variable with `x=$(cmd ..)` rather
|
||||
than pipe it.
|
||||
|
||||
* Don't use command substitution in a way that discards a command's exit
|
||||
code. When assigning to a variable, the exit code is not discarded,
|
||||
e.g.:
|
||||
```
|
||||
x=$(cmd ...) &&
|
||||
...
|
||||
```
|
||||
is OK because a crash in `cmd ...` will cause the `&&` chain
|
||||
to fail, but:
|
||||
```
|
||||
test "foo" = "$(cmd ...)"
|
||||
```
|
||||
is not OK and a crash could go undetected.
|
||||
|
||||
* Don't use `sh` without spelling it as `$SHELL_PATH`. Although not
|
||||
strictly necessary on Linux, doing so aids readability and consistency
|
||||
across scripts.
|
||||
|
||||
* Don't `cd` around in tests. It is not sufficient to `chdir` to
|
||||
somewhere and then `chdir` back to the original location later in
|
||||
the test, as any intermediate step can fail and abort the test,
|
||||
causing the next test to start in an unexpected directory. Do so
|
||||
inside a subshell if necessary, e.g.:
|
||||
```
|
||||
mkdir foo &&
|
||||
(
|
||||
cd foo &&
|
||||
cmd ... &&
|
||||
test ...
|
||||
) &&
|
||||
rm -rf foo
|
||||
```
|
||||
|
||||
* Don't save and verify the standard error of compound commands, i.e.,
|
||||
group commands, subshells, and shell functions (except test helper
|
||||
functions like `test_must_fail`) like this:
|
||||
```
|
||||
( cd dir && cmd ... ) 2>error &&
|
||||
test_cmp expect error
|
||||
```
|
||||
When running the test with `-x` tracing flag, then the trace of commands
|
||||
executed in the compound command will be included in standard error
|
||||
as well, quite possibly throwing off the subsequent checks examining
|
||||
the output. Instead, save only the relevant command's standard
|
||||
error:
|
||||
```
|
||||
( cd dir && cmd ... 2>../error ) &&
|
||||
test_cmp expect error
|
||||
```
|
||||
|
||||
* Don't break the TAP output.
|
||||
|
||||
The raw output from your test may be interpreted by a TAP harness. TAP
|
||||
harnesses will ignore everything they don't know about, but don't step
|
||||
on their toes in these areas:
|
||||
|
||||
* Don't print lines like `$x..$y` where `$x` and `$y` are integers.
|
||||
|
||||
* Don't print lines that begin with `ok` or `not ok`.
|
||||
|
||||
TAP harnesses expect a line that begins with either `ok` and `not
|
||||
ok` to signal a test passed or failed (and our libraries already
|
||||
produce such lines), so your script shouldn't emit such lines to
|
||||
their output.
|
||||
|
||||
You can glean some further possible issues from the
|
||||
[TAP grammar](https://metacpan.org/pod/TAP::Parser::Grammar#TAP-GRAMMAR)
|
||||
but the best indication is to just run the tests with `make prove`
|
||||
as `prove` will complain if anything is amiss.
|
||||
|
||||
### Programmatically Skipping Tests
|
||||
|
||||
If you need to skip tests programmatically based on some condition
|
||||
detected at test run time, you should do so by using the three-arg form
|
||||
of the `test_*` functions (see the
|
||||
[Test Harness Library](#test-harness-library) section below), e.g.:
|
||||
```
|
||||
test_expect_success PIPE 'I need pipes' '
|
||||
echo foo | cmd ... &&
|
||||
...
|
||||
'
|
||||
```
|
||||
|
||||
The advantage of skipping tests this way is that platforms that don't
|
||||
have the PIPE prerequisite flag set will get an indication of how
|
||||
many tests they're missing.
|
||||
|
||||
If the test code is too hairy for that (i.e., does a lot of setup work
|
||||
outside test assertions) you can also skip all remaining tests by
|
||||
setting `skip_all` and immediately call `test_done`:
|
||||
```
|
||||
if ! test_have_prereq PIPE
|
||||
then
|
||||
skip_all='skipping pipe tests, I/O pipes not available'
|
||||
test_done
|
||||
fi
|
||||
```
|
||||
|
||||
The string you give to `skip_all` will be used as an explanation for why
|
||||
the test was skipped.
|
||||
|
||||
The set of pre-defined prerequisite flags is relatively limited
|
||||
(see the [Prerequisites](#prerequisites) section below),
|
||||
but additional flags may be defined a test run time using the
|
||||
`test_set_prereq` function, described below.
|
||||
|
||||
### Test Harness Library
|
||||
|
||||
There are a handful of helper functions defined in the test harness
|
||||
library for your script to use.
|
||||
|
||||
* `test_expect_success [<prereq>] <message> <script>`
|
||||
|
||||
Usually takes two strings as parameters, and evaluates the
|
||||
`<script>`. If it yields success, test is considered
|
||||
successful. The `<message>` should state what it is testing.
|
||||
|
||||
Example:
|
||||
```
|
||||
test_expect_success 'check command output' '
|
||||
cmd ... >output &&
|
||||
test_cmp expect output
|
||||
'
|
||||
```
|
||||
|
||||
If you supply three parameters the first will be taken to be a
|
||||
prerequisite; see the `test_set_prereq` and `test_have_prereq`
|
||||
documentation below:
|
||||
```
|
||||
test_expect_success SYMLINKS 'check symlinks' '
|
||||
ln -s foo bar &&
|
||||
test_cmp foo bar
|
||||
'
|
||||
```
|
||||
|
||||
You can also supply a comma-separated list of prerequisites, in the
|
||||
rare case where your test depends on more than one:
|
||||
```
|
||||
test_expect_success PIPE,SYMLINKS 'check output to symlink' '
|
||||
cmd ... >output &&
|
||||
ln -s output sym &&
|
||||
test_cmp expect sym
|
||||
'
|
||||
```
|
||||
|
||||
* `test_expect_failure [<prereq>] <message> <script>`
|
||||
|
||||
This is _not_ the opposite of `test_expect_success`, but is used
|
||||
to mark a test that demonstrates a known breakage. Unlike
|
||||
the usual `test_expect_success` tests, which say `ok` on
|
||||
success and `FAIL` on failure, this will say `FIXED` on
|
||||
success and `still broken` on failure.
|
||||
|
||||
Failures from these tests won't cause the test script to stop,
|
||||
even if the `-i` (or `--immediate`) option was specified.
|
||||
|
||||
Like `test_expect_success` this function can optionally use a three
|
||||
argument invocation with a prerequisite as the first argument.
|
||||
|
||||
* `test_debug <script>`
|
||||
|
||||
This takes a single argument, `<script>`, and evaluates it only
|
||||
when the test script is started with the `-d` (or `--debug`)
|
||||
option. This is primarily meant for use during the
|
||||
development of a new test script.
|
||||
|
||||
* `debug <command>`
|
||||
|
||||
Run a command inside a debugger. This is primarily meant for
|
||||
use when debugging a failing test script.
|
||||
|
||||
* `test_done`
|
||||
|
||||
Your test script must have `test_done` at the end. Its purpose
|
||||
is to summarize successes and failures in the test script and
|
||||
exit with an appropriate error code.
|
||||
|
||||
* `test_set_prereq <prereq>`
|
||||
|
||||
Set a test prerequisite to be used later with `test_have_prereq`. The
|
||||
test library will set some prerequisites for you; see the
|
||||
[Prerequisites](#prerequisites) section below for a full list of these.
|
||||
|
||||
Others you can set yourself, and then use later with either
|
||||
`test_have_prereq` directly or a three-argument invocation of
|
||||
`test_expect_success` and `test_expect_failure`.
|
||||
|
||||
* `test_have_prereq <prereq>`
|
||||
|
||||
Check if we have a prerequisite previously set with `test_set_prereq`.
|
||||
The most common way to use this explicitly (as opposed to the
|
||||
implicit use when an third argument is passed to `test_expect_*`) is to
|
||||
skip all the tests at the start of the test script if we don't have some
|
||||
essential prerequisite:
|
||||
```
|
||||
if ! test_have_prereq PIPE
|
||||
then
|
||||
skip_all='skipping pipe tests, I/O pipes not available'
|
||||
test_done
|
||||
fi
|
||||
```
|
||||
|
||||
* `test_external [<prereq>] <message> <external> <script>`
|
||||
|
||||
Execute a `<script>` with an `<external>` interpreter (like Python).
|
||||
|
||||
If the test outputs its own TAP-formatted results then you should set
|
||||
the `test_external_has_tap` variable to a non-zero value before calling
|
||||
the first `test_external*` function, e.g.:
|
||||
```
|
||||
# The external test will output its own plan
|
||||
test_external_has_tap=1
|
||||
test_external 'check python output' python "$TEST_DIRECTORY"/t000/test.py
|
||||
```
|
||||
|
||||
* `test_external_without_stderr [<prereq>] <message> <external> <script>`
|
||||
|
||||
Like `test_external` but will fail if there's any output on stderr,
|
||||
instead of checking the exit code.
|
||||
|
||||
* `test_expect_code <exit-code> <command>`
|
||||
|
||||
Run a command and ensure that it exits with the given exit code.
|
||||
For example:
|
||||
```
|
||||
test_expect_success 'check command exit code' '
|
||||
echo foo > foo &&
|
||||
echo bar > bar &&
|
||||
test_expect_code 1 diff foo bar
|
||||
test_expect_code 2 diff foo
|
||||
'
|
||||
```
|
||||
|
||||
* `test_must_fail [<options>] <command>`
|
||||
|
||||
Run a command and ensure it fails in a controlled way. Use
|
||||
this instead of `! <command>`. When the command dies due to a
|
||||
segfault, `test_must_fail` diagnoses it as an error, whereas using
|
||||
`! <command>` would treat it as an expected failure, which could let
|
||||
a bug go unnoticed.
|
||||
|
||||
Accepts the following options:
|
||||
|
||||
* `ok=<signal-name>[,<signal-name>[,...]]`
|
||||
|
||||
Don't treat an exit caused by the given signal as error.
|
||||
Multiple signals can be specified as a comma separated list.
|
||||
|
||||
Currently recognized signal names are: `sigpipe` and `success`
|
||||
(but don't use `success`, use `test_might_fail` instead; see below).
|
||||
|
||||
* `test_might_fail [<options>] <command>`
|
||||
|
||||
Similar to `test_must_fail`, but tolerates success, too. Use this
|
||||
instead of `<command> || :` to catch failures due to segfaults.
|
||||
|
||||
Accepts the same options as `test_must_fail`.
|
||||
|
||||
* `test_match_signal <expected> <actual>`
|
||||
|
||||
Check whether the signal number in `<actual>` matches that in
|
||||
`<expected>`, accounting for shell signal number offsets.
|
||||
Both parameters should be given numerically.
|
||||
|
||||
* `test_env [<var>=<value> [...]] <command>`
|
||||
|
||||
Run `<command>` in a subshell after setting any environment
|
||||
variables defined by the `<var>=<value>` parameters, e.g.:
|
||||
```
|
||||
test_env PATH=/tmp TRACE=true cmd ...
|
||||
```
|
||||
|
||||
* `test_cmp <expected> <actual>`
|
||||
|
||||
Check whether the content of the `<actual>` file matches the
|
||||
`<expected>` file. This behaves like `cmp` but produces more
|
||||
helpful output when the test is run with `-v` or `-V` (or `--verbose`
|
||||
or `--verbose-log`) options.
|
||||
|
||||
* `test_cmp_bin <expected> <actual>`
|
||||
|
||||
Check whether the binary content of the `<actual>` file matches the
|
||||
`<expected>` file, using `cmp`.
|
||||
|
||||
* `test_line_count <op> <length> <file>`
|
||||
|
||||
Check whether a file has the length it is expected to, using an
|
||||
`<op>` test operator available to the
|
||||
[test(1)](http://man7.org/linux/man-pages/man1/test.1.html) command.
|
||||
For example:
|
||||
```
|
||||
test_write_lines 1 2 3 4 5 >foo &&
|
||||
test_line_count -gt 4 foo &&
|
||||
test_line_count -lt 6 foo &&
|
||||
test_line_count = 5 foo
|
||||
```
|
||||
|
||||
* `test_path_is_file <path> [<diagnosis>]`
|
||||
`test_path_is_dir <path> [<diagnosis>]`
|
||||
`test_path_exists <path> [<diagnosis>]`
|
||||
`test_path_is_missing <path> [<diagnosis>]`
|
||||
`test_dir_is_empty <path>`
|
||||
`test_must_be_empty <path>`
|
||||
|
||||
Check if the named path is a file, if the named path is a
|
||||
directory, if the named path exists, if the named path does not exist,
|
||||
if the named path is an empty directory, or if the named path
|
||||
is an empty file, respectively, and fail otherwise, showing the
|
||||
`<diagnosis>` text where applicable.
|
||||
|
||||
* `test_when_finished <script>`
|
||||
|
||||
Prepend `<script>` to a list of commands to run to clean up
|
||||
at the end of the current test. If some clean-up command
|
||||
fails, the test will not pass. For example:
|
||||
```
|
||||
test_expect_success 'test with cleanup' '
|
||||
mkdir foo &&
|
||||
test_when_finished "rm -fr foo" &&
|
||||
( cd foo && ... )
|
||||
'
|
||||
```
|
||||
|
||||
* `test_skip_or_die auto|true <message>`
|
||||
|
||||
Exit the test script, either by skipping all remaining tests or by
|
||||
exiting with an error. If the first argument is `auto` then skip
|
||||
all remaining tests; otherwise, if it is true, report an error.
|
||||
The `<message>` is output in either case.
|
||||
|
||||
* `test_tristate <var>`
|
||||
|
||||
Normalize the value of the environment variable `<var>` to one
|
||||
of `auto`, `true`, or `false`. This allows a test script to
|
||||
decide whether to perform a specific test based on a user-supplied
|
||||
environment variable, for example, to skip any large-file tests
|
||||
in the test suite:
|
||||
```
|
||||
PROJFS_TEST_BIGFILES= make test
|
||||
```
|
||||
|
||||
If the user sets the variable `<var>` to an empty string or the
|
||||
value `false`, then `test_tristate <var>` will normalize the value
|
||||
to `false`. If the variable is unset or has the value `auto`, the
|
||||
value is normalized to `auto`. Any other value is normalized to `true`.
|
||||
|
||||
After calling `test_tristate`, the test script can therefore decide
|
||||
whether to execute or skip a test based on the tri-state value in
|
||||
`<var>`, with `true` meaning "test", `false` meaning "do not test",
|
||||
and `auto` meaning "automatically decide".
|
||||
|
||||
* `test_write_lines <lines>`
|
||||
|
||||
Write `<lines>` on standard output, one line per argument.
|
||||
Useful to prepare multi-line files in a compact form. For example,
|
||||
```
|
||||
test_write_lines a b c d e f g >foo
|
||||
```
|
||||
is a more compact equivalent of:
|
||||
```
|
||||
cat >foo <<-EOF
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
g
|
||||
EOF
|
||||
```
|
||||
|
||||
* `test_seq [<start>] <end>`
|
||||
|
||||
Print a sequence of integers in increasing order from `<start>` to
|
||||
`<end>`, or from 1 to `<end>` if `<start>` is not supplied.
|
||||
|
||||
* `test_pause`
|
||||
|
||||
This command is useful for writing and debugging tests, but should be
|
||||
removed before committing a new test.
|
||||
|
||||
It halts the execution of the test and spawns a shell in the trash
|
||||
directory, allowing the developer to examine the state of the test
|
||||
at this point. Exit the shell to continue the test. Example:
|
||||
```
|
||||
test_expect_success 'test under development' '
|
||||
cmd ... >actual &&
|
||||
test_pause &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
|
||||
These are the prerequisites that the test library pre-defines with
|
||||
`test_have_prereq` for use with the `test_have_prereq` function.
|
||||
|
||||
See also the discussion of the `<prereq>` argument to the `test_*`
|
||||
functions, in the [Test Harness Library](#test-harness-library) section
|
||||
above.
|
||||
|
||||
You can also use `test_set_prereq` to define your own test
|
||||
prerequisite flags.
|
||||
|
||||
* `PIPE`
|
||||
|
||||
The filesystem we're on supports creation of FIFOs (named pipes)
|
||||
via [mkfifo(1)](http://man7.org/linux/man-pages/man1/mkfifo.1.html).
|
||||
|
||||
* `SYMLINKS`
|
||||
|
||||
The filesystem we're on supports symbolic links.
|
||||
|
||||
* `NOT_ROOT`
|
||||
|
||||
Test is not run by the super-user.
|
||||
|
||||
* `SANITY`
|
||||
|
||||
Test is not run by the super-user, and an attempt to write to an
|
||||
unwritable file is expected to fail correctly.
|
||||
|
||||
[automake-tests]: https://www.gnu.org/software/automake/manual/html_node/Command_002dline-arguments-for-test-drivers.html
|
||||
[automake-tap]: https://www.gnu.org/software/automake/manual/html_node/Use-TAP-with-the-Automake-test-harness.html
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
# Copyright (C) Junio C Hamano and Git contributors.
|
||||
#
|
||||
# See the NOTICE file distributed with this library for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Detect broken &&-chains in tests.
|
||||
#
|
||||
# At present, only &&-chains in subshells are examined by this linter;
|
||||
# top-level &&-chains are instead checked directly by the test framework. Like
|
||||
# the top-level &&-chain linter, the subshell linter (intentionally) does not
|
||||
# check &&-chains within {...} blocks.
|
||||
#
|
||||
# Checking for &&-chain breakage is done line-by-line by pure textual
|
||||
# inspection.
|
||||
#
|
||||
# Incomplete lines (those ending with "\") are stitched together with following
|
||||
# lines to simplify processing, particularly of "one-liner" statements.
|
||||
# Top-level here-docs are swallowed to avoid false positives within the
|
||||
# here-doc body, although the statement to which the here-doc is attached is
|
||||
# retained.
|
||||
#
|
||||
# Heuristics are used to detect end-of-subshell when the closing ")" is cuddled
|
||||
# with the final subshell statement on the same line:
|
||||
#
|
||||
# (cd foo &&
|
||||
# bar)
|
||||
#
|
||||
# in order to avoid misinterpreting the ")" in constructs such as "x=$(...)"
|
||||
# and "case $x in *)" as ending the subshell.
|
||||
#
|
||||
# Lines missing a final "&&" are flagged with "?!AMP?!", and lines which chain
|
||||
# commands with ";" internally rather than "&&" are flagged "?!SEMI?!". A line
|
||||
# may be flagged for both violations.
|
||||
#
|
||||
# Detection of a missing &&-link in a multi-line subshell is complicated by the
|
||||
# fact that the last statement before the closing ")" must not end with "&&".
|
||||
# Since processing is line-by-line, it is not known whether a missing "&&" is
|
||||
# legitimate or not until the _next_ line is seen. To accommodate this, within
|
||||
# multi-line subshells, each line is stored in sed's "hold" area until after
|
||||
# the next line is seen and processed. If the next line is a stand-alone ")",
|
||||
# then a missing "&&" on the previous line is legitimate; otherwise a missing
|
||||
# "&&" is a break in the &&-chain.
|
||||
#
|
||||
# (
|
||||
# cd foo &&
|
||||
# bar
|
||||
# )
|
||||
#
|
||||
# In practical terms, when "bar" is encountered, it is flagged with "?!AMP?!",
|
||||
# but when the stand-alone ")" line is seen which closes the subshell, the
|
||||
# "?!AMP?!" violation is removed from the "bar" line (retrieved from the "hold"
|
||||
# area) since the final statement of a subshell must not end with "&&". The
|
||||
# final line of a subshell may still break the &&-chain by using ";" internally
|
||||
# to chain commands together rather than "&&", so "?!SEMI?!" is never removed
|
||||
# from a line (even though "?!AMP?!" might be).
|
||||
#
|
||||
# Care is taken to recognize the last _statement_ of a multi-line subshell, not
|
||||
# necessarily the last textual _line_ within the subshell, since &&-chaining
|
||||
# applies to statements, not to lines. Consequently, blank lines, comment
|
||||
# lines, and here-docs are swallowed (but not the command to which the here-doc
|
||||
# is attached), leaving the last statement in the "hold" area, not the last
|
||||
# line, thus simplifying &&-link checking.
|
||||
#
|
||||
# The final statement before "done" in for- and while-loops, and before "elif",
|
||||
# "else", and "fi" in if-then-else likewise must not end with "&&", thus
|
||||
# receives similar treatment.
|
||||
#
|
||||
# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a
|
||||
# line such as "cat <<EOF >out" is seen, the here-doc tag is moved to the front
|
||||
# of the line enclosed in angle brackets as a sentinel, giving "<EOF>cat >out".
|
||||
# As each subsequent line is read, it is appended to the target line and a
|
||||
# (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if
|
||||
# the content inside "<...>" matches the entirety of the newly-read line. For
|
||||
# instance, if the next line read is "some data", when concatenated with the
|
||||
# target line, it becomes "<EOF>cat >out\nsome data", and a match is attempted
|
||||
# to see if "EOF" matches "some data". Since it doesn't, the next line is
|
||||
# attempted. When a line consisting of only "EOF" (and possible whitespace) is
|
||||
# encountered, it is appended to the target line giving "<EOF>cat >out\nEOF",
|
||||
# in which case the "EOF" inside "<...>" does match the text following the
|
||||
# newline, thus the closing here-doc tag has been found. The closing tag line
|
||||
# and the "<...>" prefix on the target line are then discarded, leaving just
|
||||
# the target line "cat >out".
|
||||
#
|
||||
# To facilitate regression testing (and manual debugging), a ">" annotation is
|
||||
# applied to the line containing ")" which closes a subshell, ">>" to a line
|
||||
# closing a nested subshell, and ">>>" to a line closing both at once. This
|
||||
# makes it easy to detect whether the heuristics correctly identify
|
||||
# end-of-subshell.
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# incomplete line -- slurp up next line
|
||||
:squash
|
||||
/\\$/ {
|
||||
N
|
||||
s/\\\n//
|
||||
bsquash
|
||||
}
|
||||
|
||||
# here-doc -- swallow it to avoid false hits within its body (but keep the
|
||||
# command to which it was attached)
|
||||
/<<[ ]*[-\\'"]*[A-Za-z0-9_]/ {
|
||||
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
|
||||
s/[ ]*<<//
|
||||
:hered
|
||||
N
|
||||
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
|
||||
s/\n.*$//
|
||||
bhered
|
||||
}
|
||||
s/^<[^>]*>//
|
||||
s/\n.*$//
|
||||
}
|
||||
|
||||
# one-liner "(...) &&"
|
||||
/^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline
|
||||
|
||||
# same as above but without trailing "&&"
|
||||
/^[ ]*!*[ ]*(..*)[ ]*$/boneline
|
||||
|
||||
# one-liner "(...) >x" (or "2>x" or "<x" or "|x" or "&"
|
||||
/^[ ]*!*[ ]*(..*)[ ]*[0-9]*[<>|&]/boneline
|
||||
|
||||
# multi-line "(...\n...)"
|
||||
/^[ ]*(/bsubshell
|
||||
|
||||
# innocuous line -- print it and advance to next line
|
||||
b
|
||||
|
||||
# found one-liner "(...)" -- mark suspect if it uses ";" internally rather than
|
||||
# "&&" (but not ";" in a string)
|
||||
:oneline
|
||||
/;/{
|
||||
/"[^"]*;[^"]*"/!s/^/?!SEMI?!/
|
||||
}
|
||||
b
|
||||
|
||||
:subshell
|
||||
# bare "(" line? -- stash for later printing
|
||||
/^[ ]*([ ]*$/ {
|
||||
h
|
||||
bnextline
|
||||
}
|
||||
# "(..." line -- split off and stash "(", then process "..." as its own line
|
||||
x
|
||||
s/.*/(/
|
||||
x
|
||||
s/(//
|
||||
bslurp
|
||||
|
||||
:nextline
|
||||
N
|
||||
s/.*\n//
|
||||
|
||||
:slurp
|
||||
# incomplete line "...\"
|
||||
/\\$/bicmplte
|
||||
# multi-line quoted string "...\n..."?
|
||||
/"/bdqstring
|
||||
# multi-line quoted string '...\n...'? (but not contraction in string "it's")
|
||||
/'/{
|
||||
/"[^'"]*'[^'"]*"/!bsqstring
|
||||
}
|
||||
:folded
|
||||
# here-doc -- swallow it
|
||||
/<<[ ]*[-\\'"]*[A-Za-z0-9_]/bheredoc
|
||||
# comment or empty line -- discard since final non-comment, non-empty line
|
||||
# before closing ")", "done", "elsif", "else", or "fi" will need to be
|
||||
# re-visited to drop "suspect" marking since final line of those constructs
|
||||
# legitimately lacks "&&", so "suspect" mark must be removed
|
||||
/^[ ]*#/bnextline
|
||||
/^[ ]*$/bnextline
|
||||
# in-line comment -- strip it (but not "#" in a string, Bash ${#...} array
|
||||
# length, or Perforce "//depot/path#42" revision in filespec)
|
||||
/[ ]#/{
|
||||
/"[^"]*#[^"]*"/!s/[ ]#.*$//
|
||||
}
|
||||
# one-liner "case ... esac"
|
||||
/^[ ]*case[ ]*..*esac/bchkchn
|
||||
# multi-line "case ... esac"
|
||||
/^[ ]*case[ ]..*[ ]in/bcase
|
||||
# multi-line "for ... done" or "while ... done"
|
||||
/^[ ]*for[ ]..*[ ]in/bcontinue
|
||||
/^[ ]*while[ ]/bcontinue
|
||||
/^[ ]*do[ ]/bcontinue
|
||||
/^[ ]*do[ ]*$/bcontinue
|
||||
/;[ ]*do/bcontinue
|
||||
/^[ ]*done[ ]*&&[ ]*$/bdone
|
||||
/^[ ]*done[ ]*$/bdone
|
||||
/^[ ]*done[ ]*[<>|]/bdone
|
||||
/^[ ]*done[ ]*)/bdone
|
||||
/||[ ]*exit[ ]/bcontinue
|
||||
/||[ ]*exit[ ]*$/bcontinue
|
||||
# multi-line "if...elsif...else...fi"
|
||||
/^[ ]*if[ ]/bcontinue
|
||||
/^[ ]*then[ ]/bcontinue
|
||||
/^[ ]*then[ ]*$/bcontinue
|
||||
/;[ ]*then/bcontinue
|
||||
/^[ ]*elif[ ]/belse
|
||||
/^[ ]*elif[ ]*$/belse
|
||||
/^[ ]*else[ ]/belse
|
||||
/^[ ]*else[ ]*$/belse
|
||||
/^[ ]*fi[ ]*&&[ ]*$/bdone
|
||||
/^[ ]*fi[ ]*$/bdone
|
||||
/^[ ]*fi[ ]*[<>|]/bdone
|
||||
/^[ ]*fi[ ]*)/bdone
|
||||
# nested one-liner "(...) &&"
|
||||
/^[ ]*(.*)[ ]*&&[ ]*$/bchkchn
|
||||
# nested one-liner "(...)"
|
||||
/^[ ]*(.*)[ ]*$/bchkchn
|
||||
# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x")
|
||||
/^[ ]*(.*)[ ]*[0-9]*[<>|]/bchkchn
|
||||
# nested multi-line "(...\n...)"
|
||||
/^[ ]*(/bnest
|
||||
# multi-line "{...\n...}"
|
||||
/^[ ]*{/bblock
|
||||
# closing ")" on own line -- exit subshell
|
||||
/^[ ]*)/bclssolo
|
||||
# "$((...))" -- arithmetic expansion; not closing ")"
|
||||
/\$(([^)][^)]*))[^)]*$/bchkchn
|
||||
# "$(...)" -- command substitution; not closing ")"
|
||||
/\$([^)][^)]*)[^)]*$/bchkchn
|
||||
# multi-line "$(...\n...)" -- command substitution; treat as nested subshell
|
||||
/\$([^)]*$/bnest
|
||||
# "=(...)" -- Bash array assignment; not closing ")"
|
||||
/=(/bchkchn
|
||||
# closing "...) &&"
|
||||
/)[ ]*&&[ ]*$/bclose
|
||||
# closing "...)"
|
||||
/)[ ]*$/bclose
|
||||
# closing "...) >x" (or "2>x" or "<x" or "|x")
|
||||
/)[ ]*[<>|]/bclose
|
||||
:chkchn
|
||||
# mark suspect if line uses ";" internally rather than "&&" (but not ";" in a
|
||||
# string and not ";;" in one-liner "case...esac")
|
||||
/;/{
|
||||
/;;/!{
|
||||
/"[^"]*;[^"]*"/!s/^/?!SEMI?!/
|
||||
}
|
||||
}
|
||||
# line ends with pipe "...|" -- valid; not missing "&&"
|
||||
/|[ ]*$/bcontinue
|
||||
# missing end-of-line "&&" -- mark suspect
|
||||
/&&[ ]*$/!s/^/?!AMP?!/
|
||||
:continue
|
||||
# retrieve and print previous line
|
||||
x
|
||||
n
|
||||
bslurp
|
||||
|
||||
# found incomplete line "...\" -- slurp up next line
|
||||
:icmplte
|
||||
N
|
||||
s/\\\n//
|
||||
bslurp
|
||||
|
||||
# check for multi-line double-quoted string "...\n..." -- fold to one line
|
||||
:dqstring
|
||||
# remove all quote pairs
|
||||
s/"\([^"]*\)"/@!\1@!/g
|
||||
# done if no dangling quote
|
||||
/"/!bdqdone
|
||||
# otherwise, slurp next line and try again
|
||||
N
|
||||
s/\n//
|
||||
bdqstring
|
||||
:dqdone
|
||||
s/@!/"/g
|
||||
bfolded
|
||||
|
||||
# check for multi-line single-quoted string '...\n...' -- fold to one line
|
||||
:sqstring
|
||||
# remove all quote pairs
|
||||
s/'\([^']*\)'/@!\1@!/g
|
||||
# done if no dangling quote
|
||||
/'/!bsqdone
|
||||
# otherwise, slurp next line and try again
|
||||
N
|
||||
s/\n//
|
||||
bsqstring
|
||||
:sqdone
|
||||
s/@!/'/g
|
||||
bfolded
|
||||
|
||||
# found here-doc -- swallow it to avoid false hits within its body (but keep
|
||||
# the command to which it was attached)
|
||||
:heredoc
|
||||
s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
|
||||
s/[ ]*<<//
|
||||
:heredsub
|
||||
N
|
||||
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
|
||||
s/\n.*$//
|
||||
bheredsub
|
||||
}
|
||||
s/^<[^>]*>//
|
||||
s/\n.*$//
|
||||
bfolded
|
||||
|
||||
# found "case ... in" -- pass through untouched
|
||||
:case
|
||||
x
|
||||
n
|
||||
/^[ ]*esac/bslurp
|
||||
bcase
|
||||
|
||||
# found "else" or "elif" -- drop "suspect" from final line before "else" since
|
||||
# that line legitimately lacks "&&"
|
||||
:else
|
||||
x
|
||||
s/?!AMP?!//
|
||||
x
|
||||
bcontinue
|
||||
|
||||
# found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop
|
||||
# "suspect" from final contained line since that line legitimately lacks "&&"
|
||||
:done
|
||||
x
|
||||
s/?!AMP?!//
|
||||
x
|
||||
# is 'done' or 'fi' cuddled with ")" to close subshell?
|
||||
/done.*)/bclose
|
||||
/fi.*)/bclose
|
||||
bchkchn
|
||||
|
||||
# found nested multi-line "(...\n...)" -- pass through untouched
|
||||
:nest
|
||||
x
|
||||
:nstslurp
|
||||
n
|
||||
# closing ")" on own line -- stop nested slurp
|
||||
/^[ ]*)/bnstclose
|
||||
# comment -- not closing ")" if in comment
|
||||
/^[ ]*#/bnstcnt
|
||||
# "$((...))" -- arithmetic expansion; not closing ")"
|
||||
/\$(([^)][^)]*))[^)]*$/bnstcnt
|
||||
# "$(...)" -- command substitution; not closing ")"
|
||||
/\$([^)][^)]*)[^)]*$/bnstcnt
|
||||
# closing "...)" -- stop nested slurp
|
||||
/)/bnstclose
|
||||
:nstcnt
|
||||
x
|
||||
bnstslurp
|
||||
:nstclose
|
||||
s/^/>>/
|
||||
# is it "))" which closes nested and parent subshells?
|
||||
/)[ ]*)/bslurp
|
||||
bchkchn
|
||||
|
||||
# found multi-line "{...\n...}" block -- pass through untouched
|
||||
:block
|
||||
x
|
||||
n
|
||||
# closing "}" -- stop block slurp
|
||||
/}/bchkchn
|
||||
bblock
|
||||
|
||||
# found closing ")" on own line -- drop "suspect" from final line of subshell
|
||||
# since that line legitimately lacks "&&" and exit subshell loop
|
||||
:clssolo
|
||||
x
|
||||
s/?!AMP?!//
|
||||
p
|
||||
x
|
||||
s/^/>/
|
||||
b
|
||||
|
||||
# found closing "...)" -- exit subshell loop
|
||||
:close
|
||||
x
|
||||
p
|
||||
x
|
||||
s/^/>/
|
||||
b
|
|
@ -0,0 +1,51 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE // for basename() in <string.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <errno>\n", basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
err = tst_find_retval(0, argv[1], "errno");
|
||||
|
||||
if (err > 0) {
|
||||
fprintf(stderr, "%s: invalid errno: %s\n", basename(argv[0]),
|
||||
argv[1]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("%s\n", strerror(-err));
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs filesystem mirroring read tests
|
||||
|
||||
Check that basic filesystem read operations (directory listing,
|
||||
file read) function through a mirrored projfs mount.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_projfs_simple source target || exit 1
|
||||
|
||||
EXPECT_DIR="$TEST_DIRECTORY/$(basename "$0" .t | sed 's/-.*//')"
|
||||
|
||||
test_expect_success 'create source tree' '
|
||||
mkdir source/d1 &&
|
||||
mkdir source/d1/d2 &&
|
||||
echo file1 > source/f1.txt &&
|
||||
echo file2 > source/f2.txt &&
|
||||
echo file1 > source/d1/f1.txt &&
|
||||
echo file2 > source/d1/d2/f2.txt
|
||||
'
|
||||
|
||||
test_expect_success 'check target tree structure' '
|
||||
test_path_is_dir target/d1 &&
|
||||
test_path_is_dir target/d1/d2 &&
|
||||
test_path_is_file target/f1.txt &&
|
||||
test_path_is_file target/f2.txt &&
|
||||
test_path_is_file target/d1/f1.txt &&
|
||||
test_path_is_file target/d1/d2/f2.txt &&
|
||||
ls -a target >ls.target &&
|
||||
ls -a target/d1 >ls.d1 &&
|
||||
ls -a target/d1/d2 >ls.d2 &&
|
||||
test_cmp ls.target "$EXPECT_DIR/ls.target" &&
|
||||
test_cmp ls.d1 "$EXPECT_DIR/ls.d1" &&
|
||||
test_cmp ls.d2 "$EXPECT_DIR/ls.d2"
|
||||
'
|
||||
|
||||
test_expect_success 'check target tree content' '
|
||||
test_cmp target/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp target/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp target/d1/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp target/d1/d2/f2.txt "$EXPECT_DIR/f2.txt"
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1 @@
|
|||
file1
|
|
@ -0,0 +1 @@
|
|||
file2
|
|
@ -0,0 +1,4 @@
|
|||
.
|
||||
..
|
||||
d2
|
||||
f1.txt
|
|
@ -0,0 +1,3 @@
|
|||
.
|
||||
..
|
||||
f2.txt
|
|
@ -0,0 +1,5 @@
|
|||
.
|
||||
..
|
||||
d1
|
||||
f1.txt
|
||||
f2.txt
|
|
@ -0,0 +1,45 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs filesystem mirroring mkdir tests
|
||||
|
||||
Check that the filesystem directory creation operation (mkdir)
|
||||
functions through a mirrored projfs mount.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_projfs_simple source target || exit 1
|
||||
|
||||
EXPECT_DIR="$TEST_DIRECTORY/$(basename "$0" .t | sed 's/-.*//')"
|
||||
|
||||
test_expect_success 'create source tree' '
|
||||
mkdir source/d1 &&
|
||||
mkdir source/d1/d2
|
||||
'
|
||||
|
||||
test_expect_success 'create target tree' '
|
||||
mkdir target/d3 &&
|
||||
mkdir target/d3/d4 &&
|
||||
mkdir target/d1/d5 &&
|
||||
mkdir target/d1/d5/d6 &&
|
||||
mkdir target/d1/d2/d7
|
||||
'
|
||||
|
||||
test_expect_success 'check source tree' '
|
||||
find source >find.source &&
|
||||
sort find.source >sort.source &&
|
||||
test_cmp sort.source "$EXPECT_DIR/sort.source"
|
||||
'
|
||||
test_expect_success 'check target tree' '
|
||||
find target >find.target &&
|
||||
sort find.target >sort.target &&
|
||||
test_cmp sort.target "$EXPECT_DIR/sort.target"
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
source
|
||||
source/d1
|
||||
source/d1/d2
|
||||
source/d1/d2/d7
|
||||
source/d1/d5
|
||||
source/d1/d5/d6
|
||||
source/d3
|
||||
source/d3/d4
|
|
@ -0,0 +1,8 @@
|
|||
target
|
||||
target/d1
|
||||
target/d1/d2
|
||||
target/d1/d2/d7
|
||||
target/d1/d5
|
||||
target/d1/d5/d6
|
||||
target/d3
|
||||
target/d3/d4
|
|
@ -0,0 +1,62 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs filesystem mirroring write tests
|
||||
|
||||
Check that the basic filesystem file-write operations
|
||||
(create, copy, overwrite) function through a mirrored projfs mount.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_projfs_simple source target || exit 1
|
||||
|
||||
EXPECT_DIR="$TEST_DIRECTORY/$(basename "$0" .t | sed 's/-.*//')"
|
||||
|
||||
test_expect_success 'create source tree' '
|
||||
mkdir -p source/d1 &&
|
||||
mkdir -p source/d1/d2 &&
|
||||
echo file1 > source/f1.txt &&
|
||||
echo file1 > source/d1/f1.txt
|
||||
'
|
||||
|
||||
test_expect_success 'create target tree' '
|
||||
echo file2 > target/f2.txt &&
|
||||
echo file2 > target/d1/f2.txt &&
|
||||
cp target/f2.txt target/d1/d2 &&
|
||||
echo file1-NEW > target/f1.txt &&
|
||||
echo file1-NEW > target/d1/f1.txt
|
||||
'
|
||||
|
||||
test_expect_success 'check source tree' '
|
||||
test_path_is_file source/f1.txt &&
|
||||
test_path_is_file source/f2.txt &&
|
||||
test_path_is_file source/d1/f1.txt &&
|
||||
test_path_is_file source/d1/f2.txt &&
|
||||
test_path_is_file source/d1/d2/f2.txt &&
|
||||
test_cmp source/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp source/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp source/d1/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp source/d1/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp source/d1/d2/f2.txt "$EXPECT_DIR/f2.txt"
|
||||
'
|
||||
|
||||
test_expect_success 'check target tree' '
|
||||
test_path_is_file target/f1.txt &&
|
||||
test_path_is_file target/f2.txt &&
|
||||
test_path_is_file target/d1/f1.txt &&
|
||||
test_path_is_file target/d1/f2.txt &&
|
||||
test_path_is_file target/d1/d2/f2.txt &&
|
||||
test_cmp target/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp target/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp target/d1/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp target/d1/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp target/d1/d2/f2.txt "$EXPECT_DIR/f2.txt"
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1 @@
|
|||
file1-NEW
|
|
@ -0,0 +1 @@
|
|||
file2
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs filesystem mirroring remove tests
|
||||
|
||||
Check that the basic filesystem delete operations (rmdir, rm)
|
||||
function through a mirrored projfs mount.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_projfs_simple source target || exit 1
|
||||
|
||||
EXPECT_DIR="$TEST_DIRECTORY/$(basename "$0" .t | sed 's/-.*//')"
|
||||
|
||||
test_expect_success 'create source tree' '
|
||||
mkdir -p source/d1 &&
|
||||
mkdir -p source/d1/d2 &&
|
||||
mkdir -p source/d3 &&
|
||||
echo file1 > source/f1.txt &&
|
||||
echo file2 > source/f2.txt &&
|
||||
echo file1 > source/d1/f1.txt &&
|
||||
echo file2 > source/d1/d2/f2.txt
|
||||
'
|
||||
|
||||
test_expect_success 'remove target tree components' '
|
||||
rm target/d1/d2/f2.txt &&
|
||||
rm target/f2.txt &&
|
||||
rmdir target/d1/d2 &&
|
||||
rmdir target/d3
|
||||
'
|
||||
|
||||
test_expect_success 'check source tree' '
|
||||
find source >find.source &&
|
||||
sort find.source >sort.source &&
|
||||
test_cmp sort.source "$EXPECT_DIR/sort.source"
|
||||
'
|
||||
test_expect_success 'check target tree' '
|
||||
find target >find.target &&
|
||||
sort find.target >sort.target &&
|
||||
test_cmp sort.target "$EXPECT_DIR/sort.target"
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
source
|
||||
source/d1
|
||||
source/d1/f1.txt
|
||||
source/f1.txt
|
|
@ -0,0 +1,4 @@
|
|||
target
|
||||
target/d1
|
||||
target/d1/f1.txt
|
||||
target/f1.txt
|
|
@ -0,0 +1,78 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs file operation event tests
|
||||
|
||||
Check that projfs file operation notification and permission request
|
||||
events are received and handled correctly.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_projfs_handlers source target || exit 1
|
||||
|
||||
projfs_event_printf notify create_dir d1
|
||||
test_expect_success 'test event handler on parent directory creation' '
|
||||
projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_event_printf notify create_dir d1/d2
|
||||
test_expect_success 'test event handler on nested directory creation' '
|
||||
projfs_event_exec mkdir target/d1/d2 &&
|
||||
test_path_is_dir target/d1/d2
|
||||
'
|
||||
|
||||
# TODO: also use 'echo ... >' to exercise open() not create()
|
||||
|
||||
projfs_event_printf notify create_file f1.txt
|
||||
test_expect_success 'test event handler on top-level file creation' '
|
||||
projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf notify create_file d1/d2/f2.txt
|
||||
test_expect_success 'test event handler on nested file creation' '
|
||||
projfs_event_exec touch target/d1/d2/f2.txt &&
|
||||
test_path_is_file target/d1/d2/f2.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_file d1/d2/f2.txt
|
||||
test_expect_success 'test permission granted on nested file deletion' '
|
||||
projfs_event_exec rm target/d1/d2/f2.txt &&
|
||||
test_path_is_missing target/d1/d2/f2.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_file f1.txt
|
||||
test_expect_success 'test permission granted on top-level file deletion' '
|
||||
projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_missing target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_dir d1/d2
|
||||
test_expect_success 'test permission granted on nested directory deletion' '
|
||||
projfs_event_exec rmdir target/d1/d2 &&
|
||||
test_path_is_missing target/d1/d2
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_dir d1
|
||||
test_expect_success 'test permission granted on parent directory deletion' '
|
||||
projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_missing target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_projfs_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check no event error messages' '
|
||||
test_must_be_empty test_projfs_handlers.err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs file operation event handler error tests
|
||||
|
||||
Check that projfs file operation notification and permission request
|
||||
events respond to handler errors.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_projfs_handlers source target --retval ENOMEM || exit 1
|
||||
|
||||
# TODO: we expect mkdir to create a dir despite the handler error and
|
||||
# regardless of mkdir's failure exit code
|
||||
projfs_event_printf error ENOMEM notify create_dir d1
|
||||
test_expect_success 'test event handler error on directory creation' '
|
||||
test_must_fail projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
# TODO: we expect touch to create a file despite the handler error and
|
||||
# to not report a failure exit code
|
||||
projfs_event_printf error ENOMEM notify create_file f1.txt
|
||||
test_expect_success 'test event handler error on file creation' '
|
||||
test_might_fail projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf error ENOMEM perm delete_file f1.txt
|
||||
test_expect_success 'test event handler error on file deletion' '
|
||||
test_must_fail projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf error ENOMEM perm delete_dir d1
|
||||
test_expect_success 'test event handler error on directory deletion' '
|
||||
test_must_fail projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_projfs_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check all event error messages' '
|
||||
test_cmp test_projfs_handlers.err "$EVENT_ERR"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs file operation permission denial tests
|
||||
|
||||
Check that projfs file operation permission requests respond to
|
||||
denial responses from event handlers.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_projfs_handlers source target --retval deny || exit 1
|
||||
|
||||
projfs_event_printf notify create_dir d1
|
||||
test_expect_success 'test event handler on directory creation' '
|
||||
projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_event_printf notify create_file f1.txt
|
||||
test_expect_success 'test event handler on file creation' '
|
||||
projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_file f1.txt
|
||||
test_expect_success 'test permission request denied on file deletion' '
|
||||
test_must_fail projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_dir d1
|
||||
test_expect_success 'test permission request denied on directory deletion' '
|
||||
test_must_fail projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_projfs_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check no event error messages' '
|
||||
test_must_be_empty test_projfs_handlers.err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs file operation permission denial tests
|
||||
|
||||
Check that projfs file operation permission requests respond to
|
||||
denial responses caused by event handlers returning null.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_projfs_handlers source target --retval null || exit 1
|
||||
|
||||
projfs_event_printf notify create_dir d1
|
||||
test_expect_success 'test event handler on directory creation' '
|
||||
projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_event_printf notify create_file f1.txt
|
||||
test_expect_success 'test event handler on file creation' '
|
||||
projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_file f1.txt
|
||||
test_expect_success 'test permission request denied on file deletion' '
|
||||
test_must_fail projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_dir d1
|
||||
test_expect_success 'test permission request denied on directory deletion' '
|
||||
test_must_fail projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_projfs_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check no event error messages' '
|
||||
test_must_be_empty test_projfs_handlers.err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs file operation permission allowed tests
|
||||
|
||||
Check that projfs file operation permission requests respond to
|
||||
explicit allowed responses from event handlers.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_projfs_handlers source target --retval allow || exit 1
|
||||
|
||||
projfs_event_printf notify create_dir d1
|
||||
test_expect_success 'test event handler on directory creation' '
|
||||
projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_event_printf notify create_file f1.txt
|
||||
test_expect_success 'test event handler on file creation' '
|
||||
projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_file f1.txt
|
||||
test_expect_success 'test permission request allowed on file deletion' '
|
||||
projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_missing target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf perm delete_dir d1
|
||||
test_expect_success 'test permission request allowed on directory deletion' '
|
||||
projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_missing target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_projfs_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check no event error messages' '
|
||||
test_must_be_empty test_projfs_handlers.err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API filesystem mirroring read tests
|
||||
|
||||
Check that basic filesystem read operations (directory listing,
|
||||
file read) function through a mirrored projfs mount and the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_vfsapi_simple source target || exit 1
|
||||
|
||||
EXPECT_DIR="$TEST_DIRECTORY/$(basename "$0" .t | sed 's/-.*//')"
|
||||
|
||||
test_expect_success 'create source tree' '
|
||||
mkdir source/d1 &&
|
||||
mkdir source/d1/d2 &&
|
||||
echo file1 > source/f1.txt &&
|
||||
echo file2 > source/f2.txt &&
|
||||
echo file1 > source/d1/f1.txt &&
|
||||
echo file2 > source/d1/d2/f2.txt
|
||||
'
|
||||
|
||||
test_expect_success 'check target tree structure' '
|
||||
test_path_is_dir target/d1 &&
|
||||
test_path_is_dir target/d1/d2 &&
|
||||
test_path_is_file target/f1.txt &&
|
||||
test_path_is_file target/f2.txt &&
|
||||
test_path_is_file target/d1/f1.txt &&
|
||||
test_path_is_file target/d1/d2/f2.txt &&
|
||||
ls -a target >ls.target &&
|
||||
ls -a target/d1 >ls.d1 &&
|
||||
ls -a target/d1/d2 >ls.d2 &&
|
||||
test_cmp ls.target "$EXPECT_DIR/ls.target" &&
|
||||
test_cmp ls.d1 "$EXPECT_DIR/ls.d1" &&
|
||||
test_cmp ls.d2 "$EXPECT_DIR/ls.d2"
|
||||
'
|
||||
|
||||
test_expect_success 'check target tree content' '
|
||||
test_cmp target/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp target/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp target/d1/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp target/d1/d2/f2.txt "$EXPECT_DIR/f2.txt"
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1 @@
|
|||
file1
|
|
@ -0,0 +1 @@
|
|||
file2
|
|
@ -0,0 +1,4 @@
|
|||
.
|
||||
..
|
||||
d2
|
||||
f1.txt
|
|
@ -0,0 +1,3 @@
|
|||
.
|
||||
..
|
||||
f2.txt
|
|
@ -0,0 +1,5 @@
|
|||
.
|
||||
..
|
||||
d1
|
||||
f1.txt
|
||||
f2.txt
|
|
@ -0,0 +1,45 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API filesystem mirroring mkdir tests
|
||||
|
||||
Check that the filesystem directory creation operation (mkdir)
|
||||
functions through a mirrored projfs mount and the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_vfsapi_simple source target || exit 1
|
||||
|
||||
EXPECT_DIR="$TEST_DIRECTORY/$(basename "$0" .t | sed 's/-.*//')"
|
||||
|
||||
test_expect_success 'create source tree' '
|
||||
mkdir source/d1 &&
|
||||
mkdir source/d1/d2
|
||||
'
|
||||
|
||||
test_expect_success 'create target tree' '
|
||||
mkdir target/d3 &&
|
||||
mkdir target/d3/d4 &&
|
||||
mkdir target/d1/d5 &&
|
||||
mkdir target/d1/d5/d6 &&
|
||||
mkdir target/d1/d2/d7
|
||||
'
|
||||
|
||||
test_expect_success 'check source tree' '
|
||||
find source >find.source &&
|
||||
sort find.source >sort.source &&
|
||||
test_cmp sort.source "$EXPECT_DIR/sort.source"
|
||||
'
|
||||
test_expect_success 'check target tree' '
|
||||
find target >find.target &&
|
||||
sort find.target >sort.target &&
|
||||
test_cmp sort.target "$EXPECT_DIR/sort.target"
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
source
|
||||
source/d1
|
||||
source/d1/d2
|
||||
source/d1/d2/d7
|
||||
source/d1/d5
|
||||
source/d1/d5/d6
|
||||
source/d3
|
||||
source/d3/d4
|
|
@ -0,0 +1,8 @@
|
|||
target
|
||||
target/d1
|
||||
target/d1/d2
|
||||
target/d1/d2/d7
|
||||
target/d1/d5
|
||||
target/d1/d5/d6
|
||||
target/d3
|
||||
target/d3/d4
|
|
@ -0,0 +1,63 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API filesystem mirroring write tests
|
||||
|
||||
Check that the basic filesystem file-write operations
|
||||
(create, copy, overwrite) function through a mirrored projfs mount and
|
||||
the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_vfsapi_simple source target || exit 1
|
||||
|
||||
EXPECT_DIR="$TEST_DIRECTORY/$(basename "$0" .t | sed 's/-.*//')"
|
||||
|
||||
test_expect_success 'create source tree' '
|
||||
mkdir -p source/d1 &&
|
||||
mkdir -p source/d1/d2 &&
|
||||
echo file1 > source/f1.txt &&
|
||||
echo file1 > source/d1/f1.txt
|
||||
'
|
||||
|
||||
test_expect_success 'create target tree' '
|
||||
echo file2 > target/f2.txt &&
|
||||
echo file2 > target/d1/f2.txt &&
|
||||
cp target/f2.txt target/d1/d2 &&
|
||||
echo file1-NEW > target/f1.txt &&
|
||||
echo file1-NEW > target/d1/f1.txt
|
||||
'
|
||||
|
||||
test_expect_success 'check source tree' '
|
||||
test_path_is_file source/f1.txt &&
|
||||
test_path_is_file source/f2.txt &&
|
||||
test_path_is_file source/d1/f1.txt &&
|
||||
test_path_is_file source/d1/f2.txt &&
|
||||
test_path_is_file source/d1/d2/f2.txt &&
|
||||
test_cmp source/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp source/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp source/d1/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp source/d1/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp source/d1/d2/f2.txt "$EXPECT_DIR/f2.txt"
|
||||
'
|
||||
|
||||
test_expect_success 'check target tree' '
|
||||
test_path_is_file target/f1.txt &&
|
||||
test_path_is_file target/f2.txt &&
|
||||
test_path_is_file target/d1/f1.txt &&
|
||||
test_path_is_file target/d1/f2.txt &&
|
||||
test_path_is_file target/d1/d2/f2.txt &&
|
||||
test_cmp target/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp target/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp target/d1/f1.txt "$EXPECT_DIR/f1.txt" &&
|
||||
test_cmp target/d1/f2.txt "$EXPECT_DIR/f2.txt" &&
|
||||
test_cmp target/d1/d2/f2.txt "$EXPECT_DIR/f2.txt"
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1 @@
|
|||
file1-NEW
|
|
@ -0,0 +1 @@
|
|||
file2
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API filesystem mirroring remove tests
|
||||
|
||||
Check that the basic filesystem delete operations (rmdir, rm)
|
||||
function through a mirrored projfs mount and the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
projfs_start test_vfsapi_simple source target || exit 1
|
||||
|
||||
EXPECT_DIR="$TEST_DIRECTORY/$(basename "$0" .t | sed 's/-.*//')"
|
||||
|
||||
test_expect_success 'create source tree' '
|
||||
mkdir -p source/d1 &&
|
||||
mkdir -p source/d1/d2 &&
|
||||
mkdir -p source/d3 &&
|
||||
echo file1 > source/f1.txt &&
|
||||
echo file2 > source/f2.txt &&
|
||||
echo file1 > source/d1/f1.txt &&
|
||||
echo file2 > source/d1/d2/f2.txt
|
||||
'
|
||||
|
||||
test_expect_success 'remove target tree components' '
|
||||
rm target/d1/d2/f2.txt &&
|
||||
rm target/f2.txt &&
|
||||
rmdir target/d1/d2 &&
|
||||
rmdir target/d3
|
||||
'
|
||||
|
||||
test_expect_success 'check source tree' '
|
||||
find source >find.source &&
|
||||
sort find.source >sort.source &&
|
||||
test_cmp sort.source "$EXPECT_DIR/sort.source"
|
||||
'
|
||||
test_expect_success 'check target tree' '
|
||||
find target >find.target &&
|
||||
sort find.target >sort.target &&
|
||||
test_cmp sort.target "$EXPECT_DIR/sort.target"
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
source
|
||||
source/d1
|
||||
source/d1/f1.txt
|
||||
source/f1.txt
|
|
@ -0,0 +1,4 @@
|
|||
target
|
||||
target/d1
|
||||
target/d1/f1.txt
|
||||
target/f1.txt
|
|
@ -0,0 +1,78 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API file operation event tests
|
||||
|
||||
Check that projfs file operation notification and permission request
|
||||
events are received and handled correctly through the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_vfsapi_handlers source target || exit 1
|
||||
|
||||
projfs_event_printf vfs create_dir d1
|
||||
test_expect_success 'test event handler on parent directory creation' '
|
||||
projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_event_printf vfs create_dir d1/d2
|
||||
test_expect_success 'test event handler on nested directory creation' '
|
||||
projfs_event_exec mkdir target/d1/d2 &&
|
||||
test_path_is_dir target/d1/d2
|
||||
'
|
||||
|
||||
# TODO: also use 'echo ... >' to exercise open() not create()
|
||||
|
||||
projfs_event_printf vfs create_file f1.txt
|
||||
test_expect_success 'test event handler on top-level file creation' '
|
||||
projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf vfs create_file d1/d2/f2.txt
|
||||
test_expect_success 'test event handler on nested file creation' '
|
||||
projfs_event_exec touch target/d1/d2/f2.txt &&
|
||||
test_path_is_file target/d1/d2/f2.txt
|
||||
'
|
||||
|
||||
projfs_event_printf vfs delete_file d1/d2/f2.txt
|
||||
test_expect_success 'test vfs ission granted on nested file deletion' '
|
||||
projfs_event_exec rm target/d1/d2/f2.txt &&
|
||||
test_path_is_missing target/d1/d2/f2.txt
|
||||
'
|
||||
|
||||
projfs_event_printf vfs delete_file f1.txt
|
||||
test_expect_success 'test vfs ission granted on top-level file deletion' '
|
||||
projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_missing target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf vfs delete_dir d1/d2
|
||||
test_expect_success 'test vfs ission granted on nested directory deletion' '
|
||||
projfs_event_exec rmdir target/d1/d2 &&
|
||||
test_path_is_missing target/d1/d2
|
||||
'
|
||||
|
||||
projfs_event_printf vfs delete_dir d1
|
||||
test_expect_success 'test vfs ission granted on parent directory deletion' '
|
||||
projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_missing target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_vfsapi_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check no event error messages' '
|
||||
test_must_be_empty test_vfsapi_handlers.err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API file operation event handler error tests
|
||||
|
||||
Check that projfs file operation notification and permission request
|
||||
events respond to handler errors through the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_vfsapi_handlers source target --retval EOutOfMemory || exit 1
|
||||
|
||||
# TODO: we expect mkdir to create a dir despite the handler error and
|
||||
# regardless of mkdir's failure exit code
|
||||
projfs_event_printf error ENOMEM vfs create_dir d1
|
||||
test_expect_success 'test event handler error on directory creation' '
|
||||
test_must_fail projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
# TODO: we expect touch to create a file despite the handler error and
|
||||
# to not report a failure exit code
|
||||
projfs_event_printf error ENOMEM vfs create_file f1.txt
|
||||
test_expect_success 'test event handler error on file creation' '
|
||||
test_might_fail projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf error ENOMEM vfs delete_file f1.txt
|
||||
test_expect_success 'test event handler error on file deletion' '
|
||||
test_must_fail projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf error ENOMEM vfs delete_dir d1
|
||||
test_expect_success 'test event handler error on directory deletion' '
|
||||
test_must_fail projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_vfsapi_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check all event error messages' '
|
||||
test_cmp test_vfsapi_handlers.err "$EVENT_ERR"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API file operation permission denial tests
|
||||
|
||||
Check that projfs file operation permission requests respond to
|
||||
denial responses from event handlers through the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_vfsapi_handlers source target --retval deny || exit 1
|
||||
|
||||
# TODO: test_vfsapi_handlers always returns EPERM with --retval deny, unlike
|
||||
# test_projfs_handlers, so mkdir gets a handler error; like t701.1,
|
||||
# we expect mkdir to create a dir despite the handler error and
|
||||
# regardless of mkdir's failure exit code
|
||||
projfs_event_printf error EPERM vfs create_dir d1
|
||||
test_expect_success 'test event handler on directory creation' '
|
||||
test_must_fail projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
# TODO: test_vfsapi_handlers always returns EPERM with --retval deny, unlike
|
||||
# test_projfs_handlers, so touch sees a handler error; like t701.2,
|
||||
# we expect touch to create a file despite the handler error and
|
||||
# to not report a failure exit code
|
||||
projfs_event_printf error EPERM vfs create_file f1.txt
|
||||
test_expect_success 'test event handler on file creation' '
|
||||
projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf vfs delete_file f1.txt
|
||||
test_expect_success 'test permission request denied on file deletion' '
|
||||
test_must_fail projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf vfs delete_dir d1
|
||||
test_expect_success 'test permission request denied on directory deletion' '
|
||||
test_must_fail projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_vfsapi_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check all event error messages' '
|
||||
test_cmp test_vfsapi_handlers.err "$EVENT_ERR"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API file operation permission denial tests
|
||||
|
||||
Check that projfs file operation permission requests respond to
|
||||
denial responses caused by event handlers returning null through
|
||||
the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_vfsapi_handlers source target --retval null || exit 1
|
||||
|
||||
# TODO: test_vfsapi_handlers always returns EINVAL with --retval null, unlike
|
||||
# test_projfs_handlers, so mkdir sees a handler error; like t701.1,
|
||||
# we expect mkdir to create a dir despite the handler error and
|
||||
# regardless of mkdir's failure exit code
|
||||
projfs_event_printf error EINVAL vfs create_dir d1
|
||||
test_expect_success 'test event handler on directory creation' '
|
||||
test_must_fail projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
# TODO: test_vfsapi_handlers always returns EINVAL with --retval deny, unlike
|
||||
# test_projfs_handlers, so touch sees a handler error; like t701.2,
|
||||
# we expect touch to create a file despite the handler error and
|
||||
# to not report a failure exit code
|
||||
projfs_event_printf error EINVAL vfs create_file f1.txt
|
||||
test_expect_success 'test event handler on file creation' '
|
||||
test_might_fail projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf error EINVAL vfs delete_file f1.txt
|
||||
test_expect_success 'test permission request denied on file deletion' '
|
||||
test_must_fail projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf error EINVAL vfs delete_dir d1
|
||||
test_expect_success 'test permission request denied on directory deletion' '
|
||||
test_must_fail projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_vfsapi_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check all event error messages' '
|
||||
test_cmp test_vfsapi_handlers.err "$EVENT_ERR"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
test_description='projfs VFS API file operation permission allowed tests
|
||||
|
||||
Check that projfs file operation permission requests respond to
|
||||
explicit allowed responses from event handlers through the VFS API.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/test-lib-event.sh
|
||||
|
||||
projfs_start test_vfsapi_handlers source target --retval allow || exit 1
|
||||
|
||||
projfs_event_printf vfs create_dir d1
|
||||
test_expect_success 'test event handler on directory creation' '
|
||||
projfs_event_exec mkdir target/d1 &&
|
||||
test_path_is_dir target/d1
|
||||
'
|
||||
|
||||
projfs_event_printf vfs create_file f1.txt
|
||||
test_expect_success 'test event handler on file creation' '
|
||||
projfs_event_exec touch target/f1.txt &&
|
||||
test_path_is_file target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf vfs delete_file f1.txt
|
||||
test_expect_success 'test permission request allowed on file deletion' '
|
||||
projfs_event_exec rm target/f1.txt &&
|
||||
test_path_is_missing target/f1.txt
|
||||
'
|
||||
|
||||
projfs_event_printf vfs delete_dir d1
|
||||
test_expect_success 'test permission request allowed on directory deletion' '
|
||||
projfs_event_exec rmdir target/d1 &&
|
||||
test_path_is_missing target/d1
|
||||
'
|
||||
|
||||
projfs_stop || exit 1
|
||||
|
||||
test_expect_success 'check all event notifications' '
|
||||
test_cmp test_vfsapi_handlers.log "$EVENT_LOG"
|
||||
'
|
||||
|
||||
test_expect_success 'check no event error messages' '
|
||||
test_must_be_empty test_vfsapi_handlers.err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# Event notification test library included by some test scripts.
|
||||
#
|
||||
# Copyright (C) 2019 GitHub, Inc.
|
||||
#
|
||||
|
||||
EVENT_LOG="expect.event.log"
|
||||
EVENT_ERR="expect.event.err"
|
||||
|
||||
event_msg_notify="test event notification for"
|
||||
event_msg_perm="test permission request for"
|
||||
|
||||
event_msg_vfs="TestNotifyOperation for"
|
||||
|
||||
event_create_dir="0x0001-40000000"
|
||||
event_delete_dir="0x0000-40000400"
|
||||
|
||||
event_delete_file="0x0000-00000400"
|
||||
event_create_file="0x0001-00000000"
|
||||
|
||||
event_vfs_create_dir="1, 0x00000004"
|
||||
event_vfs_delete_dir="1, 0x00000010"
|
||||
|
||||
event_vfs_create_file="0, 0x00000004"
|
||||
event_vfs_delete_file="0, 0x00000010"
|
||||
|
||||
# Format into "$event_msg" and "$event_err_msg" log and error messages
|
||||
# matching those output by the test mount helper programs. If an error
|
||||
# is expected, "$1" should be "error" and "$2" should contain the errno
|
||||
# name, e.g., "ENOMEM". The next three arguments (starting at either
|
||||
# "$3" or "$1", depending on whether an error is expected or not) should be
|
||||
# the category of event (e.g., "notify" or "perm"), and the type of event
|
||||
# (e.g., "create_dir" or "delete_file"), and the file path
|
||||
# (relative to the projfs mount point) on which the event is expected,
|
||||
projfs_event_printf () {
|
||||
if test ":$1" = ":error"
|
||||
then
|
||||
shift
|
||||
err=$("$TEST_DIRECTORY"/get_strerror "$1"); shift
|
||||
else
|
||||
err=""
|
||||
event_err_msg=""
|
||||
fi
|
||||
|
||||
eval msg=\$event_msg_"$1"
|
||||
eval code=\$event_"$2"
|
||||
|
||||
if test ":$1" = ":vfs"
|
||||
then
|
||||
eval vfs_code=\$event_vfs_"$2"
|
||||
event_msg_head=" $msg $3: "
|
||||
event_msg_tail=", $vfs_code"
|
||||
else
|
||||
event_msg_head=" $msg $3: $code, "
|
||||
event_msg_tail=""
|
||||
fi
|
||||
|
||||
if test ":$err" != ":"
|
||||
then
|
||||
event_err_msg="projfs: event handler failed"
|
||||
event_err_msg="$event_err_msg: $err; event mask $code, pid "
|
||||
fi
|
||||
}
|
||||
|
||||
# Execute a command, capturing its process ID, then format the pid into
|
||||
# messages as expected from the test mount helper programs. The messages
|
||||
# are appended to "$EVENT_LOG" and, if an error is expected, into
|
||||
# "$EVENT_ERR".
|
||||
# Requires that projfs_event_printf has been called first to format the
|
||||
# expected messages (without the pid) and flag whether an error is expected.
|
||||
projfs_event_exec () {
|
||||
if test ":$event_msg_tail" != ":"
|
||||
then
|
||||
event_msg_tail=", $1$event_msg_tail"; # prepend cmd for VFS
|
||||
fi &&
|
||||
if test ":$event_err_msg" = ":"
|
||||
then
|
||||
event_err=""
|
||||
else
|
||||
event_err="$EVENT_ERR"
|
||||
fi &&
|
||||
projfs_log_exec "$EVENT_LOG" "$event_msg_head" "$event_msg_tail" \
|
||||
"$event_err" "$event_err_msg" "$@"
|
||||
}
|
||||
|
|
@ -0,0 +1,807 @@
|
|||
# Library of functions shared by all test scripts, included by
|
||||
# test-lib.sh.
|
||||
#
|
||||
# Copyright (C) 2005 Junio C Hamano
|
||||
#
|
||||
# See the NOTICE file distributed with this library for additional
|
||||
# information regarding copyright ownership. Additional functions are:
|
||||
#
|
||||
# Copyright (C) 2018-2019 GitHub, Inc.
|
||||
#
|
||||
# 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/ .
|
||||
|
||||
# In some bourne shell implementations, the "unset" builtin returns
|
||||
# nonzero status when a variable to be unset was not set in the first
|
||||
# place.
|
||||
#
|
||||
# Use sane_unset when that should not be considered an error.
|
||||
|
||||
sane_unset () {
|
||||
unset "$@"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Stop execution and start a shell. This is useful for debugging tests.
|
||||
#
|
||||
# Be sure to remove all invocations of this command before submitting.
|
||||
|
||||
test_pause () {
|
||||
"$SHELL_PATH" <&6 >&5 2>&7
|
||||
}
|
||||
|
||||
# Wrap cmd with a debugger. Adding this to a command can make it easier
|
||||
# to understand what is going on in a failing test.
|
||||
#
|
||||
# Examples:
|
||||
# debug cmd ...
|
||||
# debug --debugger=nemiver cmd $ARGS
|
||||
# debug -d "valgrind --tool=memcheck --track-origins=yes" cmd $ARGS
|
||||
debug () {
|
||||
case "$1" in
|
||||
-d)
|
||||
PROJFS_DEBUGGER="$2" &&
|
||||
shift 2
|
||||
;;
|
||||
--debugger=*)
|
||||
PROJFS_DEBUGGER="${1#*=}" &&
|
||||
shift 1
|
||||
;;
|
||||
*)
|
||||
PROJFS_DEBUGGER=1
|
||||
;;
|
||||
esac &&
|
||||
PROJFS_DEBUGGER="${PROJFS_DEBUGGER}" "$@" <&6 >&5 2>&7
|
||||
}
|
||||
|
||||
# Get the modebits from a file.
|
||||
test_modebits () {
|
||||
ls -l "$1" | $SED -e 's|^\(..........\).*|\1|'
|
||||
}
|
||||
|
||||
write_script () {
|
||||
{
|
||||
echo "#!${2-"$SHELL_PATH"}" &&
|
||||
cat
|
||||
} >"$1" &&
|
||||
chmod +x "$1"
|
||||
}
|
||||
|
||||
# Use test_set_prereq to tell that a particular prerequisite is available.
|
||||
# The prerequisite can later be checked for in two ways:
|
||||
#
|
||||
# - Explicitly using test_have_prereq.
|
||||
#
|
||||
# - Implicitly by specifying the prerequisite tag in the calls to
|
||||
# test_expect_{success,failure,code}.
|
||||
#
|
||||
# The single parameter is the prerequisite tag (a simple word, in all
|
||||
# capital letters by convention).
|
||||
|
||||
test_unset_prereq () {
|
||||
! test_have_prereq "$1" ||
|
||||
satisfied_prereq="${satisfied_prereq% $1 *} ${satisfied_prereq#* $1 }"
|
||||
}
|
||||
|
||||
test_set_prereq () {
|
||||
case "$1" in
|
||||
!*)
|
||||
test_unset_prereq "${1#!}"
|
||||
;;
|
||||
*)
|
||||
satisfied_prereq="$satisfied_prereq$1 "
|
||||
;;
|
||||
esac
|
||||
}
|
||||
satisfied_prereq=" "
|
||||
lazily_testable_prereq= lazily_tested_prereq=
|
||||
|
||||
# Usage: test_lazy_prereq PREREQ 'script'
|
||||
test_lazy_prereq () {
|
||||
lazily_testable_prereq="$lazily_testable_prereq$1 "
|
||||
eval test_prereq_lazily_$1=\$2
|
||||
}
|
||||
|
||||
test_run_lazy_prereq_ () {
|
||||
script='
|
||||
$MKDIR_P "$TRASH_DIRECTORY/prereq-test-dir" &&
|
||||
(
|
||||
cd "$TRASH_DIRECTORY/prereq-test-dir" &&'"$2"'
|
||||
)'
|
||||
say >&3 "checking prerequisite: $1"
|
||||
say >&3 "$script"
|
||||
test_eval_ "$script"
|
||||
eval_ret=$?
|
||||
rm -rf "$TRASH_DIRECTORY/prereq-test-dir"
|
||||
if test "$eval_ret" = 0; then
|
||||
say >&3 "prerequisite $1 ok"
|
||||
else
|
||||
say >&3 "prerequisite $1 not satisfied"
|
||||
fi
|
||||
return $eval_ret
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
case " $lazily_tested_prereq " in
|
||||
*" $prerequisite "*)
|
||||
;;
|
||||
*)
|
||||
case " $lazily_testable_prereq " in
|
||||
*" $prerequisite "*)
|
||||
eval "script=\$test_prereq_lazily_$prerequisite" &&
|
||||
if test_run_lazy_prereq_ "$prerequisite" "$script"
|
||||
then
|
||||
test_set_prereq $prerequisite
|
||||
fi
|
||||
lazily_tested_prereq="$lazily_tested_prereq$prerequisite "
|
||||
esac
|
||||
;;
|
||||
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
|
||||
}
|
||||
|
||||
test_declared_prereq () {
|
||||
case ",$test_prereq," in
|
||||
*,$1,*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
test_verify_prereq () {
|
||||
test -z "$test_prereq" ||
|
||||
expr >/dev/null "$test_prereq" : '[A-Z0-9_,!]*$' ||
|
||||
BUG "'$test_prereq' does not look like a prereq"
|
||||
}
|
||||
|
||||
test_expect_failure () {
|
||||
test_start_
|
||||
test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
|
||||
test "$#" = 2 ||
|
||||
BUG "not 2 or 3 parameters to test-expect-failure"
|
||||
test_verify_prereq
|
||||
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
|
||||
test_finish_
|
||||
}
|
||||
|
||||
test_expect_success () {
|
||||
test_start_
|
||||
test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
|
||||
test "$#" = 2 ||
|
||||
BUG "not 2 or 3 parameters to test-expect-success"
|
||||
test_verify_prereq
|
||||
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
|
||||
test_finish_
|
||||
}
|
||||
|
||||
# test_external runs external test scripts that provide continuous
|
||||
# test output about their progress, and succeeds/fails on
|
||||
# zero/non-zero exit code. It outputs the test output on stdout even
|
||||
# in non-verbose mode, and announces the external script with "# run
|
||||
# <n>: ..." before running it. When providing relative paths, keep in
|
||||
# mind that all scripts run in "trash directory".
|
||||
# Usage: test_external description command arguments...
|
||||
# Example: test_external 'Perl API' perl ../path/to/test.pl
|
||||
test_external () {
|
||||
test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
|
||||
test "$#" = 3 ||
|
||||
BUG "not 3 or 4 parameters to test_external"
|
||||
descr="$1"
|
||||
shift
|
||||
test_verify_prereq
|
||||
export test_prereq
|
||||
if ! test_skip "$descr" "$@"
|
||||
then
|
||||
# Announce the script to reduce confusion about the
|
||||
# test output that follows.
|
||||
say_color "" "# run $test_count: $descr ($*)"
|
||||
# Export TEST_DIRECTORY and TRASH_DIRECTORY
|
||||
# to be able to use them in script
|
||||
export TEST_DIRECTORY TRASH_DIRECTORY
|
||||
# Run command; redirect its stderr to &4 as in
|
||||
# test_run_, but keep its stdout on our stdout even in
|
||||
# non-verbose mode.
|
||||
"$@" 2>&4
|
||||
if test "$?" = 0
|
||||
then
|
||||
if test $test_external_has_tap -eq 0; then
|
||||
test_ok_ "$descr"
|
||||
else
|
||||
say_color "" "# test_external test $descr was ok"
|
||||
test_success=$(($test_success + 1))
|
||||
fi
|
||||
else
|
||||
if test $test_external_has_tap -eq 0; then
|
||||
test_failure_ "$descr" "$@"
|
||||
else
|
||||
say_color error "# test_external test $descr failed: $@"
|
||||
test_failure=$(($test_failure + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Like test_external, but in addition tests that the command generated
|
||||
# no output on stderr.
|
||||
test_external_without_stderr () {
|
||||
# The temporary file has no (and must have no) security
|
||||
# implications.
|
||||
tmp=${TMPDIR:-/tmp}
|
||||
stderr="$tmp/projfs-external-stderr.$$.tmp"
|
||||
test_external "$@" 4> "$stderr"
|
||||
test -f "$stderr" || error "Internal error: $stderr disappeared."
|
||||
descr="no stderr: $1"
|
||||
shift
|
||||
say >&3 "# expecting no stderr from previous command"
|
||||
if test ! -s "$stderr"
|
||||
then
|
||||
rm "$stderr"
|
||||
|
||||
if test $test_external_has_tap -eq 0; then
|
||||
test_ok_ "$descr"
|
||||
else
|
||||
say_color "" "# test_external_without_stderr test $descr was ok"
|
||||
test_success=$(($test_success + 1))
|
||||
fi
|
||||
else
|
||||
if test "$verbose" = t
|
||||
then
|
||||
output=$(echo; echo "# Stderr is:"; cat "$stderr")
|
||||
else
|
||||
output=
|
||||
fi
|
||||
# rm first in case test_failure exits.
|
||||
rm "$stderr"
|
||||
if test $test_external_has_tap -eq 0; then
|
||||
test_failure_ "$descr" "$@" "$output"
|
||||
else
|
||||
say_color error "# test_external_without_stderr test $descr failed: $@: $output"
|
||||
test_failure=$(($test_failure + 1))
|
||||
fi
|
||||
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
|
||||
}
|
||||
|
||||
test_path_exists () {
|
||||
if ! test -e "$1"
|
||||
then
|
||||
echo "Path $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
|
||||
}
|
||||
|
||||
test_path_is_missing () {
|
||||
if test -e "$1"
|
||||
then
|
||||
echo "Path exists:"
|
||||
ls -ld "$1"
|
||||
if test $# -ge 1
|
||||
then
|
||||
echo "$*"
|
||||
fi
|
||||
false
|
||||
fi
|
||||
}
|
||||
|
||||
# test_line_count checks that a file has the number of lines it
|
||||
# ought to. For example:
|
||||
#
|
||||
# test_expect_success 'produce exactly one line of output' '
|
||||
# do something >output &&
|
||||
# test_line_count = 1 output
|
||||
# '
|
||||
#
|
||||
# is like "test $(wc -l <output) = 1" except that it passes the
|
||||
# output through when the number of lines is wrong.
|
||||
|
||||
test_line_count () {
|
||||
if test $# != 3
|
||||
then
|
||||
BUG "not 3 parameters to test_line_count"
|
||||
elif ! test $(wc -l <"$3") "$1" "$2"
|
||||
then
|
||||
echo "test_line_count: line count for $3 !$1 $2"
|
||||
cat "$3"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Returns success if a comma separated string of keywords ($1) contains a
|
||||
# given keyword ($2).
|
||||
# Examples:
|
||||
# `list_contains "foo,bar" bar` returns 0
|
||||
# `list_contains "foo" bar` returns 1
|
||||
|
||||
list_contains () {
|
||||
case ",$1," in
|
||||
*,$2,*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# This is not among top-level (test_expect_success | test_expect_failure)
|
||||
# but is a prefix that can be used in the test script, like:
|
||||
#
|
||||
# test_expect_success 'complain and die' '
|
||||
# do something &&
|
||||
# do something else &&
|
||||
# test_must_fail cmd ...
|
||||
# '
|
||||
#
|
||||
# Writing this as "! cmd ..." is wrong, because
|
||||
# the failure could be due to a segv. We want a controlled failure.
|
||||
#
|
||||
# Accepts the following options:
|
||||
#
|
||||
# ok=<signal-name>[,<...>]:
|
||||
# Don't treat an exit caused by the given signal as error.
|
||||
# Multiple signals can be specified as a comma separated list.
|
||||
# Currently recognized signal names are: sigpipe, success.
|
||||
# (Don't use 'success', use 'test_might_fail' instead.)
|
||||
|
||||
test_must_fail () {
|
||||
case "$1" in
|
||||
ok=*)
|
||||
_test_ok=${1#ok=}
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
_test_ok=
|
||||
;;
|
||||
esac
|
||||
"$@" 2>&7
|
||||
exit_code=$?
|
||||
if test $exit_code -eq 0 && ! list_contains "$_test_ok" success
|
||||
then
|
||||
echo >&4 "test_must_fail: command succeeded: $*"
|
||||
return 1
|
||||
elif test_match_signal 13 $exit_code && list_contains "$_test_ok" sigpipe
|
||||
then
|
||||
return 0
|
||||
elif test $exit_code -gt 129 && test $exit_code -le 192
|
||||
then
|
||||
echo >&4 "test_must_fail: died by signal $(($exit_code - 128)): $*"
|
||||
return 1
|
||||
elif test $exit_code -eq 127
|
||||
then
|
||||
echo >&4 "test_must_fail: command not found: $*"
|
||||
return 1
|
||||
elif test $exit_code -eq 126
|
||||
then
|
||||
echo >&4 "test_must_fail: valgrind error: $*"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
} 7>&2 2>&4
|
||||
|
||||
# Similar to test_must_fail, but tolerates success, too. This is
|
||||
# meant to be used in contexts like:
|
||||
#
|
||||
# test_expect_success 'some command works without configuration' '
|
||||
# test_might_fail cmd ... &&
|
||||
# do something
|
||||
# '
|
||||
#
|
||||
# Writing "cmd ... || :" would be wrong,
|
||||
# because we want to notice if it fails due to segv.
|
||||
#
|
||||
# Accepts the same options as test_must_fail.
|
||||
|
||||
test_might_fail () {
|
||||
test_must_fail ok=success "$@" 2>&7
|
||||
} 7>&2 2>&4
|
||||
|
||||
# Similar to test_must_fail and test_might_fail, but check that a
|
||||
# given command exited with a given exit code. Meant to be used as:
|
||||
#
|
||||
# test_expect_success 'Merge with d/f conflicts' '
|
||||
# test_expect_code 1 cmd ...
|
||||
# '
|
||||
|
||||
test_expect_code () {
|
||||
want_code=$1
|
||||
shift
|
||||
"$@" 2>&7
|
||||
exit_code=$?
|
||||
if test $exit_code = $want_code
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo >&4 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
|
||||
return 1
|
||||
} 7>&2 2>&4
|
||||
|
||||
# test_cmp is a helper function to compare actual and expected output.
|
||||
# You can use it like:
|
||||
#
|
||||
# test_expect_success 'foo works' '
|
||||
# echo expected >expected &&
|
||||
# foo >actual &&
|
||||
# test_cmp expected actual
|
||||
# '
|
||||
#
|
||||
# This could be written as either "cmp" or "diff -u", but:
|
||||
# - cmp's output is not nearly as easy to read as diff -u
|
||||
# - not all diff versions understand "-u"
|
||||
|
||||
test_cmp() {
|
||||
$PROJFS_TEST_CMP "$@"
|
||||
}
|
||||
|
||||
# test_cmp_bin - helper to compare binary files
|
||||
|
||||
test_cmp_bin() {
|
||||
cmp "$@"
|
||||
}
|
||||
|
||||
# Call any command "$@" but be more verbose about its
|
||||
# failure. This is handy for commands like "test" which do
|
||||
# not output anything when they fail.
|
||||
verbose () {
|
||||
"$@" && return 0
|
||||
echo -n >&4 "command failed: "
|
||||
echo >&4 "$@"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if the file expected to be empty is indeed empty, and barfs
|
||||
# otherwise.
|
||||
|
||||
test_must_be_empty () {
|
||||
test_path_is_file "$1" &&
|
||||
if test -s "$1"
|
||||
then
|
||||
echo "'$1' is not empty, it contains:"
|
||||
cat "$1"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Print a sequence of integers in increasing order, either with
|
||||
# two arguments (start and end):
|
||||
#
|
||||
# test_seq 1 5 -- outputs 1 2 3 4 5 one line at a time
|
||||
#
|
||||
# or with one argument (end), in which case it starts counting
|
||||
# from 1.
|
||||
|
||||
test_seq () {
|
||||
case $# in
|
||||
1) set 1 "$@" ;;
|
||||
2) ;;
|
||||
*) BUG "not 1 or 2 parameters to test_seq" ;;
|
||||
esac
|
||||
test_seq_counter__=$1
|
||||
while test "$test_seq_counter__" -le "$2"
|
||||
do
|
||||
echo "$test_seq_counter__"
|
||||
test_seq_counter__=$(( $test_seq_counter__ + 1 ))
|
||||
done
|
||||
}
|
||||
|
||||
# This function can be used to schedule some commands to be run
|
||||
# unconditionally at the end of the test to restore sanity:
|
||||
#
|
||||
# test_expect_success 'test core.capslock' '
|
||||
# cmd ... &&
|
||||
# test_when_finished "reset-cmd ..." &&
|
||||
# hello world
|
||||
# '
|
||||
#
|
||||
# That would be roughly equivalent to
|
||||
#
|
||||
# test_expect_success 'test core.capslock' '
|
||||
# cmd ... &&
|
||||
# hello world
|
||||
# reset-cmd ...
|
||||
# '
|
||||
#
|
||||
# except that the greeting and config --unset must both succeed for
|
||||
# the test to pass.
|
||||
#
|
||||
# Note that under --immediate mode, no clean-up is done to help diagnose
|
||||
# what went wrong.
|
||||
|
||||
test_when_finished () {
|
||||
# We cannot detect when we are in a subshell in general, but by
|
||||
# doing so on Bash is better than nothing (the test will
|
||||
# silently pass on other shells).
|
||||
test "${BASH_SUBSHELL-0}" = 0 ||
|
||||
BUG "test_when_finished does nothing in a subshell"
|
||||
test_cleanup="{ $*
|
||||
} && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
|
||||
}
|
||||
|
||||
# This function writes out its parameters, one per line
|
||||
test_write_lines () {
|
||||
printf "%s\n" "$@"
|
||||
}
|
||||
|
||||
# Given a variable $1, normalize the value of it to one of "true",
|
||||
# "false", or "auto" and store the result to it.
|
||||
#
|
||||
# test_tristate PROJFS_TEST_FOO
|
||||
#
|
||||
# A variable set to an empty string is set to 'false'.
|
||||
# A variable set to 'false' or 'auto' keeps its value.
|
||||
# Anything else is set to 'true'.
|
||||
# An unset variable defaults to 'auto'.
|
||||
#
|
||||
# The last rule is to allow people to set the variable to an empty
|
||||
# string and export it to decline testing the particular feature
|
||||
# for versions both before and after this change. We used to treat
|
||||
# both unset and empty variable as a signal for "do not test" and
|
||||
# took any non-empty string as "please test".
|
||||
|
||||
test_tristate () {
|
||||
if eval "test x\"\${$1+isset}\" = xisset"
|
||||
then
|
||||
# explicitly set
|
||||
eval "
|
||||
case \"\$$1\" in
|
||||
'') $1=false ;;
|
||||
auto) ;;
|
||||
*) $1=\$(test_normalize_bool \$$1 || echo true) ;;
|
||||
esac
|
||||
"
|
||||
else
|
||||
eval "$1=auto"
|
||||
fi
|
||||
}
|
||||
|
||||
# Exit the test suite, either by skipping all remaining tests or by
|
||||
# exiting with an error. If "$1" is "auto", we then we assume we were
|
||||
# opportunistically trying to set up some tests and we skip. If it is
|
||||
# "true", then we report a failure.
|
||||
#
|
||||
# The error/skip message should be given by $2.
|
||||
#
|
||||
test_skip_or_die () {
|
||||
case "$1" in
|
||||
auto)
|
||||
skip_all=$2
|
||||
test_done
|
||||
;;
|
||||
true)
|
||||
error "$2"
|
||||
;;
|
||||
*)
|
||||
error "BUG: test tristate is '$1' (real error: $2)"
|
||||
esac
|
||||
}
|
||||
|
||||
# Like "env PROJFS=BAR some-program", but run inside a subshell, which means
|
||||
# it also works for shell functions (though those functions cannot impact
|
||||
# the environment outside of the test_env invocation).
|
||||
test_env () {
|
||||
(
|
||||
while test $# -gt 0
|
||||
do
|
||||
case "$1" in
|
||||
*=*)
|
||||
eval "${1%%=*}=\${1#*=}"
|
||||
eval "export ${1%%=*}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
"$@" 2>&7
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
done
|
||||
)
|
||||
} 7>&2 2>&4
|
||||
|
||||
# Returns true if the numeric exit code in "$2" represents the expected signal
|
||||
# in "$1". Signals should be given numerically.
|
||||
test_match_signal () {
|
||||
if test "$2" = "$((128 + $1))"
|
||||
then
|
||||
# POSIX
|
||||
return 0
|
||||
elif test "$2" = "$((256 + $1))"
|
||||
then
|
||||
# ksh
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Start a projected filesystem command and wait for its mount to be
|
||||
# available. The helper program in the t/ directory should be in "$1",
|
||||
# with the relative paths of the lower filesystem to be used for storage,
|
||||
# and the mount point of the projected upper filesystem, in "$2" and "$3",
|
||||
# with any options for the helper program in the remaining arguments.
|
||||
# Any output from the helper program will be captured in a file
|
||||
# with the basename from "$1" and the extension ".log", and any errors
|
||||
# in a file with the basename from "$1" and the extension ".err".
|
||||
projfs_start () {
|
||||
helper="$TEST_DIRECTORY/$1"; shift
|
||||
lower_path="$TRASH_DIRECTORY/$1"; shift
|
||||
mount_path="$TRASH_DIRECTORY/$1"; shift
|
||||
|
||||
helper_log=$(basename "$helper").log
|
||||
helper_err=$(basename "$helper").err
|
||||
|
||||
$MKDIR_P "$lower_path" "$mount_path" &&
|
||||
lower_dev=$(stat -c %D "$mount_path") || exit 1
|
||||
|
||||
"$helper" "$@" "$lower_path" "$mount_path" \
|
||||
>"$helper_log" 2>"$helper_err" &
|
||||
projfs_pid=$!
|
||||
|
||||
"$TEST_DIRECTORY"/wait_mount "0x$lower_dev" "$mount_path" \
|
||||
$PROJFS_TIMEOUT &&
|
||||
mount_dev=$(stat -c %D "$mount_path")
|
||||
|
||||
if test $? -ne 0
|
||||
then
|
||||
projfs_stop
|
||||
return 1
|
||||
fi
|
||||
|
||||
trap projfs_stop EXIT
|
||||
}
|
||||
|
||||
# Stop the projected filesystem command that was started by projfs_start()
|
||||
# and wait for its umount operation to be completed.
|
||||
projfs_stop () {
|
||||
if test -n "$projfs_pid"
|
||||
then
|
||||
kill "$projfs_pid" &&
|
||||
"$TEST_DIRECTORY"/wait_mount "0x$mount_dev" "$mount_path" \
|
||||
$PROJFS_TIMEOUT &&
|
||||
wait "$projfs_pid" &&
|
||||
projfs_pid=""
|
||||
fi
|
||||
}
|
||||
|
||||
EXEC_PID="exec.pid"
|
||||
EXEC_LOG="exec.log"
|
||||
EXEC_ERR="exec.err"
|
||||
|
||||
# Execute a command, capturing its process ID, and then append a message
|
||||
# followed by the command's pid into a log file and an optional error log
|
||||
# file. The log file should be given in "$1", the log message head and
|
||||
# tail in "$2" and "$3", the error log file in "$4", the error log message
|
||||
# in "$5", and the command in the remaining arguments.
|
||||
# To skip logging to an error log, "$4" should be an empty string.
|
||||
# Note that command arguments with internal spaces must have single quotes
|
||||
# passed in the argument, e.g., "'foo bar'".
|
||||
# Output and errors from the command will be appended to "$EXEC_OUT" and
|
||||
# "$EXEC_ERR", respectively.
|
||||
projfs_log_exec () {
|
||||
exec_log="$1"; shift
|
||||
log_msg_head="$1"; shift
|
||||
log_msg_tail="$1"; shift
|
||||
exec_err="$1"; shift
|
||||
err_msg="$1"; shift
|
||||
|
||||
# Do not chain because we want to proceed in the case of errors
|
||||
$SHELL_PATH -c "echo -n \$\$ >$EXEC_PID && exec $*" \
|
||||
>>"$EXEC_LOG" 2>>"$EXEC_ERR"; ret=$?
|
||||
|
||||
echo -n "$log_msg_head" >>"$exec_log" &&
|
||||
cat "$EXEC_PID" >>"$exec_log" &&
|
||||
echo "$log_msg_tail" >>"$exec_log" &&
|
||||
if test ":$exec_err" != ":"
|
||||
then
|
||||
echo -n "$err_msg" >>"$exec_err" &&
|
||||
cat $EXEC_PID >>"$exec_err" &&
|
||||
echo '' >>"$exec_err"
|
||||
fi
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
|
@ -0,0 +1,915 @@
|
|||
# Copyright (C) 2005 Junio C Hamano
|
||||
#
|
||||
# See the NOTICE file distributed with this library for additional
|
||||
# information regarding copyright ownership. Additional functions are:
|
||||
#
|
||||
# Copyright (C) 2018-2019 GitHub, Inc.
|
||||
#
|
||||
# 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/ .
|
||||
|
||||
# I/O redirections used in this library:
|
||||
#
|
||||
# fd 3: verbose output, if any
|
||||
# fd 4: verbose errors, if any
|
||||
# fd 5: stdout
|
||||
# fd 6: stdin
|
||||
# fd 7: stderr
|
||||
#
|
||||
# The file descriptors 5 to 7 are used when a test function needs to
|
||||
# read/write to a "real" fd from inside a test_eval() call stack, where
|
||||
# fds 0 to 2 have already been redirected.
|
||||
|
||||
# Timeout in seconds before abandoning attempt to wait for mount/umount
|
||||
# of projfs.
|
||||
PROJFS_TIMEOUT=30
|
||||
|
||||
# If no positional arguments were provided and $PROJFS_TEST_OPTS was,
|
||||
# we are running within the automake TAP harness and should respect any
|
||||
# flags the user passed via the environment instead of the command line.
|
||||
test "$#" -eq 0 && test -n "$PROJFS_TEST_OPTS" && \
|
||||
eval set -- "$PROJFS_TEST_OPTS"
|
||||
|
||||
# Test the binaries we have just built. The tests are kept in
|
||||
# t/ subdirectory and are run in 'trash directory' subdirectory.
|
||||
if test -z "$TEST_DIRECTORY"
|
||||
then
|
||||
# We allow tests to override this, in case they want to run tests
|
||||
# outside of t/, e.g. for running tests on the test library
|
||||
# itself.
|
||||
TEST_DIRECTORY=$(pwd)
|
||||
else
|
||||
# ensure that TEST_DIRECTORY is an absolute path so that it
|
||||
# is valid even if the current working directory is changed
|
||||
TEST_DIRECTORY=$(cd "$TEST_DIRECTORY" && pwd) || exit 1
|
||||
fi
|
||||
if test -z "$TEST_OUTPUT_DIRECTORY"
|
||||
then
|
||||
# Similarly, override this to store the test-output subdir
|
||||
# elsewhere
|
||||
TEST_OUTPUT_DIRECTORY=$TEST_DIRECTORY
|
||||
fi
|
||||
PROJFS_BUILD_DIR="$TEST_DIRECTORY"/..
|
||||
|
||||
# If we were built with ASAN, it may complain about leaks
|
||||
# of program-lifetime variables. Disable it by default to lower
|
||||
# the noise level. This needs to happen at the start of the script,
|
||||
# before we even do our "did we build projfs yet" check (since we don't
|
||||
# want that one to complain to stderr).
|
||||
: ${ASAN_OPTIONS=detect_leaks=0:abort_on_error=1}
|
||||
export ASAN_OPTIONS
|
||||
|
||||
# If LSAN is in effect we _do_ want leak checking, but we still
|
||||
# want to abort so that we notice the problems.
|
||||
: ${LSAN_OPTIONS=abort_on_error=1}
|
||||
export LSAN_OPTIONS
|
||||
|
||||
if test ! -f "$PROJFS_BUILD_DIR"/config.sh
|
||||
then
|
||||
echo >&2 'error: config.sh missing (has libprojfs been built?)'
|
||||
exit 1
|
||||
fi
|
||||
. "$PROJFS_BUILD_DIR"/config.sh
|
||||
export SHELL_PATH
|
||||
|
||||
################################################################
|
||||
# It appears that people try to run tests without building...
|
||||
if ! test -x "$TEST_DIRECTORY"/test_projfs_simple
|
||||
then
|
||||
echo >&2 'error: test programs missing (has "make" been run yet?)'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# if --tee was passed, write the output not only to the terminal, but
|
||||
# additionally to the file test-output/$BASENAME.out, too.
|
||||
case "$PROJFS_TEST_TEE_STARTED, $* " in
|
||||
done,*)
|
||||
# do not redirect again
|
||||
;;
|
||||
*' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*)
|
||||
$MKDIR_P "$TEST_OUTPUT_DIRECTORY/test-output"
|
||||
BASE="$TEST_OUTPUT_DIRECTORY/test-output/$(basename "$0" .sh)"
|
||||
|
||||
# Make this filename available to the sub-process in case it is using
|
||||
# --verbose-log.
|
||||
PROJFS_TEST_TEE_OUTPUT_FILE=$BASE.out
|
||||
export PROJFS_TEST_TEE_OUTPUT_FILE
|
||||
|
||||
# Truncate before calling "tee -a" to get rid of the results
|
||||
# from any previous runs.
|
||||
>"$PROJFS_TEST_TEE_OUTPUT_FILE"
|
||||
|
||||
(PROJFS_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1;
|
||||
echo $? >"$BASE.exit") | tee -a "$PROJFS_TEST_TEE_OUTPUT_FILE"
|
||||
test "$(cat "$BASE.exit")" = 0
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
# For repeatability, reset the environment to known value.
|
||||
# TERM is sanitized below, after saving color control sequences.
|
||||
LANG=C
|
||||
LC_ALL=C
|
||||
export LANG LC_ALL
|
||||
unset VISUAL LANGUAGE COLUMNS
|
||||
|
||||
# Add libc MALLOC and MALLOC_PERTURB test
|
||||
# only if we are not executing the test with valgrind
|
||||
if expr " $PROJFS_TEST_OPTS " : ".* --valgrind " >/dev/null ||
|
||||
test -n "$TEST_NO_MALLOC_CHECK"
|
||||
then
|
||||
setup_malloc_check () {
|
||||
: nothing
|
||||
}
|
||||
teardown_malloc_check () {
|
||||
: nothing
|
||||
}
|
||||
else
|
||||
setup_malloc_check () {
|
||||
MALLOC_CHECK_=3 MALLOC_PERTURB_=165
|
||||
export MALLOC_CHECK_ MALLOC_PERTURB_
|
||||
}
|
||||
teardown_malloc_check () {
|
||||
unset MALLOC_CHECK_ MALLOC_PERTURB_
|
||||
}
|
||||
fi
|
||||
|
||||
# Protect ourselves from common misconfiguration to export
|
||||
# CDPATH into the environment
|
||||
unset CDPATH
|
||||
|
||||
# Each test should start with something like this, after copyright notices:
|
||||
#
|
||||
# test_description='Description of this test...
|
||||
# This test checks if command xyzzy does the right thing...
|
||||
# '
|
||||
# . ./test-lib.sh
|
||||
test "x$TERM" != "xdumb" && (
|
||||
test -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 ;;
|
||||
-r)
|
||||
shift; test "$#" -ne 0 || {
|
||||
echo 'error: -r requires an argument' >&2;
|
||||
exit 1;
|
||||
}
|
||||
run_list=$1; shift ;;
|
||||
--run=*)
|
||||
run_list=${1#--*=}; shift ;;
|
||||
-h|--h|--he|--hel|--help)
|
||||
help=t; shift ;;
|
||||
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
|
||||
verbose=t; shift ;;
|
||||
--verbose-only=*)
|
||||
verbose_only=${1#--*=}
|
||||
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 ;;
|
||||
--no-color)
|
||||
color=; shift ;;
|
||||
--va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
|
||||
valgrind=memcheck
|
||||
shift ;;
|
||||
--valgrind=*)
|
||||
valgrind=${1#--*=}
|
||||
shift ;;
|
||||
--valgrind-only=*)
|
||||
valgrind_only=${1#--*=}
|
||||
shift ;;
|
||||
--tee)
|
||||
shift ;; # was handled already
|
||||
--root=*)
|
||||
root=${1#--*=}
|
||||
shift ;;
|
||||
--chain-lint)
|
||||
PROJFS_TEST_CHAIN_LINT=1
|
||||
shift ;;
|
||||
--no-chain-lint)
|
||||
PROJFS_TEST_CHAIN_LINT=0
|
||||
shift ;;
|
||||
-x)
|
||||
# Some test scripts can't be reliably traced with '-x',
|
||||
# unless the test is run with a Bash version supporting
|
||||
# BASH_XTRACEFD (introduced in Bash v4.1). Check whether
|
||||
# this test is marked as such, and ignore '-x' if it
|
||||
# isn't executed with a suitable Bash version.
|
||||
if test -z "$test_untraceable" || {
|
||||
test -n "$BASH_VERSION" && {
|
||||
test ${BASH_VERSINFO[0]} -gt 4 || {
|
||||
test ${BASH_VERSINFO[0]} -eq 4 &&
|
||||
test ${BASH_VERSINFO[1]} -ge 1
|
||||
}
|
||||
}
|
||||
}
|
||||
then
|
||||
trace=t
|
||||
else
|
||||
echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD"
|
||||
fi
|
||||
shift ;;
|
||||
-V|--verbose-log)
|
||||
verbose_log=t
|
||||
shift ;;
|
||||
*)
|
||||
echo "error: unknown test option '$1'" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if test -n "$valgrind_only"
|
||||
then
|
||||
test -z "$valgrind" && valgrind=memcheck
|
||||
test -z "$verbose" && verbose_only="$valgrind_only"
|
||||
elif test -n "$valgrind"
|
||||
then
|
||||
test -z "$verbose_log" && verbose=t
|
||||
fi
|
||||
|
||||
if test -n "$trace" && test -z "$verbose_log"
|
||||
then
|
||||
verbose=t
|
||||
fi
|
||||
|
||||
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: $*"
|
||||
PROJFS_EXIT_OK=t
|
||||
exit 1
|
||||
}
|
||||
|
||||
BUG () {
|
||||
error >&7 "bug in the test script: $*"
|
||||
}
|
||||
|
||||
say () {
|
||||
say_color info "$*"
|
||||
}
|
||||
|
||||
if test -n "$HARNESS_ACTIVE"
|
||||
then
|
||||
if test "$verbose" = t || test -n "$verbose_only"
|
||||
then
|
||||
printf 'Bail out! %s\n' \
|
||||
'verbose mode forbidden under TAP harness; try --verbose-log'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
test "${test_description}" != "" ||
|
||||
error "Test script did not set test_description."
|
||||
|
||||
if test "$help" = "t"
|
||||
then
|
||||
printf '%s\n' "$test_description"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec 5>&1
|
||||
exec 6<&0
|
||||
exec 7>&2
|
||||
if test "$verbose_log" = "t"
|
||||
then
|
||||
exec 3>>"$PROJFS_TEST_TEE_OUTPUT_FILE" 4>&3
|
||||
elif test "$verbose" = "t"
|
||||
then
|
||||
exec 4>&2 3>&1
|
||||
else
|
||||
exec 4>/dev/null 3>/dev/null
|
||||
fi
|
||||
|
||||
# Send any "-x" output directly to stderr to avoid polluting tests
|
||||
# which capture stderr. We can do this unconditionally since it
|
||||
# has no effect if tracing isn't turned on.
|
||||
#
|
||||
# Note that this sets up the trace fd as soon as we assign the variable, so it
|
||||
# must come after the creation of descriptor 4 above. Likewise, we must never
|
||||
# unset this, as it has the side effect of closing descriptor 4, which we
|
||||
# use to show verbose tests to the user.
|
||||
#
|
||||
# Note also that we don't need or want to export it. The tracing is local to
|
||||
# this shell, and we would not want to influence any shells we exec.
|
||||
BASH_XTRACEFD=4
|
||||
|
||||
test_failure=0
|
||||
test_count=0
|
||||
test_fixed=0
|
||||
test_broken=0
|
||||
test_success=0
|
||||
|
||||
test_external_has_tap=0
|
||||
|
||||
die () {
|
||||
code=$?
|
||||
if test -n "$PROJFS_EXIT_OK"
|
||||
then
|
||||
exit $code
|
||||
else
|
||||
echo >&5 "FATAL: Unexpected exit with code $code"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
PROJFS_EXIT_OK=
|
||||
trap 'die' EXIT
|
||||
trap 'exit $?' INT
|
||||
|
||||
# The user-facing functions are loaded from a separate file so that
|
||||
# test_perf subshells can have them too
|
||||
. "$TEST_DIRECTORY/test-lib-functions.sh"
|
||||
|
||||
# You are not expected to call test_ok_ and test_failure_ directly, use
|
||||
# the test_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
|
||||
printf '%s\n' "$*" | $SED -e 's/^/# /'
|
||||
test "$immediate" = "" || { PROJFS_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"
|
||||
}
|
||||
|
||||
test_debug () {
|
||||
test "$debug" = "" || eval "$1"
|
||||
}
|
||||
|
||||
match_pattern_list () {
|
||||
arg="$1"
|
||||
shift
|
||||
test -z "$*" && return 1
|
||||
for pattern_
|
||||
do
|
||||
case "$arg" in
|
||||
$pattern_)
|
||||
return 0
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
match_test_selector_list () {
|
||||
title="$1"
|
||||
shift
|
||||
arg="$1"
|
||||
shift
|
||||
test -z "$1" && return 0
|
||||
|
||||
# Both commas and whitespace are accepted as separators.
|
||||
OLDIFS=$IFS
|
||||
IFS=' ,'
|
||||
set -- $1
|
||||
IFS=$OLDIFS
|
||||
|
||||
# If the first selector is negative we include by default.
|
||||
include=
|
||||
case "$1" in
|
||||
!*) include=t ;;
|
||||
esac
|
||||
|
||||
for selector
|
||||
do
|
||||
orig_selector=$selector
|
||||
|
||||
positive=t
|
||||
case "$selector" in
|
||||
!*)
|
||||
positive=
|
||||
selector=${selector##?}
|
||||
;;
|
||||
esac
|
||||
|
||||
test -z "$selector" && continue
|
||||
|
||||
case "$selector" in
|
||||
*-*)
|
||||
if expr "z${selector%%-*}" : "z[0-9]*[^0-9]" >/dev/null
|
||||
then
|
||||
echo "error: $title: invalid non-numeric in range" \
|
||||
"start: '$orig_selector'" >&2
|
||||
exit 1
|
||||
fi
|
||||
if expr "z${selector#*-}" : "z[0-9]*[^0-9]" >/dev/null
|
||||
then
|
||||
echo "error: $title: invalid non-numeric in range" \
|
||||
"end: '$orig_selector'" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if expr "z$selector" : "z[0-9]*[^0-9]" >/dev/null
|
||||
then
|
||||
echo "error: $title: invalid non-numeric in test" \
|
||||
"selector: '$orig_selector'" >&2
|
||||
exit 1
|
||||
fi
|
||||
esac
|
||||
|
||||
# Short cut for "obvious" cases
|
||||
test -z "$include" && test -z "$positive" && continue
|
||||
test -n "$include" && test -n "$positive" && continue
|
||||
|
||||
case "$selector" in
|
||||
-*)
|
||||
if test $arg -le ${selector#-}
|
||||
then
|
||||
include=$positive
|
||||
fi
|
||||
;;
|
||||
*-)
|
||||
if test $arg -ge ${selector%-}
|
||||
then
|
||||
include=$positive
|
||||
fi
|
||||
;;
|
||||
*-*)
|
||||
if test ${selector%%-*} -le $arg \
|
||||
&& test $arg -le ${selector#*-}
|
||||
then
|
||||
include=$positive
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if test $arg -eq $selector
|
||||
then
|
||||
include=$positive
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
test -n "$include"
|
||||
}
|
||||
|
||||
maybe_teardown_verbose () {
|
||||
test -z "$verbose_only" && return
|
||||
exec 4>/dev/null 3>/dev/null
|
||||
verbose=
|
||||
}
|
||||
|
||||
last_verbose=t
|
||||
maybe_setup_verbose () {
|
||||
test -z "$verbose_only" && return
|
||||
if match_pattern_list $test_count $verbose_only
|
||||
then
|
||||
exec 4>&2 3>&1
|
||||
# Emit a delimiting blank line when going from
|
||||
# non-verbose to verbose. Within verbose mode the
|
||||
# delimiter is printed by test_expect_*. The choice
|
||||
# of the initial $last_verbose is such that before
|
||||
# test 1, we do not print it.
|
||||
test -z "$last_verbose" && echo >&3 ""
|
||||
verbose=t
|
||||
else
|
||||
exec 4>/dev/null 3>/dev/null
|
||||
verbose=
|
||||
fi
|
||||
last_verbose=$verbose
|
||||
}
|
||||
|
||||
maybe_teardown_valgrind () {
|
||||
test -z "$PROJFS_VALGRIND" && return
|
||||
PROJFS_VALGRIND_ENABLED=
|
||||
}
|
||||
|
||||
maybe_setup_valgrind () {
|
||||
test -z "$PROJFS_VALGRIND" && return
|
||||
if test -z "$valgrind_only"
|
||||
then
|
||||
PROJFS_VALGRIND_ENABLED=t
|
||||
return
|
||||
fi
|
||||
PROJFS_VALGRIND_ENABLED=
|
||||
if match_pattern_list $test_count $valgrind_only
|
||||
then
|
||||
PROJFS_VALGRIND_ENABLED=t
|
||||
fi
|
||||
}
|
||||
|
||||
want_trace () {
|
||||
test "$trace" = t && {
|
||||
test "$verbose" = t || test "$verbose_log" = t
|
||||
}
|
||||
}
|
||||
|
||||
# This is a separate function because some tests use
|
||||
# "return" to end a test_expect_success block early
|
||||
# (and we want to make sure we run any cleanup like
|
||||
# "set +x").
|
||||
test_eval_inner_ () {
|
||||
# Do not add anything extra (including LF) after '$*'
|
||||
eval "
|
||||
want_trace && set -x
|
||||
$*"
|
||||
}
|
||||
|
||||
test_eval_ () {
|
||||
# If "-x" tracing is in effect, then we want to avoid polluting stderr
|
||||
# with non-test commands. But once in "set -x" mode, we cannot prevent
|
||||
# the shell from printing the "set +x" to turn it off (nor the saving
|
||||
# of $? before that). But we can make sure that the output goes to
|
||||
# /dev/null.
|
||||
#
|
||||
# There are a few subtleties here:
|
||||
#
|
||||
# - we have to redirect descriptor 4 in addition to 2, to cover
|
||||
# BASH_XTRACEFD
|
||||
#
|
||||
# - the actual eval has to come before the redirection block (since
|
||||
# it needs to see descriptor 4 to set up its stderr)
|
||||
#
|
||||
# - likewise, any error message we print must be outside the block to
|
||||
# access descriptor 4
|
||||
#
|
||||
# - checking $? has to come immediately after the eval, but it must
|
||||
# be _inside_ the block to avoid polluting the "set -x" output
|
||||
#
|
||||
|
||||
test_eval_inner_ "$@" </dev/null >&3 2>&4
|
||||
{
|
||||
test_eval_ret_=$?
|
||||
if want_trace
|
||||
then
|
||||
set +x
|
||||
fi
|
||||
} 2>/dev/null 4>&2
|
||||
|
||||
if test "$test_eval_ret_" != 0 && want_trace
|
||||
then
|
||||
say_color error >&4 "error: last command exited with \$?=$test_eval_ret_"
|
||||
fi
|
||||
return $test_eval_ret_
|
||||
}
|
||||
|
||||
test_run_ () {
|
||||
test_cleanup=:
|
||||
expecting_failure=$2
|
||||
|
||||
if test "${PROJFS_TEST_CHAIN_LINT:-1}" != 0; then
|
||||
# turn off tracing for this test-eval, as it simply creates
|
||||
# confusing noise in the "-x" output
|
||||
trace_tmp=$trace
|
||||
trace=
|
||||
# 117 is magic because it is unlikely to match the exit
|
||||
# code of other programs
|
||||
if $(printf '%s\n' "$1" | $SED -f "$PROJFS_BUILD_DIR/t/chainlint.sed" | grep -q '?![A-Z][A-Z]*?!') ||
|
||||
test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)"
|
||||
then
|
||||
BUG "broken &&-chain or run-away HERE-DOC: $1"
|
||||
fi
|
||||
trace=$trace_tmp
|
||||
fi
|
||||
|
||||
setup_malloc_check
|
||||
test_eval_ "$1"
|
||||
eval_ret=$?
|
||||
teardown_malloc_check
|
||||
|
||||
if test -z "$immediate" || test $eval_ret = 0 ||
|
||||
test -n "$expecting_failure" && test "$test_cleanup" != ":"
|
||||
then
|
||||
setup_malloc_check
|
||||
test_eval_ "$test_cleanup"
|
||||
teardown_malloc_check
|
||||
fi
|
||||
if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"
|
||||
then
|
||||
echo ""
|
||||
fi
|
||||
return "$eval_ret"
|
||||
}
|
||||
|
||||
test_start_ () {
|
||||
test_count=$(($test_count+1))
|
||||
maybe_setup_verbose
|
||||
maybe_setup_valgrind
|
||||
}
|
||||
|
||||
test_finish_ () {
|
||||
echo >&3 ""
|
||||
maybe_teardown_valgrind
|
||||
maybe_teardown_verbose
|
||||
}
|
||||
|
||||
test_skip () {
|
||||
to_skip=
|
||||
skipped_reason=
|
||||
if match_pattern_list $this_test.$test_count $PROJFS_SKIP_TESTS
|
||||
then
|
||||
to_skip=t
|
||||
skipped_reason="PROJFS_SKIP_TESTS"
|
||||
fi
|
||||
if test -z "$to_skip" && test -n "$test_prereq" &&
|
||||
! test_have_prereq "$test_prereq"
|
||||
then
|
||||
to_skip=t
|
||||
|
||||
of_prereq=
|
||||
if test "$missing_prereq" != "$test_prereq"
|
||||
then
|
||||
of_prereq=" of $test_prereq"
|
||||
fi
|
||||
skipped_reason="missing $missing_prereq${of_prereq}"
|
||||
fi
|
||||
if test -z "$to_skip" && test -n "$run_list" &&
|
||||
! match_test_selector_list '--run' $test_count "$run_list"
|
||||
then
|
||||
to_skip=t
|
||||
skipped_reason="--run"
|
||||
fi
|
||||
|
||||
case "$to_skip" in
|
||||
t)
|
||||
say_color skip >&3 "skipping test: $@"
|
||||
say_color skip "ok $test_count # skip $1 ($skipped_reason)"
|
||||
: true
|
||||
;;
|
||||
*)
|
||||
false
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# stub; perf-lib overrides it
|
||||
test_at_end_hook_ () {
|
||||
:
|
||||
}
|
||||
|
||||
test_done () {
|
||||
PROJFS_EXIT_OK=t
|
||||
|
||||
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)
|
||||
if test $test_external_has_tap -eq 0
|
||||
then
|
||||
if test $test_remaining -gt 0
|
||||
then
|
||||
say_color pass "# passed all $msg"
|
||||
fi
|
||||
|
||||
# Maybe print SKIP message
|
||||
test -z "$skip_all" || skip_all="# SKIP $skip_all"
|
||||
case "$test_count" in
|
||||
0)
|
||||
say "1..$test_count${skip_all:+ $skip_all}"
|
||||
;;
|
||||
*)
|
||||
test -z "$skip_all" ||
|
||||
say_color warn "$skip_all"
|
||||
say "1..$test_count"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if test -z "$debug"
|
||||
then
|
||||
test -d "$TRASH_DIRECTORY" ||
|
||||
error "Tests passed but trash directory already removed before test cleanup; aborting"
|
||||
|
||||
cd "$TRASH_DIRECTORY/.." &&
|
||||
rm -fr "$TRASH_DIRECTORY" ||
|
||||
error "Tests passed but test cleanup failed; aborting"
|
||||
fi
|
||||
test_at_end_hook_
|
||||
|
||||
exit 0 ;;
|
||||
|
||||
*)
|
||||
if test $test_external_has_tap -eq 0
|
||||
then
|
||||
say_color error "# failed $test_failure among $msg"
|
||||
say "1..$test_count"
|
||||
fi
|
||||
|
||||
exit 1 ;;
|
||||
|
||||
esac
|
||||
}
|
||||
|
||||
# TODO: revise as needed if using valgrind; see git/git/t/valgrind
|
||||
if test -n "$valgrind"
|
||||
then
|
||||
make_symlink () {
|
||||
test -h "$2" &&
|
||||
test "$1" = "$(readlink "$2")" || {
|
||||
# be super paranoid
|
||||
if mkdir "$2".lock
|
||||
then
|
||||
rm -f "$2" &&
|
||||
ln -s "$1" "$2" &&
|
||||
rm -r "$2".lock
|
||||
else
|
||||
while test -d "$2".lock
|
||||
do
|
||||
say "Waiting for lock on $2."
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
}
|
||||
}
|
||||
|
||||
make_valgrind_symlink () {
|
||||
base=$(basename "$1")
|
||||
symlink_target="$PROJFS_BUILD_DIR/t/$base"
|
||||
# create the link, or replace it if it is out of date
|
||||
make_symlink "$symlink_target" "$PROJFS_VALGRIND/bin/$base" \
|
||||
|| exit
|
||||
}
|
||||
|
||||
# override all projfs executables in TEST_DIRECTORY/..
|
||||
PROJFS_VALGRIND=$TEST_DIRECTORY/valgrind
|
||||
$MKDIR_P "$PROJFS_VALGRIND"/bin
|
||||
for file in $PROJFS_BUILD_DIR/t/test_projfs_* \
|
||||
$PROJFS_BUILD_DIR/t/wait_mount
|
||||
do
|
||||
make_valgrind_symlink $file
|
||||
done
|
||||
|
||||
PATH=$PROJFS_VALGRIND/bin:$PATH
|
||||
export PATH
|
||||
|
||||
export PROJFS_VALGRIND
|
||||
PROJFS_VALGRIND_MODE="$valgrind"
|
||||
export PROJFS_VALGRIND_MODE
|
||||
PROJFS_VALGRIND_ENABLED=t
|
||||
test -n "$valgrind_only" && PROJFS_VALGRIND_ENABLED=
|
||||
export PROJFS_VALGRIND_ENABLED
|
||||
fi
|
||||
|
||||
if test -z "$PROJFS_TEST_CMP"
|
||||
then
|
||||
if test -n "$PROJFS_TEST_CMP_USE_COPIED_CONTEXT"
|
||||
then
|
||||
PROJFS_TEST_CMP="$DIFF -c"
|
||||
else
|
||||
PROJFS_TEST_CMP="$DIFF -u"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test mount points
|
||||
TRASH_DIRECTORY="test-mounts/$(basename "$0" .t)"
|
||||
test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY"
|
||||
case "$TRASH_DIRECTORY" in
|
||||
/*) ;; # absolute path is good
|
||||
*) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;;
|
||||
esac
|
||||
rm -fr "$TRASH_DIRECTORY" || {
|
||||
PROJFS_EXIT_OK=t
|
||||
echo >&5 "FATAL: Cannot prepare test area"
|
||||
exit 1
|
||||
}
|
||||
|
||||
HOME="$TRASH_DIRECTORY"
|
||||
export HOME
|
||||
|
||||
$MKDIR_P "$TRASH_DIRECTORY"
|
||||
|
||||
# Use -P to resolve symlinks in our working directory so that the cwd
|
||||
# in subprocesses like projfs equals our $PWD (for pathname comparisons).
|
||||
cd -P "$TRASH_DIRECTORY" || exit 1
|
||||
|
||||
this_test=${0##*/}
|
||||
this_test=${this_test%%-*}
|
||||
if match_pattern_list "$this_test" $PROJFS_SKIP_TESTS
|
||||
then
|
||||
say_color info >&3 "skipping test $this_test altogether"
|
||||
skip_all="skip all tests in $this_test"
|
||||
test_done
|
||||
fi
|
||||
|
||||
( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
|
||||
|
||||
test_lazy_prereq PIPE '
|
||||
# test whether the filesystem supports FIFOs
|
||||
rm -f testfifo && mkfifo testfifo
|
||||
'
|
||||
|
||||
test_lazy_prereq SYMLINKS '
|
||||
# test whether the filesystem supports symbolic links
|
||||
ln -s x y && test -h y
|
||||
'
|
||||
|
||||
test_lazy_prereq NOT_ROOT '
|
||||
uid=$(id -u) &&
|
||||
test "$uid" != 0
|
||||
'
|
||||
|
||||
# SANITY is about "can you correctly predict what the filesystem would
|
||||
# do by only looking at the permission bits of the files and
|
||||
# directories?" A typical example of !SANITY is running the test
|
||||
# suite as root, where a test may expect "chmod -r file && cat file"
|
||||
# to fail because file is supposed to be unreadable after a successful
|
||||
# chmod. In an environment (i.e. combination of what filesystem is
|
||||
# being used and who is running the tests) that lacks SANITY, you may
|
||||
# be able to delete or create a file when the containing directory
|
||||
# doesn't have write permissions, or access a file even if the
|
||||
# containing directory doesn't have read or execute permissions.
|
||||
|
||||
test_lazy_prereq SANITY '
|
||||
mkdir SANETESTD.1 SANETESTD.2 &&
|
||||
|
||||
chmod +w SANETESTD.1 SANETESTD.2 &&
|
||||
>SANETESTD.1/x 2>SANETESTD.2/x &&
|
||||
chmod -w SANETESTD.1 &&
|
||||
chmod -r SANETESTD.1/x &&
|
||||
chmod -rx SANETESTD.2 ||
|
||||
BUG "cannot prepare SANETESTD"
|
||||
|
||||
! test -r SANETESTD.1/x &&
|
||||
! rm SANETESTD.1/x && ! test -f SANETESTD.2/x
|
||||
status=$?
|
||||
|
||||
chmod +rwx SANETESTD.1 SANETESTD.2 &&
|
||||
rm -rf SANETESTD.1 SANETESTD.2 ||
|
||||
BUG "cannot clean SANETESTD"
|
||||
return $status
|
||||
'
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE // for basename() in <string.h>
|
||||
|
||||
#include "../include/config.h"
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include "../include/projfs_notify.h"
|
||||
|
||||
#define RETVAL_OPT_NAME "--retval"
|
||||
#define RETVAL_OPT_HELP "allow|deny|null|<error>"
|
||||
|
||||
#define retval_entry(s) #s, -s
|
||||
|
||||
struct retval {
|
||||
const char *name;
|
||||
int val;
|
||||
};
|
||||
|
||||
// list based on VFS API convert_result_to_errno()
|
||||
static struct retval errno_retvals[] = {
|
||||
{ "null", 0 },
|
||||
{ "allow", PROJFS_ALLOW },
|
||||
{ "deny", PROJFS_DENY },
|
||||
{ retval_entry(EBADF) },
|
||||
{ retval_entry(EINPROGRESS) },
|
||||
{ retval_entry(EINVAL) },
|
||||
{ retval_entry(EIO) },
|
||||
{ retval_entry(ENODEV) },
|
||||
{ retval_entry(ENOENT) },
|
||||
{ retval_entry(ENOMEM) },
|
||||
{ retval_entry(ENOTSUP) },
|
||||
{ retval_entry(EPERM) },
|
||||
{ retval_entry(ENOSYS) },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
#define VFSAPI_PREFIX "PrjFS_Result_"
|
||||
#define VFSAPI_PREFIX_LEN (sizeof(VFSAPI_PREFIX) - 1)
|
||||
|
||||
#ifdef PROJFS_VFSAPI
|
||||
#define get_retvals(v) ((v) ? vfsapi_retvals : errno_retvals)
|
||||
|
||||
#define retval_vfsapi_entry(s) #s, s
|
||||
|
||||
// list based on VFS API convert_result_to_errno()
|
||||
static struct retval vfsapi_retvals[] = {
|
||||
{ "null", PrjFS_Result_Invalid },
|
||||
{ "allow", PrjFS_Result_Success },
|
||||
{ "deny", PrjFS_Result_EAccessDenied },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_Invalid) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_Success) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_Pending) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EInvalidArgs) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EInvalidOperation) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_ENotSupported) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EDriverNotLoaded) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EOutOfMemory) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EFileNotFound) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EPathNotFound) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EAccessDenied) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EInvalidHandle) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_EIOError) },
|
||||
{ retval_vfsapi_entry(PrjFS_Result_ENotYetImplemented) },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
#else /* !PROJFS_VFSAPI */
|
||||
#define get_retvals(v) errno_retvals
|
||||
#endif /* !PROJFS_VFSAPI */
|
||||
|
||||
int tst_find_retval(int vfsapi, const char *retname, const char *optname)
|
||||
{
|
||||
const struct retval *retvals = get_retvals(vfsapi);
|
||||
int i = 0;
|
||||
|
||||
while (retvals[i].name != NULL) {
|
||||
const char *name = retvals[i].name;
|
||||
|
||||
if (!strcasecmp(name, retname) ||
|
||||
(vfsapi &&
|
||||
!strncmp(name, VFSAPI_PREFIX, VFSAPI_PREFIX_LEN) &&
|
||||
!strcasecmp(name + VFSAPI_PREFIX_LEN, retname)))
|
||||
return retvals[i].val;
|
||||
++i;
|
||||
}
|
||||
|
||||
errx(EXIT_FAILURE, "invalid %s option: %s",
|
||||
optname, retname);
|
||||
}
|
||||
|
||||
void tst_parse_opts(int argc, const char **argv, int vfsapi,
|
||||
const char **lower_path, const char **mount_path,
|
||||
int *retval)
|
||||
{
|
||||
const char *retname = NULL;
|
||||
int arg_offset = 0;
|
||||
|
||||
if (retval != NULL && argc > 2 && !strcmp(argv[1], RETVAL_OPT_NAME)) {
|
||||
retname = argv[2];
|
||||
arg_offset = 2;
|
||||
}
|
||||
|
||||
if (argc - arg_offset != 3) {
|
||||
fprintf(stderr, "Usage: %s ", basename(argv[0]));
|
||||
if (retval != NULL) {
|
||||
fprintf(stderr, "\\\n\t[%s %s] ",
|
||||
RETVAL_OPT_NAME, RETVAL_OPT_HELP);
|
||||
}
|
||||
fprintf(stderr, "<lower-path> <mount-path>\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
*lower_path = argv[1 + arg_offset];
|
||||
*mount_path = argv[2 + arg_offset];
|
||||
|
||||
if (retval != NULL) {
|
||||
if (retname == NULL)
|
||||
*retval = RETVAL_DEFAULT;
|
||||
else
|
||||
*retval = tst_find_retval(vfsapi, retname,
|
||||
RETVAL_OPT_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
struct projfs *tst_start_mount(const char *lowerdir, const char *mountdir,
|
||||
const struct projfs_handlers *handlers,
|
||||
size_t handlers_size, void *user_data)
|
||||
{
|
||||
struct projfs *fs;
|
||||
|
||||
fs = projfs_new(lowerdir, mountdir, handlers, handlers_size,
|
||||
user_data);
|
||||
|
||||
if (fs == NULL)
|
||||
err(EXIT_FAILURE, "unable to create filesystem");
|
||||
|
||||
if (projfs_start(fs) < 0)
|
||||
err(EXIT_FAILURE, "unable to start filesystem");
|
||||
|
||||
return fs;
|
||||
}
|
||||
|
||||
void *tst_stop_mount(struct projfs *fs)
|
||||
{
|
||||
return projfs_stop(fs);
|
||||
}
|
||||
|
||||
#ifdef PROJFS_VFSAPI
|
||||
void tst_start_vfsapi_mount(const char *storageRootFullPath,
|
||||
const char *virtualizationRootFullPath,
|
||||
PrjFS_Callbacks callbacks,
|
||||
unsigned int poolThreadCount,
|
||||
PrjFS_MountHandle** mountHandle)
|
||||
{
|
||||
PrjFS_Result ret;
|
||||
|
||||
ret = PrjFS_StartVirtualizationInstance(storageRootFullPath,
|
||||
virtualizationRootFullPath,
|
||||
callbacks, poolThreadCount,
|
||||
mountHandle);
|
||||
|
||||
if (ret != PrjFS_Result_Success)
|
||||
err(EXIT_FAILURE, "unable to start filesystem: %d", ret);
|
||||
}
|
||||
|
||||
void tst_stop_vfsapi_mount(PrjFS_MountHandle* mountHandle)
|
||||
{
|
||||
PrjFS_StopVirtualizationInstance(mountHandle);
|
||||
}
|
||||
#endif /* PROJFS_VFSAPI */
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
}
|
||||
|
||||
void tst_wait_signal(void)
|
||||
{
|
||||
int tty = isatty(STDIN_FILENO);
|
||||
|
||||
if (tty < 0)
|
||||
warn("unable to check stdin");
|
||||
else if (tty) {
|
||||
printf("hit Enter to stop: ");
|
||||
getchar();
|
||||
}
|
||||
else {
|
||||
struct sigaction sa;
|
||||
|
||||
memset(&sa, 0, sizeof(struct sigaction));
|
||||
sa.sa_handler = signal_handler;
|
||||
sigemptyset(&(sa.sa_mask));
|
||||
sa.sa_flags = 0;
|
||||
|
||||
/* replace libfuse's handler so we can exit tests cleanly */
|
||||
if (sigaction(SIGTERM, &sa, 0) < 0)
|
||||
warn("unable to set signal handler");
|
||||
else
|
||||
pause();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../include/projfs.h"
|
||||
#ifdef PROJFS_VFSAPI
|
||||
#include "../include/projfs_vfsapi.h"
|
||||
#endif /* PROJFS_VFSAPI */
|
||||
|
||||
#define RETVAL_DEFAULT 1000 // magic unused value
|
||||
|
||||
int tst_find_retval(int vfsapi, const char *retname, const char *optname);
|
||||
|
||||
void tst_parse_opts(int argc, const char **argv, int vfsapi,
|
||||
const char **lower_path, const char **mount_path,
|
||||
int *retval);
|
||||
|
||||
struct projfs *tst_start_mount(const char *lowerdir, const char *mountdir,
|
||||
const struct projfs_handlers *handlers,
|
||||
size_t handlers_size, void *user_data);
|
||||
|
||||
void *tst_stop_mount(struct projfs *fs);
|
||||
|
||||
#ifdef PROJFS_VFSAPI
|
||||
void tst_start_vfsapi_mount(const char *storageRootFullPath,
|
||||
const char *virtualizationRootFullPath,
|
||||
PrjFS_Callbacks callbacks,
|
||||
unsigned int poolThreadCount,
|
||||
PrjFS_MountHandle** mountHandle);
|
||||
|
||||
void tst_stop_vfsapi_mount(PrjFS_MountHandle* handle);
|
||||
#endif /* PROJFS_VFSAPI */
|
||||
|
||||
void tst_wait_signal(void);
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
static int retval;
|
||||
|
||||
static int test_handle_event(struct projfs_event *event, const char *desc,
|
||||
int perm)
|
||||
{
|
||||
int ret = retval;
|
||||
|
||||
printf(" test %s for %s: "
|
||||
"0x%04" PRIx64 "-%08" PRIx64 ", %d\n",
|
||||
desc, event->path,
|
||||
event->mask >> 32, event->mask & 0xFFFFFFFF, event->pid);
|
||||
|
||||
if (ret == RETVAL_DEFAULT)
|
||||
ret = perm ? PROJFS_ALLOW : 0;
|
||||
else if(!perm && ret > 0)
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int test_notify_event(struct projfs_event *event)
|
||||
{
|
||||
return test_handle_event(event, "event notification", 0);
|
||||
}
|
||||
|
||||
static int test_perm_event(struct projfs_event *event)
|
||||
{
|
||||
return test_handle_event(event, "permission request", 1);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char *lower_path, *mount_path;
|
||||
struct projfs *fs;
|
||||
struct projfs_handlers handlers;
|
||||
|
||||
tst_parse_opts(argc, argv, 0, &lower_path, &mount_path, &retval);
|
||||
|
||||
handlers.handle_notify_event = &test_notify_event;
|
||||
handlers.handle_perm_event = &test_perm_event;
|
||||
|
||||
fs = tst_start_mount(lower_path, mount_path,
|
||||
&handlers, sizeof(handlers), NULL);
|
||||
tst_wait_signal();
|
||||
tst_stop_mount(fs);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char *lower_path, *mount_path;
|
||||
struct projfs *fs;
|
||||
|
||||
tst_parse_opts(argc, argv, 0, &lower_path, &mount_path, NULL);
|
||||
|
||||
fs = tst_start_mount(lower_path, mount_path, NULL, 0, NULL);
|
||||
tst_wait_signal();
|
||||
tst_stop_mount(fs);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
static int retval;
|
||||
|
||||
static PrjFS_Result TestNotifyOperation(
|
||||
_In_ unsigned long commandId,
|
||||
_In_ const char* relativePath,
|
||||
_In_ unsigned char providerId[PrjFS_PlaceholderIdLength],
|
||||
_In_ unsigned char contentId[PrjFS_PlaceholderIdLength],
|
||||
_In_ int triggeringProcessId,
|
||||
_In_ const char* triggeringProcessName,
|
||||
_In_ bool isDirectory,
|
||||
_In_ PrjFS_NotificationType notificationType,
|
||||
_In_ const char* destinationRelativePath
|
||||
)
|
||||
{
|
||||
printf(" TestNotifyOperation for %s: %d, %s, %hhd, 0x%08X\n",
|
||||
relativePath, triggeringProcessId, triggeringProcessName,
|
||||
isDirectory, notificationType);
|
||||
|
||||
(void) commandId;
|
||||
(void) providerId;
|
||||
(void) contentId;
|
||||
(void) destinationRelativePath;
|
||||
|
||||
return (retval == RETVAL_DEFAULT) ? PrjFS_Result_Success : retval;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char *lower_path, *mount_path;
|
||||
PrjFS_MountHandle *handle;
|
||||
PrjFS_Callbacks callbacks;
|
||||
|
||||
tst_parse_opts(argc, argv, 1, &lower_path, &mount_path, &retval);
|
||||
|
||||
memset(&callbacks, 0, sizeof(PrjFS_Callbacks));
|
||||
callbacks.NotifyOperation = TestNotifyOperation;
|
||||
|
||||
tst_start_vfsapi_mount(lower_path, mount_path, callbacks, 0, &handle);
|
||||
tst_wait_signal();
|
||||
tst_stop_vfsapi_mount(handle);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char *lower_path, *mount_path;
|
||||
PrjFS_MountHandle *handle;
|
||||
PrjFS_Callbacks callbacks;
|
||||
|
||||
tst_parse_opts(argc, argv, 1, &lower_path, &mount_path, NULL);
|
||||
|
||||
memset(&callbacks, 0, sizeof(PrjFS_Callbacks));
|
||||
|
||||
tst_start_vfsapi_mount(lower_path, mount_path, callbacks, 0, &handle);
|
||||
tst_wait_signal();
|
||||
tst_stop_vfsapi_mount(handle);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/* Linux Projected Filesystem
|
||||
Copyright (C) 2018-2019 GitHub, Inc.
|
||||
|
||||
See the NOTICE file distributed with this library for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library, in the file COPYING.LIB; if not,
|
||||
see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE // for basename() in <string.h>
|
||||
// and nanosleep()
|
||||
|
||||
#include <err.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MOUNT_MAX_WAIT_SEC 30
|
||||
#define MOUNT_WAIT_TIMESPEC { 0, 1000*1000 }
|
||||
|
||||
static int get_curr_time(time_t *sec)
|
||||
{
|
||||
struct timeval tv;
|
||||
int ret = 0;
|
||||
|
||||
if (gettimeofday(&tv, NULL) < 0) {
|
||||
warn("unable to get current time");
|
||||
ret = -1;
|
||||
} else
|
||||
*sec = tv.tv_sec;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* modelled on wait_for_mount() in FUSE's test/util.py */
|
||||
static int wait_for_mount(dev_t prior_dev, const char *mountdir,
|
||||
time_t max_wait)
|
||||
{
|
||||
struct stat mnt;
|
||||
const struct timespec wait_req = MOUNT_WAIT_TIMESPEC;
|
||||
time_t start, now, wait = 0;
|
||||
time_t warn_sec = 0;
|
||||
int ret = 0;
|
||||
|
||||
ret = get_curr_time(&start);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
do {
|
||||
if (stat(mountdir, &mnt) != 0) {
|
||||
// limit warnings to once per second
|
||||
if (warn_sec < wait) {
|
||||
warn("unable to check mount point");
|
||||
warn_sec = wait;
|
||||
}
|
||||
} else if (prior_dev != mnt.st_dev)
|
||||
break;
|
||||
|
||||
nanosleep(&wait_req, NULL);
|
||||
|
||||
ret = get_curr_time(&now);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
wait = now - start;
|
||||
if (wait >= max_wait) {
|
||||
warnx("timeout waiting for filesystem mount");
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
long int prior_dev;
|
||||
int max_wait = MOUNT_MAX_WAIT_SEC;
|
||||
|
||||
if (argc < 3 || argc > 4) {
|
||||
fprintf(stderr, "Usage: %s <dev-id> <mount-path> "
|
||||
"[<max-wait-sec>]\n",
|
||||
basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (sscanf(argv[1], "%li", &prior_dev) != 1 || prior_dev <= 0) {
|
||||
fprintf(stderr, "invalid device ID: %s\n", argv[1]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (argc == 4 && (sscanf(argv[3], "%d", &max_wait) != 1 ||
|
||||
max_wait < 0)) {
|
||||
fprintf(stderr, "invalid timeout value: %s\n", argv[3]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (wait_for_mount((dev_t) prior_dev, argv[2], (time_t) max_wait) < 0)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
|
@ -0,0 +1,651 @@
|
|||
#! /bin/sh
|
||||
# Copyright (C) 2011-2019 Free Software Foundation, Inc.
|
||||
#
|
||||
# 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, 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
# As a special exception to the GNU General Public License, if you
|
||||
# distribute this file as part of a program that contains a
|
||||
# configuration script generated by Autoconf, you may include it under
|
||||
# the same distribution terms that you use for the rest of that program.
|
||||
|
||||
# This file is maintained in Automake, please report
|
||||
# bugs to <bug-automake@gnu.org> or send patches to
|
||||
# <automake-patches@gnu.org>.
|
||||
|
||||
scriptversion=2013-12-23.17; # UTC
|
||||
|
||||
# Make unconditional expansion of undefined variables an error. This
|
||||
# helps a lot in preventing typo-related bugs.
|
||||
set -u
|
||||
|
||||
me=tap-driver.sh
|
||||
|
||||
fatal ()
|
||||
{
|
||||
echo "$me: fatal: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage_error ()
|
||||
{
|
||||
echo "$me: $*" >&2
|
||||
print_usage >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
print_usage ()
|
||||
{
|
||||
cat <<END
|
||||
Usage:
|
||||
tap-driver.sh --test-name NAME --log-file PATH --trs-file PATH
|
||||
[--expect-failure {yes|no}] [--color-tests {yes|no}]
|
||||
[--enable-hard-errors {yes|no}] [--ignore-exit]
|
||||
[--diagnostic-string STRING] [--merge|--no-merge]
|
||||
[--comments|--no-comments] [--] TEST-COMMAND
|
||||
The '--test-name', '-log-file' and '--trs-file' options are mandatory.
|
||||
END
|
||||
}
|
||||
|
||||
# TODO: better error handling in option parsing (in particular, ensure
|
||||
# TODO: $log_file, $trs_file and $test_name are defined).
|
||||
test_name= # Used for reporting.
|
||||
log_file= # Where to save the result and output of the test script.
|
||||
trs_file= # Where to save the metadata of the test run.
|
||||
expect_failure=0
|
||||
color_tests=0
|
||||
merge=0
|
||||
ignore_exit=0
|
||||
comments=0
|
||||
diag_string='#'
|
||||
while test $# -gt 0; do
|
||||
case $1 in
|
||||
--help) print_usage; exit $?;;
|
||||
--version) echo "$me $scriptversion"; exit $?;;
|
||||
--test-name) test_name=$2; shift;;
|
||||
--log-file) log_file=$2; shift;;
|
||||
--trs-file) trs_file=$2; shift;;
|
||||
--color-tests) color_tests=$2; shift;;
|
||||
--expect-failure) expect_failure=$2; shift;;
|
||||
--enable-hard-errors) shift;; # No-op.
|
||||
--merge) merge=1;;
|
||||
--no-merge) merge=0;;
|
||||
--ignore-exit) ignore_exit=1;;
|
||||
--comments) comments=1;;
|
||||
--no-comments) comments=0;;
|
||||
--diagnostic-string) diag_string=$2; shift;;
|
||||
--) shift; break;;
|
||||
-*) usage_error "invalid option: '$1'";;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
test $# -gt 0 || usage_error "missing test command"
|
||||
|
||||
case $expect_failure in
|
||||
yes) expect_failure=1;;
|
||||
*) expect_failure=0;;
|
||||
esac
|
||||
|
||||
if test $color_tests = yes; then
|
||||
init_colors='
|
||||
color_map["red"]="[0;31m" # Red.
|
||||
color_map["grn"]="[0;32m" # Green.
|
||||
color_map["lgn"]="[1;32m" # Light green.
|
||||
color_map["blu"]="[1;34m" # Blue.
|
||||
color_map["mgn"]="[0;35m" # Magenta.
|
||||
color_map["std"]="[m" # No color.
|
||||
color_for_result["ERROR"] = "mgn"
|
||||
color_for_result["PASS"] = "grn"
|
||||
color_for_result["XPASS"] = "red"
|
||||
color_for_result["FAIL"] = "red"
|
||||
color_for_result["XFAIL"] = "lgn"
|
||||
color_for_result["SKIP"] = "blu"'
|
||||
else
|
||||
init_colors=''
|
||||
fi
|
||||
|
||||
# :; is there to work around a bug in bash 3.2 (and earlier) which
|
||||
# does not always set '$?' properly on redirection failure.
|
||||
# See the Autoconf manual for more details.
|
||||
:;{
|
||||
(
|
||||
# Ignore common signals (in this subshell only!), to avoid potential
|
||||
# problems with Korn shells. Some Korn shells are known to propagate
|
||||
# to themselves signals that have killed a child process they were
|
||||
# waiting for; this is done at least for SIGINT (and usually only for
|
||||
# it, in truth). Without the `trap' below, such a behaviour could
|
||||
# cause a premature exit in the current subshell, e.g., in case the
|
||||
# test command it runs gets terminated by a SIGINT. Thus, the awk
|
||||
# script we are piping into would never seen the exit status it
|
||||
# expects on its last input line (which is displayed below by the
|
||||
# last `echo $?' statement), and would thus die reporting an internal
|
||||
# error.
|
||||
# For more information, see the Autoconf manual and the threads:
|
||||
# <https://lists.gnu.org/archive/html/bug-autoconf/2011-09/msg00004.html>
|
||||
# <http://mail.opensolaris.org/pipermail/ksh93-integration-discuss/2009-February/004121.html>
|
||||
trap : 1 3 2 13 15
|
||||
if test $merge -gt 0; then
|
||||
exec 2>&1
|
||||
else
|
||||
exec 2>&3
|
||||
fi
|
||||
"$@"
|
||||
echo $?
|
||||
) | LC_ALL=C ${AM_TAP_AWK-awk} \
|
||||
-v me="$me" \
|
||||
-v test_script_name="$test_name" \
|
||||
-v log_file="$log_file" \
|
||||
-v trs_file="$trs_file" \
|
||||
-v expect_failure="$expect_failure" \
|
||||
-v merge="$merge" \
|
||||
-v ignore_exit="$ignore_exit" \
|
||||
-v comments="$comments" \
|
||||
-v diag_string="$diag_string" \
|
||||
'
|
||||
# TODO: the usages of "cat >&3" below could be optimized when using
|
||||
# GNU awk, and/on on systems that supports /dev/fd/.
|
||||
|
||||
# Implementation note: in what follows, `result_obj` will be an
|
||||
# associative array that (partly) simulates a TAP result object
|
||||
# from the `TAP::Parser` perl module.
|
||||
|
||||
## ----------- ##
|
||||
## FUNCTIONS ##
|
||||
## ----------- ##
|
||||
|
||||
function fatal(msg)
|
||||
{
|
||||
print me ": " msg | "cat >&2"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function abort(where)
|
||||
{
|
||||
fatal("internal error " where)
|
||||
}
|
||||
|
||||
# Convert a boolean to a "yes"/"no" string.
|
||||
function yn(bool)
|
||||
{
|
||||
return bool ? "yes" : "no";
|
||||
}
|
||||
|
||||
function add_test_result(result)
|
||||
{
|
||||
if (!test_results_index)
|
||||
test_results_index = 0
|
||||
test_results_list[test_results_index] = result
|
||||
test_results_index += 1
|
||||
test_results_seen[result] = 1;
|
||||
}
|
||||
|
||||
# Whether the test script should be re-run by "make recheck".
|
||||
function must_recheck()
|
||||
{
|
||||
for (k in test_results_seen)
|
||||
if (k != "XFAIL" && k != "PASS" && k != "SKIP")
|
||||
return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# Whether the content of the log file associated to this test should
|
||||
# be copied into the "global" test-suite.log.
|
||||
function copy_in_global_log()
|
||||
{
|
||||
for (k in test_results_seen)
|
||||
if (k != "PASS")
|
||||
return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
function get_global_test_result()
|
||||
{
|
||||
if ("ERROR" in test_results_seen)
|
||||
return "ERROR"
|
||||
if ("FAIL" in test_results_seen || "XPASS" in test_results_seen)
|
||||
return "FAIL"
|
||||
all_skipped = 1
|
||||
for (k in test_results_seen)
|
||||
if (k != "SKIP")
|
||||
all_skipped = 0
|
||||
if (all_skipped)
|
||||
return "SKIP"
|
||||
return "PASS";
|
||||
}
|
||||
|
||||
function stringify_result_obj(result_obj)
|
||||
{
|
||||
if (result_obj["is_unplanned"] || result_obj["number"] != testno)
|
||||
return "ERROR"
|
||||
|
||||
if (plan_seen == LATE_PLAN)
|
||||
return "ERROR"
|
||||
|
||||
if (result_obj["directive"] == "TODO")
|
||||
return result_obj["is_ok"] ? "XPASS" : "XFAIL"
|
||||
|
||||
if (result_obj["directive"] == "SKIP")
|
||||
return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL;
|
||||
|
||||
if (length(result_obj["directive"]))
|
||||
abort("in function stringify_result_obj()")
|
||||
|
||||
return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL
|
||||
}
|
||||
|
||||
function decorate_result(result)
|
||||
{
|
||||
color_name = color_for_result[result]
|
||||
if (color_name)
|
||||
return color_map[color_name] "" result "" color_map["std"]
|
||||
# If we are not using colorized output, or if we do not know how
|
||||
# to colorize the given result, we should return it unchanged.
|
||||
return result
|
||||
}
|
||||
|
||||
function report(result, details)
|
||||
{
|
||||
if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/)
|
||||
{
|
||||
msg = ": " test_script_name
|
||||
add_test_result(result)
|
||||
}
|
||||
else if (result == "#")
|
||||
{
|
||||
msg = " " test_script_name ":"
|
||||
}
|
||||
else
|
||||
{
|
||||
abort("in function report()")
|
||||
}
|
||||
if (length(details))
|
||||
msg = msg " " details
|
||||
# Output on console might be colorized.
|
||||
print decorate_result(result) msg
|
||||
# Log the result in the log file too, to help debugging (this is
|
||||
# especially true when said result is a TAP error or "Bail out!").
|
||||
print result msg | "cat >&3";
|
||||
}
|
||||
|
||||
function testsuite_error(error_message)
|
||||
{
|
||||
report("ERROR", "- " error_message)
|
||||
}
|
||||
|
||||
function handle_tap_result()
|
||||
{
|
||||
details = result_obj["number"];
|
||||
if (length(result_obj["description"]))
|
||||
details = details " " result_obj["description"]
|
||||
|
||||
if (plan_seen == LATE_PLAN)
|
||||
{
|
||||
details = details " # AFTER LATE PLAN";
|
||||
}
|
||||
else if (result_obj["is_unplanned"])
|
||||
{
|
||||
details = details " # UNPLANNED";
|
||||
}
|
||||
else if (result_obj["number"] != testno)
|
||||
{
|
||||
details = sprintf("%s # OUT-OF-ORDER (expecting %d)",
|
||||
details, testno);
|
||||
}
|
||||
else if (result_obj["directive"])
|
||||
{
|
||||
details = details " # " result_obj["directive"];
|
||||
if (length(result_obj["explanation"]))
|
||||
details = details " " result_obj["explanation"]
|
||||
}
|
||||
|
||||
report(stringify_result_obj(result_obj), details)
|
||||
}
|
||||
|
||||
# `skip_reason` should be empty whenever planned > 0.
|
||||
function handle_tap_plan(planned, skip_reason)
|
||||
{
|
||||
planned += 0 # Avoid getting confused if, say, `planned` is "00"
|
||||
if (length(skip_reason) && planned > 0)
|
||||
abort("in function handle_tap_plan()")
|
||||
if (plan_seen)
|
||||
{
|
||||
# Error, only one plan per stream is acceptable.
|
||||
testsuite_error("multiple test plans")
|
||||
return;
|
||||
}
|
||||
planned_tests = planned
|
||||
# The TAP plan can come before or after *all* the TAP results; we speak
|
||||
# respectively of an "early" or a "late" plan. If we see the plan line
|
||||
# after at least one TAP result has been seen, assume we have a late
|
||||
# plan; in this case, any further test result seen after the plan will
|
||||
# be flagged as an error.
|
||||
plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN)
|
||||
# If testno > 0, we have an error ("too many tests run") that will be
|
||||
# automatically dealt with later, so do not worry about it here. If
|
||||
# $plan_seen is true, we have an error due to a repeated plan, and that
|
||||
# has already been dealt with above. Otherwise, we have a valid "plan
|
||||
# with SKIP" specification, and should report it as a particular kind
|
||||
# of SKIP result.
|
||||
if (planned == 0 && testno == 0)
|
||||
{
|
||||
if (length(skip_reason))
|
||||
skip_reason = "- " skip_reason;
|
||||
report("SKIP", skip_reason);
|
||||
}
|
||||
}
|
||||
|
||||
function extract_tap_comment(line)
|
||||
{
|
||||
if (index(line, diag_string) == 1)
|
||||
{
|
||||
# Strip leading `diag_string` from `line`.
|
||||
line = substr(line, length(diag_string) + 1)
|
||||
# And strip any leading and trailing whitespace left.
|
||||
sub("^[ \t]*", "", line)
|
||||
sub("[ \t]*$", "", line)
|
||||
# Return what is left (if any).
|
||||
return line;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
# When this function is called, we know that line is a TAP result line,
|
||||
# so that it matches the (perl) RE "^(not )?ok\b".
|
||||
function setup_result_obj(line)
|
||||
{
|
||||
# Get the result, and remove it from the line.
|
||||
result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0)
|
||||
sub("^(not )?ok[ \t]*", "", line)
|
||||
|
||||
# If the result has an explicit number, get it and strip it; otherwise,
|
||||
# automatically assing the next progresive number to it.
|
||||
if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/)
|
||||
{
|
||||
match(line, "^[0-9]+")
|
||||
# The final `+ 0` is to normalize numbers with leading zeros.
|
||||
result_obj["number"] = substr(line, 1, RLENGTH) + 0
|
||||
line = substr(line, RLENGTH + 1)
|
||||
}
|
||||
else
|
||||
{
|
||||
result_obj["number"] = testno
|
||||
}
|
||||
|
||||
if (plan_seen == LATE_PLAN)
|
||||
# No further test results are acceptable after a "late" TAP plan
|
||||
# has been seen.
|
||||
result_obj["is_unplanned"] = 1
|
||||
else if (plan_seen && testno > planned_tests)
|
||||
result_obj["is_unplanned"] = 1
|
||||
else
|
||||
result_obj["is_unplanned"] = 0
|
||||
|
||||
# Strip trailing and leading whitespace.
|
||||
sub("^[ \t]*", "", line)
|
||||
sub("[ \t]*$", "", line)
|
||||
|
||||
# This will have to be corrected if we have a "TODO"/"SKIP" directive.
|
||||
result_obj["description"] = line
|
||||
result_obj["directive"] = ""
|
||||
result_obj["explanation"] = ""
|
||||
|
||||
if (index(line, "#") == 0)
|
||||
return # No possible directive, nothing more to do.
|
||||
|
||||
# Directives are case-insensitive.
|
||||
rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*"
|
||||
|
||||
# See whether we have the directive, and if yes, where.
|
||||
pos = match(line, rx "$")
|
||||
if (!pos)
|
||||
pos = match(line, rx "[^a-zA-Z0-9_]")
|
||||
|
||||
# If there was no TAP directive, we have nothing more to do.
|
||||
if (!pos)
|
||||
return
|
||||
|
||||
# Let`s now see if the TAP directive has been escaped. For example:
|
||||
# escaped: ok \# SKIP
|
||||
# not escaped: ok \\# SKIP
|
||||
# escaped: ok \\\\\# SKIP
|
||||
# not escaped: ok \ # SKIP
|
||||
if (substr(line, pos, 1) == "#")
|
||||
{
|
||||
bslash_count = 0
|
||||
for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--)
|
||||
bslash_count += 1
|
||||
if (bslash_count % 2)
|
||||
return # Directive was escaped.
|
||||
}
|
||||
|
||||
# Strip the directive and its explanation (if any) from the test
|
||||
# description.
|
||||
result_obj["description"] = substr(line, 1, pos - 1)
|
||||
# Now remove the test description from the line, that has been dealt
|
||||
# with already.
|
||||
line = substr(line, pos)
|
||||
# Strip the directive, and save its value (normalized to upper case).
|
||||
sub("^[ \t]*#[ \t]*", "", line)
|
||||
result_obj["directive"] = toupper(substr(line, 1, 4))
|
||||
line = substr(line, 5)
|
||||
# Now get the explanation for the directive (if any), with leading
|
||||
# and trailing whitespace removed.
|
||||
sub("^[ \t]*", "", line)
|
||||
sub("[ \t]*$", "", line)
|
||||
result_obj["explanation"] = line
|
||||
}
|
||||
|
||||
function get_test_exit_message(status)
|
||||
{
|
||||
if (status == 0)
|
||||
return ""
|
||||
if (status !~ /^[1-9][0-9]*$/)
|
||||
abort("getting exit status")
|
||||
if (status < 127)
|
||||
exit_details = ""
|
||||
else if (status == 127)
|
||||
exit_details = " (command not found?)"
|
||||
else if (status >= 128 && status <= 255)
|
||||
exit_details = sprintf(" (terminated by signal %d?)", status - 128)
|
||||
else if (status > 256 && status <= 384)
|
||||
# We used to report an "abnormal termination" here, but some Korn
|
||||
# shells, when a child process die due to signal number n, can leave
|
||||
# in $? an exit status of 256+n instead of the more standard 128+n.
|
||||
# Apparently, both behaviours are allowed by POSIX (2008), so be
|
||||
# prepared to handle them both. See also Austing Group report ID
|
||||
# 0000051 <http://www.austingroupbugs.net/view.php?id=51>
|
||||
exit_details = sprintf(" (terminated by signal %d?)", status - 256)
|
||||
else
|
||||
# Never seen in practice.
|
||||
exit_details = " (abnormal termination)"
|
||||
return sprintf("exited with status %d%s", status, exit_details)
|
||||
}
|
||||
|
||||
function write_test_results()
|
||||
{
|
||||
print ":global-test-result: " get_global_test_result() > trs_file
|
||||
print ":recheck: " yn(must_recheck()) > trs_file
|
||||
print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file
|
||||
for (i = 0; i < test_results_index; i += 1)
|
||||
print ":test-result: " test_results_list[i] > trs_file
|
||||
close(trs_file);
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
|
||||
## ------- ##
|
||||
## SETUP ##
|
||||
## ------- ##
|
||||
|
||||
'"$init_colors"'
|
||||
|
||||
# Properly initialized once the TAP plan is seen.
|
||||
planned_tests = 0
|
||||
|
||||
COOKED_PASS = expect_failure ? "XPASS": "PASS";
|
||||
COOKED_FAIL = expect_failure ? "XFAIL": "FAIL";
|
||||
|
||||
# Enumeration-like constants to remember which kind of plan (if any)
|
||||
# has been seen. It is important that NO_PLAN evaluates "false" as
|
||||
# a boolean.
|
||||
NO_PLAN = 0
|
||||
EARLY_PLAN = 1
|
||||
LATE_PLAN = 2
|
||||
|
||||
testno = 0 # Number of test results seen so far.
|
||||
bailed_out = 0 # Whether a "Bail out!" directive has been seen.
|
||||
|
||||
# Whether the TAP plan has been seen or not, and if yes, which kind
|
||||
# it is ("early" is seen before any test result, "late" otherwise).
|
||||
plan_seen = NO_PLAN
|
||||
|
||||
## --------- ##
|
||||
## PARSING ##
|
||||
## --------- ##
|
||||
|
||||
is_first_read = 1
|
||||
|
||||
while (1)
|
||||
{
|
||||
# Involutions required so that we are able to read the exit status
|
||||
# from the last input line.
|
||||
st = getline
|
||||
if (st < 0) # I/O error.
|
||||
fatal("I/O error while reading from input stream")
|
||||
else if (st == 0) # End-of-input
|
||||
{
|
||||
if (is_first_read)
|
||||
abort("in input loop: only one input line")
|
||||
break
|
||||
}
|
||||
if (is_first_read)
|
||||
{
|
||||
is_first_read = 0
|
||||
nextline = $0
|
||||
continue
|
||||
}
|
||||
else
|
||||
{
|
||||
curline = nextline
|
||||
nextline = $0
|
||||
$0 = curline
|
||||
}
|
||||
# Copy any input line verbatim into the log file.
|
||||
print | "cat >&3"
|
||||
# Parsing of TAP input should stop after a "Bail out!" directive.
|
||||
if (bailed_out)
|
||||
continue
|
||||
|
||||
# TAP test result.
|
||||
if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/)
|
||||
{
|
||||
testno += 1
|
||||
setup_result_obj($0)
|
||||
handle_tap_result()
|
||||
}
|
||||
# TAP plan (normal or "SKIP" without explanation).
|
||||
else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/)
|
||||
{
|
||||
# The next two lines will put the number of planned tests in $0.
|
||||
sub("^1\\.\\.", "")
|
||||
sub("[^0-9]*$", "")
|
||||
handle_tap_plan($0, "")
|
||||
continue
|
||||
}
|
||||
# TAP "SKIP" plan, with an explanation.
|
||||
else if ($0 ~ /^1\.\.0+[ \t]*#/)
|
||||
{
|
||||
# The next lines will put the skip explanation in $0, stripping
|
||||
# any leading and trailing whitespace. This is a little more
|
||||
# tricky in truth, since we want to also strip a potential leading
|
||||
# "SKIP" string from the message.
|
||||
sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "")
|
||||
sub("[ \t]*$", "");
|
||||
handle_tap_plan(0, $0)
|
||||
}
|
||||
# "Bail out!" magic.
|
||||
# Older versions of prove and TAP::Harness (e.g., 3.17) did not
|
||||
# recognize a "Bail out!" directive when preceded by leading
|
||||
# whitespace, but more modern versions (e.g., 3.23) do. So we
|
||||
# emulate the latter, "more modern" behaviour.
|
||||
else if ($0 ~ /^[ \t]*Bail out!/)
|
||||
{
|
||||
bailed_out = 1
|
||||
# Get the bailout message (if any), with leading and trailing
|
||||
# whitespace stripped. The message remains stored in `$0`.
|
||||
sub("^[ \t]*Bail out![ \t]*", "");
|
||||
sub("[ \t]*$", "");
|
||||
# Format the error message for the
|
||||
bailout_message = "Bail out!"
|
||||
if (length($0))
|
||||
bailout_message = bailout_message " " $0
|
||||
testsuite_error(bailout_message)
|
||||
}
|
||||
# Maybe we have too look for dianogtic comments too.
|
||||
else if (comments != 0)
|
||||
{
|
||||
comment = extract_tap_comment($0);
|
||||
if (length(comment))
|
||||
report("#", comment);
|
||||
}
|
||||
}
|
||||
|
||||
## -------- ##
|
||||
## FINISH ##
|
||||
## -------- ##
|
||||
|
||||
# A "Bail out!" directive should cause us to ignore any following TAP
|
||||
# error, as well as a non-zero exit status from the TAP producer.
|
||||
if (!bailed_out)
|
||||
{
|
||||
if (!plan_seen)
|
||||
{
|
||||
testsuite_error("missing test plan")
|
||||
}
|
||||
else if (planned_tests != testno)
|
||||
{
|
||||
bad_amount = testno > planned_tests ? "many" : "few"
|
||||
testsuite_error(sprintf("too %s tests run (expected %d, got %d)",
|
||||
bad_amount, planned_tests, testno))
|
||||
}
|
||||
if (!ignore_exit)
|
||||
{
|
||||
# Fetch exit status from the last line.
|
||||
exit_message = get_test_exit_message(nextline)
|
||||
if (exit_message)
|
||||
testsuite_error(exit_message)
|
||||
}
|
||||
}
|
||||
|
||||
write_test_results()
|
||||
|
||||
exit 0
|
||||
|
||||
} # End of "BEGIN" block.
|
||||
'
|
||||
|
||||
# TODO: document that we consume the file descriptor 3 :-(
|
||||
} 3>"$log_file"
|
||||
|
||||
test $? -eq 0 || fatal "I/O or internal error"
|
||||
|
||||
# Local Variables:
|
||||
# mode: shell-script
|
||||
# sh-indentation: 2
|
||||
# eval: (add-hook 'before-save-hook 'time-stamp)
|
||||
# time-stamp-start: "scriptversion="
|
||||
# time-stamp-format: "%:y-%02m-%02d.%02H"
|
||||
# time-stamp-time-zone: "UTC0"
|
||||
# time-stamp-end: "; # UTC"
|
||||
# End:
|
|
@ -0,0 +1,148 @@
|
|||
#! /bin/sh
|
||||
# test-driver - basic testsuite driver script.
|
||||
|
||||
scriptversion=2013-07-13.22; # UTC
|
||||
|
||||
# Copyright (C) 2011-2014 Free Software Foundation, Inc.
|
||||
#
|
||||
# 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, 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/>.
|
||||
|
||||
# As a special exception to the GNU General Public License, if you
|
||||
# distribute this file as part of a program that contains a
|
||||
# configuration script generated by Autoconf, you may include it under
|
||||
# the same distribution terms that you use for the rest of that program.
|
||||
|
||||
# This file is maintained in Automake, please report
|
||||
# bugs to <bug-automake@gnu.org> or send patches to
|
||||
# <automake-patches@gnu.org>.
|
||||
|
||||
# Make unconditional expansion of undefined variables an error. This
|
||||
# helps a lot in preventing typo-related bugs.
|
||||
set -u
|
||||
|
||||
usage_error ()
|
||||
{
|
||||
echo "$0: $*" >&2
|
||||
print_usage >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
print_usage ()
|
||||
{
|
||||
cat <<END
|
||||
Usage:
|
||||
test-driver --test-name=NAME --log-file=PATH --trs-file=PATH
|
||||
[--expect-failure={yes|no}] [--color-tests={yes|no}]
|
||||
[--enable-hard-errors={yes|no}] [--]
|
||||
TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS]
|
||||
The '--test-name', '--log-file' and '--trs-file' options are mandatory.
|
||||
END
|
||||
}
|
||||
|
||||
test_name= # Used for reporting.
|
||||
log_file= # Where to save the output of the test script.
|
||||
trs_file= # Where to save the metadata of the test run.
|
||||
expect_failure=no
|
||||
color_tests=no
|
||||
enable_hard_errors=yes
|
||||
while test $# -gt 0; do
|
||||
case $1 in
|
||||
--help) print_usage; exit $?;;
|
||||
--version) echo "test-driver $scriptversion"; exit $?;;
|
||||
--test-name) test_name=$2; shift;;
|
||||
--log-file) log_file=$2; shift;;
|
||||
--trs-file) trs_file=$2; shift;;
|
||||
--color-tests) color_tests=$2; shift;;
|
||||
--expect-failure) expect_failure=$2; shift;;
|
||||
--enable-hard-errors) enable_hard_errors=$2; shift;;
|
||||
--) shift; break;;
|
||||
-*) usage_error "invalid option: '$1'";;
|
||||
*) break;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
missing_opts=
|
||||
test x"$test_name" = x && missing_opts="$missing_opts --test-name"
|
||||
test x"$log_file" = x && missing_opts="$missing_opts --log-file"
|
||||
test x"$trs_file" = x && missing_opts="$missing_opts --trs-file"
|
||||
if test x"$missing_opts" != x; then
|
||||
usage_error "the following mandatory options are missing:$missing_opts"
|
||||
fi
|
||||
|
||||
if test $# -eq 0; then
|
||||
usage_error "missing argument"
|
||||
fi
|
||||
|
||||
if test $color_tests = yes; then
|
||||
# Keep this in sync with 'lib/am/check.am:$(am__tty_colors)'.
|
||||
red='[0;31m' # Red.
|
||||
grn='[0;32m' # Green.
|
||||
lgn='[1;32m' # Light green.
|
||||
blu='[1;34m' # Blue.
|
||||
mgn='[0;35m' # Magenta.
|
||||
std='[m' # No color.
|
||||
else
|
||||
red= grn= lgn= blu= mgn= std=
|
||||
fi
|
||||
|
||||
do_exit='rm -f $log_file $trs_file; (exit $st); exit $st'
|
||||
trap "st=129; $do_exit" 1
|
||||
trap "st=130; $do_exit" 2
|
||||
trap "st=141; $do_exit" 13
|
||||
trap "st=143; $do_exit" 15
|
||||
|
||||
# Test script is run here.
|
||||
"$@" >$log_file 2>&1
|
||||
estatus=$?
|
||||
|
||||
if test $enable_hard_errors = no && test $estatus -eq 99; then
|
||||
tweaked_estatus=1
|
||||
else
|
||||
tweaked_estatus=$estatus
|
||||
fi
|
||||
|
||||
case $tweaked_estatus:$expect_failure in
|
||||
0:yes) col=$red res=XPASS recheck=yes gcopy=yes;;
|
||||
0:*) col=$grn res=PASS recheck=no gcopy=no;;
|
||||
77:*) col=$blu res=SKIP recheck=no gcopy=yes;;
|
||||
99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;;
|
||||
*:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;;
|
||||
*:*) col=$red res=FAIL recheck=yes gcopy=yes;;
|
||||
esac
|
||||
|
||||
# Report the test outcome and exit status in the logs, so that one can
|
||||
# know whether the test passed or failed simply by looking at the '.log'
|
||||
# file, without the need of also peaking into the corresponding '.trs'
|
||||
# file (automake bug#11814).
|
||||
echo "$res $test_name (exit status: $estatus)" >>$log_file
|
||||
|
||||
# Report outcome to console.
|
||||
echo "${col}${res}${std}: $test_name"
|
||||
|
||||
# Register the test result, and other relevant metadata.
|
||||
echo ":test-result: $res" > $trs_file
|
||||
echo ":global-test-result: $res" >> $trs_file
|
||||
echo ":recheck: $recheck" >> $trs_file
|
||||
echo ":copy-in-global-log: $gcopy" >> $trs_file
|
||||
|
||||
# Local Variables:
|
||||
# mode: shell-script
|
||||
# sh-indentation: 2
|
||||
# eval: (add-hook 'write-file-hooks 'time-stamp)
|
||||
# time-stamp-start: "scriptversion="
|
||||
# time-stamp-format: "%:y-%02m-%02d.%02H"
|
||||
# time-stamp-time-zone: "UTC"
|
||||
# time-stamp-end: "; # UTC"
|
||||
# End:
|
Загрузка…
Ссылка в новой задаче