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:
Ashe Connor 2019-01-14 10:09:36 +11:00
Коммит a2b8541645
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 997E657B1C52B6C5
76 изменённых файлов: 8028 добавлений и 0 удалений

37
.gitignore поставляемый Normal file
Просмотреть файл

@ -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

502
COPYING.LIB Normal file
Просмотреть файл

@ -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!

339
COPYING.TESTLIB Normal file
Просмотреть файл

@ -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.

21
COPYING.VFSAPI Normal file
Просмотреть файл

@ -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

35
Makefile.am Normal file
Просмотреть файл

@ -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

22
NOTICE Normal file
Просмотреть файл

@ -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

65
README.md Normal file
Просмотреть файл

@ -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

11
autogen.sh Executable file
Просмотреть файл

@ -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 "$@"

7
config.sh.in Normal file
Просмотреть файл

@ -0,0 +1,7 @@
AWK='@AWK@'
DIFF='@DIFF@'
MKDIR_P='@MKDIR_P@'
SED='@SED@'
SHELL='@SHELL@'
SHELL_PATH="$SHELL"

90
configure.ac Normal file
Просмотреть файл

@ -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

8
include/Makefile.am Normal file
Просмотреть файл

@ -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

136
include/projfs.h Normal file
Просмотреть файл

@ -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 */

84
include/projfs_notify.h Normal file
Просмотреть файл

@ -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 */

284
include/projfs_vfsapi.h Normal file
Просмотреть файл

@ -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 */

19
lib/Makefile.am Normal file
Просмотреть файл

@ -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@"

266
lib/projfs.c Normal file
Просмотреть файл

@ -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;
}

41
lib/projfs_i.h Normal file
Просмотреть файл

@ -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 */

262
lib/projfs_vfsapi.c Normal file
Просмотреть файл

@ -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;
}

11
projfs.pc.in Normal file
Просмотреть файл

@ -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

8
script/cibuild Executable file
Просмотреть файл

@ -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

96
t/Makefile.am Normal file
Просмотреть файл

@ -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

922
t/README.md Normal file
Просмотреть файл

@ -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

374
t/chainlint.sed Normal file
Просмотреть файл

@ -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

51
t/get_strerror.c Normal file
Просмотреть файл

@ -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);
}

52
t/t000-mirror-read.t Executable file
Просмотреть файл

@ -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

1
t/t000/f1.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
file1

1
t/t000/f2.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
file2

4
t/t000/ls.d1 Normal file
Просмотреть файл

@ -0,0 +1,4 @@
.
..
d2
f1.txt

3
t/t000/ls.d2 Normal file
Просмотреть файл

@ -0,0 +1,3 @@
.
..
f2.txt

5
t/t000/ls.target Normal file
Просмотреть файл

@ -0,0 +1,5 @@
.
..
d1
f1.txt
f2.txt

45
t/t001-mirror-mkdir.t Executable file
Просмотреть файл

@ -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

8
t/t001/sort.source Normal file
Просмотреть файл

@ -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

8
t/t001/sort.target Normal file
Просмотреть файл

@ -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

62
t/t002-mirror-write.t Executable file
Просмотреть файл

@ -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

1
t/t002/f1.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
file1-NEW

1
t/t002/f2.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
file2

49
t/t003-mirror-remove.t Executable file
Просмотреть файл

@ -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

4
t/t003/sort.source Normal file
Просмотреть файл

@ -0,0 +1,4 @@
source
source/d1
source/d1/f1.txt
source/f1.txt

4
t/t003/sort.target Normal file
Просмотреть файл

@ -0,0 +1,4 @@
target
target/d1
target/d1/f1.txt
target/f1.txt

78
t/t200-event-ok.t Executable file
Просмотреть файл

@ -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

56
t/t201-event-err.t Executable file
Просмотреть файл

@ -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

52
t/t202-event-deny.t Executable file
Просмотреть файл

@ -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

52
t/t203-event-null.t Executable file
Просмотреть файл

@ -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

52
t/t204-event-allow.t Executable file
Просмотреть файл

@ -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

52
t/t500-vfs-mirror-read.t Executable file
Просмотреть файл

@ -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

1
t/t500/f1.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
file1

1
t/t500/f2.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
file2

4
t/t500/ls.d1 Normal file
Просмотреть файл

@ -0,0 +1,4 @@
.
..
d2
f1.txt

3
t/t500/ls.d2 Normal file
Просмотреть файл

@ -0,0 +1,3 @@
.
..
f2.txt

5
t/t500/ls.target Normal file
Просмотреть файл

@ -0,0 +1,5 @@
.
..
d1
f1.txt
f2.txt

45
t/t501-vfs-mirror-mkdir.t Executable file
Просмотреть файл

@ -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

8
t/t501/sort.source Normal file
Просмотреть файл

@ -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

8
t/t501/sort.target Normal file
Просмотреть файл

@ -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

63
t/t502-vfs-mirror-write.t Executable file
Просмотреть файл

@ -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

1
t/t502/f1.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
file1-NEW

1
t/t502/f2.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
file2

49
t/t503-vfs-mirror-remove.t Executable file
Просмотреть файл

@ -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

4
t/t503/sort.source Normal file
Просмотреть файл

@ -0,0 +1,4 @@
source
source/d1
source/d1/f1.txt
source/f1.txt

4
t/t503/sort.target Normal file
Просмотреть файл

@ -0,0 +1,4 @@
target
target/d1
target/d1/f1.txt
target/f1.txt

78
t/t700-vfs-event-ok.t Executable file
Просмотреть файл

@ -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

56
t/t701-vfs-event-err.t Executable file
Просмотреть файл

@ -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

60
t/t702-vfs-event-deny.t Executable file
Просмотреть файл

@ -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

61
t/t703-vfs-event-null.t Executable file
Просмотреть файл

@ -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

52
t/t704-vfs-event-allow.t Executable file
Просмотреть файл

@ -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

84
t/test-lib-event.sh Normal file
Просмотреть файл

@ -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" "$@"
}

807
t/test-lib-functions.sh Normal file
Просмотреть файл

@ -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
}

915
t/test-lib.sh Normal file
Просмотреть файл

@ -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
'

230
t/test_common.c Normal file
Просмотреть файл

@ -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();
}
}

52
t/test_common.h Normal file
Просмотреть файл

@ -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);

76
t/test_projfs_handlers.c Normal file
Просмотреть файл

@ -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);
}

40
t/test_projfs_simple.c Normal file
Просмотреть файл

@ -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);
}

73
t/test_vfsapi_handlers.c Normal file
Просмотреть файл

@ -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);
}

46
t/test_vfsapi_simple.c Normal file
Просмотреть файл

@ -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);
}

121
t/wait_mount.c Normal file
Просмотреть файл

@ -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);
}

651
tap-driver.sh Normal file
Просмотреть файл

@ -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"]="" # Red.
color_map["grn"]="" # Green.
color_map["lgn"]="" # Light green.
color_map["blu"]="" # Blue.
color_map["mgn"]="" # Magenta.
color_map["std"]="" # 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:

148
test-driver Executable file
Просмотреть файл

@ -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='' # Red.
grn='' # Green.
lgn='' # Light green.
blu='' # Blue.
mgn='' # Magenta.
std='' # 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: