mirror of
https://git.sr.ht/~cismonx/bookmarkfs
synced 2025-06-07 11:48:51 +00:00
init: prepare for Savannah
This commit is contained in:
commit
cdf0ddfc53
64 changed files with 20104 additions and 0 deletions
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty, provided the copyright notice and
|
||||
# this notice are preserved. This file is offered as-is, without any warranty.
|
||||
#
|
||||
|
||||
# Generated by devel tools
|
||||
\#*#
|
||||
*~
|
||||
*.swp
|
||||
/.ccls
|
||||
/.ccls-cache/
|
||||
/.idea/
|
||||
/.vscode/
|
||||
|
||||
# Generated by the build system
|
||||
/aclocal.m4
|
||||
/autom4te.cache/
|
||||
/autoscan.log
|
||||
/compile
|
||||
/config.guess
|
||||
/config.h.in
|
||||
/config.sub
|
||||
/configure
|
||||
/depcomp
|
||||
/doc/*.info
|
||||
/doc/mdate-sh
|
||||
/doc/stamp-vti
|
||||
/doc/texinfo.tex
|
||||
/doc/version.texi
|
||||
/install-sh
|
||||
/ltmain.sh
|
||||
/m4/libtool.m4
|
||||
/m4/lt*.m4
|
||||
/missing
|
||||
Makefile.in
|
||||
|
||||
# The build tree
|
||||
/build/
|
674
COPYING
Normal file
674
COPYING
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. 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
|
||||
them 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 prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. 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.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey 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;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If 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 convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU 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 that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
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.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
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.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
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
|
||||
state 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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program 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, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU 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. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
204
INSTALL.md
Normal file
204
INSTALL.md
Normal file
|
@ -0,0 +1,204 @@
|
|||
<!--
|
||||
Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
|
||||
Copying and distribution of this file, with or without modification, are
|
||||
permitted in any medium without royalty, provided the copyright notice and
|
||||
this notice are preserved. This file is offered as-is, without any warranty.
|
||||
-->
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
### Operating System
|
||||
|
||||
Currently, BookmarkFS runs on the following operating systems:
|
||||
|
||||
- GNU/Linux
|
||||
- FreeBSD (with caveats)
|
||||
|
||||
See the user manual for comments if you wish to port BookmarkFS to other
|
||||
operating systems.
|
||||
|
||||
### Dependencies
|
||||
|
||||
| Library Name | Minimal Version |
|
||||
| ------------ | --------------- |
|
||||
| libfuse | 3.5 |
|
||||
| libseccomp | 2.5 |
|
||||
| SQLite | 3.35 |
|
||||
| [Jansson] | 2.14 |
|
||||
| [Nettle] | 3.4 |
|
||||
| GNU Readline | 6.0 |
|
||||
| [Tcl] | 8.6 |
|
||||
| [uriparser] | 0.9 |
|
||||
| xxHash | 8.8 |
|
||||
|
||||
Some of them can be optional, depending on which components of BookmarkFS
|
||||
are to be built, and which features are enabled.
|
||||
|
||||
### Build Tools
|
||||
|
||||
- The GNU build system
|
||||
* Autoconf
|
||||
* Automake
|
||||
* Libtool
|
||||
* Autoconf Archive
|
||||
- pkg-config
|
||||
- POSIX-compatible make
|
||||
- C99-capable C compiler (GCC or Clang is recommended)
|
||||
|
||||
Optionally:
|
||||
|
||||
- GNU Texinfo, for building the user manual
|
||||
- DejaGNU, for running tests
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
### Configure Build
|
||||
|
||||
Select a build directory and generate configuration scripts:
|
||||
|
||||
$ mkdir build && cd build
|
||||
$ autoreconf -i ..
|
||||
|
||||
To list all available configuration options, run:
|
||||
|
||||
$ ../configure --help
|
||||
|
||||
BookmarkFS has multiple components. By default, none will be built.
|
||||
Except for the utility library, each component can be enabled independently:
|
||||
|
||||
- The BookmarkFS utility library: `--enable-bookmarkfs-util`
|
||||
* Requires: libseccomp (Linux-only), xxHash
|
||||
* Automatically enabled if required by other components
|
||||
- The `mount.bookmarkfs` program: `--enable-bookmarkfs-mount`
|
||||
* Requires: libfuse, bookmarkfs-util
|
||||
- The `fsck.bookmarkfs` program: `--enable-bookmarkfs-fsck`
|
||||
* Requires: Readline, bookmarkfs-util
|
||||
- The `mkfs.bookmarkfs` program: `--enable-bookmarkfs-mkfs`
|
||||
- The `bookmarkctl` program: `--enable-bookmarkctl`
|
||||
- The Firefox backend: `--enable-backend-firefox`
|
||||
* Requires: SQLite, Nettle, uriparser, bookmarkfs-util
|
||||
- The Chromium backend: `--enable-backend-chromium`
|
||||
* Requires: Jansson, Nettle, bookmarkfs-util
|
||||
- The Tcl-based handler for `fsck.bookmarkfs`: `--enable-fsck-handler-tcl`
|
||||
* Requires: Tcl
|
||||
|
||||
For each of the required third-party libraries, if installed in a
|
||||
custom location, it should be specified with `--with-<foo>=<pkgconfdir>`,
|
||||
where `<foo>` is the library name, and `<pkgconfdir>` is the directory
|
||||
holding its pkg-config file.
|
||||
|
||||
Other options:
|
||||
|
||||
- `--disable-sandbox`
|
||||
* Build the utility library without sandboxing features
|
||||
* No longer requires: libseccomp
|
||||
- `--disable-landlock` (Linux-only)
|
||||
* Disable the [Landlock] feature in the sandbox implementation
|
||||
- `--enable-xxhash-inline`
|
||||
* Use xxHash as a header-only library
|
||||
- `--disable-backend-firefox-write`
|
||||
* Build the Firefox backend without write features
|
||||
* No longer requires: Nettle, uriparser
|
||||
- `--disable-backend-chromium-write`
|
||||
* Build the Chromium backend without write features
|
||||
* No longer requires: Nettle
|
||||
- `--enable-boookmarkfs-debug`
|
||||
* Add more run-time checks and logs for debugging
|
||||
- `--disable-native-watcher`
|
||||
* Build the file watcher without platform-specific API dependency
|
||||
- `--disable-interactive-fsck`
|
||||
* Disable interactive features for `fsck.bookmarkfs`
|
||||
* No longer requires: Readline
|
||||
|
||||
An example configuration:
|
||||
|
||||
$ ../configure --prefix=$HOME/.local \
|
||||
> --enable-bookmarkfs-mount --enable-backend-firefox \
|
||||
> --with-fuse3=$HOME/.local/lib/pkgconfig \
|
||||
> CFLAGS='-O2'
|
||||
|
||||
### Build and Install
|
||||
|
||||
After configuration, build the binaries:
|
||||
|
||||
$ make
|
||||
|
||||
Run tests:
|
||||
|
||||
$ make check
|
||||
|
||||
Install the binaries:
|
||||
|
||||
$ make install-exec
|
||||
|
||||
Install the development headers and the pkg-config file:
|
||||
|
||||
$ make install-data
|
||||
|
||||
Install the user manual:
|
||||
|
||||
$ make install-man install-info
|
||||
|
||||
Uninstall:
|
||||
|
||||
$ make uninstall
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
### Linking to an Existing BookmarkFS Utility Library
|
||||
|
||||
If a BookmarkFS utility library is already installed, it can be
|
||||
specified with `--with-bookmarkfs[=<pkgconfdir>]` during configuration,
|
||||
in the same way like with other dependencies.
|
||||
|
||||
In this case, the utility library will not be built from source,
|
||||
and other components will link to the specified library instead.
|
||||
|
||||
### Targeting 32-bit Platforms
|
||||
|
||||
BookmarkFS requires 64-bit `off_t` and `time_t`, which may not be supported
|
||||
on 32-bit platforms (e.g. i386 FreeBSD does not support 64-bit `time_t`).
|
||||
|
||||
If using Autoconf 2.72 or later, the configuration script automatically
|
||||
performs checks and defines necessary macros, and fails if unsupported.
|
||||
With legacy Autoconf, only `off_t` is checked.
|
||||
|
||||
To manually configure 64-bit `time_t`, add preprocessor flags
|
||||
`-D_TIME_BITS=64` (or something equivalent, depending on the toolchain).
|
||||
If unsupported, `make` will fail.
|
||||
|
||||
### FreeBSD and GNU libiconv
|
||||
|
||||
NOTE: You may skip this section if _not_ building the Chromium backend.
|
||||
|
||||
Before 10.0-RELEASE, the FreeBSD libc did not have a native iconv,
|
||||
and linked to GNU libiconv from the `converters/libiconv` port.
|
||||
Nowadays, that port is still used by many other ports, thus it is likely
|
||||
to be installed on your system, which could be problematic.
|
||||
|
||||
The system `iconv.h` header is installed as `/usr/include/iconv.h`,
|
||||
and the libiconv one `/usr/local/include/iconv.h`.
|
||||
When we `#include <iconv.h>`, the latter may be chosen by the preprocessor
|
||||
instead of the former, where `iconv*()` function calls are redefined to
|
||||
`libiconv*()` ones, which results in "undefined symbols" error
|
||||
since we're not linking to libiconv.
|
||||
|
||||
To workaround this issue, you may add preprocessor flags
|
||||
`-D_LIBICONV_H -include /usr/include/iconv.h` to choose the system iconv,
|
||||
or linker flags `-L/usr/local/lib -liconv` to choose GNU libiconv.
|
||||
|
||||
|
||||
<!-- reflinks -->
|
||||
|
||||
[Jansson]: https://github.com/akheron/jansson
|
||||
[Nettle]: https://www.lysator.liu.se/~nisse/nettle/
|
||||
[Landlock]: https://docs.kernel.org/userspace-api/landlock.html
|
||||
[Tcl]: https://www.tcl-lang.org/
|
||||
[uriparser]: https://github.com/uriparser/uriparser
|
12
Makefile.am
Normal file
12
Makefile.am
Normal file
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty, provided the copyright notice and
|
||||
# this notice are preserved. This file is offered as-is, without any warranty.
|
||||
#
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
SUBDIRS = doc src tests
|
||||
|
||||
pkgconfig_DATA = bookmarkfs.pc
|
39
README.md
Normal file
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!--
|
||||
Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
|
||||
Copying and distribution of this file, with or without modification, are
|
||||
permitted in any medium without royalty, provided the copyright notice and
|
||||
this notice are preserved. This file is offered as-is, without any warranty.
|
||||
-->
|
||||
|
||||
|
||||
About
|
||||
-----
|
||||
|
||||
BookmarkFS is a FUSE-based pseudo-filesystem which provides an interface
|
||||
to the bookmark data of web browsers.
|
||||
|
||||
Currently, the following browsers (and their derivatives) are supported:
|
||||
- Firefox
|
||||
- Chromium
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
See 'INSTALL.md' for instructions on how to build and install BookmarkFS.
|
||||
See the files under 'doc/' for the user manual.
|
||||
|
||||
Project homepage: <https://nongnu.org/bookmarkfs>.
|
||||
|
||||
|
||||
Copying
|
||||
-------
|
||||
|
||||
BookmarkFS is free software, distributed under the terms of the
|
||||
GNU General Public License, either version 3, or any later version of
|
||||
the license. For more information, see the file 'COPYING'.
|
||||
|
||||
The user manual and other supporting files in this codebase
|
||||
are distributed under separeate licenses.
|
||||
Refer to the corresponding license notice for details.
|
22
bookmarkfs.pc.in
Normal file
22
bookmarkfs.pc.in
Normal file
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty, provided the copyright notice and
|
||||
# this notice are preserved. This file is offered as-is, without any warranty.
|
||||
#
|
||||
|
||||
package=@PACKAGE@
|
||||
version=@VERSION@
|
||||
|
||||
prefix=@prefix@
|
||||
exec_prefix=@exec_prefix@
|
||||
includedir=@includedir@
|
||||
libdir=@libdir@
|
||||
|
||||
Name: ${package}
|
||||
Description: BookmarkFS
|
||||
Version: ${version}
|
||||
URL: https://nongnu.org/bookmarkfs
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lbookmarkfs_util
|
245
configure.ac
Normal file
245
configure.ac
Normal file
|
@ -0,0 +1,245 @@
|
|||
dnl
|
||||
dnl Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
dnl
|
||||
dnl Copying and distribution of this file, with or without modification,
|
||||
dnl are permitted in any medium without royalty,
|
||||
dnl provided the copyright notice and this notice are preserved.
|
||||
dnl This file is offered as-is, without any warranty.
|
||||
dnl
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([bookmarkfs], [0.1.0], [bookmarkfs-devel@nongnu.org])
|
||||
AC_CONFIG_SRCDIR([bookmarkfs.pc.in])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AM_INIT_AUTOMAKE([1.14 foreign no-installinfo no-installman])
|
||||
AM_EXTRA_RECURSIVE_TARGETS([install-man])
|
||||
PKG_PROG_PKG_CONFIG([0.27])
|
||||
LT_PREREQ([2.4.3])
|
||||
LT_INIT([disable-static dlopen])
|
||||
|
||||
# -- Checks for operating system --
|
||||
|
||||
AC_CANONICAL_HOST
|
||||
AS_CASE(["${host_os}"], [linux*], [
|
||||
AS_VAR_SET([host_os_is_linux], [yes])
|
||||
], [freebsd*], [
|
||||
AS_VAR_SET([host_os_is_freebsd], [yes])
|
||||
], [
|
||||
AC_MSG_WARN(m4_normalize([
|
||||
Unsupported platform "${host_os}".
|
||||
]))
|
||||
])
|
||||
|
||||
# -- Checks for programs --
|
||||
|
||||
AC_PROG_CC
|
||||
AC_PROG_CPP
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_MAKE_SET
|
||||
|
||||
# -- Checks for platform-specific features --
|
||||
|
||||
AC_MSG_CHECKING([if null pointers have an all-zero representation])
|
||||
AC_RUN_IFELSE([
|
||||
AC_LANG_PROGRAM([[#include <string.h>]],
|
||||
[[void *ptr = NULL; char buf[sizeof(ptr)] = { 0 };
|
||||
if (0 != memcmp(buf, &ptr, sizeof(ptr))) return 1;]])
|
||||
], [
|
||||
AC_MSG_RESULT([yes])
|
||||
], [
|
||||
AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR(m4_normalize([
|
||||
Null pointers do not have an all-zero representation on this platform.
|
||||
]))
|
||||
])
|
||||
|
||||
# -- Checks for features --
|
||||
|
||||
BOOKMARKFS_FEAT([bookmarkfs-util], [no], [the Bookmarkfs utility library])
|
||||
|
||||
BOOKMARKFS_FEAT([bookmarkctl], [no], [the bookmarkctl program])
|
||||
|
||||
BOOKMARKFS_FEAT([bookmarkfs-fsck], [no], [the fsck.bookmarkfs program], [
|
||||
AS_VAR_SET([enable_bookmarkfs_util], [yes])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([bookmarkfs-mkfs], [no], [the mkfs.bookmarkfs program])
|
||||
|
||||
BOOKMARKFS_FEAT([bookmarkfs-mount], [no], [the mount.bookmarkfs program], [
|
||||
AS_VAR_SET([enable_bookmarkfs_util], [yes])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([sandbox], [yes], [sandboxing], [
|
||||
AC_DEFINE([BOOKMARKFS_SANDBOX], [1],
|
||||
[Define to 1 if sandboxing is enabled.])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([landlock], [yes], [Linux Landlock features for sandboxing], [
|
||||
AC_DEFINE([BOOKMARKFS_SANDBOX_LANDLOCK], [1],
|
||||
[Define to 1 if Linux Landlock is enabled.])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([xxhash-inline], [no],
|
||||
[using xxhash as a header-only library],
|
||||
[
|
||||
AC_DEFINE([BOOKMARKFS_XXHASH_INLINE], [1],
|
||||
[Define to 1 if using xxhash as a header-only library.])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([bookmarkfs-debug], [no],
|
||||
[debugging features for BookmarkFS],
|
||||
[
|
||||
AC_DEFINE([BOOKMARKFS_DEBUG], [1],
|
||||
[Define to 1 if BookmarkFS debugging is enabled.])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([backend-firefox], [no], [Firefox backend], [
|
||||
AS_VAR_SET([enable_bookmarkfs_util], [yes])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([backend-firefox-write], [yes],
|
||||
[write support for the Firefox backend],
|
||||
[
|
||||
AC_DEFINE([BOOKMARKFS_BACKEND_FIREFOX_WRITE], [1],
|
||||
[Define to 1 if the Firefox backend supports writing.])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([backend-chromium], [no], [Chromium backend], [
|
||||
AS_VAR_SET([enable_bookmarkfs_util], [yes])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([backend-chromium-write], [yes],
|
||||
[write support for the Chromium backend],
|
||||
[
|
||||
AC_DEFINE([BOOKMARKFS_BACKEND_CHROMIUM_WRITE], [1],
|
||||
[Define to 1 if the Chromium backend supports writing.])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([native-watcher], [yes], [platform-specific file watcher], [
|
||||
AC_DEFINE([BOOKMARKFS_NATIVE_WATCHER], [1],
|
||||
[Define to 1 if platform-specific file watcher is enabled.])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([interactive-fsck], [yes],
|
||||
[interactive features for fsck.bookmarkfs],
|
||||
[
|
||||
AS_VAR_IF([enable_bookmarkfs_fsck], [no], [
|
||||
AS_VAR_SET([enable_interactive_fsck], [no])
|
||||
], [
|
||||
AC_DEFINE([BOOKMARKFS_INTERACTIVE_FSCK], [1],
|
||||
[Define to 1 if interactive fsck features are enabled.])
|
||||
])
|
||||
])
|
||||
|
||||
BOOKMARKFS_FEAT([fsck-handler-tcl], [no], [Tcl-based fsck handler])
|
||||
|
||||
# -- Checks for libraries --
|
||||
|
||||
AX_PTHREAD
|
||||
|
||||
BOOKMARKFS_DEP([bookmarkfs], [>= 0.1], [The BookmarkFS utility library], [
|
||||
AS_VAR_SET([enable_bookmarkfs_util], [no])
|
||||
])
|
||||
|
||||
BOOKMARKFS_DEP([fuse3], [>= 3.5], [fuse3 library], , [bookmarkfs-mount])
|
||||
|
||||
AS_VAR_IF([host_os_is_linux], [yes], [
|
||||
BOOKMARKFS_DEP([libseccomp], [>= 2.5], [libseccomp library], , [sandbox])
|
||||
])
|
||||
|
||||
BOOKMARKFS_DEP([sqlite3], [>= 3.35], [SQLite3 library], , [backend-firefox])
|
||||
|
||||
BOOKMARKFS_DEP([jansson], [>= 2.14], [Jansson library], , [backend-chromium])
|
||||
|
||||
BOOKMARKFS_DEP([nettle], [>= 3.4], [Nettle library], ,
|
||||
[backend-firefox-write], [backend-chromium-write])
|
||||
|
||||
BOOKMARKFS_DEP([readline], [>= 6.0], [GNU Readline library], ,
|
||||
[interactive-fsck])
|
||||
|
||||
BOOKMARKFS_DEP([tcl], [>= 8.6], [Tcl library], , [fsck-handler-tcl])
|
||||
|
||||
BOOKMARKFS_DEP([liburiparser], [>= 0.9], [uriparser library], ,
|
||||
[backend-firefox-write])
|
||||
|
||||
BOOKMARKFS_DEP([libxxhash], [>= 0.8], [xxHash library], [
|
||||
AS_VAR_IF([enable_xxhash_inline], [yes], [
|
||||
AS_VAR_SET([LIBXXHASH_LIBS])
|
||||
])
|
||||
], [bookmarkfs-util])
|
||||
|
||||
BOOKMARKFS_AMCOND([bookmarkfs-util], [bookmarkctl], [bookmarkfs-fsck],
|
||||
[bookmarkfs-mkfs], [bookmarkfs-mount], [sandbox], [backend-firefox],
|
||||
[backend-firefox-write], [backend-chromium], [backend-chromium-write],
|
||||
[interactive-fsck], [fsck-handler-tcl])
|
||||
|
||||
# -- Checks for compiler builtins and attributes --
|
||||
|
||||
AX_GCC_BUILTIN([__builtin_ctz])
|
||||
AX_GCC_BUILTIN([__builtin_ctzl])
|
||||
AX_GCC_BUILTIN([__builtin_expect])
|
||||
AX_GCC_BUILTIN([__builtin_unreachable])
|
||||
AX_GCC_FUNC_ATTRIBUTE([cold])
|
||||
AX_GCC_FUNC_ATTRIBUTE([destructor])
|
||||
AX_GCC_FUNC_ATTRIBUTE([malloc])
|
||||
AX_GCC_FUNC_ATTRIBUTE([noreturn])
|
||||
AX_GCC_FUNC_ATTRIBUTE([returns_nonnull])
|
||||
AX_GCC_FUNC_ATTRIBUTE([visibility])
|
||||
AX_GCC_VAR_ATTRIBUTE([unused])
|
||||
|
||||
AM_CONDITIONAL([HIDE_MODULE_SYMBOLS],
|
||||
[test x${ax_cv_have_func_attribute_visibility} != xno])
|
||||
|
||||
# -- Checks for macros and typedefs --
|
||||
|
||||
AC_MSG_CHECKING([for __FILE_NAME__])
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_LANG_SOURCE([[char const *name = __FILE_NAME__;]])
|
||||
], [
|
||||
AC_MSG_RESULT([yes])
|
||||
], [
|
||||
AC_MSG_RESULT([no])
|
||||
AX_CHECK_COMPILE_FLAG([-fmacro-prefix-map==], [
|
||||
AS_VAR_SET([macro_prefix_map], [yes])
|
||||
], [
|
||||
AC_MSG_WARN(m4_normalize([
|
||||
The -fmacro-prefix-map CPPFLAGS is not supported.
|
||||
]))
|
||||
])
|
||||
])
|
||||
AM_CONDITIONAL([MACRO_PREFIX_MAP], [test x${macro_prefix_map} != xno])
|
||||
|
||||
AC_SYS_LARGEFILE
|
||||
AS_VAR_IF([ac_have_largefile], [no], [
|
||||
AC_MSG_ERROR(m4_normalize([
|
||||
64-bit off_t (required by libfuse) is unsupported on this platform.
|
||||
]))
|
||||
])
|
||||
|
||||
m4_version_prereq([2.72], [
|
||||
AC_SYS_YEAR2038
|
||||
AS_VAR_IF([ac_have_year2038], [no], [
|
||||
AC_MSG_ERROR(m4_normalize([
|
||||
64-bit time_t (required by some backends) is unsupported on
|
||||
this platform.
|
||||
]))
|
||||
])
|
||||
], [
|
||||
dnl fallback to compile-time check...
|
||||
AX_COMPILE_CHECK_SIZEOF([time_t])
|
||||
])
|
||||
|
||||
AX_COMPILE_CHECK_SIZEOF([uintptr_t], [#include <stdint.h>])
|
||||
|
||||
# -- Output --
|
||||
|
||||
PKG_INSTALLDIR()
|
||||
AC_CONFIG_FILES([
|
||||
Makefile
|
||||
doc/Makefile
|
||||
src/Makefile
|
||||
tests/Makefile
|
||||
bookmarkfs.pc
|
||||
])
|
||||
AC_OUTPUT
|
28
doc/Makefile.am
Normal file
28
doc/Makefile.am
Normal file
|
@ -0,0 +1,28 @@
|
|||
#
|
||||
# Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty, provided the copyright notice and
|
||||
# this notice are preserved. This file is offered as-is, without any warranty.
|
||||
#
|
||||
|
||||
info_TEXINFOS = bookmarkfs.texi
|
||||
dist_man1_MANS =
|
||||
|
||||
bookmarkfs_TEXINFOS = fdl.texi
|
||||
|
||||
if BOOKMARKCTL
|
||||
dist_man1_MANS += bookmarkctl.1
|
||||
endif # BOOKMARKCTL
|
||||
|
||||
if BOOKMARKFS_FSCK
|
||||
dist_man1_MANS += fsck.bookmarkfs.1
|
||||
endif # BOOKMARKFS_FSCK
|
||||
|
||||
if BOOKMARKFS_MKFS
|
||||
dist_man1_MANS += mkfs.bookmarkfs.1
|
||||
endif # BOOKMARKFS_MKFS
|
||||
|
||||
if BOOKMARKFS_MOUNT
|
||||
dist_man1_MANS += mount.bookmarkfs.1
|
||||
endif # BOOKMARKFS_MOUNT
|
123
doc/bookmarkctl.1
Normal file
123
doc/bookmarkctl.1
Normal file
|
@ -0,0 +1,123 @@
|
|||
.TH BOOKMARKCTL 1 "Dec 01, 2024" 0.1.0 "BookmarkFS User Manual"
|
||||
.
|
||||
.SH NAME
|
||||
bookmarkctl - manage a mounted BookmarkFS filesystem
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
.B bookmarkctl
|
||||
.I subcmd
|
||||
.RI [ args ]
|
||||
.PP
|
||||
.B "bookmarkctl permd"
|
||||
.I pathname
|
||||
.I op
|
||||
.I name1
|
||||
.I name2
|
||||
.PP
|
||||
.B "bookmarkctl fsck"
|
||||
.I pathname
|
||||
.I op
|
||||
.PP
|
||||
.B "bookmarkctl help"
|
||||
.PP
|
||||
.B "bookmarkctl version"
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B bookmarkctl
|
||||
program is a command-line wrapper for various I/O controls of a BookmarkFS
|
||||
filesystem.
|
||||
.SS Permute directory entries
|
||||
The
|
||||
.B permd
|
||||
sub-command re-arranges the order of the directory entries returned from
|
||||
.BR readdir (3)
|
||||
calls.
|
||||
.PP
|
||||
The
|
||||
.I pathname
|
||||
argument is the path to the directory.
|
||||
The
|
||||
.I name1
|
||||
and
|
||||
.I name2
|
||||
arguments are filenames under that directory.
|
||||
.PP
|
||||
The
|
||||
.I op
|
||||
argument is the operation to perform on the directory:
|
||||
.TP
|
||||
.B swap
|
||||
Exchange the positions of the directory entries represented by
|
||||
.I name1
|
||||
and
|
||||
.IR name2 .
|
||||
.TP
|
||||
.BR move\-before | move\-after
|
||||
Move the directory entry represented by
|
||||
.I name1
|
||||
to the position just before/after the one represented by
|
||||
.IR name2 .
|
||||
.SS Filesystem check
|
||||
The
|
||||
.B fsck
|
||||
sub-command checks for errors within a BookmarkFS filesystem.
|
||||
.PP
|
||||
The
|
||||
.I pathname
|
||||
argument is the path to the directory to perform checks on.
|
||||
.PP
|
||||
The
|
||||
.I op
|
||||
argument is the operation to perform on the directory:
|
||||
.TP
|
||||
.B list
|
||||
Display a list of errors found under the given directory.
|
||||
Will not recurse into subdirectories.
|
||||
.PP
|
||||
For the full fsck functionalities, use
|
||||
.BR fsck.bookmarkfs (1).
|
||||
.
|
||||
.SH EXIT STATUS
|
||||
.TP
|
||||
0
|
||||
The operation completed successfully.
|
||||
.TP
|
||||
1
|
||||
An error occurred.
|
||||
.
|
||||
.SH NOTES
|
||||
.SS Availability on FreeBSD
|
||||
By the time this manual is written, this program is not available on
|
||||
stable releases of FreeBSD, since the latter does not yet support
|
||||
.BR ioctl (2)
|
||||
in its
|
||||
.BR fusefs (5)
|
||||
implementation.
|
||||
.
|
||||
.SH SEE ALSO
|
||||
.BR fsck.bookmarkfs (1),
|
||||
.BR mount.bookmarkfs (1),
|
||||
.BR ioctl (2),
|
||||
.BR readdir (3),
|
||||
.BR fusefs (5)
|
||||
.PP
|
||||
The full BookmarkFS User Manual is maintained as a Texinfo document.
|
||||
To read it locally, run:
|
||||
.PP
|
||||
.in +4n
|
||||
.EX
|
||||
.B info bookmarkfs
|
||||
.EE
|
||||
.in
|
||||
.
|
||||
.SH COPYRIGHT
|
||||
Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
.PP
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||
.PP
|
||||
You should have received a copy of the license along with this document.
|
||||
If not, see <https://www.gnu.org/licenses/fdl-1.3.html>.
|
309
doc/bookmarkfs.texi
Normal file
309
doc/bookmarkfs.texi
Normal file
|
@ -0,0 +1,309 @@
|
|||
\input texinfo @c -*-texinfo-*-
|
||||
|
||||
@setfilename bookmarkfs.info
|
||||
@include version.texi
|
||||
@settitle BookmarkFS User Manual
|
||||
|
||||
@macro manpage {name, section, url}
|
||||
@uref{\url\,, @code{\name\(\section\)}}
|
||||
@end macro
|
||||
|
||||
@macro linuxmanpage {name, section}
|
||||
@manpage{\name\, \section\,
|
||||
https://man7.org/linux/man-pages/man\section\/\name\.\section\.html}
|
||||
@end macro
|
||||
|
||||
@macro freebsdmanpage {name, section}
|
||||
@manpage{\name\, \section\,
|
||||
https://man.freebsd.org/cgi/man.cgi?\name\(\section\)}
|
||||
@end macro
|
||||
|
||||
@macro posixfuncmanpage {name}
|
||||
@manpage{\name\, 3p,
|
||||
https://pubs.opengroup.org/onlinepubs/9799919799/functions/\name\.html}
|
||||
@end macro
|
||||
|
||||
@tex
|
||||
\global\def\linkcolor{0 0 1}
|
||||
\global\def\urlcolor{0 0 1}
|
||||
\global\urefurlonlylinktrue
|
||||
@end tex
|
||||
|
||||
@copying
|
||||
This manual is for BookmarkFS, version @value{VERSION}.
|
||||
|
||||
@quotation
|
||||
Copyright @copyright{} 2024 CismonX <admin@@cismon.net>
|
||||
|
||||
Permission is granted to copy, distribute and/or modify this document under
|
||||
the terms of the GNU Free Documentation License, Version 1.3 or any later
|
||||
version published by the Free Software Foundation; with no Invariant Sections,
|
||||
no Front-Cover Texts, and no Back-Cover Texts. A copy of the license
|
||||
is included in the section entitled ``GNU Free Documentation License''.
|
||||
@end quotation
|
||||
@end copying
|
||||
|
||||
@titlepage
|
||||
@title BookmarkFS
|
||||
@subtitle version @value{VERSION}
|
||||
@author CismonX
|
||||
|
||||
@page
|
||||
@vskip 0pt plus 1filll
|
||||
@insertcopying
|
||||
|
||||
@end titlepage
|
||||
|
||||
|
||||
@summarycontents
|
||||
@contents
|
||||
|
||||
@node Top
|
||||
@top BookmarkFS User Manual
|
||||
|
||||
@insertcopying
|
||||
|
||||
|
||||
@node Overview
|
||||
@chapter Overview
|
||||
|
||||
BookmarkFS is a FUSE-based pseudo-filesystem which provides an interface to
|
||||
the bookmark data of web browsers.
|
||||
|
||||
Currently, the following browsers (and their derivatives) are supported:
|
||||
|
||||
@itemize @bullet{}
|
||||
@item Firefox
|
||||
@item Chromium
|
||||
@end itemize
|
||||
|
||||
BookmarkFS is free software, distributed under the terms of the GNU General
|
||||
Public License, either version 3, or any later version of the license.
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
BookmarkFS. If not, see @uref{https://www.gnu.org/licenses/}.
|
||||
|
||||
|
||||
@node Porting
|
||||
@section Porting BookmarkFS
|
||||
|
||||
Currently, BookmarkFS only runs on GNU/Linux and FreeBSD.
|
||||
|
||||
Although BookmarkFS sticks hard to POSIX and avoids using platform-specific
|
||||
features, porting it to other operating systems is not trivial.
|
||||
|
||||
The major pitfall is the
|
||||
@uref{https://docs.kernel.org/filesystems/fuse.html, FUSE} dependency.
|
||||
Generally speaking, FUSE is Linux-only. FreeBSD partially implements the
|
||||
FUSE protocol in its kernel, to the extent that BookmarkFS is mostly usable.
|
||||
However, that's not the case for other operating systems.
|
||||
|
||||
For example, OpenBSD implements its own FUSE protocol, which is incompatible
|
||||
with the Linux one. While OpenBSD does provide a libfuse-compatible library,
|
||||
however, it only covers the high-level API, and BookmarkFS uses the
|
||||
@uref{https://libfuse.github.io/doxygen/fuse__lowlevel_8h.html, low-level API}.
|
||||
For a similar reason, @uref{https://github.com/winfsp/winfsp, WinFsp}
|
||||
won't work if you're trying to port BookmarkFS to Microsoft Windows.
|
||||
|
||||
|
||||
@node Sandboxing
|
||||
@section Sandboxing
|
||||
|
||||
A BookmarkFS backend can be instructed to enter a sandboxed state, where it
|
||||
irrevocably relinquishes most access to the system resources that it's not
|
||||
supposed to touch. For example, it can only access the directory that contains
|
||||
the bookmark file; it cannot establish socket connections; it cannot execute
|
||||
other files; ...
|
||||
|
||||
This mechanism reduces the attack surface for exploit, if a vulnerability
|
||||
is discovered in BookmarkFS and/or its dependencies.
|
||||
However, it only deals with untrusted input, and cannot help if the
|
||||
operating system has already been compromised.
|
||||
|
||||
Example of what ``untrusted input'' may include:
|
||||
|
||||
@itemize @bullet{}
|
||||
@item Bookmark files that are @emph{not} created by the user using a
|
||||
trusted program (e.g. a file obtained from some random person on the internet).
|
||||
@item Filesystem calls from untrusted programs. The program itself may be
|
||||
isolated, but it has a chance to escape the isolated environment
|
||||
if it can exploit BookmarkFS.
|
||||
@end itemize
|
||||
|
||||
On Linux, sandboxing is achieved using @linuxmanpage{seccomp, 2} and
|
||||
@linuxmanpage{landlock, 7}.
|
||||
On FreeBSD, @freebsdmanpage{capsicum, 4} is used.
|
||||
|
||||
|
||||
@node Contributing
|
||||
@section Contributing to BookmarkFS
|
||||
|
||||
BookmarkFS is hosted on Savannah. Write to the
|
||||
@uref{https://savannah.nongnu.org/mail/?group=bookmarkfs, mailing lists}
|
||||
for bug reports, feature requests, and other discussions.
|
||||
|
||||
BookmarkFS is a personal hobby project, and is currently in
|
||||
experimental stage. Thus, it it not yet ready for open collaboration,
|
||||
which means patches are generally rejected unless trivial (e.g. typo fix).
|
||||
|
||||
|
||||
@node Utilities
|
||||
@chapter Utility Programs
|
||||
|
||||
|
||||
@node mount.bookmarkfs
|
||||
@section @code{mount.bookmarkfs}
|
||||
|
||||
|
||||
@node fsck.bookmarkfs
|
||||
@section @code{fsck.bookmarkfs}
|
||||
|
||||
|
||||
@node mkfs.bookmarkfs
|
||||
@section @code{mkfs.bookmarkfs}
|
||||
|
||||
|
||||
@node bookmarkctl
|
||||
@section @code{bookmarkctl}
|
||||
|
||||
|
||||
@node Filesystem
|
||||
@chapter The Filesystem
|
||||
|
||||
|
||||
@node Hierarchy
|
||||
@section Filesystem Hierarchy
|
||||
|
||||
|
||||
@node Extended Attributes
|
||||
@section Extended Attributes
|
||||
|
||||
BookmarkFS uses extended attributes to manage additional information associated
|
||||
with a bookmark.
|
||||
|
||||
Extended attributes is a platform-specific feature. On Linux, see
|
||||
@linuxmanpage{xattr, 7}. On FreeBSD, see @freebsdmanpage{extattr, 2}.
|
||||
|
||||
All BookmarkFS extended attributes fall under the ``user'' namespace,
|
||||
which means they have a @code{user.} name prefix on Linux, and should be
|
||||
accessed with @code{EXTATTR_NAMESPACE_USER} on FreeBSD.
|
||||
|
||||
BookmarkFS does not define any common attributes, neither can users create
|
||||
arbitrary ones. The backend decides which attributes are available during
|
||||
initialization, and all bookmark files share the same set of attributes.
|
||||
All attributes have a @code{bookmarkfs.} name prefix.
|
||||
|
||||
For example, to get the GUID of a bookmark file (Firefox backend) on GNU/Linux:
|
||||
|
||||
@example
|
||||
$ getfattr --absolute-names -n user.bookmarkfs.guid \
|
||||
> /mnt/firefox/bookmarks/toolbar/Example
|
||||
@end example
|
||||
|
||||
The FreeBSD equivalent:
|
||||
|
||||
@example
|
||||
$ getextattr user bookmarkfs.guid \
|
||||
> /mnt/firefox/bookmarks/toolbar/Example
|
||||
@end example
|
||||
|
||||
|
||||
@node Permute Directory Entries
|
||||
@section Permute Directory Entries
|
||||
|
||||
POSIX does not specify the ordering of the directory entries retrieved from
|
||||
the directory stream using @posixfuncmanpage{readdir}. It only guarantees
|
||||
that if an entry is not added or removed from the directory after the most
|
||||
recent call to @posixfuncmanpage{opendir} or @posixfuncmanpage{rewinddir},
|
||||
that entry is returned once and only once.
|
||||
|
||||
This allows filesystem implementations to organize directory entries in a more
|
||||
relaxed manner. There could be extra overhead to maintain a predictable
|
||||
ordering of directory entries, since they may not have a linear structure on
|
||||
modern on-disk filesystems (e.g. ext4 uses ``HTree'' for large directories).
|
||||
|
||||
As for users of a filesystem, the order of directory entries generally
|
||||
does not matter. If they care, they can add a prefix to the filename,
|
||||
and let the application do the sorting.
|
||||
|
||||
However, the order of which a bookmark entry appears in the web browser
|
||||
sometimes does matter. Thus, BookmarkFS guarantees it to be equivalent to the
|
||||
directory traversal order. New entries are appended to the end; removed
|
||||
entries do not affect the order of other entries.
|
||||
|
||||
|
||||
@node Check for Errors
|
||||
@section Check for Errors
|
||||
|
||||
|
||||
@node Backends
|
||||
@chapter Backends
|
||||
|
||||
In BookmarkFS, each backend provides a way to manipulate a certain kind of
|
||||
application bookmarks.
|
||||
|
||||
BookmarkFS ships with two backends. One for Firefox, the other for Chromium.
|
||||
If you which to add support for more backends, you may submit a feature request
|
||||
or implement one using the Backend API.
|
||||
|
||||
Typically, backends are built into shared libraries, and are installed as:
|
||||
|
||||
@example
|
||||
$@{pkglibdir@}/backend-$@{short_name@}$@{shlib_suffix@}
|
||||
@end example
|
||||
|
||||
Where @code{$@{short_name@}} is the name passed to the frontend program, and
|
||||
@code{$@{shlib_suffix@}} is the common filename extension for shared library
|
||||
files on the current platform (e.g. @code{.so} on GNU/Linux and FreeBSD).
|
||||
|
||||
For example, when mounting a BookmarkFS filesystem with:
|
||||
|
||||
@example
|
||||
$ /usr/local/bin/mount.bookmarkfs -o backend=firefox \
|
||||
> ~/.mozilla/firefox/my-profile/places.sqlite /mnt/firefox
|
||||
@end example
|
||||
|
||||
The @code{mount.bookmarkfs} program loads the bookmark file using the
|
||||
backend module @code{/usr/local/lib/bookmarkfs/backend-firefox.so}.
|
||||
|
||||
|
||||
@node Firefox
|
||||
@section Firefox Backend
|
||||
|
||||
|
||||
@node Chromium
|
||||
@section Chromium Backend
|
||||
|
||||
|
||||
@node Backend API
|
||||
@section Backend API
|
||||
|
||||
|
||||
@node Error Handlers
|
||||
@chapter Error Handlers
|
||||
|
||||
|
||||
@node Built-in Error Handler
|
||||
@section Built-in Error Handler
|
||||
|
||||
|
||||
@node Tcl-Based Error Handler
|
||||
@section Tcl-Based Error Handler
|
||||
|
||||
|
||||
@node Error Handler API
|
||||
@section Error Handler API
|
||||
|
||||
|
||||
@node General Index
|
||||
@appendix General Index
|
||||
|
||||
@printindex cp
|
||||
|
||||
|
||||
@node GNU Free Documentation License
|
||||
@appendix GNU Free Documentation License
|
||||
|
||||
@include fdl.texi
|
||||
|
||||
|
||||
@bye
|
505
doc/fdl.texi
Normal file
505
doc/fdl.texi
Normal file
|
@ -0,0 +1,505 @@
|
|||
@c The GNU Free Documentation License.
|
||||
@center Version 1.3, 3 November 2008
|
||||
|
||||
@c This file is intended to be included within another document,
|
||||
@c hence no sectioning command or @node.
|
||||
|
||||
@display
|
||||
Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
|
||||
@uref{https://fsf.org/}
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
@end display
|
||||
|
||||
@enumerate 0
|
||||
@item
|
||||
PREAMBLE
|
||||
|
||||
The purpose of this License is to make a manual, textbook, or other
|
||||
functional and useful document @dfn{free} in the sense of freedom: to
|
||||
assure everyone the effective freedom to copy and redistribute it,
|
||||
with or without modifying it, either commercially or noncommercially.
|
||||
Secondarily, this License preserves for the author and publisher a way
|
||||
to get credit for their work, while not being considered responsible
|
||||
for modifications made by others.
|
||||
|
||||
This License is a kind of ``copyleft'', which means that derivative
|
||||
works of the document must themselves be free in the same sense. It
|
||||
complements the GNU General Public License, which is a copyleft
|
||||
license designed for free software.
|
||||
|
||||
We have designed this License in order to use it for manuals for free
|
||||
software, because free software needs free documentation: a free
|
||||
program should come with manuals providing the same freedoms that the
|
||||
software does. But this License is not limited to software manuals;
|
||||
it can be used for any textual work, regardless of subject matter or
|
||||
whether it is published as a printed book. We recommend this License
|
||||
principally for works whose purpose is instruction or reference.
|
||||
|
||||
@item
|
||||
APPLICABILITY AND DEFINITIONS
|
||||
|
||||
This License applies to any manual or other work, in any medium, that
|
||||
contains a notice placed by the copyright holder saying it can be
|
||||
distributed under the terms of this License. Such a notice grants a
|
||||
world-wide, royalty-free license, unlimited in duration, to use that
|
||||
work under the conditions stated herein. The ``Document'', below,
|
||||
refers to any such manual or work. Any member of the public is a
|
||||
licensee, and is addressed as ``you''. You accept the license if you
|
||||
copy, modify or distribute the work in a way requiring permission
|
||||
under copyright law.
|
||||
|
||||
A ``Modified Version'' of the Document means any work containing the
|
||||
Document or a portion of it, either copied verbatim, or with
|
||||
modifications and/or translated into another language.
|
||||
|
||||
A ``Secondary Section'' is a named appendix or a front-matter section
|
||||
of the Document that deals exclusively with the relationship of the
|
||||
publishers or authors of the Document to the Document's overall
|
||||
subject (or to related matters) and contains nothing that could fall
|
||||
directly within that overall subject. (Thus, if the Document is in
|
||||
part a textbook of mathematics, a Secondary Section may not explain
|
||||
any mathematics.) The relationship could be a matter of historical
|
||||
connection with the subject or with related matters, or of legal,
|
||||
commercial, philosophical, ethical or political position regarding
|
||||
them.
|
||||
|
||||
The ``Invariant Sections'' are certain Secondary Sections whose titles
|
||||
are designated, as being those of Invariant Sections, in the notice
|
||||
that says that the Document is released under this License. If a
|
||||
section does not fit the above definition of Secondary then it is not
|
||||
allowed to be designated as Invariant. The Document may contain zero
|
||||
Invariant Sections. If the Document does not identify any Invariant
|
||||
Sections then there are none.
|
||||
|
||||
The ``Cover Texts'' are certain short passages of text that are listed,
|
||||
as Front-Cover Texts or Back-Cover Texts, in the notice that says that
|
||||
the Document is released under this License. A Front-Cover Text may
|
||||
be at most 5 words, and a Back-Cover Text may be at most 25 words.
|
||||
|
||||
A ``Transparent'' copy of the Document means a machine-readable copy,
|
||||
represented in a format whose specification is available to the
|
||||
general public, that is suitable for revising the document
|
||||
straightforwardly with generic text editors or (for images composed of
|
||||
pixels) generic paint programs or (for drawings) some widely available
|
||||
drawing editor, and that is suitable for input to text formatters or
|
||||
for automatic translation to a variety of formats suitable for input
|
||||
to text formatters. A copy made in an otherwise Transparent file
|
||||
format whose markup, or absence of markup, has been arranged to thwart
|
||||
or discourage subsequent modification by readers is not Transparent.
|
||||
An image format is not Transparent if used for any substantial amount
|
||||
of text. A copy that is not ``Transparent'' is called ``Opaque''.
|
||||
|
||||
Examples of suitable formats for Transparent copies include plain
|
||||
ASCII without markup, Texinfo input format, La@TeX{} input
|
||||
format, SGML or XML using a publicly available
|
||||
DTD, and standard-conforming simple HTML,
|
||||
PostScript or PDF designed for human modification. Examples
|
||||
of transparent image formats include PNG, XCF and
|
||||
JPG@. Opaque formats include proprietary formats that can be
|
||||
read and edited only by proprietary word processors, SGML or
|
||||
XML for which the DTD and/or processing tools are
|
||||
not generally available, and the machine-generated HTML,
|
||||
PostScript or PDF produced by some word processors for
|
||||
output purposes only.
|
||||
|
||||
The ``Title Page'' means, for a printed book, the title page itself,
|
||||
plus such following pages as are needed to hold, legibly, the material
|
||||
this License requires to appear in the title page. For works in
|
||||
formats which do not have any title page as such, ``Title Page'' means
|
||||
the text near the most prominent appearance of the work's title,
|
||||
preceding the beginning of the body of the text.
|
||||
|
||||
The ``publisher'' means any person or entity that distributes copies
|
||||
of the Document to the public.
|
||||
|
||||
A section ``Entitled XYZ'' means a named subunit of the Document whose
|
||||
title either is precisely XYZ or contains XYZ in parentheses following
|
||||
text that translates XYZ in another language. (Here XYZ stands for a
|
||||
specific section name mentioned below, such as ``Acknowledgements'',
|
||||
``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title''
|
||||
of such a section when you modify the Document means that it remains a
|
||||
section ``Entitled XYZ'' according to this definition.
|
||||
|
||||
The Document may include Warranty Disclaimers next to the notice which
|
||||
states that this License applies to the Document. These Warranty
|
||||
Disclaimers are considered to be included by reference in this
|
||||
License, but only as regards disclaiming warranties: any other
|
||||
implication that these Warranty Disclaimers may have is void and has
|
||||
no effect on the meaning of this License.
|
||||
|
||||
@item
|
||||
VERBATIM COPYING
|
||||
|
||||
You may copy and distribute the Document in any medium, either
|
||||
commercially or noncommercially, provided that this License, the
|
||||
copyright notices, and the license notice saying this License applies
|
||||
to the Document are reproduced in all copies, and that you add no other
|
||||
conditions whatsoever to those of this License. You may not use
|
||||
technical measures to obstruct or control the reading or further
|
||||
copying of the copies you make or distribute. However, you may accept
|
||||
compensation in exchange for copies. If you distribute a large enough
|
||||
number of copies you must also follow the conditions in section 3.
|
||||
|
||||
You may also lend copies, under the same conditions stated above, and
|
||||
you may publicly display copies.
|
||||
|
||||
@item
|
||||
COPYING IN QUANTITY
|
||||
|
||||
If you publish printed copies (or copies in media that commonly have
|
||||
printed covers) of the Document, numbering more than 100, and the
|
||||
Document's license notice requires Cover Texts, you must enclose the
|
||||
copies in covers that carry, clearly and legibly, all these Cover
|
||||
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
|
||||
the back cover. Both covers must also clearly and legibly identify
|
||||
you as the publisher of these copies. The front cover must present
|
||||
the full title with all words of the title equally prominent and
|
||||
visible. You may add other material on the covers in addition.
|
||||
Copying with changes limited to the covers, as long as they preserve
|
||||
the title of the Document and satisfy these conditions, can be treated
|
||||
as verbatim copying in other respects.
|
||||
|
||||
If the required texts for either cover are too voluminous to fit
|
||||
legibly, you should put the first ones listed (as many as fit
|
||||
reasonably) on the actual cover, and continue the rest onto adjacent
|
||||
pages.
|
||||
|
||||
If you publish or distribute Opaque copies of the Document numbering
|
||||
more than 100, you must either include a machine-readable Transparent
|
||||
copy along with each Opaque copy, or state in or with each Opaque copy
|
||||
a computer-network location from which the general network-using
|
||||
public has access to download using public-standard network protocols
|
||||
a complete Transparent copy of the Document, free of added material.
|
||||
If you use the latter option, you must take reasonably prudent steps,
|
||||
when you begin distribution of Opaque copies in quantity, to ensure
|
||||
that this Transparent copy will remain thus accessible at the stated
|
||||
location until at least one year after the last time you distribute an
|
||||
Opaque copy (directly or through your agents or retailers) of that
|
||||
edition to the public.
|
||||
|
||||
It is requested, but not required, that you contact the authors of the
|
||||
Document well before redistributing any large number of copies, to give
|
||||
them a chance to provide you with an updated version of the Document.
|
||||
|
||||
@item
|
||||
MODIFICATIONS
|
||||
|
||||
You may copy and distribute a Modified Version of the Document under
|
||||
the conditions of sections 2 and 3 above, provided that you release
|
||||
the Modified Version under precisely this License, with the Modified
|
||||
Version filling the role of the Document, thus licensing distribution
|
||||
and modification of the Modified Version to whoever possesses a copy
|
||||
of it. In addition, you must do these things in the Modified Version:
|
||||
|
||||
@enumerate A
|
||||
@item
|
||||
Use in the Title Page (and on the covers, if any) a title distinct
|
||||
from that of the Document, and from those of previous versions
|
||||
(which should, if there were any, be listed in the History section
|
||||
of the Document). You may use the same title as a previous version
|
||||
if the original publisher of that version gives permission.
|
||||
|
||||
@item
|
||||
List on the Title Page, as authors, one or more persons or entities
|
||||
responsible for authorship of the modifications in the Modified
|
||||
Version, together with at least five of the principal authors of the
|
||||
Document (all of its principal authors, if it has fewer than five),
|
||||
unless they release you from this requirement.
|
||||
|
||||
@item
|
||||
State on the Title page the name of the publisher of the
|
||||
Modified Version, as the publisher.
|
||||
|
||||
@item
|
||||
Preserve all the copyright notices of the Document.
|
||||
|
||||
@item
|
||||
Add an appropriate copyright notice for your modifications
|
||||
adjacent to the other copyright notices.
|
||||
|
||||
@item
|
||||
Include, immediately after the copyright notices, a license notice
|
||||
giving the public permission to use the Modified Version under the
|
||||
terms of this License, in the form shown in the Addendum below.
|
||||
|
||||
@item
|
||||
Preserve in that license notice the full lists of Invariant Sections
|
||||
and required Cover Texts given in the Document's license notice.
|
||||
|
||||
@item
|
||||
Include an unaltered copy of this License.
|
||||
|
||||
@item
|
||||
Preserve the section Entitled ``History'', Preserve its Title, and add
|
||||
to it an item stating at least the title, year, new authors, and
|
||||
publisher of the Modified Version as given on the Title Page. If
|
||||
there is no section Entitled ``History'' in the Document, create one
|
||||
stating the title, year, authors, and publisher of the Document as
|
||||
given on its Title Page, then add an item describing the Modified
|
||||
Version as stated in the previous sentence.
|
||||
|
||||
@item
|
||||
Preserve the network location, if any, given in the Document for
|
||||
public access to a Transparent copy of the Document, and likewise
|
||||
the network locations given in the Document for previous versions
|
||||
it was based on. These may be placed in the ``History'' section.
|
||||
You may omit a network location for a work that was published at
|
||||
least four years before the Document itself, or if the original
|
||||
publisher of the version it refers to gives permission.
|
||||
|
||||
@item
|
||||
For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve
|
||||
the Title of the section, and preserve in the section all the
|
||||
substance and tone of each of the contributor acknowledgements and/or
|
||||
dedications given therein.
|
||||
|
||||
@item
|
||||
Preserve all the Invariant Sections of the Document,
|
||||
unaltered in their text and in their titles. Section numbers
|
||||
or the equivalent are not considered part of the section titles.
|
||||
|
||||
@item
|
||||
Delete any section Entitled ``Endorsements''. Such a section
|
||||
may not be included in the Modified Version.
|
||||
|
||||
@item
|
||||
Do not retitle any existing section to be Entitled ``Endorsements'' or
|
||||
to conflict in title with any Invariant Section.
|
||||
|
||||
@item
|
||||
Preserve any Warranty Disclaimers.
|
||||
@end enumerate
|
||||
|
||||
If the Modified Version includes new front-matter sections or
|
||||
appendices that qualify as Secondary Sections and contain no material
|
||||
copied from the Document, you may at your option designate some or all
|
||||
of these sections as invariant. To do this, add their titles to the
|
||||
list of Invariant Sections in the Modified Version's license notice.
|
||||
These titles must be distinct from any other section titles.
|
||||
|
||||
You may add a section Entitled ``Endorsements'', provided it contains
|
||||
nothing but endorsements of your Modified Version by various
|
||||
parties---for example, statements of peer review or that the text has
|
||||
been approved by an organization as the authoritative definition of a
|
||||
standard.
|
||||
|
||||
You may add a passage of up to five words as a Front-Cover Text, and a
|
||||
passage of up to 25 words as a Back-Cover Text, to the end of the list
|
||||
of Cover Texts in the Modified Version. Only one passage of
|
||||
Front-Cover Text and one of Back-Cover Text may be added by (or
|
||||
through arrangements made by) any one entity. If the Document already
|
||||
includes a cover text for the same cover, previously added by you or
|
||||
by arrangement made by the same entity you are acting on behalf of,
|
||||
you may not add another; but you may replace the old one, on explicit
|
||||
permission from the previous publisher that added the old one.
|
||||
|
||||
The author(s) and publisher(s) of the Document do not by this License
|
||||
give permission to use their names for publicity for or to assert or
|
||||
imply endorsement of any Modified Version.
|
||||
|
||||
@item
|
||||
COMBINING DOCUMENTS
|
||||
|
||||
You may combine the Document with other documents released under this
|
||||
License, under the terms defined in section 4 above for modified
|
||||
versions, provided that you include in the combination all of the
|
||||
Invariant Sections of all of the original documents, unmodified, and
|
||||
list them all as Invariant Sections of your combined work in its
|
||||
license notice, and that you preserve all their Warranty Disclaimers.
|
||||
|
||||
The combined work need only contain one copy of this License, and
|
||||
multiple identical Invariant Sections may be replaced with a single
|
||||
copy. If there are multiple Invariant Sections with the same name but
|
||||
different contents, make the title of each such section unique by
|
||||
adding at the end of it, in parentheses, the name of the original
|
||||
author or publisher of that section if known, or else a unique number.
|
||||
Make the same adjustment to the section titles in the list of
|
||||
Invariant Sections in the license notice of the combined work.
|
||||
|
||||
In the combination, you must combine any sections Entitled ``History''
|
||||
in the various original documents, forming one section Entitled
|
||||
``History''; likewise combine any sections Entitled ``Acknowledgements'',
|
||||
and any sections Entitled ``Dedications''. You must delete all
|
||||
sections Entitled ``Endorsements.''
|
||||
|
||||
@item
|
||||
COLLECTIONS OF DOCUMENTS
|
||||
|
||||
You may make a collection consisting of the Document and other documents
|
||||
released under this License, and replace the individual copies of this
|
||||
License in the various documents with a single copy that is included in
|
||||
the collection, provided that you follow the rules of this License for
|
||||
verbatim copying of each of the documents in all other respects.
|
||||
|
||||
You may extract a single document from such a collection, and distribute
|
||||
it individually under this License, provided you insert a copy of this
|
||||
License into the extracted document, and follow this License in all
|
||||
other respects regarding verbatim copying of that document.
|
||||
|
||||
@item
|
||||
AGGREGATION WITH INDEPENDENT WORKS
|
||||
|
||||
A compilation of the Document or its derivatives with other separate
|
||||
and independent documents or works, in or on a volume of a storage or
|
||||
distribution medium, is called an ``aggregate'' if the copyright
|
||||
resulting from the compilation is not used to limit the legal rights
|
||||
of the compilation's users beyond what the individual works permit.
|
||||
When the Document is included in an aggregate, this License does not
|
||||
apply to the other works in the aggregate which are not themselves
|
||||
derivative works of the Document.
|
||||
|
||||
If the Cover Text requirement of section 3 is applicable to these
|
||||
copies of the Document, then if the Document is less than one half of
|
||||
the entire aggregate, the Document's Cover Texts may be placed on
|
||||
covers that bracket the Document within the aggregate, or the
|
||||
electronic equivalent of covers if the Document is in electronic form.
|
||||
Otherwise they must appear on printed covers that bracket the whole
|
||||
aggregate.
|
||||
|
||||
@item
|
||||
TRANSLATION
|
||||
|
||||
Translation is considered a kind of modification, so you may
|
||||
distribute translations of the Document under the terms of section 4.
|
||||
Replacing Invariant Sections with translations requires special
|
||||
permission from their copyright holders, but you may include
|
||||
translations of some or all Invariant Sections in addition to the
|
||||
original versions of these Invariant Sections. You may include a
|
||||
translation of this License, and all the license notices in the
|
||||
Document, and any Warranty Disclaimers, provided that you also include
|
||||
the original English version of this License and the original versions
|
||||
of those notices and disclaimers. In case of a disagreement between
|
||||
the translation and the original version of this License or a notice
|
||||
or disclaimer, the original version will prevail.
|
||||
|
||||
If a section in the Document is Entitled ``Acknowledgements'',
|
||||
``Dedications'', or ``History'', the requirement (section 4) to Preserve
|
||||
its Title (section 1) will typically require changing the actual
|
||||
title.
|
||||
|
||||
@item
|
||||
TERMINATION
|
||||
|
||||
You may not copy, modify, sublicense, or distribute the Document
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense, or distribute it is void, and
|
||||
will automatically terminate your rights under this License.
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, receipt of a copy of some or all of the same material does
|
||||
not give you any rights to use it.
|
||||
|
||||
@item
|
||||
FUTURE REVISIONS OF THIS LICENSE
|
||||
|
||||
The Free Software Foundation may publish new, revised versions
|
||||
of the GNU Free Documentation 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. See
|
||||
@uref{https://www.gnu.org/licenses/}.
|
||||
|
||||
Each version of the License is given a distinguishing version number.
|
||||
If the Document specifies that a particular numbered version of this
|
||||
License ``or any later version'' applies to it, you have the option of
|
||||
following the terms and conditions either of that specified version or
|
||||
of any later version that has been published (not as a draft) by the
|
||||
Free Software Foundation. If the Document does not specify a version
|
||||
number of this License, you may choose any version ever published (not
|
||||
as a draft) by the Free Software Foundation. If the Document
|
||||
specifies that a proxy can decide which future versions of this
|
||||
License can be used, that proxy's public statement of acceptance of a
|
||||
version permanently authorizes you to choose that version for the
|
||||
Document.
|
||||
|
||||
@item
|
||||
RELICENSING
|
||||
|
||||
``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any
|
||||
World Wide Web server that publishes copyrightable works and also
|
||||
provides prominent facilities for anybody to edit those works. A
|
||||
public wiki that anybody can edit is an example of such a server. A
|
||||
``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the
|
||||
site means any set of copyrightable works thus published on the MMC
|
||||
site.
|
||||
|
||||
``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0
|
||||
license published by Creative Commons Corporation, a not-for-profit
|
||||
corporation with a principal place of business in San Francisco,
|
||||
California, as well as future copyleft versions of that license
|
||||
published by that same organization.
|
||||
|
||||
``Incorporate'' means to publish or republish a Document, in whole or
|
||||
in part, as part of another Document.
|
||||
|
||||
An MMC is ``eligible for relicensing'' if it is licensed under this
|
||||
License, and if all works that were first published under this License
|
||||
somewhere other than this MMC, and subsequently incorporated in whole
|
||||
or in part into the MMC, (1) had no cover texts or invariant sections,
|
||||
and (2) were thus incorporated prior to November 1, 2008.
|
||||
|
||||
The operator of an MMC Site may republish an MMC contained in the site
|
||||
under CC-BY-SA on the same site at any time before August 1, 2009,
|
||||
provided the MMC is eligible for relicensing.
|
||||
|
||||
@end enumerate
|
||||
|
||||
@page
|
||||
@heading ADDENDUM: How to use this License for your documents
|
||||
|
||||
To use this License in a document you have written, include a copy of
|
||||
the License in the document and put the following copyright and
|
||||
license notices just after the title page:
|
||||
|
||||
@smallexample
|
||||
@group
|
||||
Copyright (C) @var{year} @var{your name}.
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
|
||||
Texts. A copy of the license is included in the section entitled ``GNU
|
||||
Free Documentation License''.
|
||||
@end group
|
||||
@end smallexample
|
||||
|
||||
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
|
||||
replace the ``with@dots{}Texts.''@: line with this:
|
||||
|
||||
@smallexample
|
||||
@group
|
||||
with the Invariant Sections being @var{list their titles}, with
|
||||
the Front-Cover Texts being @var{list}, and with the Back-Cover Texts
|
||||
being @var{list}.
|
||||
@end group
|
||||
@end smallexample
|
||||
|
||||
If you have Invariant Sections without Cover Texts, or some other
|
||||
combination of the three, merge those two alternatives to suit the
|
||||
situation.
|
||||
|
||||
If your document contains nontrivial examples of program code, we
|
||||
recommend releasing these examples in parallel under your choice of
|
||||
free software license, such as the GNU General Public License,
|
||||
to permit their use in free software.
|
||||
|
||||
@c Local Variables:
|
||||
@c ispell-local-pdict: "ispell-dict"
|
||||
@c End:
|
160
doc/fsck.bookmarkfs.1
Normal file
160
doc/fsck.bookmarkfs.1
Normal file
|
@ -0,0 +1,160 @@
|
|||
.TH FSCK.BOOKMARKFS 1 "Dec 01, 2024" 0.1.0 "BookmarkFS User Manual"
|
||||
.
|
||||
.SH NAME
|
||||
fsck.bookmarkfs - check and repair a BookmarkFS filesystem
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
.B fsck.bookmarkfs
|
||||
.RI [ options ]
|
||||
.I pathname
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B fsck.bookmarkfs
|
||||
program checks and optionally repairs a BookmarkFS filesystem.
|
||||
.PP
|
||||
Unlike fsck on general-purpose filesystems (e.g. ext4), this program does not
|
||||
check the integrity or validity of a bookmark file.
|
||||
Instead, it checks for bookmark names that are not valid as filename
|
||||
(e.g. contains slash characters).
|
||||
.PP
|
||||
See the full user manual for details.
|
||||
.SS Online mode
|
||||
In online mode, fsck is performed on a mounted BookmarkFS filesystem.
|
||||
.PP
|
||||
The
|
||||
.I pathname
|
||||
argument refers to the directory to perform fsck on.
|
||||
.SS Offline mode
|
||||
In offline mode, fsck is performed directly on the bookmark storage
|
||||
via the corresponding backend.
|
||||
.PP
|
||||
The
|
||||
.I pathname
|
||||
argument is a colon-separated string
|
||||
.RI \[dq] mount_src : dir \[dq],
|
||||
where
|
||||
.I mount_src
|
||||
is equivalent to the
|
||||
.I src
|
||||
argument for
|
||||
.BR mount.bookmarkfs (1),
|
||||
and
|
||||
.I dir
|
||||
is the path to the directory to perform fsck on,
|
||||
relative to the root directory of the bookmark storage.
|
||||
.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-o backend=\fIname\fR
|
||||
The backend used by the filesystem.
|
||||
.IP
|
||||
If this option is not provided, or
|
||||
.I name
|
||||
is empty, performs online fsck.
|
||||
.TP
|
||||
\fB\-o @\fIkey\fR[\fB=\fIvalue\fR]
|
||||
A backend-specific option.
|
||||
This option can be provided multiple times.
|
||||
.TP
|
||||
\fB\-o handler=\fIname\fR
|
||||
The handler for resolving errors found during fsck.
|
||||
.IP
|
||||
If this option is not provided, or
|
||||
.I name
|
||||
is empty, a built-in handler will be used.
|
||||
.TP
|
||||
\fB\-o %\fIkey\fR[\fB=\fIvalue\fR]
|
||||
A handler-specific option.
|
||||
This option can be provided multiple times.
|
||||
.TP
|
||||
.B "\-o repair"
|
||||
Attempt to repair errors found during fsck.
|
||||
.TP
|
||||
\fB\-o rl_app=\fIname\fR
|
||||
Readline application name in interactive mode.
|
||||
Defaults to "fsck.bookmarkfs".
|
||||
.TP
|
||||
.BR "\-o type=bookmark" | tag | keyword
|
||||
Bookmark type.
|
||||
Defaults to "bookmark".
|
||||
.IP
|
||||
This option is ignored when performing online fsck.
|
||||
.TP
|
||||
.B \-i
|
||||
Enable interactive mode.
|
||||
.TP
|
||||
.B \-R
|
||||
Perform fsck on subdirectories recursively.
|
||||
.IP
|
||||
This option is ignored when performing fsck on tags or keywords.
|
||||
.TP
|
||||
.B "\-o no_sandbox"
|
||||
Do not enable sandboxing features.
|
||||
.TP
|
||||
.B "\-o no_landlock"
|
||||
Do not use
|
||||
.BR landlock (7)
|
||||
for sandboxing.
|
||||
This option is ignored on non-Linux platforms.
|
||||
.TP
|
||||
.B \-h
|
||||
Print help text, and then exit.
|
||||
.TP
|
||||
.B \-V
|
||||
Print version and feature information, and then exit.
|
||||
.
|
||||
.SH EXIT STATUS
|
||||
.TP
|
||||
0
|
||||
The fsck operation is completed successfully.
|
||||
.TP
|
||||
1
|
||||
An error occurred.
|
||||
.
|
||||
.SH NOTES
|
||||
.SS Backends
|
||||
See the NOTES section in
|
||||
.BR mount.bookmarkfs (1)
|
||||
for details.
|
||||
.SS Handlers
|
||||
A BookmarkFS fsck handler provides a way to deal with errors found during fsck.
|
||||
In addition to the default built-in handler, there is also a "tcl" handler,
|
||||
which allows handling fsck errors with Tcl scripting.
|
||||
.PP
|
||||
Like backends, fsck handlers can also be implemented as loadable modules.
|
||||
See the full user manual for details.
|
||||
.SS Availability on FreeBSD
|
||||
Online fsck requires that the FUSE client supports
|
||||
.BR ioctl (2),
|
||||
which is currently not available on stable releases of FreeBSD.
|
||||
See the NOTES section in
|
||||
.BR bookmarkctl (1)
|
||||
for details.
|
||||
.PP
|
||||
Offline fsck does not have this limitation.
|
||||
.
|
||||
.SH SEE ALSO
|
||||
.BR mount.bookmarkfs (1),
|
||||
.BR landlock (7),
|
||||
.BR tcl (n)
|
||||
.PP
|
||||
The full BookmarkFS User Manual is maintained as a Texinfo document.
|
||||
To read it locally, run:
|
||||
.PP
|
||||
.in +4n
|
||||
.EX
|
||||
.B info bookmarkfs
|
||||
.EE
|
||||
.in
|
||||
.
|
||||
.SH COPYRIGHT
|
||||
Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
.PP
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||
.PP
|
||||
You should have received a copy of the license along with this document.
|
||||
If not, see <https://www.gnu.org/licenses/fdl-1.3.html>.
|
71
doc/mkfs.bookmarkfs.1
Normal file
71
doc/mkfs.bookmarkfs.1
Normal file
|
@ -0,0 +1,71 @@
|
|||
.TH MKFS.BOOKMARKFS 1 "Dec 01, 2024" 0.1.0 "BookmarkFS User Manual"
|
||||
.
|
||||
.SH NAME
|
||||
mkfs.bookmarkfs - create a BookmarkFS filesystem
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
.B mkfs.bookmarkfs
|
||||
.RI [ options ]
|
||||
.I pathname
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mkfs.bookmarkfs
|
||||
program creates a BookmarkFS filesystem on the file specified by
|
||||
.IR pathname .
|
||||
.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-o backend=\fIname\fR
|
||||
The backend used for creating the filesystem.
|
||||
This option is mandatory.
|
||||
.TP
|
||||
\fB\-o @\fIkey\fR[\fB=\fIvalue\fR]
|
||||
A backend-specific option.
|
||||
This option can be provided multiple times.
|
||||
.TP
|
||||
.B "\-o force"
|
||||
Overwrite existing files when creating the filesystem.
|
||||
.TP
|
||||
.B \-h
|
||||
Print help text, and then exit.
|
||||
.TP
|
||||
.B \-V
|
||||
Print version and feature information, and then exit.
|
||||
.
|
||||
.SH EXIT STATUS
|
||||
.TP
|
||||
0
|
||||
The filesystem is created successfully.
|
||||
.TP
|
||||
1
|
||||
An error occurred.
|
||||
.
|
||||
.SH NOTES
|
||||
.SS Backends
|
||||
See the NOTES section in
|
||||
.BR mount.bookmarkfs (1)
|
||||
for details.
|
||||
.
|
||||
.SH SEE ALSO
|
||||
.BR mount.bookmarkfs (1)
|
||||
.PP
|
||||
The full BookmarkFS User Manual is maintained as a Texinfo document.
|
||||
To read it locally, run:
|
||||
.PP
|
||||
.in +4n
|
||||
.EX
|
||||
.B info bookmarkfs
|
||||
.EE
|
||||
.in
|
||||
.
|
||||
.SH COPYRIGHT
|
||||
Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
.PP
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||
.PP
|
||||
You should have received a copy of the license along with this document.
|
||||
If not, see <https://www.gnu.org/licenses/fdl-1.3.html>.
|
143
doc/mount.bookmarkfs.1
Normal file
143
doc/mount.bookmarkfs.1
Normal file
|
@ -0,0 +1,143 @@
|
|||
.TH MOUNT.BOOKMARKFS 1 "Dec 01, 2024" 0.1.0 "BookmarkFS User Manual"
|
||||
.
|
||||
.SH NAME
|
||||
mount.bookmarkfs - mount a BookmarkFS filesystem
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
.B mount.bookmarkfs
|
||||
.RI [ options ]
|
||||
.I src
|
||||
.I target
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mount.bookmarkfs
|
||||
program mounts a BookmarkFS filesystem to the location specified by
|
||||
.IR target .
|
||||
.PP
|
||||
The
|
||||
.I src
|
||||
argument is presumably the pathname of a file that stores bookmark data.
|
||||
Its exact interpretation is backend-defined.
|
||||
.PP
|
||||
To unmount a BookmarkFS filesystem, use
|
||||
.BR fusermount3 (1)
|
||||
or
|
||||
.BR umount (8)
|
||||
on
|
||||
.IR target .
|
||||
.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-o backend=\fIname\fR
|
||||
The backend used by the filesystem.
|
||||
This option is mandatory.
|
||||
.TP
|
||||
\fB\-o @\fIkey\fR[\fB=\fIvalue\fR]
|
||||
A backend-specific option.
|
||||
This option can be provided multiple times.
|
||||
.TP
|
||||
\fB\-o accmode=\fImode\fR
|
||||
File access mode.
|
||||
Defaults to 0700.
|
||||
.IP
|
||||
This option applies to both directories and regular files.
|
||||
Execution bits on regular files are masked off.
|
||||
.TP
|
||||
.B "\-o ctime"
|
||||
Maintains file change time, while modification time follows change time.
|
||||
If this option is not provided, maintains file modification time instead.
|
||||
.IP
|
||||
See the full user manual for the rationale behind this option.
|
||||
.TP
|
||||
.B "\-o eol"
|
||||
Adds a newline (ASCII LF character) to the end of each file.
|
||||
.TP
|
||||
\fB\-o file_max=\fIbytes\fR
|
||||
Max file size limit.
|
||||
Defaults to 32768.
|
||||
.TP
|
||||
.B "\-o no_sandbox"
|
||||
Do not enable sandboxing features.
|
||||
.TP
|
||||
.B "\-o no_landlock"
|
||||
Do not use
|
||||
.BR landlock (7)
|
||||
for sandboxing.
|
||||
This option is ignored on non-Linux platforms.
|
||||
.TP
|
||||
.B \-F
|
||||
Stay in the foreground, do not daemonize.
|
||||
.TP
|
||||
.B \-h
|
||||
Print help text, and then exit.
|
||||
.TP
|
||||
.B \-V
|
||||
Print version and feature information, and then exit.
|
||||
.
|
||||
.SH EXIT STATUS
|
||||
.TP
|
||||
0
|
||||
The filesystem is mounted successfully.
|
||||
.TP
|
||||
1
|
||||
An error occurred.
|
||||
.PP
|
||||
For the daemon process (or the main process if the
|
||||
.B \-F
|
||||
option is given):
|
||||
.TP
|
||||
0
|
||||
The filesystem is unmounted.
|
||||
.
|
||||
.SH NOTES
|
||||
.SS Backends
|
||||
In BookmarkFS, each backend provides a way to manipulate a certain kind of
|
||||
application bookmarks.
|
||||
The currently supported backends are "firefox" and "chromium".
|
||||
.PP
|
||||
Backends are implemented as loadable modules, and communicate with "frontend"
|
||||
prgrams using the BookmarkFS Backend API.
|
||||
New backends can be added without having to change existing code of BookmarkFS.
|
||||
.PP
|
||||
See the full user manual for details regarding the existing backends,
|
||||
and the specification of the BookmarkFS Backend API.
|
||||
.SS Extra options
|
||||
The options documented in this page is not a comprehensive list of all
|
||||
available options.
|
||||
Unrecognized options are passed to libfuse (and subsequently to the kernel,
|
||||
if applicable) as-is.
|
||||
.PP
|
||||
See the manual page for
|
||||
.BR mount.fuse3 (8)
|
||||
for details.
|
||||
.SS POSIX compatibility
|
||||
BookmarkFS is partially compatible with the filesystem specifications
|
||||
in POSIX.1.
|
||||
See the full user manual for details.
|
||||
.
|
||||
.SH SEE ALSO
|
||||
.BR fusermount (1),
|
||||
.BR landlock (7),
|
||||
.BR mount.fuse3 (8),
|
||||
.BR umount (8)
|
||||
.PP
|
||||
The full BookmarkFS User Manual is maintained as a Texinfo document.
|
||||
To read it locally, run:
|
||||
.PP
|
||||
.in +4n
|
||||
.EX
|
||||
.B info bookmarkfs
|
||||
.EE
|
||||
.in
|
||||
.
|
||||
.SH COPYRIGHT
|
||||
Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
.PP
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||
.PP
|
||||
You should have received a copy of the license along with this document.
|
||||
If not, see <https://www.gnu.org/licenses/fdl-1.3.html>.
|
88
m4/bookmarkfs_ac.m4
Normal file
88
m4/bookmarkfs_ac.m4
Normal file
|
@ -0,0 +1,88 @@
|
|||
dnl
|
||||
dnl Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
dnl
|
||||
dnl Copying and distribution of this file, with or without modification,
|
||||
dnl are permitted in any medium without royalty,
|
||||
dnl provided the copyright notice and this notice are preserved.
|
||||
dnl This file is offered as-is, without any warranty.
|
||||
dnl
|
||||
|
||||
dnl
|
||||
dnl BOOKMARKFS_FEAT(feature, default-value, description,
|
||||
dnl [action-if-enabled], [action-if-disabled])
|
||||
dnl
|
||||
dnl Provides an option to enable or disable a feature.
|
||||
dnl
|
||||
AC_DEFUN([BOOKMARKFS_FEAT], [
|
||||
m4_pushdef([arg_action_], m4_if([$2], [no], [enable], [disable]))
|
||||
m4_pushdef([feat_name_], m4_translit([$1], [-], [_]))
|
||||
AC_MSG_CHECKING(m4_normalize([if $3 is enabled]))
|
||||
AC_ARG_ENABLE([$1], m4_normalize([
|
||||
AS_HELP_STRING([--]arg_action_[-$1], arg_action_ [$3])
|
||||
]), , [
|
||||
AS_VAR_SET([enable_]feat_name_, [$2])
|
||||
])
|
||||
AS_VAR_IF([enable_]feat_name_, [no], [
|
||||
AC_MSG_RESULT([no])
|
||||
$5
|
||||
], [
|
||||
AC_MSG_RESULT([yes])
|
||||
$4
|
||||
])
|
||||
AS_VAR_SET([desc_]feat_name_, ["$3"])
|
||||
m4_popdef([arg_action_])
|
||||
m4_popdef([feat_name_])
|
||||
])
|
||||
|
||||
dnl
|
||||
dnl BOOKMARKFS_DEP(pkg-name, version, pkg-desc, [action-if-found],
|
||||
dnl [required-by-features]...)
|
||||
dnl
|
||||
dnl Checks if a package exists with `pkg-config', and provides option for
|
||||
dnl the config script to specify the package's custom install location.
|
||||
dnl
|
||||
AC_DEFUN([BOOKMARKFS_DEP], [
|
||||
m4_pushdef([with_var_], [with_]m4_translit([$1], [-], [_]))
|
||||
AC_ARG_WITH([$1], m4_normalize([
|
||||
AS_HELP_STRING([--with-$1[[=PKGCONFIGDIR]]],
|
||||
[pkg-config search path for $3])
|
||||
]), , [
|
||||
AS_VAR_SET([with_var_], [no])
|
||||
m4_foreach([feat_name_], [m4_shiftn(4, $@)], [
|
||||
AS_VAR_IF([enable_]m4_translit(feat_name_, [-], [_]), [yes], [
|
||||
AS_VAR_SET([with_var_], [yes])
|
||||
])
|
||||
])
|
||||
])
|
||||
AS_VAR_IF([with_var_], [no], [
|
||||
m4_foreach([feat_name_], [m4_shiftn(4, $@)], [
|
||||
AS_VAR_IF([enable_]m4_translit(feat_name_, [-], [_]), [yes], [
|
||||
AC_MSG_ERROR(m4_normalize([
|
||||
Bad option `[--without-]feat_name_'. The $3 is mandatory
|
||||
for AS_VAR_GET([desc_]m4_translit(feat_name_, [-], [_])).
|
||||
]))
|
||||
])
|
||||
])
|
||||
], [
|
||||
AS_VAR_SET([SAVED_PKG_CONFIG_PATH_], ["${PKG_CONFIG_PATH}"])
|
||||
AS_VAR_IF([with_var_], [yes], , [
|
||||
AS_VAR_SET([PKG_CONFIG_PATH], ["${with_$1}:${PKG_CONFIG_PATH}"])
|
||||
])
|
||||
export PKG_CONFIG_PATH
|
||||
PKG_CHECK_MODULES(m4_toupper([$1]), [$1 $2], [$4])
|
||||
AS_VAR_SET([PKG_CONFIG_PATH], ["${SAVED_PKG_CONFIG_PATH_}"])
|
||||
])
|
||||
m4_popdef([with_var_])
|
||||
])
|
||||
|
||||
dnl
|
||||
dnl BOOKMARKFS_AMCOND([features]...)
|
||||
dnl
|
||||
dnl Export feature flags to Automake makefiles.
|
||||
dnl
|
||||
AC_DEFUN([BOOKMARKFS_AMCOND], [
|
||||
m4_foreach([feat_name_], [$@], [
|
||||
AM_CONDITIONAL(m4_translit(feat_name_, [-a-z], [_A-Z]),
|
||||
[test x$][{enable_]m4_translit(feat_name_, [-], [_])[} != xno])
|
||||
])
|
||||
])
|
130
src/Makefile.am
Normal file
130
src/Makefile.am
Normal file
|
@ -0,0 +1,130 @@
|
|||
#
|
||||
# Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty, provided the copyright notice and
|
||||
# this notice are preserved. This file is offered as-is, without any warranty.
|
||||
#
|
||||
|
||||
bin_PROGRAMS =
|
||||
pkginclude_HEADERS = backend.h common.h ioctl.h fsck_handler.h version.h
|
||||
noinst_HEADERS = backend_util.h db.h defs.h frontend_util.h fs_ops.h \
|
||||
fsck_ops.h fsck_util.h json.h lib.h macros.h uuid.h xstd.h
|
||||
lib_LTLIBRARIES =
|
||||
pkglib_LTLIBRARIES =
|
||||
|
||||
BASE_CPPFLAGS_ = -DBUILDING_BOOKMARKFS
|
||||
if MACRO_PREFIX_MAP
|
||||
BASE_CPPFLAGS_ += -fmacro-prefix-map=$(srcdir)/=
|
||||
endif # MACRO_PREFIX_MAP
|
||||
|
||||
MODULE_CFLAGS_ =
|
||||
if HIDE_MODULE_SYMBOLS
|
||||
MODULE_CFLAGS_ += -fvisibility=hidden
|
||||
endif # HIDE_MODULE_SYMBOLS
|
||||
|
||||
UTIL_LIBS_ =
|
||||
if BOOKMARKFS_UTIL
|
||||
UTIL_LIBS_ += libbookmarkfs_util.la
|
||||
else
|
||||
UTIL_LIBS_ += $(BOOKMARKFS_LIBS)
|
||||
endif # BOOKMARKFS_UTIL
|
||||
|
||||
BACKEND_SOURCES_ = backend_util.c lib.c xstd.c
|
||||
FRONTEND_SOURCES_ = frontend_util.c xstd.c
|
||||
LT_VERSION_INFO_ = -version-info 0:0:0
|
||||
MODULE_LDFLAGS_ = $(LT_VERSION_INFO_) -module
|
||||
|
||||
if BOOKMARKFS_UTIL
|
||||
lib_LTLIBRARIES += libbookmarkfs_util.la
|
||||
pkginclude_HEADERS += hash.h hashmap.h prng.h sandbox.h watcher.h
|
||||
|
||||
libbookmarkfs_util_la_CPPFLAGS = $(BASE_CPPFLAGS_) $(PTHREAD_CFLAGS) \
|
||||
$(LIBXXHASH_CFLAGS)
|
||||
libbookmarkfs_util_la_LDFLAGS = $(LT_VERSION_INFO_)
|
||||
libbookmarkfs_util_la_LIBADD = $(PTHREAD_LIBS) $(LIBXXHASH_LIBS)
|
||||
libbookmarkfs_util_la_SOURCES = hash.c hashmap.c prng.c sandbox.c \
|
||||
version.c watcher.c xstd.c
|
||||
|
||||
if SANDBOX
|
||||
libbookmarkfs_util_la_CPPFLAGS += $(LIBSECCOMP_CFLAGS)
|
||||
libbookmarkfs_util_la_LIBADD += $(LIBSECCOMP_LIBS)
|
||||
endif # SANDBOX
|
||||
endif # BOOKMARKFS_UTIL
|
||||
|
||||
if BOOKMARKCTL
|
||||
bin_PROGRAMS += bookmarkctl
|
||||
|
||||
bookmarkctl_SOURCES = bookmarkctl.c fsck_util.c
|
||||
endif # BOOKMARKCTL
|
||||
|
||||
if BOOKMARKFS_FSCK
|
||||
bin_PROGRAMS += fsck.bookmarkfs
|
||||
|
||||
fsck_bookmarkfs_CPPFLAGS = $(BASE_CPPFLAGS_)
|
||||
fsck_bookmarkfs_LDADD = $(UTIL_LIBS_)
|
||||
fsck_bookmarkfs_SOURCES = $(FRONTEND_SOURCES_) backend_util.c fsck.c \
|
||||
fsck_handler_simple.c fsck_offline.c \
|
||||
fsck_online.c fsck_util.c lib.c
|
||||
|
||||
if INTERACTIVE_FSCK
|
||||
fsck_bookmarkfs_CPPFLAGS += $(READLINE_CFLAGS)
|
||||
fsck_bookmarkfs_LDADD += $(READLINE_LIBS)
|
||||
endif # INTERACTIVE_FSCK
|
||||
endif # BOOKMARKFS_FSCK
|
||||
|
||||
if BOOKMARKFS_MKFS
|
||||
bin_PROGRAMS += mkfs.bookmarkfs
|
||||
|
||||
mkfs_bookmarkfs_CPPFLAGS = $(BASE_CPPFLAGS_)
|
||||
mkfs_bookmarkfs_SOURCES = $(FRONTEND_SOURCES_) mkfs.c
|
||||
endif # BOOKMARKFS_MKFS
|
||||
|
||||
if BOOKMARKFS_MOUNT
|
||||
bin_PROGRAMS += mount.bookmarkfs
|
||||
|
||||
mount_bookmarkfs_CPPFLAGS = $(BASE_CPPFLAGS_) $(FUSE3_CFLAGS)
|
||||
mount_bookmarkfs_LDADD = $(UTIL_LIBS_) $(FUSE3_LIBS)
|
||||
mount_bookmarkfs_SOURCES = $(FRONTEND_SOURCES_) fs_ops.c lib.c mount.c
|
||||
endif # BOOKMARKFS_MOUNT
|
||||
|
||||
if BACKEND_FIREFOX
|
||||
pkglib_LTLIBRARIES += backend_firefox.la
|
||||
|
||||
backend_firefox_la_CFLAGS = $(MODULE_CFLAGS_)
|
||||
backend_firefox_la_CPPFLAGS = $(BASE_CPPFLAGS_) $(SQLITE3_CFLAGS)
|
||||
backend_firefox_la_LDFLAGS = $(MODULE_LDFLAGS_)
|
||||
backend_firefox_la_LIBADD = $(UTIL_LIBS_) $(SQLITE3_LIBS)
|
||||
backend_firefox_la_SOURCES = $(BACKEND_SOURCES_) backend_firefox.c db.c
|
||||
|
||||
if BACKEND_FIREFOX_WRITE
|
||||
backend_firefox_la_CPPFLAGS += $(NETTLE_CFLAGS) $(LIBURIPARSER_CFLAGS)
|
||||
backend_firefox_la_LIBADD += $(NETTLE_LIBS) $(LIBURIPARSER_LIBS)
|
||||
endif # BACKEND_FIREFOX_WRITE
|
||||
endif # BACKEND_FIREFOX
|
||||
|
||||
if BACKEND_CHROMIUM
|
||||
pkglib_LTLIBRARIES += backend_chromium.la
|
||||
|
||||
backend_chromium_la_CFLAGS = $(MODULE_CFLAGS_)
|
||||
backend_chromium_la_CPPFLAGS = $(BASE_CPPFLAGS_) $(JANSSON_CFLAGS)
|
||||
backend_chromium_la_LDFLAGS = $(MODULE_LDFLAGS_)
|
||||
backend_chromium_la_LIBADD = $(UTIL_LIBS_) $(JANSSON_LIBS)
|
||||
backend_chromium_la_SOURCES = $(BACKEND_SOURCES_) backend_chromium.c \
|
||||
json.c uuid.c
|
||||
|
||||
if BACKEND_CHROMIUM_WRITE
|
||||
backend_chromium_la_CPPFLAGS += $(NETTLE_CFLAGS)
|
||||
backend_chromium_la_LIBADD += $(LIBICONV_LIBS) $(NETTLE_LIBS)
|
||||
endif # BACKEND_CHROMIUM_WRITE
|
||||
endif # BACKEND_CHROMIUM
|
||||
|
||||
if FSCK_HANDLER_TCL
|
||||
pkglib_LTLIBRARIES += fsck_handler_tcl.la
|
||||
|
||||
fsck_handler_tcl_la_CFLAGS = $(MODULE_CFLAGS_)
|
||||
fsck_handler_tcl_la_CPPFLAGS = $(BASE_CPPFLAGS_) $(TCL_CFLAGS)
|
||||
fsck_handler_tcl_la_LDFLAGS = $(MODULE_LDFLAGS_)
|
||||
fsck_handler_tcl_la_LIBADD = $(TCL_LIBS)
|
||||
fsck_handler_tcl_la_SOURCES = backend_util.c fsck_handler_tcl.c xstd.c
|
||||
endif # FSCK_HANDLER_TCL
|
287
src/backend.h
Normal file
287
src/backend.h
Normal file
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* bookmarkfs/src/backend.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_BACKEND_H_
|
||||
#define BOOKMARKFS_BACKEND_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef BUILDING_BOOKMARKFS
|
||||
# include "common.h"
|
||||
# include "ioctl.h"
|
||||
#else
|
||||
# include <bookmarkfs/common.h>
|
||||
# include <bookmarkfs/ioctl.h>
|
||||
#endif
|
||||
|
||||
// backend_create() flags
|
||||
#define BOOKMARKFS_BACKEND_READONLY ( 1u << 0 )
|
||||
#define BOOKMARKFS_BACKEND_CTIME ( 1u << 1 )
|
||||
#define BOOKMARKFS_BACKEND_NO_SANDBOX ( 1u << 2 )
|
||||
#define BOOKMARKFS_BACKEND_NO_LANDLOCK ( 1u << 3 )
|
||||
#define BOOKMARKFS_BACKEND_FSCK_ONLY ( 1u << 4 )
|
||||
|
||||
#define BOOKMARKFS_BACKEND_INFO_HELP ( 1u << 0 )
|
||||
#define BOOKMARKFS_BACKEND_INFO_VERSION ( 1u << 1 )
|
||||
|
||||
#define BOOKMARKFS_BACKEND_MKFS_FORCE ( 1u << 0 )
|
||||
|
||||
// backend_create() response flags
|
||||
#define BOOKMARKFS_BACKEND_EXCLUSIVE ( 1u << 0 )
|
||||
#define BOOKMARKFS_BACKEND_HAS_KEYWORD ( 1u << 1 )
|
||||
|
||||
// backend_init() flags
|
||||
#define BOOKMARKFS_BACKEND_LIB_READY ( 1u << 0 )
|
||||
|
||||
#define BOOKMARKFS_FRONTEND_FSCK ( 1u << 16 )
|
||||
#define BOOKMARKFS_FRONTEND_MOUNT ( 1u << 17 )
|
||||
#define BOOKMARKFS_FRONTEND_MKFS ( 1u << 18 )
|
||||
|
||||
#define BOOKMARKFS_BOOKMARK_CREATE_DIR ( 1u << 0 )
|
||||
#define BOOKMARKFS_BOOKMARK_DELETE_DIR ( 1u << 0 )
|
||||
#define BOOKMARKFS_BOOKMARK_LIST_WITHSTAT ( 1u << 0 )
|
||||
#define BOOKMARKFS_BOOKMARK_RENAME_NOREPLACE ( 1u << 0 )
|
||||
#define BOOKMARKFS_BOOKMARK_SET_TIME ( 1u << 0 )
|
||||
|
||||
#define BOOKMARKFS_BOOKMARK_TYPE_BITS 3
|
||||
#define BOOKMARKFS_BOOKMARK_TYPE_SHIFT ( 32 - BOOKMARKFS_BOOKMARK_TYPE_BITS )
|
||||
#define BOOKMARKFS_BOOKMARK_TYPE_MASK \
|
||||
( ((UINT32_C(1) << BOOKMARKFS_BOOKMARK_TYPE_BITS) - 1) \
|
||||
<< BOOKMARKFS_BOOKMARK_TYPE_SHIFT )
|
||||
#define BOOKMARKFS_BOOKMARK_TYPE(t) \
|
||||
( BOOKMARKFS_BOOKMARK_TYPE_##t << BOOKMARKFS_BOOKMARK_TYPE_SHIFT )
|
||||
#define BOOKMARKFS_BOOKMARK_IS_TYPE(f, t) \
|
||||
( ((f) & BOOKMARKFS_BOOKMARK_TYPE_MASK) == BOOKMARKFS_BOOKMARK_TYPE(t) )
|
||||
|
||||
#define BOOKMARKFS_MAX_ID \
|
||||
( (UINT64_C(1) << (64 - BOOKMARKFS_BOOKMARK_TYPE_BITS)) - 1 )
|
||||
|
||||
enum bookmarkfs_bookmark_type {
|
||||
BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK = 0, // must be 0
|
||||
BOOKMARKFS_BOOKMARK_TYPE_TAG,
|
||||
BOOKMARKFS_BOOKMARK_TYPE_KEYWORD,
|
||||
};
|
||||
|
||||
enum bookmarkfs_object_type {
|
||||
BOOKMARKFS_OBJECT_TYPE_BGCOOKIE,
|
||||
BOOKMARKFS_OBJECT_TYPE_BLCOOKIE,
|
||||
};
|
||||
|
||||
struct bookmarkfs_backend_conf;
|
||||
struct bookmarkfs_backend_init_resp;
|
||||
struct bookmarkfs_bookmark_entry;
|
||||
struct bookmarkfs_bookmark_stat;
|
||||
|
||||
typedef int (bookmarkfs_backend_create_func) (
|
||||
struct bookmarkfs_backend_conf const *conf,
|
||||
struct bookmarkfs_backend_init_resp *resp
|
||||
);
|
||||
|
||||
typedef void (bookmarkfs_backend_free_func) (
|
||||
void *backend_ctx
|
||||
);
|
||||
|
||||
typedef void (bookmarkfs_backend_info_func) (
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_backend_init_func) (
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_backend_mkfs_func) (
|
||||
struct bookmarkfs_backend_conf const *conf
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_backend_sandbox_func) (
|
||||
void *backend_ctx,
|
||||
int fusefd,
|
||||
struct bookmarkfs_backend_init_resp *resp
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_backend_sync_func) (
|
||||
void *backend_ctx
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_create_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t parent_id,
|
||||
char const *name,
|
||||
uint32_t flags,
|
||||
struct bookmarkfs_bookmark_stat *stat_buf
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_delete_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t parent_id,
|
||||
char const *name,
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_fsck_cb) (
|
||||
void *user_data,
|
||||
int result,
|
||||
uint64_t id,
|
||||
uint64_t extra,
|
||||
char const *name
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_fsck_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t id,
|
||||
struct bookmarkfs_fsck_data const *fsck_data,
|
||||
uint32_t flags,
|
||||
bookmarkfs_bookmark_fsck_cb *callback,
|
||||
void *user_data,
|
||||
void **cookie_ptr
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_get_cb) (
|
||||
void *user_data,
|
||||
void const *value,
|
||||
size_t value_len
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_get_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t id,
|
||||
char const *attr_key,
|
||||
bookmarkfs_bookmark_get_cb *callback,
|
||||
void *user_data,
|
||||
void **cookie_ptr
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_list_cb) (
|
||||
void *user_data,
|
||||
struct bookmarkfs_bookmark_entry const *entry
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_list_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t id,
|
||||
off_t off,
|
||||
uint32_t flags,
|
||||
bookmarkfs_bookmark_list_cb *callback,
|
||||
void *user_data,
|
||||
void **cookie_ptr
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_lookup_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t id,
|
||||
char const *name,
|
||||
uint32_t flags,
|
||||
struct bookmarkfs_bookmark_stat *stat_buf
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_permute_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t parent_id,
|
||||
enum bookmarkfs_permd_op op,
|
||||
char const *name1,
|
||||
char const *name2,
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_rename_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t old_parent_id,
|
||||
char const *old_name,
|
||||
uint64_t new_parent_id,
|
||||
char const *new_name,
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_bookmark_set_func) (
|
||||
void *backend_ctx,
|
||||
uint64_t id,
|
||||
char const *attr_key,
|
||||
uint32_t flags,
|
||||
void const *val,
|
||||
size_t val_len
|
||||
);
|
||||
|
||||
typedef void (bookmarkfs_object_free_func) (
|
||||
void *backend_ctx,
|
||||
void *object,
|
||||
enum bookmarkfs_object_type object_type
|
||||
);
|
||||
|
||||
struct bookmarkfs_backend {
|
||||
bookmarkfs_backend_create_func *backend_create;
|
||||
bookmarkfs_backend_free_func *backend_free;
|
||||
bookmarkfs_backend_info_func *backend_info;
|
||||
bookmarkfs_backend_init_func *backend_init;
|
||||
bookmarkfs_backend_mkfs_func *backend_mkfs;
|
||||
bookmarkfs_backend_sandbox_func *backend_sandbox;
|
||||
bookmarkfs_backend_sync_func *backend_sync;
|
||||
|
||||
bookmarkfs_bookmark_get_func *bookmark_get;
|
||||
bookmarkfs_bookmark_list_func *bookmark_list;
|
||||
bookmarkfs_bookmark_lookup_func *bookmark_lookup;
|
||||
|
||||
bookmarkfs_bookmark_create_func *bookmark_create;
|
||||
bookmarkfs_bookmark_delete_func *bookmark_delete;
|
||||
bookmarkfs_bookmark_fsck_func *bookmark_fsck;
|
||||
bookmarkfs_bookmark_permute_func *bookmark_permute;
|
||||
bookmarkfs_bookmark_rename_func *bookmark_rename;
|
||||
bookmarkfs_bookmark_set_func *bookmark_set;
|
||||
|
||||
bookmarkfs_object_free_func *object_free;
|
||||
};
|
||||
|
||||
struct bookmarkfs_backend_conf {
|
||||
uint32_t version;
|
||||
uint32_t flags;
|
||||
char *store_path;
|
||||
|
||||
struct bookmarkfs_conf_opt *opts;
|
||||
};
|
||||
|
||||
struct bookmarkfs_backend_init_resp {
|
||||
char const *name;
|
||||
void *backend_ctx;
|
||||
uint64_t bookmarks_root_id;
|
||||
uint64_t tags_root_id;
|
||||
char const *bookmark_attrs;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
struct bookmarkfs_bookmark_stat {
|
||||
uint64_t id;
|
||||
ssize_t value_len;
|
||||
|
||||
struct timespec atime;
|
||||
struct timespec mtime;
|
||||
};
|
||||
|
||||
struct bookmarkfs_bookmark_entry {
|
||||
char const *name;
|
||||
off_t next;
|
||||
|
||||
struct bookmarkfs_bookmark_stat stat;
|
||||
};
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_BACKEND_H_) */
|
2729
src/backend_chromium.c
Normal file
2729
src/backend_chromium.c
Normal file
File diff suppressed because it is too large
Load diff
3625
src/backend_firefox.c
Normal file
3625
src/backend_firefox.c
Normal file
File diff suppressed because it is too large
Load diff
160
src/backend_util.c
Normal file
160
src/backend_util.c
Normal file
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* bookmarkfs/src/backend_util.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "backend_util.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "ioctl.h"
|
||||
#include "xstd.h"
|
||||
|
||||
int
|
||||
basename_opendir (
|
||||
char *path,
|
||||
char **basename_ptr
|
||||
) {
|
||||
char *basename = strrchr(path, '/');
|
||||
if (basename != NULL) {
|
||||
*(basename++) = '\0';
|
||||
} else {
|
||||
basename = path;
|
||||
path = ".";
|
||||
}
|
||||
*basename_ptr = basename;
|
||||
|
||||
int fd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
log_printf("open(): %s: %s", path, xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int
|
||||
print_backend_opt_err_ (
|
||||
int err_type,
|
||||
char const *key,
|
||||
char const *val
|
||||
) {
|
||||
switch (err_type) {
|
||||
case BACKEND_OPT_ERR_BAD_KEY_:
|
||||
log_printf("bad backend option '%s'", key);
|
||||
break;
|
||||
|
||||
case BACKEND_OPT_ERR_BAD_VAL_:
|
||||
log_printf("bad value '%s' for backend option '%s'", val, key);
|
||||
break;
|
||||
|
||||
case BACKEND_OPT_ERR_HAS_VAL_:
|
||||
log_printf("unexpected value for backend option '%s'", key);
|
||||
break;
|
||||
|
||||
case BACKEND_OPT_ERR_NO_VAL_:
|
||||
log_printf("no value given for backend option '%s'", key);
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
validate_filename (
|
||||
char const *str,
|
||||
size_t str_len,
|
||||
char const **end_ptr
|
||||
) {
|
||||
switch (str_len) {
|
||||
case 2:
|
||||
if (str[1] != '.') {
|
||||
break;
|
||||
}
|
||||
// fallthrough
|
||||
case 1:
|
||||
if (str[0] != '.') {
|
||||
break;
|
||||
}
|
||||
return FILENAME_DOTDOT;
|
||||
|
||||
default:
|
||||
if (str_len <= NAME_MAX) {
|
||||
break;
|
||||
}
|
||||
// fallthrough
|
||||
case 0:
|
||||
return FILENAME_BADLEN;
|
||||
}
|
||||
|
||||
char const *end = memchr(str, '/', str_len);
|
||||
if (end != NULL) {
|
||||
if (end_ptr != NULL) {
|
||||
*end_ptr = end;
|
||||
}
|
||||
return FILENAME_BADCHAR;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
validate_filename_fsck (
|
||||
char const *str,
|
||||
size_t str_len,
|
||||
int *result_ptr,
|
||||
uint64_t *extra_ptr
|
||||
) {
|
||||
uint64_t extra;
|
||||
int result;
|
||||
|
||||
char const *end;
|
||||
int status = validate_filename(str, str_len, &end);
|
||||
switch (status) {
|
||||
case FILENAME_BADCHAR:
|
||||
extra = end - str;
|
||||
result = BOOKMARKFS_FSCK_RESULT_NAME_BADCHAR;
|
||||
break;
|
||||
|
||||
case FILENAME_BADLEN:
|
||||
extra = str_len;
|
||||
result = BOOKMARKFS_FSCK_RESULT_NAME_BADLEN;
|
||||
break;
|
||||
|
||||
case FILENAME_DOTDOT:
|
||||
extra = BOOKMARKFS_NAME_INVALID_REASON_DOTDOT;
|
||||
result = BOOKMARKFS_FSCK_RESULT_NAME_INVALID;
|
||||
break;
|
||||
|
||||
default:
|
||||
debug_assert(status == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*result_ptr = result;
|
||||
*extra_ptr = extra;
|
||||
return status;
|
||||
}
|
111
src/backend_util.h
Normal file
111
src/backend_util.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* bookmarkfs/src/backend_util.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_BACKEND_UTIL_H_
|
||||
#define BOOKMARKFS_BACKEND_UTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
#define BACKEND_OPT_ERR_BAD_KEY_ 0
|
||||
#define BACKEND_OPT_ERR_BAD_VAL_ 1
|
||||
#define BACKEND_OPT_ERR_HAS_VAL_ 2
|
||||
#define BACKEND_OPT_ERR_NO_VAL_ 3
|
||||
#define BACKEND_OPT_ERR_(err_type) \
|
||||
print_backend_opt_err_(BACKEND_OPT_ERR_##err_type##_, key_, val_)
|
||||
|
||||
#define BACKEND_OPT_START(opts) \
|
||||
for (; (opts) != NULL; (opts) = (opts)->next) { \
|
||||
char const *key_ = (opts)->key; \
|
||||
char const *val_ = (opts)->val; \
|
||||
if (0);
|
||||
#define BACKEND_OPT_END \
|
||||
else return BACKEND_OPT_ERR_(BAD_KEY); \
|
||||
}
|
||||
#define BACKEND_OPT_KEY(name) else if (0 == strcmp(name, key_))
|
||||
#define BACKEND_OPT_VAL(name) else if (0 == strcmp(name, val_))
|
||||
#define BACKEND_OPT_VAL_START \
|
||||
if (val_ == NULL) return BACKEND_OPT_ERR_(NO_VAL);
|
||||
#define BACKEND_OPT_VAL_END else return BACKEND_OPT_ERR_(BAD_VAL);
|
||||
#define BACKEND_OPT_BAD_VAL() BACKEND_OPT_ERR_(BAD_VAL)
|
||||
#define BACKEND_OPT_NO_VAL \
|
||||
if (val_ != NULL) return BACKEND_OPT_ERR_(HAS_VAL);
|
||||
#define BACKEND_OPT_VAL_STR val_
|
||||
|
||||
#define FILENAME_BADCHAR -1
|
||||
#define FILENAME_BADLEN -2
|
||||
#define FILENAME_DOTDOT -3
|
||||
|
||||
/**
|
||||
* Opens the parent directory of a file, and stores its basename
|
||||
* to `basename_ptr`.
|
||||
*
|
||||
* NOTE: The function may modify `path`, and the pointer stored to
|
||||
* `basename_ptr` is always within the address range of `path`.
|
||||
*
|
||||
* Returns the directory file descriptor if successful.
|
||||
* On error, -1 is returned, and errno is set.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
int
|
||||
basename_opendir (
|
||||
char *path,
|
||||
char **basename_ptr
|
||||
);
|
||||
|
||||
BOOKMARKFS_INTERNAL
|
||||
FUNCATTR_COLD
|
||||
int
|
||||
print_backend_opt_err_ (
|
||||
int err_type,
|
||||
char const *key,
|
||||
char const *val
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if a string is a valid path component.
|
||||
* If the string contains any NUL byte, the result is unspecified.
|
||||
*
|
||||
* Returns 0 if name is valid, FILENAME_* if not.
|
||||
* If returning FILENAME_BADCHAR, stores the pointer to the first
|
||||
* bad character to `end_ptr` unless it is NULL.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
int
|
||||
validate_filename (
|
||||
char const *str,
|
||||
size_t str_len,
|
||||
char const **end_ptr
|
||||
);
|
||||
|
||||
BOOKMARKFS_INTERNAL
|
||||
int
|
||||
validate_filename_fsck (
|
||||
char const *str,
|
||||
size_t str_len,
|
||||
int *result_ptr,
|
||||
uint64_t *extra_ptr
|
||||
);
|
||||
|
||||
#endif /* defined(BOOKMARKFS_BACKEND_UTIL_H_) */
|
208
src/bookmarkctl.c
Normal file
208
src/bookmarkctl.c
Normal file
|
@ -0,0 +1,208 @@
|
|||
/**
|
||||
* bookmarkfs/src/bookmarkctl.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fsck_util.h"
|
||||
#include "ioctl.h"
|
||||
#include "macros.h"
|
||||
#include "version.h"
|
||||
#include "xstd.h"
|
||||
|
||||
// Forward declaration start
|
||||
static int dispatch_subcmds (int, char *[]);
|
||||
static void print_help (void);
|
||||
static void print_version (void);
|
||||
static int subcmd_fsck (int, char *[]);
|
||||
static int subcmd_permd (int, char *[]);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
dispatch_subcmds (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
if (--argc < 1) {
|
||||
log_puts("command not given; run 'bookmarkctl help' for usage");
|
||||
return -1;
|
||||
}
|
||||
char const *cmd = *(++argv);
|
||||
|
||||
int status = 0;
|
||||
if (0 == strcmp("permd", cmd)) {
|
||||
status = subcmd_permd(argc, argv);
|
||||
} else if (0 == strcmp("fsck", cmd)) {
|
||||
status = subcmd_fsck(argc, argv);
|
||||
} else if (0 == strcmp("help", cmd)) {
|
||||
print_help();
|
||||
} else if (0 == strcmp("version", cmd)) {
|
||||
print_version();
|
||||
} else {
|
||||
log_printf("bad command '%s'; run 'bookmarkctl help' for usage", cmd);
|
||||
status = -1;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
print_help (void)
|
||||
{
|
||||
puts("Usage: bookmarkctl <cmd> [args]\n"
|
||||
"\n"
|
||||
"Main commands:\n"
|
||||
" permd Permute directory entries\n"
|
||||
" fsck Check filesystem\n"
|
||||
"\n"
|
||||
"Other commands:\n"
|
||||
" help Print help message\n"
|
||||
" version Print version information\n"
|
||||
"\n"
|
||||
"See the bookmarkctl(1) manpage for more information,\n"
|
||||
"or run 'info bookmarkfs' for the full user manual.\n"
|
||||
"\n"
|
||||
"Project homepage: <" BOOKMARKFS_HOMEPAGE_URL ">.");
|
||||
}
|
||||
|
||||
static void
|
||||
print_version (void)
|
||||
{
|
||||
printf("bookmarkctl (BookmarkFS) %d.%d.%d\n",
|
||||
BOOKMARKFS_VER_MAJOR, BOOKMARKFS_VER_MINOR, BOOKMARKFS_VER_PATCH);
|
||||
}
|
||||
|
||||
static int
|
||||
subcmd_fsck (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
#define FSCK_NARGS 2
|
||||
if (--argc != FSCK_NARGS) {
|
||||
log_printf("fsck: " STRINGIFY(FSCK_NARGS) " args expected, %d given",
|
||||
argc);
|
||||
return -1;
|
||||
}
|
||||
char const *path = *(++argv);
|
||||
char const *opstr = *(++argv);
|
||||
|
||||
if (0 == strcmp("list", opstr)) {
|
||||
// NOOP
|
||||
} else {
|
||||
log_printf("fsck: bad operation name '%s'", opstr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int dirfd = open(path, O_RDONLY | O_DIRECTORY);
|
||||
if (dirfd < 0) {
|
||||
log_printf("open(): %s: %s", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int status = -1;
|
||||
do {
|
||||
struct bookmarkfs_fsck_data fsck_data;
|
||||
status = ioctl(dirfd, BOOKMARKFS_IOC_FSCK_NEXT, &fsck_data);
|
||||
if (status < 0) {
|
||||
log_printf("ioctl(): %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
if (0 != explain_fsck_result(status, &fsck_data)) {
|
||||
break;
|
||||
}
|
||||
} while (status != BOOKMARKFS_FSCK_RESULT_END);
|
||||
close(dirfd);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
subcmd_permd (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
#define PERMD_NARGS 4
|
||||
if (--argc != PERMD_NARGS) {
|
||||
log_printf("permd: " STRINGIFY(PERMD_NARGS) " args expected, %d given",
|
||||
argc);
|
||||
return -1;
|
||||
}
|
||||
char const *path = *(++argv);
|
||||
char const *opstr = *(++argv);
|
||||
char const *name1 = *(++argv);
|
||||
char const *name2 = *(++argv);
|
||||
|
||||
struct bookmarkfs_permd_data permd_data;
|
||||
|
||||
if (0 == strcmp("swap", opstr)) {
|
||||
permd_data.op = BOOKMARKFS_PERMD_OP_SWAP;
|
||||
} else if (0 == strcmp("move-before", opstr)) {
|
||||
permd_data.op = BOOKMARKFS_PERMD_OP_MOVE_BEFORE;
|
||||
} else if (0 == strcmp("move-after", opstr)) {
|
||||
permd_data.op = BOOKMARKFS_PERMD_OP_MOVE_AFTER;
|
||||
} else {
|
||||
log_printf("permd: bad operation name '%s'", opstr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define COPY_NAME(dst, src) \
|
||||
if ((dst) + sizeof(dst) == stpncpy(dst, src, sizeof(dst))) { \
|
||||
log_printf("permd: %s: %s", src, strerror(ENAMETOOLONG)); \
|
||||
return -1; \
|
||||
}
|
||||
COPY_NAME(permd_data.name1, name1);
|
||||
COPY_NAME(permd_data.name2, name2);
|
||||
|
||||
int dirfd = open(path, O_RDONLY | O_DIRECTORY);
|
||||
if (dirfd < 0) {
|
||||
log_printf("open(): %s: %s", path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int status = ioctl(dirfd, BOOKMARKFS_IOC_PERMD, &permd_data);
|
||||
if (status < 0) {
|
||||
log_printf("ioctl(): %s", strerror(errno));
|
||||
}
|
||||
close(dirfd);
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
main (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
if (0 != dispatch_subcmds(argc, argv)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
33
src/common.h
Normal file
33
src/common.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* bookmarkfs/src/common.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_COMMON_H_
|
||||
#define BOOKMARKFS_COMMON_H_
|
||||
|
||||
struct bookmarkfs_conf_opt {
|
||||
char *key;
|
||||
char *val;
|
||||
|
||||
struct bookmarkfs_conf_opt *next;
|
||||
};
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_COMMON_H_) */
|
431
src/db.c
Normal file
431
src/db.c
Normal file
|
@ -0,0 +1,431 @@
|
|||
/**
|
||||
* bookmarkfs/src/db.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "db.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "xstd.h"
|
||||
|
||||
struct db_check_ctx {
|
||||
int status;
|
||||
};
|
||||
|
||||
struct db_pragma_ctx {
|
||||
char const *val;
|
||||
size_t val_len;
|
||||
|
||||
int status;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
static int db_check_cb (void *, sqlite3_stmt *);
|
||||
static int db_pragma_cb (void *, sqlite3_stmt *);
|
||||
static void safeincr (sqlite3_context *, int, sqlite3_value **);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
db_check_cb (
|
||||
void *user_data,
|
||||
sqlite3_stmt *stmt
|
||||
) {
|
||||
struct db_check_ctx *ctx = user_data;
|
||||
|
||||
size_t nbytes = sqlite3_column_bytes(stmt, 0);
|
||||
unsigned char const *result = sqlite3_column_text(stmt, 0);
|
||||
xassert(result != NULL);
|
||||
|
||||
ctx->status = 0;
|
||||
if (nbytes != strlen("ok") || 0 != memcmp("ok", result, nbytes)) {
|
||||
log_printf("%s: expected 'ok', got '%s'", sqlite3_sql(stmt), result);
|
||||
ctx->status = -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
db_pragma_cb (
|
||||
void *user_data,
|
||||
sqlite3_stmt *stmt
|
||||
) {
|
||||
struct db_pragma_ctx *ctx = user_data;
|
||||
|
||||
size_t val_len = sqlite3_column_bytes(stmt, 0);
|
||||
unsigned char const *val = sqlite3_column_text(stmt, 0);
|
||||
xassert(val != NULL);
|
||||
|
||||
ctx->status = 0;
|
||||
if (val_len != ctx->val_len || 0 != memcmp(ctx->val, val, val_len)) {
|
||||
log_printf("%s: expected '%s', got '%s'", sqlite3_sql(stmt),
|
||||
ctx->val, val);
|
||||
ctx->status = -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
safeincr (
|
||||
sqlite3_context *dbctx,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
) {
|
||||
debug_assert(argc == 1);
|
||||
sqlite3_value *val = argv[0];
|
||||
|
||||
if (unlikely(SQLITE_INTEGER != sqlite3_value_type(val))) {
|
||||
sqlite3_result_error(dbctx, "value is not an integer", -1);
|
||||
return;
|
||||
}
|
||||
int64_t ival = sqlite3_value_int64(val);
|
||||
if (unlikely(ival == INT64_MAX)) {
|
||||
sqlite3_result_error(dbctx, "integer overflow", -1);
|
||||
return;
|
||||
}
|
||||
sqlite3_result_int64(dbctx, ival + 1);
|
||||
}
|
||||
|
||||
int
|
||||
db_check (
|
||||
sqlite3 *db
|
||||
) {
|
||||
sqlite3_stmt *stmt = db_prepare(db, SQL_PRAGMA("quick_check(1)"), false);
|
||||
if (stmt == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct db_check_ctx qctx;
|
||||
ssize_t nrows = db_query(stmt, NULL, 0, false, db_check_cb, &qctx);
|
||||
if (nrows < 0) {
|
||||
return nrows;
|
||||
}
|
||||
xassert(nrows == 1);
|
||||
return qctx.status;
|
||||
}
|
||||
|
||||
int
|
||||
db_config (
|
||||
sqlite3 *db,
|
||||
struct db_conf_item const *items,
|
||||
size_t items_cnt
|
||||
) {
|
||||
for (size_t idx = 0; idx < items_cnt; ++idx) {
|
||||
struct db_conf_item const *item = items + idx;
|
||||
|
||||
int result;
|
||||
int status = sqlite3_db_config(db, item->op, item->value, &result);
|
||||
if (unlikely(status != SQLITE_OK)) {
|
||||
log_printf("sqlite3_db_config(): %s", sqlite3_errstr(status));
|
||||
return status;
|
||||
}
|
||||
if (unlikely(result != item->value)) {
|
||||
log_puts("sqlite3_db_config() failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
db_errno (
|
||||
int err
|
||||
) {
|
||||
if ((err & SQLITE_BUSY) == SQLITE_BUSY) {
|
||||
return EBUSY;
|
||||
}
|
||||
if (err == SQLITE_FULL) {
|
||||
return ENOSPC;
|
||||
}
|
||||
if (err == SQLITE_CONSTRAINT_UNIQUE) {
|
||||
// NOTE: This is an internal error and should not be
|
||||
// implicitly exposed to the filesystem.
|
||||
return EEXIST;
|
||||
}
|
||||
return EIO;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
db_exec (
|
||||
sqlite3 *db,
|
||||
char const *sql,
|
||||
size_t sql_len,
|
||||
sqlite3_stmt **stmt_ptr,
|
||||
int64_t *values_buf
|
||||
) {
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
if (stmt_ptr != NULL) {
|
||||
stmt = *stmt_ptr;
|
||||
if (stmt != NULL) {
|
||||
goto query;
|
||||
}
|
||||
}
|
||||
|
||||
stmt = db_prepare(db, sql, sql_len, stmt_ptr != NULL);
|
||||
if (unlikely(stmt == NULL)) {
|
||||
return -EIO;
|
||||
}
|
||||
if (stmt_ptr != NULL) {
|
||||
*stmt_ptr = stmt;
|
||||
}
|
||||
|
||||
query:
|
||||
return db_query(stmt, NULL, 0, stmt_ptr != NULL,
|
||||
values_buf == NULL ? NULL : db_query_i64_cb, values_buf);
|
||||
}
|
||||
|
||||
int
|
||||
db_fcntl (
|
||||
sqlite3 *db,
|
||||
int op,
|
||||
int val
|
||||
) {
|
||||
int status = sqlite3_file_control(db, "main", op, &val);
|
||||
if (status != SQLITE_OK) {
|
||||
log_printf("sqlite3_file_control: %s", sqlite3_errstr(status));
|
||||
return status;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sqlite3 *
|
||||
db_open (
|
||||
char const *path,
|
||||
bool readonly
|
||||
) {
|
||||
sqlite3 *db;
|
||||
int flags = SQLITE_OPEN_READWRITE;
|
||||
if (readonly) {
|
||||
flags = SQLITE_OPEN_READONLY;
|
||||
}
|
||||
flags |= SQLITE_OPEN_EXRESCODE;
|
||||
if (SQLITE_OK != sqlite3_open_v2(path, &db, flags, NULL)) {
|
||||
log_printf("sqlite3_open_v2(): %s", sqlite3_errmsg(db));
|
||||
goto fail;
|
||||
}
|
||||
if (!readonly) {
|
||||
if (0 != sqlite3_db_readonly(db, "main")) {
|
||||
log_puts("cannot open database for read/write");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return db;
|
||||
|
||||
fail:
|
||||
sqlite3_close(db);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
db_pragma (
|
||||
sqlite3 *db,
|
||||
struct db_pragma_item const *items,
|
||||
size_t items_cnt
|
||||
) {
|
||||
for (size_t idx = 0; idx < items_cnt; ++idx) {
|
||||
struct db_pragma_item const *item = items + idx;
|
||||
|
||||
char const *sql = item->sql;
|
||||
if (sql == NULL) {
|
||||
continue;
|
||||
}
|
||||
size_t sql_len = item->sql_len;
|
||||
size_t val_len = item->val_len;
|
||||
|
||||
struct db_pragma_ctx pragma_ctx = {
|
||||
.val = sql + sql_len - val_len,
|
||||
.val_len = val_len,
|
||||
};
|
||||
bool again = true;
|
||||
|
||||
query: ;
|
||||
sqlite3_stmt *stmt = db_prepare(db, sql, sql_len, false);
|
||||
if (unlikely(stmt == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t nrows
|
||||
= db_query(stmt, NULL, 0, false, db_pragma_cb, &pragma_ctx);
|
||||
if (nrows > 0) {
|
||||
if (pragma_ctx.status != 0) {
|
||||
return -1;
|
||||
}
|
||||
} else if (nrows == 0) {
|
||||
if (!again) {
|
||||
log_printf("bad pragma: %s", sql);
|
||||
return -1;
|
||||
}
|
||||
again = false;
|
||||
// strlen(" = ") == 3
|
||||
sql_len = pragma_ctx.val - sql - 3;
|
||||
goto query;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sqlite3_stmt *
|
||||
db_prepare (
|
||||
sqlite3 *db,
|
||||
char const *sql,
|
||||
size_t sql_len,
|
||||
bool persistent
|
||||
) {
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
int flags = 0;
|
||||
if (persistent) {
|
||||
flags |= SQLITE_PREPARE_PERSISTENT;
|
||||
}
|
||||
int err = sqlite3_prepare_v3(db, sql, sql_len, flags, &stmt, NULL);
|
||||
if (unlikely(err != SQLITE_OK)) {
|
||||
log_printf("sqlite3_prepare_v3(): %s, %s", sql, sqlite3_errmsg(db));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
db_query (
|
||||
sqlite3_stmt *stmt,
|
||||
struct db_stmt_bind_item const *bind_items,
|
||||
int bind_cnt,
|
||||
bool persistent,
|
||||
db_query_row_func *row_cb,
|
||||
void *user_data
|
||||
) {
|
||||
int err;
|
||||
ssize_t result = 0;
|
||||
|
||||
if (bind_items == NULL) {
|
||||
goto fetch_rows;
|
||||
}
|
||||
for (int i = 0; i < bind_cnt; ) {
|
||||
struct db_stmt_bind_item const *bind_item = bind_items + i++;
|
||||
|
||||
union db_value val = bind_item->val;
|
||||
ssize_t len = bind_item->len;
|
||||
if (len < 0) {
|
||||
if (val.i64 < 0) {
|
||||
continue;
|
||||
}
|
||||
err = sqlite3_bind_int64(stmt, i, val.i64);
|
||||
} else {
|
||||
if (val.text == NULL) {
|
||||
continue;
|
||||
}
|
||||
err = sqlite3_bind_text(stmt, i, val.text, len, SQLITE_STATIC);
|
||||
}
|
||||
if (unlikely(err != SQLITE_OK)) {
|
||||
log_printf("sqlite3_bind(): %d: %s", i, sqlite3_errstr(err));
|
||||
result = -err;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
fetch_rows:
|
||||
#if 1 && defined(BOOKMARKFS_DEBUG)
|
||||
{
|
||||
char *sql = sqlite3_expanded_sql(stmt);
|
||||
debug_printf("%s", sql);
|
||||
sqlite3_free(sql);
|
||||
}
|
||||
#endif
|
||||
for (size_t nrows = 0; ; ) {
|
||||
err = sqlite3_step(stmt);
|
||||
switch (err) {
|
||||
case SQLITE_ROW:
|
||||
break;
|
||||
|
||||
case SQLITE_DONE:
|
||||
no_more_rows:
|
||||
result = nrows;
|
||||
// fallthrough
|
||||
default:
|
||||
goto end;
|
||||
}
|
||||
|
||||
++nrows;
|
||||
if (row_cb == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (0 != row_cb(user_data, stmt)) {
|
||||
goto no_more_rows;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
if (persistent) {
|
||||
if (bind_cnt > 0) {
|
||||
sqlite3_clear_bindings(stmt);
|
||||
}
|
||||
err = sqlite3_reset(stmt);
|
||||
} else {
|
||||
err = sqlite3_finalize(stmt);
|
||||
}
|
||||
if (err != SQLITE_OK) {
|
||||
char const *errmsg;
|
||||
if ((err & SQLITE_ERROR) == SQLITE_ERROR) {
|
||||
errmsg = sqlite3_errmsg(sqlite3_db_handle(stmt));
|
||||
} else {
|
||||
errmsg = sqlite3_errstr(err);
|
||||
}
|
||||
log_printf("sqlite3_step(): %s", errmsg);
|
||||
result = -db_errno(err);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
db_query_i64_cb (
|
||||
void *user_data,
|
||||
sqlite3_stmt *stmt
|
||||
) {
|
||||
int64_t *arr = user_data;
|
||||
|
||||
int ncols = sqlite3_column_count(stmt);
|
||||
for (int i = 0; i < ncols; ++i) {
|
||||
arr[i] = sqlite3_column_int64(stmt, i);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
db_register_safeincr (
|
||||
sqlite3 *db
|
||||
) {
|
||||
int flags = SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS | SQLITE_DIRECTONLY;
|
||||
int err = sqlite3_create_function(db, "safeincr", 1, flags | SQLITE_UTF8,
|
||||
NULL, safeincr, NULL, NULL);
|
||||
if (unlikely(err != SQLITE_OK)) {
|
||||
log_printf("sqlite3_create_function(): %s", sqlite3_errstr(err));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
167
src/db.h
Normal file
167
src/db.h
Normal file
|
@ -0,0 +1,167 @@
|
|||
/**
|
||||
* bookmarkfs/src/db.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_DB_H_
|
||||
#define BOOKMARKFS_DB_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
#define SQL_PRAGMA(p) STR_WITHLEN("PRAGMA " p)
|
||||
#define SQL_PRAGMA_ITEM(p, v) \
|
||||
(struct db_pragma_item) { SQL_PRAGMA(p " = " v), strlen(v) }
|
||||
|
||||
#define DB_ITEMS_CNT_(arr, name) ( sizeof(arr) / sizeof(struct name##_item) )
|
||||
#define DB_PRAGMA_ITEMS_CNT(arr) DB_ITEMS_CNT_(arr, db_pragma)
|
||||
#define DB_CONFIG_ITEMS_CNT(arr) DB_ITEMS_CNT_(arr, db_conf)
|
||||
#define DB_BIND_ITEMS_CNT(arr) DB_ITEMS_CNT_(arr, db_stmt_bind)
|
||||
|
||||
#define DB_QUERY_BIND_TEXT(s, l) { { .text = (s) }, (l) }
|
||||
#define DB_QUERY_BIND_INT64(v) { { .i64 = (v) }, -1 }
|
||||
|
||||
#define db_txn_begin(db, stmt_ptr) \
|
||||
db_exec(db, STR_WITHLEN("BEGIN IMMEDIATE"), stmt_ptr, NULL)
|
||||
#define db_txn_commit(db, stmt_ptr) \
|
||||
db_exec(db, STR_WITHLEN("END"), stmt_ptr, NULL)
|
||||
#define db_txn_rollback(db, stmt_ptr) \
|
||||
db_exec(db, STR_WITHLEN("ROLLBACK"), stmt_ptr, NULL)
|
||||
|
||||
typedef int (db_query_row_func) (
|
||||
void *user_data,
|
||||
sqlite3_stmt *stmt
|
||||
);
|
||||
|
||||
union db_value {
|
||||
int64_t i64;
|
||||
char const *text;
|
||||
};
|
||||
|
||||
struct db_conf_item {
|
||||
int op;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct db_pragma_item {
|
||||
char const *sql;
|
||||
size_t sql_len;
|
||||
size_t val_len;
|
||||
};
|
||||
|
||||
struct db_stmt_bind_item {
|
||||
union db_value val;
|
||||
ssize_t len;
|
||||
};
|
||||
|
||||
int
|
||||
db_check (
|
||||
sqlite3 *db
|
||||
);
|
||||
|
||||
int
|
||||
db_config (
|
||||
sqlite3 *db,
|
||||
struct db_conf_item const *items,
|
||||
size_t items_cnt
|
||||
);
|
||||
|
||||
int
|
||||
db_errno (
|
||||
int err
|
||||
);
|
||||
|
||||
ssize_t
|
||||
db_exec (
|
||||
sqlite3 *db,
|
||||
char const *sql,
|
||||
size_t sql_len,
|
||||
sqlite3_stmt **stmt_ptr,
|
||||
int64_t *values_buf
|
||||
);
|
||||
|
||||
int
|
||||
db_fcntl (
|
||||
sqlite3 *db,
|
||||
int op,
|
||||
int val
|
||||
);
|
||||
|
||||
sqlite3 *
|
||||
db_open (
|
||||
char const *path,
|
||||
bool readonly
|
||||
);
|
||||
|
||||
int
|
||||
db_pragma (
|
||||
sqlite3 *db,
|
||||
struct db_pragma_item const *items,
|
||||
size_t items_cnt
|
||||
);
|
||||
|
||||
sqlite3_stmt *
|
||||
db_prepare (
|
||||
sqlite3 *db,
|
||||
char const *sql,
|
||||
size_t sql_len,
|
||||
bool persistent
|
||||
);
|
||||
|
||||
ssize_t
|
||||
db_query (
|
||||
sqlite3_stmt *stmt,
|
||||
struct db_stmt_bind_item const *bind_items,
|
||||
int bind_cnt,
|
||||
bool persistent,
|
||||
db_query_row_func *row_cb,
|
||||
void *user_data
|
||||
);
|
||||
|
||||
int
|
||||
db_query_i64_cb (
|
||||
void *user_data,
|
||||
sqlite3_stmt *stmt
|
||||
);
|
||||
|
||||
/**
|
||||
* Registers an application-defined function `safeincr()`
|
||||
* to the given db connection.
|
||||
*
|
||||
* Use `safeincr(val)` in place of `(val + 1)`, otherwise
|
||||
* SQLite converts the result to REAL upon integer overflow.
|
||||
* See: <https://www.sqlite.org/datatype3.html#operators>.
|
||||
*
|
||||
* The `safeincr()` function fails if given a non-integer
|
||||
* argument, or if the argument value equals to INT64_MAX.
|
||||
*/
|
||||
int
|
||||
db_register_safeincr (
|
||||
sqlite3 *db
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_DB_H_) */
|
125
src/defs.h
Normal file
125
src/defs.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* bookmarkfs/src/defs.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_DEFS_H_
|
||||
#define BOOKMARKFS_DEFS_H_
|
||||
|
||||
#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) \
|
||||
|| (__STDC_HOSTED__ != 1)
|
||||
# error "unsupported compiler"
|
||||
#elif __STDC_VERSION__ >= 202311L
|
||||
# define HAVE_STDC_23 1
|
||||
#elif __STDC_VERSION__ >= 201112L
|
||||
# define HAVE_STDC_11 1
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FUNC_ATTRIBUTE_COLD
|
||||
# define FUNCATTR_COLD __attribute__((cold))
|
||||
#else
|
||||
# define FUNCATTR_COLD
|
||||
#endif /* defined(HAVE_FUNC_ATTRIBUTE_COLD) */
|
||||
|
||||
#ifdef HAVE_FUNC_ATTRIBUTE_DESTRUCTOR
|
||||
# define FUNCATTR_DTOR __attribute__((destructor))
|
||||
#else
|
||||
# define FUNCATTR_DTOR
|
||||
#endif /* defined(HAVE_FUNC_ATTRIBUTE_DESTRUCTOR) */
|
||||
|
||||
#ifdef HAVE_FUNC_ATTRIBUTE_MALLOC
|
||||
# define FUNCATTR_MALLOC __attribute__((malloc))
|
||||
#else
|
||||
# define FUNCATTR_MALLOC
|
||||
#endif /* defined(HAVE_FUNC_ATTRIBUTE_MALLOC) */
|
||||
|
||||
#if defined(HAVE_STDC_23)
|
||||
# define FUNCATTR_NORETURN [[noreturn]]
|
||||
#elif defined(HAVE_STDC_11)
|
||||
# define FUNCATTR_NORETURN _Noreturn
|
||||
#else
|
||||
# ifdef HAVE_FUNC_ATTRIBUTE_NORETURN
|
||||
# define FUNCATTR_NORETURN __attribute__((noreturn))
|
||||
# else
|
||||
# define FUNCATTR_NORETURN
|
||||
# endif /* defined(HAVE_FUNC_ATTRIBUTE_NORETURN) */
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FUNC_ATTRIBUTE_RETURNS_NONNULL
|
||||
# define FUNCATTR_RETURNS_NONNULL __attribute__((returns_nonnull))
|
||||
#else
|
||||
# define FUNCATTR_RETURNS_NONNULL
|
||||
#endif /* defined(HAVE_FUNC_ATTRIBUTE_RETURNS_NONNULL) */
|
||||
|
||||
#ifdef HAVE_FUNC_ATTRIBUTE_VISIBILITY
|
||||
# define BOOKMARKFS_API __attribute__((visibility("default")))
|
||||
# define BOOKMARKFS_INTERNAL __attribute__((visibility("hidden")))
|
||||
#else
|
||||
# define BOOKMARKFS_API
|
||||
# define BOOKMARKFS_INTERNAL
|
||||
#endif /* defined(HAVE_FUNC_ATTRIBUTE_VISIBILITY) */
|
||||
|
||||
#if defined(HAVE_STDC_23)
|
||||
# define UNUSED_VAR(name)
|
||||
#else
|
||||
# ifdef HAVE_VAR_ATTRIBUTE_UNUSED
|
||||
# define VARATTR_UNUSED_ __attribute__((unused))
|
||||
# else
|
||||
# define VARATTR_UNUSED_
|
||||
# endif /* defined(HAVE_VAR_ATTRIBUTE_UNUSED) */
|
||||
# define UNUSED_VAR(name) name##_unused_ VARATTR_UNUSED_
|
||||
#endif /* defined(HAVE_STDC_23) */
|
||||
|
||||
#if defined(HAVE_STDC_23)
|
||||
# define BOOKMARKFS_TLS thread_local
|
||||
#elif defined(HAVE_STDC_11)
|
||||
# define BOOKMARKFS_TLS _Thread_local
|
||||
// Incomprehensive list. Add more if needed.
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
# define BOOKMARKFS_TLS __thread
|
||||
#endif
|
||||
|
||||
#if defined(__FreeBSD__) || (defined(__linux__) && defined(_GNU_SOURCE))
|
||||
# define HAVE_PIPE2 1
|
||||
#else
|
||||
# define O_DIRECT 0
|
||||
# define O_PATH 0 // FreeBSD supports O_PATH since 13.1
|
||||
#endif
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
# define O_RESOLVE_BENEATH 0
|
||||
# define PROT_MAX(prot) 0
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
# define ENOATTR ENODATA
|
||||
#endif
|
||||
|
||||
#ifdef __FILE_NAME__
|
||||
# define FILE_NAME_ __FILE_NAME__
|
||||
#else
|
||||
// NOTE: Using __FILE__ may produce ugly logs and break reproducible build.
|
||||
// Workaround with `-fmacro-prefix-map=${srcdir}/=` CPPFLAGS.
|
||||
# define FILE_NAME_ __FILE__
|
||||
#endif
|
||||
|
||||
#define BOOKMARKFS_HOMEPAGE_URL "https://nongnu.org/bookmarkfs"
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_DEFS_H_) */
|
161
src/frontend_util.c
Normal file
161
src/frontend_util.c
Normal file
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* bookmarkfs/src/frontend_util.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "frontend_util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "xstd.h"
|
||||
|
||||
// Forward declaration start
|
||||
static void const * bookmarkfs_load (char const *, char const *, void **);
|
||||
// Forward declaration end
|
||||
|
||||
static void const *
|
||||
bookmarkfs_load (
|
||||
char const *name,
|
||||
char const *prefix,
|
||||
void **handle_ptr
|
||||
) {
|
||||
#define MODULE_LIB_NAME(prefix, name) \
|
||||
"$ORIGIN/../lib/bookmarkfs/" prefix "_" name ".so"
|
||||
#define MODULE_SYM_NAME(prefix, name) "bookmarkfs_" prefix "_" name
|
||||
char *lib_name, *sym_name;
|
||||
|
||||
char const *sep = strchr(name, ':');
|
||||
if (sep == NULL) {
|
||||
xasprintf(&lib_name, MODULE_LIB_NAME("%s", "%s"), prefix, name);
|
||||
xasprintf(&sym_name, MODULE_SYM_NAME("%s", "%s"), prefix, name);
|
||||
} else {
|
||||
xasprintf(&lib_name, "%.*s", (int)(sep - name), name);
|
||||
xasprintf(&sym_name, "%s", ++sep);
|
||||
}
|
||||
|
||||
void const *impl = NULL;
|
||||
void *handle = dlopen(lib_name, RTLD_NOW);
|
||||
if (handle == NULL) {
|
||||
log_printf("dlopen(): %s", dlerror());
|
||||
goto end;
|
||||
}
|
||||
*handle_ptr = handle;
|
||||
|
||||
impl = dlsym(handle, sym_name);
|
||||
if (impl == NULL) {
|
||||
log_printf("dlsym(): %s", dlerror());
|
||||
goto end;
|
||||
}
|
||||
|
||||
end:
|
||||
free(lib_name);
|
||||
free(sym_name);
|
||||
return impl;
|
||||
}
|
||||
|
||||
struct bookmarkfs_backend const *
|
||||
bookmarkfs_load_backend (
|
||||
char const *name,
|
||||
void **handle_ptr
|
||||
) {
|
||||
return bookmarkfs_load(name, "backend", handle_ptr);
|
||||
}
|
||||
|
||||
struct bookmarkfs_fsck_handler const *
|
||||
bookmarkfs_load_fsck_handler (
|
||||
char const *name,
|
||||
void **handle_ptr
|
||||
) {
|
||||
return bookmarkfs_load(name, "fsck_handler", handle_ptr);
|
||||
}
|
||||
|
||||
void
|
||||
bookmarkfs_opts_add (
|
||||
struct bookmarkfs_conf_opt **opts_ptr,
|
||||
char *opt_str
|
||||
) {
|
||||
struct bookmarkfs_conf_opt *opt = xmalloc(sizeof(*opt));
|
||||
*opt = (struct bookmarkfs_conf_opt) {
|
||||
.key = opt_str,
|
||||
.next = *opts_ptr,
|
||||
};
|
||||
*opts_ptr = opt;
|
||||
|
||||
char *sep = strchr(opt_str, '=');
|
||||
if (sep != NULL) {
|
||||
*sep = '\0';
|
||||
opt->val = sep + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
bookmarkfs_opts_free (
|
||||
struct bookmarkfs_conf_opt *opts
|
||||
) {
|
||||
for (struct bookmarkfs_conf_opt *next; opts != NULL; opts = next) {
|
||||
next = opts->next;
|
||||
free(opts);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
bookmarkfs_unload (
|
||||
void *handle
|
||||
) {
|
||||
if (handle == NULL) {
|
||||
return;
|
||||
}
|
||||
if (0 != dlclose(handle)) {
|
||||
log_printf("dlclose(): %s", dlerror());
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
print_subopt_err_ (
|
||||
int err_type,
|
||||
char const *optstr
|
||||
) {
|
||||
switch (err_type) {
|
||||
case SUBOPT_ERR_BAD_KEY_:
|
||||
log_printf("bad option '-o %s'", optstr);
|
||||
break;
|
||||
|
||||
case SUBOPT_ERR_NO_VAL_:
|
||||
log_printf("no value given for option '-o %s'", optstr);
|
||||
break;
|
||||
|
||||
case SUBOPT_ERR_HAS_VAL_:
|
||||
log_printf("unexpected value for option '-o %s'", optstr);
|
||||
break;
|
||||
|
||||
case SUBOPT_ERR_BAD_VAL_:
|
||||
log_printf("invalid value for option '-o %s'", optstr);
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
114
src/frontend_util.h
Normal file
114
src/frontend_util.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* bookmarkfs/src/frontend_util.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_FRONTEND_UTIL_H_
|
||||
#define BOOKMARKFS_FRONTEND_UTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "backend.h"
|
||||
#include "defs.h"
|
||||
#include "fsck_handler.h"
|
||||
|
||||
#define getopt_foreach(argc, argv, optstr) \
|
||||
for (int opt_; -1 != (opt_ = getopt(argc, argv, optstr)); ) \
|
||||
switch (opt_)
|
||||
|
||||
#define SUBOPT_START(opts) \
|
||||
while (optarg[0] != '\0') { \
|
||||
char *val_; \
|
||||
char *suboptstr_ = optarg; \
|
||||
/* Safe to cast away constness - POSIX guarantees that
|
||||
getsubopt() does not attempt to modify the strings. */ \
|
||||
int subopt_ = getsubopt(&optarg, (char *const *)(opts), &val_); \
|
||||
switch (subopt_) {
|
||||
#define SUBOPT_OPT(opt) break; case (opt):
|
||||
#define SUBOPT_OPT_DEFAULT() break; default:
|
||||
#ifdef __FreeBSD__
|
||||
# define SUBOPT_OPT_FALLBACK_WORKAROUND_ if (val_ != NULL) val_[-1] = '=';
|
||||
#else
|
||||
# define SUBOPT_OPT_FALLBACK_WORKAROUND_
|
||||
#endif
|
||||
// Upon getsubopt() returning -1, the value of `optionp`
|
||||
// is left unspecified by POSIX.
|
||||
//
|
||||
// The BSDs store the original option string to `suboptarg`;
|
||||
// glibc stores it to `optionp`; musl does neither.
|
||||
//
|
||||
// We should not depend on unspecified behavior.
|
||||
#define SUBOPT_OPT_FALLBACK() SUBOPT_OPT(-1) SUBOPT_OPT_FALLBACK_WORKAROUND_
|
||||
#define SUBOPT_END \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
break;
|
||||
#define SUBOPT_STR (suboptstr_)
|
||||
#define SUBOPT_VAL (val_)
|
||||
|
||||
#define SUBOPT_ERR_BAD_KEY_ 0
|
||||
#define SUBOPT_ERR_NO_VAL_ 1
|
||||
#define SUBOPT_ERR_HAS_VAL_ 2
|
||||
#define SUBOPT_ERR_BAD_VAL_ 3
|
||||
#define SUBOPT_ERR_(err_type) \
|
||||
print_subopt_err_(SUBOPT_ERR_##err_type##_, suboptstr_)
|
||||
#define SUBOPT_HAS_VAL if (val_ == NULL) return SUBOPT_ERR_(NO_VAL);
|
||||
#define SUBOPT_NO_VAL if (val_ != NULL) return SUBOPT_ERR_(HAS_VAL);
|
||||
#define SUBOPT_ERR_BAD_KEY() SUBOPT_ERR_(BAD_KEY)
|
||||
#define SUBOPT_ERR_BAD_VAL() SUBOPT_ERR_(BAD_VAL)
|
||||
|
||||
struct bookmarkfs_backend const *
|
||||
bookmarkfs_load_backend (
|
||||
char const *name,
|
||||
void **handle_ptr
|
||||
);
|
||||
|
||||
struct bookmarkfs_fsck_handler const *
|
||||
bookmarkfs_load_fsck_handler (
|
||||
char const *name,
|
||||
void **handle_ptr
|
||||
);
|
||||
|
||||
void
|
||||
bookmarkfs_opts_add (
|
||||
struct bookmarkfs_conf_opt **opts_ptr,
|
||||
char *opt_str
|
||||
);
|
||||
|
||||
void
|
||||
bookmarkfs_opts_free (
|
||||
struct bookmarkfs_conf_opt *opts
|
||||
);
|
||||
|
||||
void
|
||||
bookmarkfs_unload (
|
||||
void *handle
|
||||
);
|
||||
|
||||
FUNCATTR_COLD
|
||||
int
|
||||
print_subopt_err_ (
|
||||
int err_type,
|
||||
char const *optstr
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_FRONTEND_UITL_H_) */
|
2395
src/fs_ops.c
Normal file
2395
src/fs_ops.c
Normal file
File diff suppressed because it is too large
Load diff
273
src/fs_ops.h
Normal file
273
src/fs_ops.h
Normal file
|
@ -0,0 +1,273 @@
|
|||
/**
|
||||
* bookmarkfs/src/fs_ops.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_FS_OPS_H_
|
||||
#define BOOKMARKFS_FS_OPS_H_
|
||||
|
||||
#define FUSE_USE_VERSION 35
|
||||
#include <fuse_lowlevel.h>
|
||||
|
||||
#include "backend.h"
|
||||
|
||||
struct fs_flags {
|
||||
unsigned accmode : 9;
|
||||
unsigned ctime : 1;
|
||||
unsigned eol : 1;
|
||||
unsigned exclusive : 1;
|
||||
unsigned has_keyword : 1;
|
||||
unsigned readonly : 1;
|
||||
};
|
||||
|
||||
void
|
||||
fs_init_backend (
|
||||
struct bookmarkfs_backend const *backend_impl,
|
||||
void *backend_ctx
|
||||
);
|
||||
|
||||
void
|
||||
fs_init_fuse (
|
||||
struct fuse_session *session
|
||||
);
|
||||
|
||||
void
|
||||
fs_init_metadata (
|
||||
uint64_t bookmarks_root_id,
|
||||
uint64_t tags_root_id,
|
||||
char const *bookmark_attrs
|
||||
);
|
||||
|
||||
void
|
||||
fs_init_opts (
|
||||
struct fs_flags flags,
|
||||
size_t file_max
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_create (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t parent,
|
||||
char const *name,
|
||||
mode_t mode,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_destroy (
|
||||
void *user_data
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_fsync (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
int data_sync,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_fsyncdir (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
int data_sync,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_getattr (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_getxattr (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
char const *name,
|
||||
size_t buf_max
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_init (
|
||||
void *user_data,
|
||||
struct fuse_conn_info *conn
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_ioctl (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
unsigned cmd,
|
||||
void *arg,
|
||||
struct fuse_file_info *fi,
|
||||
unsigned flags,
|
||||
void const *ibuf,
|
||||
size_t ibuf_len,
|
||||
size_t obuf_len
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_link (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
fuse_ino_t new_parent,
|
||||
char const *new_name
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_listxattr (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
size_t buf_max
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_lookup (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t parent,
|
||||
char const *name
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_mkdir (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t parent,
|
||||
char const *name,
|
||||
mode_t mode
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_open (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_opendir (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_read (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
size_t buf_max,
|
||||
off_t off,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_readdir (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
size_t buf_max,
|
||||
off_t off,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_readdirplus (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
size_t buf_max,
|
||||
off_t off,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_release (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_releasedir (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_rename (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t parent,
|
||||
char const *name,
|
||||
fuse_ino_t new_parent,
|
||||
char const *new_name,
|
||||
unsigned flags
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_removexattr (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
char const *name
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_rmdir (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t parent,
|
||||
char const *name
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_setattr (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
struct stat *attr,
|
||||
int attr_mask,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_setxattr (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
char const *name,
|
||||
char const *val,
|
||||
size_t val_len,
|
||||
int flags
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_unlink (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t parent,
|
||||
char const *name
|
||||
);
|
||||
|
||||
void
|
||||
fs_op_write (
|
||||
fuse_req_t req,
|
||||
fuse_ino_t ino,
|
||||
char const *buf,
|
||||
size_t buf_len,
|
||||
off_t off,
|
||||
struct fuse_file_info *fi
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_FS_OPS_H_) */
|
506
src/fsck.c
Normal file
506
src/fsck.c
Normal file
|
@ -0,0 +1,506 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck.c
|
||||
*
|
||||
* Chromium backend for BookmarkFS.
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
# include <readline/history.h>
|
||||
# include <readline/readline.h>
|
||||
#endif
|
||||
|
||||
#include "frontend_util.h"
|
||||
#include "fsck_handler.h"
|
||||
#include "fsck_ops.h"
|
||||
#include "ioctl.h"
|
||||
#include "lib.h"
|
||||
#include "xstd.h"
|
||||
|
||||
struct fsck_ctx {
|
||||
struct bookmarkfs_fsck_ops const *ops;
|
||||
struct bookmarkfs_fsck_handler const *handler;
|
||||
|
||||
void *ops_ctx;
|
||||
void *handler_ctx;
|
||||
|
||||
void *backend_handle;
|
||||
void *handler_handle;
|
||||
};
|
||||
|
||||
struct fsck_info {
|
||||
struct bookmarkfs_conf_opt *backend_opts;
|
||||
struct bookmarkfs_conf_opt *handler_opts;
|
||||
|
||||
char const *backend_name;
|
||||
char const *handler_name;
|
||||
char *path;
|
||||
char const *rl_app_name;
|
||||
|
||||
struct {
|
||||
unsigned no_fsck : 1;
|
||||
unsigned no_landlock : 1;
|
||||
unsigned no_sandbox : 1;
|
||||
unsigned print_help : 1;
|
||||
unsigned print_version : 1;
|
||||
unsigned interactive : 1;
|
||||
unsigned readonly : 1;
|
||||
unsigned recursive : 1;
|
||||
unsigned type : BOOKMARKFS_BOOKMARK_TYPE_BITS;
|
||||
} flags;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
static int init_readline (struct fsck_info const *);
|
||||
#endif
|
||||
|
||||
static void destroy_ctx (struct fsck_ctx const *);
|
||||
static int do_fsck (struct fsck_ctx const *);
|
||||
static int enter_sandbox (struct fsck_ctx const *, struct fsck_info const *);
|
||||
static int init_all (struct fsck_ctx *, int, char *[]);
|
||||
static int init_backend (struct fsck_ctx *, struct fsck_info const *);
|
||||
static int init_handler (struct fsck_ctx *, struct fsck_info const *);
|
||||
static int parse_opts (struct fsck_info *, int, char *[]);
|
||||
// Forward declaration end
|
||||
|
||||
extern struct bookmarkfs_fsck_handler const fsck_handler_simple;
|
||||
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
|
||||
static int
|
||||
init_readline (
|
||||
struct fsck_info const *info
|
||||
) {
|
||||
if (!info->flags.interactive) {
|
||||
return 0;
|
||||
}
|
||||
if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
|
||||
log_puts("standard input and/or output is not a terminal");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rl_readline_name = info->rl_app_name;
|
||||
rl_inhibit_completion = 1;
|
||||
|
||||
rl_initialize();
|
||||
using_history();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* defined(BOOKMARKFS_INTERACTIVE_FSCK) */
|
||||
|
||||
static void
|
||||
destroy_ctx (
|
||||
struct fsck_ctx const *ctx
|
||||
) {
|
||||
if (ctx->ops != NULL) {
|
||||
ctx->ops->destroy(ctx->ops_ctx);
|
||||
}
|
||||
if (ctx->handler != NULL) {
|
||||
ctx->handler->destroy(ctx->handler_ctx);
|
||||
}
|
||||
bookmarkfs_unload(ctx->backend_handle);
|
||||
bookmarkfs_unload(ctx->handler_handle);
|
||||
}
|
||||
|
||||
static int
|
||||
do_fsck (
|
||||
struct fsck_ctx const *ctx
|
||||
) {
|
||||
if (ctx->ops == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
union bookmarkfs_fsck_handler_data data;
|
||||
int result = ctx->ops->next(ctx->ops_ctx, &data.entry);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (result == BOOKMARKFS_FSCK_RESULT_END) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
run_handler:
|
||||
result = ctx->handler->run(ctx->handler_ctx, result, &data);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (result == BOOKMARKFS_FSCK_NEXT) {
|
||||
continue;
|
||||
}
|
||||
if (result == BOOKMARKFS_FSCK_STOP) {
|
||||
return 0;
|
||||
}
|
||||
if (result == BOOKMARKFS_FSCK_APPLY) {
|
||||
result = ctx->ops->apply(ctx->ops_ctx, &data.entry);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
goto run_handler;
|
||||
}
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
if (result == BOOKMARKFS_FSCK_USER_INPUT) {
|
||||
putchar('\n');
|
||||
data.str = readline(data.str);
|
||||
if (data.str == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (rl_end > 0) {
|
||||
add_history(data.str);
|
||||
}
|
||||
result = -1;
|
||||
goto run_handler;
|
||||
}
|
||||
#endif
|
||||
result = ctx->ops->control(ctx->ops_ctx, result);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
result = -1;
|
||||
goto run_handler;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
enter_sandbox (
|
||||
struct fsck_ctx const *ctx,
|
||||
struct fsck_info const *info
|
||||
) {
|
||||
if (info->flags.no_sandbox) {
|
||||
return 0;
|
||||
}
|
||||
if (0 != ctx->ops->sandbox(ctx->ops_ctx)) {
|
||||
return -1;
|
||||
}
|
||||
debug_puts("sandbox entered");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_all (
|
||||
struct fsck_ctx *ctx,
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
struct fsck_info info = {
|
||||
.rl_app_name = "fsck.bookmarkfs",
|
||||
.flags = {
|
||||
.readonly = 1,
|
||||
.type = BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK,
|
||||
},
|
||||
};
|
||||
|
||||
int status = -1;
|
||||
if (0 != parse_opts(&info, argc, argv)) {
|
||||
goto end;
|
||||
}
|
||||
if (0 != init_handler(ctx, &info)) {
|
||||
if (info.flags.no_fsck) {
|
||||
status = 0;
|
||||
}
|
||||
goto end;
|
||||
}
|
||||
if (0 != init_backend(ctx, &info)) {
|
||||
if (info.flags.no_fsck) {
|
||||
status = 0;
|
||||
}
|
||||
goto end;
|
||||
}
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
if (0 != init_readline(&info)) {
|
||||
goto end;
|
||||
}
|
||||
#endif
|
||||
if (0 != enter_sandbox(ctx, &info)) {
|
||||
goto end;
|
||||
}
|
||||
status = 0;
|
||||
|
||||
end:
|
||||
bookmarkfs_opts_free(info.backend_opts);
|
||||
bookmarkfs_opts_free(info.handler_opts);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
init_backend (
|
||||
struct fsck_ctx *ctx,
|
||||
struct fsck_info const *info
|
||||
) {
|
||||
struct bookmarkfs_backend const *backend = NULL;
|
||||
struct bookmarkfs_fsck_ops const *ops = &fsck_online_ops;
|
||||
if (info->backend_name != NULL) {
|
||||
backend = bookmarkfs_load_backend(info->backend_name,
|
||||
&ctx->backend_handle);
|
||||
if (backend == NULL || ctx->backend_handle == NULL) {
|
||||
return -1;
|
||||
}
|
||||
ops = &fsck_offline_ops;
|
||||
}
|
||||
|
||||
uint32_t backend_flags = BOOKMARKFS_FRONTEND_FSCK;
|
||||
if (info->flags.no_fsck) {
|
||||
if (info->flags.print_help) {
|
||||
backend_flags |= BOOKMARKFS_BACKEND_INFO_HELP;
|
||||
} else {
|
||||
backend_flags |= BOOKMARKFS_BACKEND_INFO_VERSION;
|
||||
}
|
||||
ops->info(backend, backend_flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (backend != NULL && backend->backend_init != NULL) {
|
||||
backend_flags |= BOOKMARKFS_BACKEND_LIB_READY;
|
||||
if (0 != backend->backend_init(backend_flags)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t flags = info->flags.type << BOOKMARKFS_BOOKMARK_TYPE_SHIFT;
|
||||
if (info->flags.no_sandbox) {
|
||||
flags |= BOOKMARKFS_BACKEND_NO_SANDBOX;
|
||||
}
|
||||
if (info->flags.no_landlock) {
|
||||
flags |= BOOKMARKFS_BACKEND_NO_LANDLOCK;
|
||||
}
|
||||
if (info->flags.readonly) {
|
||||
flags |= BOOKMARKFS_BACKEND_READONLY;
|
||||
}
|
||||
if (info->flags.recursive) {
|
||||
flags |= BOOKMARKFS_FSCK_OP_RECURSIVE;
|
||||
}
|
||||
if (0 != ops->create(backend, info->path, info->backend_opts, flags,
|
||||
&ctx->ops_ctx)
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
ctx->ops = ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_handler (
|
||||
struct fsck_ctx *ctx,
|
||||
struct fsck_info const *info
|
||||
) {
|
||||
struct bookmarkfs_fsck_handler const *handler = &fsck_handler_simple;
|
||||
if (info->handler_name != NULL) {
|
||||
handler = bookmarkfs_load_fsck_handler(info->handler_name,
|
||||
&ctx->handler_handle);
|
||||
if (handler == NULL || ctx->handler_handle == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
if (info->flags.no_fsck) {
|
||||
if (info->handler_name == NULL) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t flags = BOOKMARKFS_FSCK_HANDLER_INFO_HELP;
|
||||
if (info->flags.print_version) {
|
||||
flags = BOOKMARKFS_FSCK_HANDLER_INFO_VERSION;
|
||||
}
|
||||
handler->info(flags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 != bookmarkfs_lib_init()) {
|
||||
return -1;
|
||||
}
|
||||
uint32_t flags = 0;
|
||||
if (info->flags.interactive) {
|
||||
flags |= BOOKMARKFS_FSCK_HANDLER_INTERACTIVE;
|
||||
}
|
||||
if (info->flags.readonly) {
|
||||
flags |= BOOKMARKFS_BACKEND_READONLY;
|
||||
}
|
||||
if (0 != handler->create(info->handler_opts, flags, &ctx->handler_ctx)) {
|
||||
return -1;
|
||||
}
|
||||
ctx->handler = handler;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_opts (
|
||||
struct fsck_info *info,
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
enum {
|
||||
BOOKMARKFS_OPT_BACKEND,
|
||||
BOOKMARKFS_OPT_HANDLER,
|
||||
BOOKMARKFS_OPT_NO_LANDLOCK,
|
||||
BOOKMARKFS_OPT_NO_SANDBOX,
|
||||
BOOKMARKFS_OPT_REPAIR,
|
||||
BOOKMARKFS_OPT_RL_APP,
|
||||
BOOKMARKFS_OPT_TYPE,
|
||||
|
||||
BOOKMARKFS_OPT_END_,
|
||||
};
|
||||
|
||||
#define BOOKMARKFS_OPT(name, token) [BOOKMARKFS_OPT_##name] = (token)
|
||||
char const *const opts[] = {
|
||||
BOOKMARKFS_OPT(BACKEND, "backend"),
|
||||
BOOKMARKFS_OPT(HANDLER, "handler"),
|
||||
BOOKMARKFS_OPT(NO_LANDLOCK, "no_landlock"),
|
||||
BOOKMARKFS_OPT(NO_SANDBOX, "no_sandbox"),
|
||||
BOOKMARKFS_OPT(REPAIR, "repair"),
|
||||
BOOKMARKFS_OPT(RL_APP, "rl_app"),
|
||||
BOOKMARKFS_OPT(TYPE, "type"),
|
||||
|
||||
BOOKMARKFS_OPT(END_, NULL),
|
||||
};
|
||||
|
||||
getopt_foreach(argc, argv, ":o:iRhV") {
|
||||
case 'o':
|
||||
SUBOPT_START(opts)
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_BACKEND) SUBOPT_HAS_VAL {
|
||||
char const *name = SUBOPT_VAL;
|
||||
if (name[0] == '\0') {
|
||||
name = NULL;
|
||||
}
|
||||
info->backend_name = name;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_HANDLER) SUBOPT_HAS_VAL {
|
||||
char const *name = SUBOPT_VAL;
|
||||
if (name[0] == '\0') {
|
||||
name = NULL;
|
||||
}
|
||||
info->handler_name = name;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_NO_LANDLOCK) SUBOPT_NO_VAL {
|
||||
info->flags.no_landlock = 1;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_NO_SANDBOX) SUBOPT_NO_VAL {
|
||||
info->flags.no_sandbox = 1;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_RL_APP) {
|
||||
info->rl_app_name = SUBOPT_VAL;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_REPAIR) SUBOPT_NO_VAL {
|
||||
info->flags.readonly = 0;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_TYPE) SUBOPT_HAS_VAL {
|
||||
if (0 == strcmp("tag", SUBOPT_VAL)) {
|
||||
info->flags.type = BOOKMARKFS_BOOKMARK_TYPE_TAG;
|
||||
} else if (0 == strcmp("keyword", SUBOPT_VAL)) {
|
||||
info->flags.type = BOOKMARKFS_BOOKMARK_TYPE_KEYWORD;
|
||||
} else if (0 == strcmp("bookmark", SUBOPT_VAL)) {
|
||||
info->flags.type = BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK;
|
||||
} else {
|
||||
return SUBOPT_ERR_BAD_KEY();
|
||||
}
|
||||
}
|
||||
SUBOPT_OPT_FALLBACK() {
|
||||
char *opt = SUBOPT_STR;
|
||||
if (opt[0] == '@') {
|
||||
bookmarkfs_opts_add(&info->backend_opts, opt + 1);
|
||||
} else if (opt[0] == '%') {
|
||||
bookmarkfs_opts_add(&info->handler_opts, opt + 1);
|
||||
} else {
|
||||
return SUBOPT_ERR_BAD_KEY();
|
||||
}
|
||||
}
|
||||
SUBOPT_END
|
||||
|
||||
case 'i':
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
info->flags.interactive = 1;
|
||||
break;
|
||||
#else
|
||||
log_puts("bad option '-i': "
|
||||
"interactive fsck is not enabled on this build");
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
case 'R':
|
||||
info->flags.recursive = 1;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
info->flags.print_help = 1;
|
||||
info->flags.no_fsck = 1;
|
||||
return 0;
|
||||
|
||||
case 'V':
|
||||
info->flags.print_version = 1;
|
||||
info->flags.no_fsck = 1;
|
||||
return 0;
|
||||
|
||||
case ':':
|
||||
log_printf("no value provided for option '-%c'", optopt);
|
||||
return -1;
|
||||
|
||||
case '?':
|
||||
log_printf("invalid option '-%c'", optopt);
|
||||
return -1;
|
||||
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
if (argc != 1) {
|
||||
if (argc < 1) {
|
||||
log_puts("pathname must be specified");
|
||||
} else {
|
||||
log_puts("too many arguments");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
argv += optind;
|
||||
info->path = argv[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
int status = EXIT_FAILURE;
|
||||
|
||||
struct fsck_ctx ctx = { 0 };
|
||||
if (0 != init_all(&ctx, argc, argv)) {
|
||||
goto end;
|
||||
}
|
||||
if (0 != do_fsck(&ctx)) {
|
||||
goto end;
|
||||
}
|
||||
status = EXIT_SUCCESS;
|
||||
|
||||
end:
|
||||
destroy_ctx(&ctx);
|
||||
return status;
|
||||
}
|
96
src/fsck_handler.h
Normal file
96
src/fsck_handler.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck_handler.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_FSCK_HANDLER_H_
|
||||
#define BOOKMARKFS_FSCK_HANDLER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef BUILDING_BOOKMARKFS
|
||||
# include "common.h"
|
||||
# include "ioctl.h"
|
||||
#else
|
||||
# include <bookmarkfs/common.h>
|
||||
# include <bookmarkfs/ioctl.h>
|
||||
#endif
|
||||
|
||||
// init() flags
|
||||
#define BOOKMARKFS_FSCK_HANDLER_INTERACTIVE ( 1u << 16 )
|
||||
|
||||
#define BOOKMARKFS_FSCK_HANDLER_INFO_HELP ( 1u << 0 )
|
||||
#define BOOKMARKFS_FSCK_HANDLER_INFO_VERSION ( 1u << 1 )
|
||||
|
||||
enum {
|
||||
BOOKMARKFS_FSCK_NEXT,
|
||||
BOOKMARKFS_FSCK_APPLY,
|
||||
BOOKMARKFS_FSCK_USER_INPUT,
|
||||
BOOKMARKFS_FSCK_SAVE,
|
||||
BOOKMARKFS_FSCK_STOP,
|
||||
BOOKMARKFS_FSCK_REWIND,
|
||||
BOOKMARKFS_FSCK_SKIP,
|
||||
BOOKMARKFS_FSCK_SKIP_CHILDREN,
|
||||
BOOKMARKFS_FSCK_RESET,
|
||||
};
|
||||
|
||||
union bookmarkfs_fsck_handler_data;
|
||||
|
||||
typedef int (bookmarkfs_fsck_handler_create_func) (
|
||||
struct bookmarkfs_conf_opt const *opts,
|
||||
uint32_t flags,
|
||||
void **handler_ctx_ptr
|
||||
);
|
||||
|
||||
typedef void (bookmarkfs_fsck_handler_destroy_func) (
|
||||
void *handler_ctx
|
||||
);
|
||||
|
||||
typedef void (bookmarkfs_fsck_handler_info_func) (
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_fsck_handler_run_func) (
|
||||
void *handler_ctx,
|
||||
int why,
|
||||
union bookmarkfs_fsck_handler_data *data
|
||||
);
|
||||
|
||||
struct bookmarkfs_fsck_handler {
|
||||
bookmarkfs_fsck_handler_info_func *info;
|
||||
|
||||
bookmarkfs_fsck_handler_create_func *create;
|
||||
bookmarkfs_fsck_handler_destroy_func *destroy;
|
||||
|
||||
bookmarkfs_fsck_handler_run_func *run;
|
||||
};
|
||||
|
||||
struct bookmarkfs_fsck_handler_entry {
|
||||
uint64_t parent_id;
|
||||
|
||||
struct bookmarkfs_fsck_data data;
|
||||
};
|
||||
|
||||
union bookmarkfs_fsck_handler_data {
|
||||
struct bookmarkfs_fsck_handler_entry entry;
|
||||
char *str;
|
||||
};
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_FSCK_HANDLER_H_) */
|
390
src/fsck_handler_simple.c
Normal file
390
src/fsck_handler_simple.c
Normal file
|
@ -0,0 +1,390 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck_handler_simple.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "backend.h"
|
||||
#include "backend_util.h"
|
||||
#include "fsck_handler.h"
|
||||
#include "fsck_util.h"
|
||||
#include "ioctl.h"
|
||||
#include "xstd.h"
|
||||
|
||||
#define FSCK_HANDLER_EXPECT_INPUT ( 1u << 24 )
|
||||
#define FSCK_HANDLER_HAS_ENTRY ( 1u << 25 )
|
||||
#define FSCK_HANDLER_INHIBIT_NEXT ( 1u << 26 )
|
||||
|
||||
struct handler_ctx {
|
||||
uint64_t parent_id;
|
||||
uint32_t counter;
|
||||
uint32_t flags;
|
||||
|
||||
struct parsed_opts {
|
||||
char const *prompt;
|
||||
int translit;
|
||||
} opts;
|
||||
|
||||
struct bookmarkfs_fsck_data data_buf;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
static int expect_input (struct handler_ctx *, char **);
|
||||
static int expect_next (struct handler_ctx *, int, char **);
|
||||
static int handle_input (struct handler_ctx *,
|
||||
union bookmarkfs_fsck_handler_data *);
|
||||
static void print_usage (void);
|
||||
#endif /* defined(BOOKMARKFS_INTERACTIVE_FSCK) */
|
||||
|
||||
static void fix_name_dup (struct handler_ctx *, struct bookmarkfs_fsck_data *);
|
||||
static void fix_entry (struct handler_ctx *, enum bookmarkfs_fsck_result,
|
||||
struct bookmarkfs_fsck_data *);
|
||||
static int handle_entry (struct handler_ctx *, enum bookmarkfs_fsck_result,
|
||||
union bookmarkfs_fsck_handler_data *);
|
||||
static int parse_opts (struct bookmarkfs_conf_opt const *,
|
||||
struct parsed_opts *);
|
||||
static void print_entry (struct bookmarkfs_fsck_data const *);
|
||||
// Forward declaration end
|
||||
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
|
||||
static int
|
||||
expect_input (
|
||||
struct handler_ctx *ctx,
|
||||
char **data_ptr
|
||||
) {
|
||||
*data_ptr = (char *)(ctx->opts.prompt);
|
||||
ctx->flags |= FSCK_HANDLER_EXPECT_INPUT;
|
||||
return BOOKMARKFS_FSCK_USER_INPUT;
|
||||
}
|
||||
|
||||
static int
|
||||
expect_next (
|
||||
struct handler_ctx *ctx,
|
||||
int control,
|
||||
char **data_ptr
|
||||
) {
|
||||
switch ((*data_ptr)[1]) {
|
||||
case '-':
|
||||
ctx->flags |= FSCK_HANDLER_INHIBIT_NEXT;
|
||||
break;
|
||||
|
||||
case '\0':
|
||||
break;
|
||||
|
||||
default:
|
||||
print_usage();
|
||||
return expect_input(ctx, data_ptr);
|
||||
}
|
||||
ctx->flags &= ~(FSCK_HANDLER_HAS_ENTRY | FSCK_HANDLER_EXPECT_INPUT);
|
||||
return control;
|
||||
}
|
||||
|
||||
static int
|
||||
handle_input (
|
||||
struct handler_ctx *ctx,
|
||||
union bookmarkfs_fsck_handler_data *data
|
||||
) {
|
||||
char *input = data->str;
|
||||
debug_assert(input != NULL);
|
||||
|
||||
char *next = strtok(input, " \t");
|
||||
int control;
|
||||
switch (input[0]) {
|
||||
case 'p':
|
||||
if (!(ctx->flags & FSCK_HANDLER_HAS_ENTRY)) {
|
||||
no_entry:
|
||||
puts("no entry");
|
||||
} else {
|
||||
print_entry(&ctx->data_buf);
|
||||
}
|
||||
control = expect_input(ctx, &data->str);
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
if (next == NULL) {
|
||||
goto bad_cmd;
|
||||
}
|
||||
strncpy(ctx->data_buf.name, next, sizeof(ctx->data_buf.name));
|
||||
// fallthrough
|
||||
case 'a':
|
||||
if (ctx->flags & BOOKMARKFS_BACKEND_READONLY) {
|
||||
log_puts("cannot apply, fsck is running in readonly mode");
|
||||
control = expect_input(ctx, &data->str);
|
||||
break;
|
||||
}
|
||||
if (!(ctx->flags & FSCK_HANDLER_HAS_ENTRY)) {
|
||||
goto no_entry;
|
||||
}
|
||||
data->entry.data = ctx->data_buf;
|
||||
control = expect_next(ctx, BOOKMARKFS_FSCK_APPLY, &data->str);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
control = expect_next(ctx, BOOKMARKFS_FSCK_NEXT, &data->str);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
control = expect_next(ctx, BOOKMARKFS_FSCK_SKIP, &data->str);
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
control = expect_next(ctx, BOOKMARKFS_FSCK_SKIP_CHILDREN, &data->str);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
control = expect_next(ctx, BOOKMARKFS_FSCK_REWIND, &data->str);
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
control = expect_next(ctx, BOOKMARKFS_FSCK_RESET, &data->str);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
control = expect_next(ctx, BOOKMARKFS_FSCK_SAVE, &data->str);
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
control = BOOKMARKFS_FSCK_STOP;
|
||||
break;
|
||||
|
||||
default:
|
||||
bad_cmd:
|
||||
print_usage();
|
||||
// fallthrough
|
||||
case '\0':
|
||||
control = expect_input(ctx, &data->str);
|
||||
break;
|
||||
}
|
||||
|
||||
free(input);
|
||||
return control;
|
||||
}
|
||||
|
||||
static void
|
||||
print_usage (void)
|
||||
{
|
||||
puts("Commands:\n"
|
||||
" p Print current entry\n"
|
||||
" a[-] Apply proposed fix\n"
|
||||
" e[-] <new_name> Edit proposed fix and then apply\n"
|
||||
" c Continue to next entry\n"
|
||||
" s[-] Skip current directory\n"
|
||||
" S[-] Skip current directory and its children\n"
|
||||
" r[-] Rewind current directory\n"
|
||||
" R[-] Rewind all\n"
|
||||
" w[-] Save applied changes\n"
|
||||
" q Quit\n"
|
||||
"\n"
|
||||
"The '-' suffix inhibits the default behavior of continuing to\n"
|
||||
"the next entry after the command completes successfully.\n"
|
||||
"\n"
|
||||
"See the user manual for more information.");
|
||||
}
|
||||
|
||||
#endif /* defined(BOOKMARKFS_INTERACTIVE_FSCK) */
|
||||
|
||||
static void
|
||||
fix_name_dup (
|
||||
struct handler_ctx *ctx,
|
||||
struct bookmarkfs_fsck_data *data
|
||||
) {
|
||||
size_t name_len = strnlen(data->name, sizeof(data->name));
|
||||
for (int nbytes; ; name_len = sizeof(data->name) - nbytes - 1) {
|
||||
nbytes = snprintf(data->name + name_len, sizeof(data->name) - name_len,
|
||||
"_%" PRIu32, ctx->counter);
|
||||
xassert(nbytes > 0);
|
||||
if (name_len + nbytes < sizeof(data->name)) {
|
||||
++ctx->counter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fix_entry (
|
||||
struct handler_ctx *ctx,
|
||||
enum bookmarkfs_fsck_result why,
|
||||
struct bookmarkfs_fsck_data *data
|
||||
) {
|
||||
switch (why) {
|
||||
case BOOKMARKFS_FSCK_RESULT_NAME_BADCHAR:
|
||||
if (unlikely(data->extra >= sizeof(data->name))) {
|
||||
data->extra = sizeof(data->name) - 1;
|
||||
}
|
||||
data->name[data->extra] = ctx->opts.translit;
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_RESULT_NAME_DUPLICATE:
|
||||
fix_name_dup(ctx, data);
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_RESULT_NAME_BADLEN:
|
||||
if (data->extra > 0) {
|
||||
data->name[sizeof(data->name) - 1] = '\0';
|
||||
break;
|
||||
}
|
||||
// fallthrough
|
||||
case BOOKMARKFS_FSCK_RESULT_NAME_INVALID:
|
||||
sprintf(data->name, "fsck-%" PRIu64, data->id);
|
||||
break;
|
||||
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
handle_entry (
|
||||
struct handler_ctx *ctx,
|
||||
enum bookmarkfs_fsck_result why,
|
||||
union bookmarkfs_fsck_handler_data *data
|
||||
) {
|
||||
struct bookmarkfs_fsck_handler_entry *entry = &data->entry;
|
||||
if (entry->parent_id != ctx->parent_id) {
|
||||
ctx->parent_id = entry->parent_id;
|
||||
ctx->counter = 0;
|
||||
}
|
||||
|
||||
struct bookmarkfs_fsck_data *entry_data = &entry->data;
|
||||
if (0 != explain_fsck_result(why, entry_data)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int control = BOOKMARKFS_FSCK_NEXT;
|
||||
if (!(ctx->flags & BOOKMARKFS_BACKEND_READONLY)) {
|
||||
if (why != BOOKMARKFS_FSCK_RESULT_END) {
|
||||
fix_entry(ctx, why, entry_data);
|
||||
control = BOOKMARKFS_FSCK_APPLY;
|
||||
}
|
||||
}
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
if (ctx->flags & BOOKMARKFS_FSCK_HANDLER_INTERACTIVE) {
|
||||
ctx->data_buf = *entry_data;
|
||||
control = expect_input(ctx, &data->str);
|
||||
}
|
||||
ctx->flags |= FSCK_HANDLER_HAS_ENTRY;
|
||||
#endif
|
||||
return control;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_opts (
|
||||
struct bookmarkfs_conf_opt const *opts,
|
||||
struct parsed_opts *out
|
||||
) {
|
||||
char const *prompt = "% ";
|
||||
int translit = '_';
|
||||
|
||||
BACKEND_OPT_START(opts)
|
||||
BACKEND_OPT_KEY("prompt") {
|
||||
BACKEND_OPT_VAL_START
|
||||
prompt = BACKEND_OPT_VAL_STR;
|
||||
}
|
||||
BACKEND_OPT_KEY("translit") {
|
||||
BACKEND_OPT_VAL_START
|
||||
if (1 == strlen(BACKEND_OPT_VAL_STR)) {
|
||||
translit = BACKEND_OPT_VAL_STR[0];
|
||||
}
|
||||
BACKEND_OPT_VAL_END
|
||||
}
|
||||
BACKEND_OPT_END
|
||||
|
||||
out->prompt = prompt;
|
||||
out->translit = translit;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
print_entry (
|
||||
struct bookmarkfs_fsck_data const *data
|
||||
) {
|
||||
char name_buf[sizeof(data->name)];
|
||||
escape_control_chars(name_buf, sizeof(name_buf), data->name, '?');
|
||||
|
||||
printf("id: %" PRIu64 "\nname: %s\n", data->id, name_buf);
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_handler_create (
|
||||
struct bookmarkfs_conf_opt const *opts,
|
||||
uint32_t flags,
|
||||
void **handler_ctx_ptr
|
||||
) {
|
||||
struct parsed_opts parsed_opts = { 0 };
|
||||
if (0 != parse_opts(opts, &parsed_opts)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct handler_ctx *ctx = xmalloc(sizeof(*ctx));
|
||||
*ctx = (struct handler_ctx) {
|
||||
.parent_id = UINT64_MAX,
|
||||
.opts = parsed_opts,
|
||||
.flags = flags,
|
||||
};
|
||||
*handler_ctx_ptr = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
fsck_handler_destroy (
|
||||
void *handler_ctx
|
||||
) {
|
||||
struct handler_ctx *ctx = handler_ctx;
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_handler_run (
|
||||
void *handler_ctx,
|
||||
int why,
|
||||
union bookmarkfs_fsck_handler_data *data
|
||||
) {
|
||||
struct handler_ctx *ctx = handler_ctx;
|
||||
|
||||
if (why > 0) {
|
||||
return handle_entry(ctx, why, data);
|
||||
}
|
||||
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
|
||||
if (ctx->flags & FSCK_HANDLER_EXPECT_INPUT) {
|
||||
return handle_input(ctx, data);
|
||||
}
|
||||
if (ctx->flags & FSCK_HANDLER_INHIBIT_NEXT) {
|
||||
ctx->flags &= ~FSCK_HANDLER_INHIBIT_NEXT;
|
||||
return expect_input(ctx, &data->str);
|
||||
}
|
||||
#endif
|
||||
return BOOKMARKFS_FSCK_NEXT;
|
||||
}
|
||||
|
||||
struct bookmarkfs_fsck_handler const fsck_handler_simple = {
|
||||
.create = fsck_handler_create,
|
||||
.destroy = fsck_handler_destroy,
|
||||
.run = fsck_handler_run,
|
||||
};
|
383
src/fsck_handler_tcl.c
Normal file
383
src/fsck_handler_tcl.c
Normal file
|
@ -0,0 +1,383 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck_handler_tcl.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <tcl.h>
|
||||
|
||||
#include "backend.h"
|
||||
#include "backend_util.h"
|
||||
#include "fsck_handler.h"
|
||||
#include "macros.h"
|
||||
#include "version.h"
|
||||
#include "xstd.h"
|
||||
|
||||
#define FSCK_HANDLER_UNSAFE ( 1u << 24 )
|
||||
#define FSCK_HANDLER_EXPECT_INPUT ( 1u << 25 )
|
||||
|
||||
struct handler_ctx {
|
||||
Tcl_Interp *interp;
|
||||
uint32_t flags;
|
||||
|
||||
union {
|
||||
Tcl_Obj *obj;
|
||||
int fd;
|
||||
} script;
|
||||
};
|
||||
|
||||
struct parsed_opts {
|
||||
char const *script;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
static void finalize_tcl (void) FUNCATTR_DTOR;
|
||||
static int init_ctx (struct handler_ctx *);
|
||||
static int parse_opts (struct bookmarkfs_conf_opt const *,
|
||||
struct parsed_opts *);
|
||||
static void print_help (void);
|
||||
static void print_version (void);
|
||||
static int set_tcl_var (Tcl_Interp *, char const *, size_t, int);
|
||||
// Forward declaration end
|
||||
|
||||
static void
|
||||
finalize_tcl (void)
|
||||
{
|
||||
Tcl_Finalize();
|
||||
}
|
||||
|
||||
static int
|
||||
init_ctx (
|
||||
struct handler_ctx *ctx
|
||||
) {
|
||||
if (ctx->interp != NULL) {
|
||||
return 0;
|
||||
}
|
||||
Tcl_Interp *interp = Tcl_CreateInterp();
|
||||
if (ctx->flags & FSCK_HANDLER_UNSAFE) {
|
||||
if (TCL_OK != Tcl_Init(interp)) {
|
||||
log_printf("%s", Tcl_GetStringResult(interp));
|
||||
return -1;
|
||||
}
|
||||
Tcl_InitMemory(interp);
|
||||
} else {
|
||||
xassert(TCL_OK == Tcl_MakeSafe(interp));
|
||||
// Safe interps do not have std channels by default.
|
||||
Tcl_Channel chan = Tcl_GetStdChannel(TCL_STDOUT);
|
||||
if (chan != NULL) {
|
||||
Tcl_RegisterChannel(interp, chan);
|
||||
}
|
||||
chan = Tcl_GetStdChannel(TCL_STDERR);
|
||||
if (chan != NULL) {
|
||||
Tcl_RegisterChannel(interp, chan);
|
||||
}
|
||||
}
|
||||
|
||||
#define WITH_NAMESPACE(name) "bookmarkfs::fsck::" name
|
||||
#define DO_CREATE_NAMESPACE(interp, name) \
|
||||
if (NULL == Tcl_CreateNamespace(interp, WITH_NAMESPACE(name), \
|
||||
NULL, NULL) \
|
||||
) { \
|
||||
log_printf("%s", Tcl_GetStringResult(interp)); \
|
||||
return -1; \
|
||||
}
|
||||
DO_CREATE_NAMESPACE(interp, "handler");
|
||||
DO_CREATE_NAMESPACE(interp, "result");
|
||||
|
||||
#define DO_SET_VAR(interp, name, val) \
|
||||
if (0 != set_tcl_var(interp, STR_WITHLEN(WITH_NAMESPACE(name)), val)) { \
|
||||
log_printf("%s", Tcl_GetStringResult(interp)); \
|
||||
goto fail; \
|
||||
}
|
||||
DO_SET_VAR(interp, "isInteractive",
|
||||
!!(ctx->flags & BOOKMARKFS_FSCK_HANDLER_INTERACTIVE));
|
||||
DO_SET_VAR(interp, "isReadonly",
|
||||
!!(ctx->flags & BOOKMARKFS_BACKEND_READONLY));
|
||||
DO_SET_VAR(interp, "handler::next", BOOKMARKFS_FSCK_NEXT);
|
||||
DO_SET_VAR(interp, "handler::apply", BOOKMARKFS_FSCK_APPLY);
|
||||
DO_SET_VAR(interp, "handler::userInput", BOOKMARKFS_FSCK_USER_INPUT);
|
||||
DO_SET_VAR(interp, "handler::save", BOOKMARKFS_FSCK_SAVE);
|
||||
DO_SET_VAR(interp, "handler::stop", BOOKMARKFS_FSCK_STOP);
|
||||
DO_SET_VAR(interp, "handler::rewind", BOOKMARKFS_FSCK_REWIND);
|
||||
DO_SET_VAR(interp, "handler::skip", BOOKMARKFS_FSCK_SKIP);
|
||||
DO_SET_VAR(interp, "handler::skipChildren", BOOKMARKFS_FSCK_SKIP_CHILDREN);
|
||||
DO_SET_VAR(interp, "handler::reset", BOOKMARKFS_FSCK_RESET);
|
||||
#define DO_SET_RESULT_VAR(interp, name, val) \
|
||||
DO_SET_VAR(interp, "result::" name, BOOKMARKFS_FSCK_RESULT_##val)
|
||||
DO_SET_RESULT_VAR(interp, "end", END);
|
||||
DO_SET_RESULT_VAR(interp, "nameDuplicate", NAME_DUPLICATE);
|
||||
DO_SET_RESULT_VAR(interp, "nameBadChar", NAME_BADCHAR);
|
||||
DO_SET_RESULT_VAR(interp, "nameBadLen", NAME_BADLEN);
|
||||
DO_SET_RESULT_VAR(interp, "nameInvalid", NAME_INVALID);
|
||||
|
||||
struct stat stat_buf;
|
||||
if (unlikely(0 != fstat(ctx->script.fd, &stat_buf))) {
|
||||
log_printf("fstat(): %s", xstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
void *buf = mmap(NULL, stat_buf.st_size, PROT_READ | PROT_MAX(PROT_READ),
|
||||
MAP_PRIVATE, ctx->script.fd, 0);
|
||||
if (unlikely(buf == MAP_FAILED)) {
|
||||
log_printf("mmap(): %s", xstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
int result = Tcl_EvalEx(interp, buf, stat_buf.st_size, TCL_EVAL_GLOBAL);
|
||||
munmap(buf, stat_buf.st_size);
|
||||
if (result != TCL_OK) {
|
||||
log_printf("%s", Tcl_GetStringResult(interp));
|
||||
goto fail;
|
||||
}
|
||||
close(ctx->script.fd);
|
||||
ctx->script.obj = Tcl_GetObjResult(interp);
|
||||
Tcl_IncrRefCount(ctx->script.obj);
|
||||
ctx->interp = interp;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
Tcl_DeleteInterp(interp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_opts (
|
||||
struct bookmarkfs_conf_opt const *opts,
|
||||
struct parsed_opts *parsed_opts
|
||||
) {
|
||||
char const *script = NULL;
|
||||
|
||||
BACKEND_OPT_START(opts)
|
||||
BACKEND_OPT_KEY("script") {
|
||||
BACKEND_OPT_VAL_START
|
||||
script = BACKEND_OPT_VAL_STR;
|
||||
}
|
||||
BACKEND_OPT_KEY("unsafe") {
|
||||
BACKEND_OPT_NO_VAL
|
||||
parsed_opts->flags |= FSCK_HANDLER_UNSAFE;
|
||||
}
|
||||
BACKEND_OPT_END
|
||||
|
||||
parsed_opts->script = script;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
print_help (void)
|
||||
{
|
||||
printf("Tcl-based fsck handler for BookmarkFS\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" script=<path> Path to Tcl script file\n"
|
||||
" unsafe Enable unsafe Tcl interpreter features\n"
|
||||
"\n"
|
||||
"Run 'info bookmarkfs' for more information.\n\n"
|
||||
"Project homepage: <" BOOKMARKFS_HOMEPAGE_URL ">.\n");
|
||||
}
|
||||
|
||||
static void
|
||||
print_version (void)
|
||||
{
|
||||
printf("bookmarkfs-fsck-handler-tcl %d.%d.%d\n",
|
||||
BOOKMARKFS_VER_MAJOR, BOOKMARKFS_VER_MINOR, BOOKMARKFS_VER_PATCH);
|
||||
puts(BOOKMARKFS_FEATURE_STRING(DEBUG, "debug"));
|
||||
}
|
||||
|
||||
static int
|
||||
set_tcl_var (
|
||||
Tcl_Interp *interp,
|
||||
char const *name,
|
||||
size_t name_len,
|
||||
int val
|
||||
) {
|
||||
Tcl_Obj *name_obj = Tcl_NewStringObj(name, name_len);
|
||||
Tcl_Obj *val_obj = Tcl_NewIntObj(val);
|
||||
|
||||
Tcl_IncrRefCount(name_obj);
|
||||
val_obj = Tcl_ObjSetVar2(interp, name_obj, NULL, val_obj,
|
||||
TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG);
|
||||
Tcl_DecrRefCount(name_obj);
|
||||
return val_obj == NULL ? -1 : 0;
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_handler_create (
|
||||
struct bookmarkfs_conf_opt const *opts,
|
||||
uint32_t flags,
|
||||
void **handler_ctx_ptr
|
||||
) {
|
||||
struct parsed_opts parsed_opts = { 0 };
|
||||
if (0 != parse_opts(opts, &parsed_opts)) {
|
||||
return -1;
|
||||
}
|
||||
flags |= parsed_opts.flags;
|
||||
|
||||
if (parsed_opts.script == NULL) {
|
||||
log_puts("script not specified");
|
||||
return -1;
|
||||
}
|
||||
int fd = open(parsed_opts.script, O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
log_printf("open(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct handler_ctx *ctx = xmalloc(sizeof(*ctx));
|
||||
*ctx = (struct handler_ctx) {
|
||||
.flags = flags,
|
||||
.script.fd = fd,
|
||||
};
|
||||
*handler_ctx_ptr = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
fsck_handler_destroy (
|
||||
void *handler_ctx
|
||||
) {
|
||||
struct handler_ctx *ctx = handler_ctx;
|
||||
|
||||
if (ctx->interp != NULL) {
|
||||
Tcl_DecrRefCount(ctx->script.obj);
|
||||
Tcl_DeleteInterp(ctx->interp);
|
||||
} else {
|
||||
close(ctx->script.fd);
|
||||
}
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void
|
||||
fsck_handler_info (
|
||||
uint32_t flags
|
||||
) {
|
||||
if (flags & BOOKMARKFS_FSCK_HANDLER_INFO_HELP) {
|
||||
print_help();
|
||||
} else if (flags & BOOKMARKFS_FSCK_HANDLER_INFO_VERSION) {
|
||||
print_version();
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_handler_run (
|
||||
void *handler_ctx,
|
||||
int why,
|
||||
union bookmarkfs_fsck_handler_data *data
|
||||
) {
|
||||
struct handler_ctx *ctx = handler_ctx;
|
||||
|
||||
if (0 != init_ctx(ctx)) {
|
||||
return -1;
|
||||
}
|
||||
Tcl_Interp *interp = ctx->interp;
|
||||
|
||||
Tcl_Obj *data_obj = Tcl_NewObj();
|
||||
if (why < 0) {
|
||||
if (ctx->flags & FSCK_HANDLER_EXPECT_INPUT) {
|
||||
Tcl_SetStringObj(data_obj, data->str, -1);
|
||||
}
|
||||
} else {
|
||||
struct bookmarkfs_fsck_handler_entry *entry = &data->entry;
|
||||
Tcl_Obj *elems[] = {
|
||||
Tcl_NewWideIntObj(entry->data.id),
|
||||
Tcl_NewWideIntObj(entry->data.extra),
|
||||
Tcl_NewStringObj(entry->data.name,
|
||||
strnlen(entry->data.name, sizeof(entry->data.name))),
|
||||
Tcl_NewWideIntObj(entry->parent_id),
|
||||
};
|
||||
Tcl_SetListObj(data_obj, 4, elems);
|
||||
}
|
||||
Tcl_Obj *elems[] = { Tcl_NewIntObj(why), data_obj };
|
||||
Tcl_Obj *args_obj = Tcl_NewListObj(2, elems);
|
||||
Tcl_IncrRefCount(args_obj);
|
||||
|
||||
Tcl_Obj *args[] = { ctx->script.obj, args_obj };
|
||||
int result = Tcl_EvalObjv(interp, 2, args, TCL_EVAL_GLOBAL);
|
||||
Tcl_DecrRefCount(args_obj);
|
||||
if (result != TCL_OK) {
|
||||
log_printf("%s", Tcl_GetStringResult(interp));
|
||||
return -1;
|
||||
}
|
||||
|
||||
Tcl_Obj *result_obj = Tcl_GetObjResult(interp);
|
||||
if (TCL_OK != Tcl_ListObjIndex(interp, result_obj, 0, &data_obj)) {
|
||||
log_puts("bad return value, cannot convert to list");
|
||||
return -1;
|
||||
}
|
||||
if (TCL_OK != Tcl_GetIntFromObj(interp, data_obj, &result)) {
|
||||
log_printf("bad result code '%s'", Tcl_GetString(data_obj));
|
||||
return -1;
|
||||
}
|
||||
switch (result) {
|
||||
case BOOKMARKFS_FSCK_USER_INPUT:
|
||||
if (!(ctx->flags & BOOKMARKFS_FSCK_HANDLER_INTERACTIVE)) {
|
||||
log_printf("bad result code %d, not in interactive mode", result);
|
||||
return -1;
|
||||
}
|
||||
if (TCL_OK != Tcl_ListObjIndex(interp, result_obj, 1, &data_obj)) {
|
||||
log_puts("bad return value, no prompt string given");
|
||||
return -1;
|
||||
}
|
||||
data->str = Tcl_GetString(data_obj);
|
||||
ctx->flags |= FSCK_HANDLER_EXPECT_INPUT;
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_APPLY:
|
||||
if (TCL_OK != Tcl_ListObjIndex(interp, result_obj, 1, &data_obj)) {
|
||||
log_puts("bad return value, no new name given");
|
||||
return -1;
|
||||
}
|
||||
struct bookmarkfs_fsck_data *data_buf = &data->entry.data;
|
||||
char const *new_name = Tcl_GetString(data_obj);
|
||||
strncpy(data_buf->name, new_name, sizeof(data_buf->name));
|
||||
// fallthrough
|
||||
case BOOKMARKFS_FSCK_NEXT:
|
||||
case BOOKMARKFS_FSCK_SAVE:
|
||||
case BOOKMARKFS_FSCK_STOP:
|
||||
case BOOKMARKFS_FSCK_REWIND:
|
||||
case BOOKMARKFS_FSCK_SKIP:
|
||||
case BOOKMARKFS_FSCK_SKIP_CHILDREN:
|
||||
case BOOKMARKFS_FSCK_RESET:
|
||||
ctx->flags &= ~FSCK_HANDLER_EXPECT_INPUT;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_printf("bad result code %d", result);
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BOOKMARKFS_API
|
||||
struct bookmarkfs_fsck_handler const bookmarkfs_fsck_handler_tcl = {
|
||||
.info = fsck_handler_info,
|
||||
.create = fsck_handler_create,
|
||||
.destroy = fsck_handler_destroy,
|
||||
.run = fsck_handler_run,
|
||||
};
|
468
src/fsck_offline.c
Normal file
468
src/fsck_offline.c
Normal file
|
@ -0,0 +1,468 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck_offline.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fsck_ops.h"
|
||||
#include "ioctl.h"
|
||||
#include "version.h"
|
||||
#include "xstd.h"
|
||||
|
||||
#define FSCK_ALL_DONE ( 1u << 24 )
|
||||
|
||||
#define BACKEND_CALL(ctx, name, ...) \
|
||||
(ctx)->backend->name((ctx)->backend_ctx, __VA_ARGS__)
|
||||
|
||||
struct fsck_ctx {
|
||||
struct bookmarkfs_backend const *backend;
|
||||
void *backend_ctx;
|
||||
|
||||
struct fsck_dir *dir_stack;
|
||||
size_t dir_stack_size;
|
||||
size_t dir_stack_top;
|
||||
|
||||
char *path;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
struct fsck_data {
|
||||
struct bookmarkfs_fsck_handler_entry *entry;
|
||||
|
||||
enum bookmarkfs_fsck_result result;
|
||||
};
|
||||
|
||||
struct fsck_dir {
|
||||
uint64_t id;
|
||||
void *cookie;
|
||||
off_t off;
|
||||
};
|
||||
#define FSCK_DIR_INIT(id_) (struct fsck_dir) { .id = (id_), .off = -1 }
|
||||
|
||||
// Forward declaration start
|
||||
static int do_fsck (struct fsck_ctx *, struct fsck_dir *,
|
||||
struct bookmarkfs_fsck_data const *,
|
||||
struct bookmarkfs_fsck_handler_entry *);
|
||||
static int do_fsck_cb (void *, int, uint64_t, uint64_t, char const *);
|
||||
static int do_list (struct fsck_ctx const *, struct fsck_dir *,
|
||||
uint64_t *);
|
||||
static int do_list_cb (void *, struct bookmarkfs_bookmark_entry const *);
|
||||
static void free_dir (struct fsck_ctx const *, struct fsck_dir const *);
|
||||
static int init_top (struct fsck_ctx const *, uint64_t, char *, uint64_t *);
|
||||
static int reset_top (struct fsck_ctx *);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
do_fsck (
|
||||
struct fsck_ctx *ctx,
|
||||
struct fsck_dir *dir,
|
||||
struct bookmarkfs_fsck_data const *apply_data,
|
||||
struct bookmarkfs_fsck_handler_entry *entry_buf
|
||||
) {
|
||||
struct fsck_data data;
|
||||
bookmarkfs_bookmark_fsck_cb *callback = NULL;
|
||||
// `entry_buf == NULL` means rewind
|
||||
if (entry_buf != NULL) {
|
||||
entry_buf->parent_id = dir->id;
|
||||
data.entry = entry_buf;
|
||||
callback = do_fsck_cb;
|
||||
}
|
||||
data.result = BOOKMARKFS_FSCK_RESULT_END;
|
||||
|
||||
uint32_t flags = ctx->flags & BOOKMARKFS_BOOKMARK_TYPE_MASK;
|
||||
int status = BACKEND_CALL(ctx, bookmark_fsck, dir->id, apply_data,
|
||||
flags, callback, &data, &dir->cookie);
|
||||
if (status < 0) {
|
||||
log_printf("bookmark_fsck(): %s", xstrerror(-status));
|
||||
return status;
|
||||
}
|
||||
return data.result;
|
||||
}
|
||||
|
||||
static int
|
||||
do_fsck_cb (
|
||||
void *user_data,
|
||||
int result,
|
||||
uint64_t id,
|
||||
uint64_t extra,
|
||||
char const *name
|
||||
) {
|
||||
struct fsck_data *data = user_data;
|
||||
|
||||
struct bookmarkfs_fsck_data *entry_data = &data->entry->data;
|
||||
entry_data->id = id;
|
||||
entry_data->extra = extra;
|
||||
if (name != entry_data->name) {
|
||||
strncpy(entry_data->name, name, sizeof(entry_data->name));
|
||||
}
|
||||
data->result = result;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
do_list (
|
||||
struct fsck_ctx const *ctx,
|
||||
struct fsck_dir *dir,
|
||||
uint64_t *subdir_id_ptr
|
||||
) {
|
||||
struct bookmarkfs_bookmark_entry entry = {
|
||||
.next = dir->off,
|
||||
.stat.id = UINT64_MAX,
|
||||
};
|
||||
int status = BACKEND_CALL(ctx, bookmark_list, dir->id, dir->off, 0,
|
||||
do_list_cb, &entry, &dir->cookie);
|
||||
if (status < 0) {
|
||||
return status;
|
||||
}
|
||||
dir->off = entry.next;
|
||||
*subdir_id_ptr = entry.stat.id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
do_list_cb (
|
||||
void *user_data,
|
||||
struct bookmarkfs_bookmark_entry const *entry
|
||||
) {
|
||||
struct bookmarkfs_bookmark_entry *result = user_data;
|
||||
|
||||
if (entry->stat.value_len >= 0) {
|
||||
return 0;
|
||||
}
|
||||
result->next = entry->next;
|
||||
result->stat.id = entry->stat.id;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
free_dir (
|
||||
struct fsck_ctx const *ctx,
|
||||
struct fsck_dir const *dir
|
||||
) {
|
||||
BACKEND_CALL(ctx, object_free,
|
||||
dir->cookie, BOOKMARKFS_OBJECT_TYPE_BLCOOKIE);
|
||||
}
|
||||
|
||||
static int
|
||||
init_top (
|
||||
struct fsck_ctx const *ctx,
|
||||
uint64_t id,
|
||||
char *path,
|
||||
uint64_t *result_id_ptr
|
||||
) {
|
||||
for (char *sep; path != NULL; path = sep) {
|
||||
sep = strchr(path, '/');
|
||||
if (sep != NULL) {
|
||||
*(sep++) = '\0';
|
||||
}
|
||||
if (path[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
struct bookmarkfs_bookmark_stat stat_buf;
|
||||
int status = BACKEND_CALL(ctx, bookmark_lookup, id, path, 0,
|
||||
&stat_buf);
|
||||
if (status < 0) {
|
||||
fail:
|
||||
log_printf("bookmark_lookup(): %s: %s", path, xstrerror(-status));
|
||||
return status;
|
||||
}
|
||||
if (stat_buf.value_len >= 0) {
|
||||
status = -ENOTDIR;
|
||||
goto fail;
|
||||
}
|
||||
id = stat_buf.id;
|
||||
}
|
||||
|
||||
*result_id_ptr = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
reset_top (
|
||||
struct fsck_ctx *ctx
|
||||
) {
|
||||
ctx->flags &= ~FSCK_ALL_DONE;
|
||||
|
||||
struct fsck_dir *dir = &ctx->dir_stack[0];
|
||||
if (dir->off < 0) {
|
||||
return do_fsck(ctx, dir, NULL, NULL);
|
||||
} else {
|
||||
dir->off = -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_apply (
|
||||
void *fsck_ctx,
|
||||
struct bookmarkfs_fsck_handler_entry *entry
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
struct fsck_dir *dir = ctx->dir_stack + ctx->dir_stack_top;
|
||||
return do_fsck(ctx, dir, &entry->data, entry);
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_control (
|
||||
void *fsck_ctx,
|
||||
int control
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
struct fsck_dir *dir = ctx->dir_stack + ctx->dir_stack_top;
|
||||
switch (control) {
|
||||
case BOOKMARKFS_FSCK_SKIP:
|
||||
if (dir->off >= 0) {
|
||||
break;
|
||||
}
|
||||
dir->off = 0;
|
||||
// fallthrough
|
||||
case BOOKMARKFS_FSCK_REWIND:
|
||||
return do_fsck(ctx, dir, NULL, NULL);
|
||||
|
||||
case BOOKMARKFS_FSCK_SKIP_CHILDREN:
|
||||
if (ctx->dir_stack_top == 0) {
|
||||
ctx->flags |= FSCK_ALL_DONE;
|
||||
} else {
|
||||
free_dir(ctx, dir);
|
||||
--ctx->dir_stack_top;
|
||||
}
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_RESET:
|
||||
for (; dir > ctx->dir_stack; --dir) {
|
||||
free_dir(ctx, dir);
|
||||
}
|
||||
ctx->dir_stack_top = 0;
|
||||
return reset_top(ctx);
|
||||
|
||||
case BOOKMARKFS_FSCK_SAVE:
|
||||
if (ctx->flags & BOOKMARKFS_BACKEND_READONLY) {
|
||||
break;
|
||||
}
|
||||
return ctx->backend->backend_sync(ctx->backend_ctx);
|
||||
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_create (
|
||||
struct bookmarkfs_backend const *backend,
|
||||
char *path,
|
||||
struct bookmarkfs_conf_opt *opts,
|
||||
uint32_t flags,
|
||||
void **fsck_ctx_ptr
|
||||
) {
|
||||
char *sep = strchr(path, ':');
|
||||
if (sep != NULL) {
|
||||
*(sep++) = '\0';
|
||||
}
|
||||
|
||||
struct bookmarkfs_backend_conf const conf = {
|
||||
.version = BOOKMARKFS_VERNUM,
|
||||
.flags = (flags | BOOKMARKFS_BACKEND_FSCK_ONLY) & 0xffff,
|
||||
.store_path = path,
|
||||
.opts = opts,
|
||||
};
|
||||
struct bookmarkfs_backend_init_resp resp = {
|
||||
.bookmarks_root_id = UINT64_MAX,
|
||||
.tags_root_id = UINT64_MAX,
|
||||
};
|
||||
if (0 != backend->backend_create(&conf, &resp)) {
|
||||
return -1;
|
||||
}
|
||||
debug_assert(resp.flags & BOOKMARKFS_BACKEND_EXCLUSIVE);
|
||||
|
||||
uint64_t root_id = resp.bookmarks_root_id;
|
||||
if (!BOOKMARKFS_BOOKMARK_IS_TYPE(flags, BOOKMARK)) {
|
||||
if (BOOKMARKFS_BOOKMARK_IS_TYPE(flags, TAG)) {
|
||||
root_id = resp.tags_root_id;
|
||||
if (root_id == UINT64_MAX) {
|
||||
log_puts("backend does not support tags");
|
||||
goto fail;
|
||||
}
|
||||
} else if (BOOKMARKFS_BOOKMARK_IS_TYPE(flags, KEYWORD)) {
|
||||
if (!(resp.flags & BOOKMARKFS_BACKEND_HAS_KEYWORD)) {
|
||||
log_puts("backend does not support keywords");
|
||||
goto fail;
|
||||
}
|
||||
root_id = 0;
|
||||
}
|
||||
// Tag/keyword fsck only applies to toplevel dir.
|
||||
sep = NULL;
|
||||
flags &= ~BOOKMARKFS_FSCK_OP_RECURSIVE;
|
||||
}
|
||||
|
||||
size_t dir_stack_size = 1;
|
||||
if (flags & BOOKMARKFS_FSCK_OP_RECURSIVE) {
|
||||
dir_stack_size = 16;
|
||||
}
|
||||
struct fsck_ctx *ctx = xmalloc(sizeof(*ctx));
|
||||
*ctx = (struct fsck_ctx) {
|
||||
.backend = backend,
|
||||
.backend_ctx = resp.backend_ctx,
|
||||
.dir_stack = xmalloc(sizeof(struct fsck_dir) * dir_stack_size),
|
||||
.dir_stack_size = dir_stack_size,
|
||||
.path = sep,
|
||||
.flags = flags,
|
||||
};
|
||||
struct fsck_dir *dir = &ctx->dir_stack[0];
|
||||
*dir = FSCK_DIR_INIT(root_id);
|
||||
|
||||
if (flags & BOOKMARKFS_BACKEND_NO_SANDBOX) {
|
||||
if (0 != init_top(ctx, dir->id, sep, &dir->id)) {
|
||||
free(ctx);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
*fsck_ctx_ptr = ctx;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
backend->backend_free(resp.backend_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
fsck_destroy (
|
||||
void *fsck_ctx
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
for (size_t idx = 0; idx <= ctx->dir_stack_top; ++idx) {
|
||||
free_dir(ctx, ctx->dir_stack + idx);
|
||||
}
|
||||
ctx->backend->backend_free(ctx->backend_ctx);
|
||||
free(ctx->dir_stack);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void
|
||||
fsck_info (
|
||||
struct bookmarkfs_backend const *backend,
|
||||
uint32_t flags
|
||||
) {
|
||||
backend->backend_info(flags | BOOKMARKFS_FRONTEND_FSCK);
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_next (
|
||||
void *fsck_ctx,
|
||||
struct bookmarkfs_fsck_handler_entry *entry
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
if (ctx->flags & FSCK_ALL_DONE) {
|
||||
return 0;
|
||||
}
|
||||
struct fsck_dir *dir = ctx->dir_stack + ctx->dir_stack_top;
|
||||
int result;
|
||||
while (1) {
|
||||
if (dir->off < 0) {
|
||||
result = do_fsck(ctx, dir, NULL, entry);
|
||||
if (result < 0) {
|
||||
goto end;
|
||||
}
|
||||
if (result != BOOKMARKFS_FSCK_RESULT_END) {
|
||||
goto end;
|
||||
}
|
||||
dir->off = 0;
|
||||
}
|
||||
if (!(ctx->flags & BOOKMARKFS_FSCK_OP_RECURSIVE)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t subdir_id;
|
||||
for (; ; --dir) {
|
||||
result = do_list(ctx, dir, &subdir_id);
|
||||
if (result < 0) {
|
||||
goto end;
|
||||
}
|
||||
if (subdir_id != UINT64_MAX) {
|
||||
break;
|
||||
}
|
||||
if (dir == ctx->dir_stack) {
|
||||
goto end;
|
||||
}
|
||||
free_dir(ctx, dir);
|
||||
}
|
||||
|
||||
if (++dir >= ctx->dir_stack + ctx->dir_stack_size) {
|
||||
size_t old_size = ctx->dir_stack_size;
|
||||
ctx->dir_stack_size = old_size + (old_size >> 1);
|
||||
ctx->dir_stack = xrealloc(ctx->dir_stack,
|
||||
sizeof(struct fsck_dir) * ctx->dir_stack_size);
|
||||
dir = ctx->dir_stack + old_size;
|
||||
}
|
||||
*dir = FSCK_DIR_INIT(subdir_id);
|
||||
}
|
||||
|
||||
end:
|
||||
ctx->dir_stack_top = dir - ctx->dir_stack;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_sandbox (
|
||||
void *fsck_ctx
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
struct bookmarkfs_backend_init_resp info = {
|
||||
.bookmarks_root_id = UINT64_MAX,
|
||||
};
|
||||
if (0 != BACKEND_CALL(ctx, backend_sandbox, -1, &info)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t top_id = ctx->dir_stack[0].id;
|
||||
if (top_id == UINT64_MAX) {
|
||||
top_id = info.bookmarks_root_id;
|
||||
}
|
||||
if (0 != init_top(ctx, top_id, ctx->path, &top_id)) {
|
||||
return -1;
|
||||
}
|
||||
ctx->dir_stack[0].id = top_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct bookmarkfs_fsck_ops const fsck_offline_ops = {
|
||||
.info = fsck_info,
|
||||
|
||||
.create = fsck_create,
|
||||
.sandbox = fsck_sandbox,
|
||||
.destroy = fsck_destroy,
|
||||
|
||||
.next = fsck_next,
|
||||
.control = fsck_control,
|
||||
.apply = fsck_apply,
|
||||
};
|
439
src/fsck_online.c
Normal file
439
src/fsck_online.c
Normal file
|
@ -0,0 +1,439 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck_online.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "backend.h"
|
||||
#include "fsck_ops.h"
|
||||
#include "ioctl.h"
|
||||
#include "macros.h"
|
||||
#include "sandbox.h"
|
||||
#include "version.h"
|
||||
#include "xstd.h"
|
||||
|
||||
#define FSCK_ALL_DONE ( 1u << 24 )
|
||||
|
||||
struct fsck_ctx {
|
||||
struct fsck_dir *dir_stack;
|
||||
size_t dir_stack_size;
|
||||
size_t dir_stack_top;
|
||||
|
||||
char *dent_buf;
|
||||
size_t dent_buf_size;
|
||||
size_t dent_buf_used;
|
||||
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
struct fsck_dir {
|
||||
uint64_t id;
|
||||
int fd;
|
||||
uint32_t flags;
|
||||
|
||||
size_t dent_start;
|
||||
size_t dent_off;
|
||||
};
|
||||
#define FSCK_DIR_DONE ( 1u << 0 )
|
||||
|
||||
// Forward declaration start
|
||||
static int next_subdir (struct fsck_ctx *, struct fsck_dir *,
|
||||
struct dirent const **);
|
||||
static int open_subdir (int, char const *, uint64_t *);
|
||||
static int reset_top (struct fsck_ctx *);
|
||||
static void print_help (void);
|
||||
static void print_version (void);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
next_subdir (
|
||||
struct fsck_ctx *ctx,
|
||||
struct fsck_dir *dir,
|
||||
struct dirent const **dent_ptr
|
||||
) {
|
||||
#ifdef __linux__
|
||||
# define getdents(...) syscall(SYS_getdents64, __VA_ARGS__)
|
||||
#endif
|
||||
#define DIRENT_BUFSIZE 4096
|
||||
|
||||
size_t start = dir->dent_start;
|
||||
size_t off = dir->dent_off;
|
||||
size_t len = ctx->dent_buf_used;
|
||||
while (1) {
|
||||
if (off == len) {
|
||||
if (start + DIRENT_BUFSIZE > ctx->dent_buf_size) {
|
||||
ctx->dent_buf_size = start + DIRENT_BUFSIZE;
|
||||
ctx->dent_buf = xrealloc(ctx->dent_buf, ctx->dent_buf_size);
|
||||
}
|
||||
ssize_t nbytes
|
||||
= getdents(dir->fd, ctx->dent_buf + start, DIRENT_BUFSIZE);
|
||||
if (nbytes < 0) {
|
||||
log_printf("getdents(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
*dent_ptr = NULL;
|
||||
return 0;
|
||||
}
|
||||
off = start;
|
||||
len = off + nbytes;
|
||||
ctx->dent_buf_used = len;
|
||||
}
|
||||
|
||||
struct dirent *dent = (struct dirent *)(ctx->dent_buf + off);
|
||||
off += dent->d_reclen;
|
||||
debug_assert(off <= len);
|
||||
if (dent->d_type == DT_DIR) {
|
||||
*dent_ptr = dent;
|
||||
dir->dent_off = off;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
open_subdir (
|
||||
int dirfd,
|
||||
char const *name,
|
||||
uint64_t *id_ptr
|
||||
) {
|
||||
int flags = O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_RESOLVE_BENEATH;
|
||||
int fd = openat(dirfd, name, flags);
|
||||
if (fd < 0) {
|
||||
log_printf("openat(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct stat stat_buf;
|
||||
if (0 != fstat(fd, &stat_buf)) {
|
||||
log_printf("fstat(): %s", xstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
*id_ptr = stat_buf.st_ino & BOOKMARKFS_MAX_ID;
|
||||
return fd;
|
||||
|
||||
fail:
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
reset_top (
|
||||
struct fsck_ctx *ctx
|
||||
) {
|
||||
struct fsck_dir *dir = &ctx->dir_stack[0];
|
||||
if (0 != ioctl(dir->fd, BOOKMARKFS_IOC_FSCK_REWIND)) {
|
||||
log_printf("ioctl(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (0 != lseek(dir->fd, 0, SEEK_SET)) {
|
||||
log_printf("lseek(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
dir->flags = 0;
|
||||
dir->dent_off = 0;
|
||||
|
||||
ctx->dir_stack_top = 0;
|
||||
ctx->dent_buf_used = 0;
|
||||
ctx->flags &= ~FSCK_ALL_DONE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
print_help (void)
|
||||
{
|
||||
puts("Usage: fsck.bookmarkfs [options] <pathname>\n"
|
||||
"\n"
|
||||
"Common options:\n"
|
||||
" -o backend=<name> Backend used by the filesystem\n"
|
||||
" -o @<key>[=<value>] Backend-specific option\n"
|
||||
" -o handler=<name> Handler for resolving fsck errors\n"
|
||||
" -o %<key>[=<value>] Handler-specific option\n"
|
||||
" -o repair Attempt to repair fsck errors\n"
|
||||
" -o rl_app=<name> Readline application name\n"
|
||||
" -o type=tag|keyword Perform fsck on tags/keywords\n"
|
||||
"\n"
|
||||
" -i Enable interactive mode\n"
|
||||
" -R Perform fsck on subdirectories recursively\n"
|
||||
"\n"
|
||||
"Other options:\n"
|
||||
" -o no_sandbox Disable sandbox\n"
|
||||
#ifdef __linux__
|
||||
" -o no_landlock Disable Landlock features for sandbox\n"
|
||||
#endif
|
||||
"\n"
|
||||
" -h Print help message and exit\n"
|
||||
" -V Print version information and exit\n"
|
||||
"\n"
|
||||
"See the fsck.bookmarkfs(1) manpage for more information,\n"
|
||||
"or run 'info bookmarkfs' for the full user manual.\n"
|
||||
"\n"
|
||||
"Project homepage: <" BOOKMARKFS_HOMEPAGE_URL ">.");
|
||||
}
|
||||
|
||||
static void
|
||||
print_version (void)
|
||||
{
|
||||
printf("fsck.bookmarkfs (BookmarkFS) %d.%d.%d\n",
|
||||
BOOKMARKFS_VER_MAJOR, BOOKMARKFS_VER_MINOR, BOOKMARKFS_VER_PATCH);
|
||||
puts(BOOKMARKFS_FEATURE_STRING(DEBUG, "debug"));
|
||||
puts(BOOKMARKFS_FEATURE_STRING(INTERACTIVE_FSCK, "interactive"));
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_apply (
|
||||
void *fsck_ctx,
|
||||
struct bookmarkfs_fsck_handler_entry *entry
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
if (ctx->flags & BOOKMARKFS_BACKEND_READONLY) {
|
||||
log_puts("fsck is in readonly mode");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct fsck_dir *dir = ctx->dir_stack + ctx->dir_stack_top;
|
||||
int result = ioctl(dir->fd, BOOKMARKFS_IOC_FSCK_APPLY, &entry->data);
|
||||
if (result < 0) {
|
||||
log_printf("ioctl(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
entry->parent_id = dir->id;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_control (
|
||||
void *fsck_ctx,
|
||||
int control
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
struct fsck_dir *dir = ctx->dir_stack + ctx->dir_stack_top;
|
||||
switch (control) {
|
||||
case BOOKMARKFS_FSCK_SKIP:
|
||||
dir->flags |= FSCK_DIR_DONE;
|
||||
// fallthrough
|
||||
case BOOKMARKFS_FSCK_REWIND:
|
||||
if (0 != ioctl(dir->fd, BOOKMARKFS_IOC_FSCK_REWIND)) {
|
||||
log_printf("ioctl(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_SKIP_CHILDREN:
|
||||
if (ctx->dir_stack_top == 0) {
|
||||
ctx->flags |= FSCK_ALL_DONE;
|
||||
} else {
|
||||
ctx->dent_buf_used = dir->dent_start;
|
||||
close(dir->fd);
|
||||
--ctx->dir_stack_top;
|
||||
}
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_RESET:
|
||||
for (; dir > ctx->dir_stack; --dir) {
|
||||
close(dir->fd);
|
||||
}
|
||||
ctx->dir_stack_top = 0;
|
||||
return reset_top(ctx);
|
||||
|
||||
case BOOKMARKFS_FSCK_SAVE:
|
||||
if (ctx->flags & BOOKMARKFS_BACKEND_READONLY) {
|
||||
break;
|
||||
}
|
||||
return xfsync(dir->fd);
|
||||
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_create (
|
||||
struct bookmarkfs_backend const *UNUSED_VAR(backend),
|
||||
char *path,
|
||||
struct bookmarkfs_conf_opt *UNUSED_VAR(opts),
|
||||
uint32_t flags,
|
||||
void **fsck_ctx_ptr
|
||||
) {
|
||||
int dirfd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
if (dirfd < 0) {
|
||||
log_printf("open(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t dir_stack_size = 1;
|
||||
if (flags & BOOKMARKFS_FSCK_OP_RECURSIVE) {
|
||||
dir_stack_size = 16;
|
||||
}
|
||||
struct fsck_ctx *ctx = xmalloc(sizeof(*ctx));
|
||||
*ctx = (struct fsck_ctx) {
|
||||
.dir_stack = xmalloc(sizeof(struct fsck_dir) * dir_stack_size),
|
||||
.dir_stack_size = dir_stack_size,
|
||||
.flags = flags,
|
||||
};
|
||||
ctx->dir_stack[0] = (struct fsck_dir) {
|
||||
.id = UINT64_MAX,
|
||||
.fd = dirfd,
|
||||
};
|
||||
*fsck_ctx_ptr = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
fsck_destroy (
|
||||
void *fsck_ctx
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
for (size_t idx = 0; idx <= ctx->dir_stack_top; ++idx) {
|
||||
close(ctx->dir_stack[idx].fd);
|
||||
}
|
||||
free(ctx->dir_stack);
|
||||
free(ctx->dent_buf);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void
|
||||
fsck_info (
|
||||
struct bookmarkfs_backend const *UNUSED_VAR(backend),
|
||||
uint32_t flags
|
||||
) {
|
||||
if (flags & BOOKMARKFS_BACKEND_INFO_HELP) {
|
||||
print_help();
|
||||
} else if (flags & BOOKMARKFS_BACKEND_INFO_VERSION) {
|
||||
print_version();
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_next (
|
||||
void *fsck_ctx,
|
||||
struct bookmarkfs_fsck_handler_entry *entry
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
if (ctx->flags & FSCK_ALL_DONE) {
|
||||
return 0;
|
||||
}
|
||||
struct fsck_dir *dir = ctx->dir_stack + ctx->dir_stack_top;
|
||||
int result;
|
||||
while (1) {
|
||||
if (!(dir->flags & FSCK_DIR_DONE)) {
|
||||
result = ioctl(dir->fd, BOOKMARKFS_IOC_FSCK_NEXT, &entry->data);
|
||||
if (result < 0) {
|
||||
log_printf("ioctl(): %s", xstrerror(errno));
|
||||
goto end;
|
||||
}
|
||||
if (result != BOOKMARKFS_FSCK_RESULT_END) {
|
||||
entry->parent_id = dir->id;
|
||||
goto end;
|
||||
}
|
||||
dir->flags |= FSCK_DIR_DONE;
|
||||
}
|
||||
if (!(ctx->flags & BOOKMARKFS_FSCK_OP_RECURSIVE)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct dirent const *dent;
|
||||
for (; ; --dir) {
|
||||
result = next_subdir(ctx, dir, &dent);
|
||||
if (result < 0) {
|
||||
goto end;
|
||||
}
|
||||
if (dent != NULL) {
|
||||
break;
|
||||
}
|
||||
if (dir == ctx->dir_stack) {
|
||||
goto end;
|
||||
}
|
||||
ctx->dent_buf_used = dir->dent_start;
|
||||
close(dir->fd);
|
||||
debug_puts("exiting directory...");
|
||||
}
|
||||
|
||||
uint64_t id;
|
||||
result = open_subdir(dir->fd, dent->d_name, &id);
|
||||
if (result < 0) {
|
||||
goto end;
|
||||
}
|
||||
debug_printf("entering directory '%s'...", dent->d_name);
|
||||
if (++dir >= ctx->dir_stack + ctx->dir_stack_size) {
|
||||
size_t old_size = ctx->dir_stack_size;
|
||||
ctx->dir_stack_size = old_size + (old_size >> 1);
|
||||
ctx->dir_stack = xrealloc(ctx->dir_stack,
|
||||
sizeof(struct fsck_dir) * ctx->dir_stack_size);
|
||||
dir = ctx->dir_stack + old_size;
|
||||
}
|
||||
*dir = (struct fsck_dir) {
|
||||
.id = id,
|
||||
.fd = result,
|
||||
.dent_start = ctx->dent_buf_used,
|
||||
.dent_off = ctx->dent_buf_used,
|
||||
};
|
||||
}
|
||||
|
||||
end:
|
||||
ctx->dir_stack_top = dir - ctx->dir_stack;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
fsck_sandbox (
|
||||
void *fsck_ctx
|
||||
) {
|
||||
struct fsck_ctx *ctx = fsck_ctx;
|
||||
|
||||
if (ctx->flags & BOOKMARKFS_BACKEND_NO_SANDBOX) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t flags = 0;
|
||||
if (ctx->flags & BOOKMARKFS_BACKEND_NO_LANDLOCK) {
|
||||
flags |= SANDBOX_NO_LANDLOCK;
|
||||
}
|
||||
return sandbox_enter(-1, ctx->dir_stack[0].fd, flags);
|
||||
}
|
||||
|
||||
struct bookmarkfs_fsck_ops const fsck_online_ops = {
|
||||
.info = fsck_info,
|
||||
|
||||
.create = fsck_create,
|
||||
.sandbox = fsck_sandbox,
|
||||
.destroy = fsck_destroy,
|
||||
|
||||
.next = fsck_next,
|
||||
.control = fsck_control,
|
||||
.apply = fsck_apply,
|
||||
};
|
85
src/fsck_ops.h
Normal file
85
src/fsck_ops.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck_ops.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_FSCK_OPS_H_
|
||||
#define BOOKMARKFS_FSCK_OPS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "backend.h"
|
||||
#include "fsck_handler.h"
|
||||
|
||||
// init() flags
|
||||
#define BOOKMARKFS_FSCK_OP_RECURSIVE ( 1u << 16 )
|
||||
|
||||
typedef int (bookmarkfs_fsck_apply_func) (
|
||||
void *fsck_ctx,
|
||||
struct bookmarkfs_fsck_handler_entry *entry
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_fsck_control_func) (
|
||||
void *fsck_ctx,
|
||||
int control
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_fsck_create_func) (
|
||||
struct bookmarkfs_backend const *backend,
|
||||
char *path,
|
||||
struct bookmarkfs_conf_opt *opts,
|
||||
uint32_t flags,
|
||||
void **fsck_ctx_ptr
|
||||
);
|
||||
|
||||
typedef void (bookmarkfs_fsck_destroy_func) (
|
||||
void *fsck_ctx
|
||||
);
|
||||
|
||||
typedef void (bookmarkfs_fsck_info_func) (
|
||||
struct bookmarkfs_backend const *backend,
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_fsck_next_func) (
|
||||
void *fsck_ctx,
|
||||
struct bookmarkfs_fsck_handler_entry *entry
|
||||
);
|
||||
|
||||
typedef int (bookmarkfs_fsck_sandbox_func) (
|
||||
void *fsck_ctx
|
||||
);
|
||||
|
||||
struct bookmarkfs_fsck_ops {
|
||||
bookmarkfs_fsck_info_func *info;
|
||||
|
||||
bookmarkfs_fsck_create_func *create;
|
||||
bookmarkfs_fsck_sandbox_func *sandbox;
|
||||
bookmarkfs_fsck_destroy_func *destroy;
|
||||
|
||||
bookmarkfs_fsck_next_func *next;
|
||||
bookmarkfs_fsck_control_func *control;
|
||||
bookmarkfs_fsck_apply_func *apply;
|
||||
};
|
||||
|
||||
extern struct bookmarkfs_fsck_ops const fsck_offline_ops;
|
||||
extern struct bookmarkfs_fsck_ops const fsck_online_ops;
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_FSCK_OPS_H_) */
|
89
src/fsck_util.c
Normal file
89
src/fsck_util.c
Normal file
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck_util.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "fsck_util.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "xstd.h"
|
||||
|
||||
int
|
||||
explain_fsck_result (
|
||||
enum bookmarkfs_fsck_result result,
|
||||
struct bookmarkfs_fsck_data const *data
|
||||
) {
|
||||
char name_buf[sizeof(data->name)];
|
||||
escape_control_chars(name_buf, sizeof(name_buf), data->name, '?');
|
||||
|
||||
#define PRINT_FSCK_RESULT(s) \
|
||||
printf("bookmark %" PRIu64 " name '%.*s' " s "\n", data->id, \
|
||||
(int)sizeof(name_buf), name_buf, data->extra);
|
||||
switch (result) {
|
||||
case BOOKMARKFS_FSCK_RESULT_END:
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_RESULT_NAME_DUPLICATE:
|
||||
PRINT_FSCK_RESULT("duplicates with %" PRIu64);
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_RESULT_NAME_BADCHAR:
|
||||
PRINT_FSCK_RESULT("contains a bad character at offset %" PRIu64);
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_RESULT_NAME_BADLEN:
|
||||
PRINT_FSCK_RESULT("has invalid length %" PRIu64);
|
||||
break;
|
||||
|
||||
case BOOKMARKFS_FSCK_RESULT_NAME_INVALID:
|
||||
PRINT_FSCK_RESULT("is invalid (reason number %" PRIu64 ")");
|
||||
break;
|
||||
|
||||
default:
|
||||
log_printf("unknown fsck result code: %d", result);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
escape_control_chars (
|
||||
char *restrict dst,
|
||||
size_t dst_max,
|
||||
char const *restrict src,
|
||||
char ch
|
||||
) {
|
||||
int cnt = 0;
|
||||
for (char *end = stpncpy(dst, src, dst_max); dst < end; ++dst) {
|
||||
if (iscntrl(*dst)) {
|
||||
*dst = ch;
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
50
src/fsck_util.h
Normal file
50
src/fsck_util.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* bookmarkfs/src/fsck_util.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_FSCK_UTIL_H_
|
||||
#define BOOKMARKFS_FSCK_UTIL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ioctl.h"
|
||||
|
||||
int
|
||||
explain_fsck_result (
|
||||
enum bookmarkfs_fsck_result result,
|
||||
struct bookmarkfs_fsck_data const *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Copy a NUL-terminated string from `src` to `dst`,
|
||||
* replacing all ASCII control characters to `ch`.
|
||||
*
|
||||
* Returns the number of characters replaced.
|
||||
*/
|
||||
int
|
||||
escape_control_chars (
|
||||
char *restrict dst,
|
||||
size_t dst_max,
|
||||
char const *restrict src,
|
||||
char ch
|
||||
);
|
||||
|
||||
#endif /* defined(BOOKMARKFS_FSCK_UTIL_H_) */
|
75
src/hash.c
Normal file
75
src/hash.c
Normal file
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* bookmarkfs/src/hash.c
|
||||
*
|
||||
* Non-cryptographic hash function.
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "hash.h"
|
||||
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
# define XXH_DEBUGLEVEL 1
|
||||
#endif
|
||||
#ifdef BOOKMARKFS_XXHASH_INLINE
|
||||
# define XXH_INLINE_ALL
|
||||
# define XXH_IMPLEMENTATION
|
||||
#endif
|
||||
#include <xxhash.h>
|
||||
|
||||
#include "xstd.h"
|
||||
|
||||
static uint64_t seed;
|
||||
|
||||
uint64_t
|
||||
hash_digest (
|
||||
void const *input,
|
||||
size_t len
|
||||
) {
|
||||
return XXH3_64bits_withSeed(input, len, seed);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
hash_digestv (
|
||||
struct iovec const *bufv,
|
||||
int bufcnt
|
||||
) {
|
||||
XXH3_state_t *state = XXH3_createState();
|
||||
xassert(state != NULL);
|
||||
|
||||
xassert(0 == XXH3_64bits_reset_withSeed(state, seed));
|
||||
for (struct iovec const *end = bufv + bufcnt; bufv < end; ++bufv) {
|
||||
xassert(0 == XXH3_64bits_update(state, bufv->iov_base, bufv->iov_len));
|
||||
}
|
||||
|
||||
uint64_t result = XXH3_64bits_digest(state);
|
||||
XXH3_freeState(state);
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
hash_seed (
|
||||
uint64_t s
|
||||
) {
|
||||
seed = s;
|
||||
}
|
63
src/hash.h
Normal file
63
src/hash.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* bookmarkfs/src/hash.h
|
||||
*
|
||||
* Non-cryptographic hash function.
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_HASH_H_
|
||||
#define BOOKMARKFS_HASH_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sys/uio.h>
|
||||
|
||||
/**
|
||||
* Calculate the 64-bit hash for the given input.
|
||||
*/
|
||||
uint64_t
|
||||
hash_digest (
|
||||
void const *input,
|
||||
size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Like hash_digest(), but takes an array of input buffers.
|
||||
*
|
||||
* This function does not write to the input buffers.
|
||||
*/
|
||||
uint64_t
|
||||
hash_digestv (
|
||||
struct iovec const *bufv,
|
||||
int bufcnt
|
||||
);
|
||||
|
||||
/**
|
||||
* Seed the hash function with the given value.
|
||||
* If not called, the seed equals to value 0.
|
||||
*
|
||||
* All threads share the same seed.
|
||||
*/
|
||||
void
|
||||
hash_seed (
|
||||
uint64_t s
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_HASH_H_) */
|
448
src/hashmap.c
Normal file
448
src/hashmap.c
Normal file
|
@ -0,0 +1,448 @@
|
|||
/**
|
||||
* bookmarkfs/src/hashmap.c
|
||||
*
|
||||
* A simple hashmap implementation using hopscotch hashing
|
||||
* for collision resolution.
|
||||
*
|
||||
* The original paper for hopscotch hashing:
|
||||
* <http://mcg.cs.tau.ac.il/papers/disc2008-hopscotch.pdf>
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "hashmap.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "xstd.h"
|
||||
|
||||
#if defined(UINT64_MAX) && (UINT64_MAX == ULONG_MAX)
|
||||
# define HASHMAP_WORD_SIZE 64
|
||||
# define HOP_IDX_WIDTH 6 // log2(64)
|
||||
#elif (UINT32_MAX == ULONG_MAX)
|
||||
# define HASHMAP_WORD_SIZE 32
|
||||
# define HOP_IDX_WIDTH 5 // log2(32)
|
||||
#else
|
||||
# error "unsupported sizeof(unsigned long)"
|
||||
#endif
|
||||
|
||||
#define EXP_MIN 8
|
||||
#define EXP_MAX ( HASHMAP_WORD_SIZE - HOP_IDX_WIDTH - 1 )
|
||||
|
||||
/**
|
||||
* Alloc an extra `exp - 1` buckets, so that we don't have to rehash
|
||||
* if an accidental collision happens on the last home bucket.
|
||||
*/
|
||||
#define BUCKET_CNT(exp) ( ((size_t)1 << (exp)) + ((exp) - 1) )
|
||||
|
||||
#define BUCKET_HOP_MASK(exp) ( (1ul << (exp)) - 1 )
|
||||
#define BUCKET_HASH_MASK(exp) ( ~BUCKET_HOP_MASK(exp) )
|
||||
|
||||
#define HASH_TO_IDX(hash, exp) ( (hash) >> (HASHMAP_WORD_SIZE - (exp)) )
|
||||
#define PACK_ID(hash_i, hop_i) ( ((hash_i) << HOP_IDX_WIDTH) + (hop_i) )
|
||||
|
||||
#define BIT_SET(b, i) ( (b) |= (1ul << (i)) )
|
||||
#define BIT_UNSET(b, i) ( (b) &= ~(1ul << (i)) )
|
||||
|
||||
struct bucket {
|
||||
/**
|
||||
* Lower `exp` bits is the "hop" information of the bucket,
|
||||
* the remaining bits is part of the hashcode.
|
||||
*
|
||||
* The "hop" information is a bitmask indicating whether a
|
||||
* neighborhood bucket hashes to this bucket.
|
||||
*
|
||||
* During lookup, the hashcode fragment can be used instead of
|
||||
* full hashcode without losing information, since the stripped part
|
||||
* is the home bucket index (they are always the same).
|
||||
*
|
||||
* Compared to the naive approach that uses separate machine words:
|
||||
* - The bad:
|
||||
* - Extra cycles introduced by twiddling bits
|
||||
* - Smaller hop size
|
||||
* - Less efficient insertion on a heavily loaded table
|
||||
* - Worse collision resistance
|
||||
* - The good:
|
||||
* - Smaller memory footprint
|
||||
* - Better spatial locality during lookup on a heavily loaded table
|
||||
*/
|
||||
unsigned long bits;
|
||||
|
||||
void *entry;
|
||||
};
|
||||
|
||||
struct hashmap {
|
||||
struct bucket *buckets;
|
||||
|
||||
size_t num_buckets;
|
||||
size_t num_used;
|
||||
|
||||
unsigned exp;
|
||||
|
||||
hashmap_comp_func *entry_comp;
|
||||
hashmap_hash_func *entry_hash;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
static int count_tz (unsigned long);
|
||||
static int find_entry (struct hashmap const *, void const *, struct bucket **);
|
||||
static int make_room (struct bucket *, struct bucket const *, unsigned);
|
||||
static int rehash (struct hashmap *, bool);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
count_tz (
|
||||
unsigned long val
|
||||
) {
|
||||
if (val == 0) {
|
||||
return HASHMAP_WORD_SIZE;
|
||||
}
|
||||
|
||||
#if HASHMAP_WORD_SIZE == 64
|
||||
#ifdef HAVE___BUILTIN_CTZL
|
||||
return __builtin_ctzl(val);
|
||||
#else
|
||||
// Count trailing zeroes with de Bruijn sequence.
|
||||
// Interestingly, gcc (but not clang) understands this,
|
||||
// and can treat it as if it *is* __builtin_ctzl().
|
||||
// Also applies to the 32-bit variant.
|
||||
static int lut[] = {
|
||||
0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4,
|
||||
62, 55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5,
|
||||
63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11,
|
||||
46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6,
|
||||
};
|
||||
return lut[((val & -val) * UINT64_C(0x03f79d71b4cb0a89)) >> 58];
|
||||
#endif /* defined(HAVE___BUILTIN_CTZL) */
|
||||
#else /* HASHMAP_WORD_SIZE == 32 */
|
||||
#ifdef HAVE___BUILTIN_CTZ
|
||||
return __builtin_ctz(val);
|
||||
#else
|
||||
static int lut[] = {
|
||||
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
|
||||
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9,
|
||||
};
|
||||
return lut[((val & -val) * UINT32_C(0x077cb531)) >> 27];
|
||||
#endif /* defined(HAVE___BUILTIN_CTZ) */
|
||||
#endif /* HASHMAP_WORD_SIZE == 64 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Like hashmap_search(), but assumes that the entry exists in hashmap.
|
||||
*/
|
||||
static int
|
||||
find_entry (
|
||||
struct hashmap const *h,
|
||||
void const *entry,
|
||||
struct bucket **home_ptr
|
||||
) {
|
||||
unsigned exp = h->exp;
|
||||
unsigned long hashcode = h->entry_hash(entry);
|
||||
size_t hash_idx = HASH_TO_IDX(hashcode, exp);
|
||||
struct bucket *home = h->buckets + hash_idx;
|
||||
unsigned long hop = home->bits;
|
||||
unsigned long hash_mask = BUCKET_HASH_MASK(exp);
|
||||
|
||||
debug_assert(exp < EXP_MAX);
|
||||
for (unsigned hop_idx; ; BIT_UNSET(hop, hop_idx)) {
|
||||
hop_idx = count_tz(hop);
|
||||
debug_assert(hop_idx < exp);
|
||||
|
||||
struct bucket *b = home + hop_idx;
|
||||
if ((b->bits & hash_mask) != (hashcode << h->exp)) {
|
||||
continue;
|
||||
}
|
||||
if (entry != h->buckets[b - h->buckets].entry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*home_ptr = home;
|
||||
return hop_idx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an empty slot to insert from bucket range [home, end).
|
||||
*
|
||||
* If the empty slot is not in the neighborhood,
|
||||
* attempt to swap it forward.
|
||||
*
|
||||
* Returns the index of empty slot if found, or -1 if not.
|
||||
*/
|
||||
static int
|
||||
make_room (
|
||||
struct bucket *home,
|
||||
struct bucket const *end,
|
||||
unsigned exp
|
||||
) {
|
||||
struct bucket *b;
|
||||
for (b = home; b < end; ++b) {
|
||||
// Linear probe for the first empty slot.
|
||||
if (b->entry == NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (unlikely(b == end)) {
|
||||
debug_printf("make_room(): %s",
|
||||
"reaching end of buckets, but no empty slot found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned long hash_mask = BUCKET_HASH_MASK(exp);
|
||||
for (struct bucket *swp; home + exp <= b; b = swp) {
|
||||
// Swap empty slot forward.
|
||||
for (swp = b - (exp - 1); swp < b; ++swp) {
|
||||
size_t hop_idx = count_tz(swp->bits);
|
||||
size_t distance = b - swp;
|
||||
if (hop_idx >= distance) {
|
||||
continue;
|
||||
}
|
||||
debug_assert(hop_idx < HASHMAP_WORD_SIZE);
|
||||
BIT_SET(swp->bits, distance);
|
||||
BIT_UNSET(swp->bits, hop_idx);
|
||||
|
||||
swp += hop_idx;
|
||||
b->bits ^= (b->bits ^ swp->bits) & hash_mask;
|
||||
b->entry = swp->entry;
|
||||
break;
|
||||
}
|
||||
if (unlikely(swp == b)) {
|
||||
// Not able to swap empty slot to the neighborhood of home bucket.
|
||||
debug_printf("make_room(): %s", "neighborhood too crowded");
|
||||
b->entry = NULL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return b - home;
|
||||
}
|
||||
|
||||
static int
|
||||
rehash (
|
||||
struct hashmap *h,
|
||||
bool grow
|
||||
) {
|
||||
unsigned new_exp = h->exp;
|
||||
if (grow) {
|
||||
if (unlikely(++new_exp > EXP_MAX)) {
|
||||
log_puts("rehash(): hashmap size exceeds max limit");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
--new_exp;
|
||||
}
|
||||
|
||||
size_t new_nbuckets = BUCKET_CNT(new_exp);
|
||||
struct bucket *new_buckets = xcalloc(new_nbuckets, sizeof(struct bucket));
|
||||
|
||||
struct bucket *old_buckets_end = h->buckets + h->num_buckets;
|
||||
unsigned long new_hop_mask = BUCKET_HOP_MASK(new_exp);
|
||||
for (struct bucket *old_b = h->buckets; old_b < old_buckets_end; ++old_b) {
|
||||
void *old_e = old_b->entry;
|
||||
if (old_e == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cannot trivially deduce hashcode from old hash fragment,
|
||||
// since we have to find its home bucket.
|
||||
unsigned long hashcode = h->entry_hash(old_e);
|
||||
size_t new_hash_idx = HASH_TO_IDX(hashcode, new_exp);
|
||||
struct bucket *new_home = new_buckets + new_hash_idx;
|
||||
|
||||
int hop_idx = make_room(new_home, new_buckets + new_nbuckets, new_exp);
|
||||
if (unlikely(hop_idx < 0)) {
|
||||
log_puts("rehash(): collision attack or poor hash function");
|
||||
goto fail;
|
||||
}
|
||||
BIT_SET(new_home->bits, hop_idx);
|
||||
|
||||
struct bucket *new_b = new_home + hop_idx;
|
||||
new_b->bits = (new_b->bits & new_hop_mask) | (hashcode << new_exp);
|
||||
new_b->entry = old_e;
|
||||
}
|
||||
|
||||
free(h->buckets);
|
||||
h->buckets = new_buckets;
|
||||
h->num_buckets = new_nbuckets;
|
||||
h->exp = new_exp;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
free(new_buckets);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct hashmap *
|
||||
hashmap_create (
|
||||
hashmap_comp_func *entry_comp,
|
||||
hashmap_hash_func *entry_hash
|
||||
) {
|
||||
size_t buckets_len = BUCKET_CNT(EXP_MIN);
|
||||
// XXX: Null pointers have an implementation-defined value
|
||||
// per ISO C standard, and should not be zero-initialized with calloc().
|
||||
// However, it is guaranteed to be zero on most, if not all,
|
||||
// modern ABI standards that we know of.
|
||||
//
|
||||
// Nevertheless, we add a build-time check just in case (see configure.ac).
|
||||
//
|
||||
// See:
|
||||
// - <https://github.com/ARM-software/abi-aa>
|
||||
// - <https://github.com/riscv-non-isa/riscv-elf-psabi-doc>
|
||||
// - <https://gitlab.com/x86-psABIs/x86-64-ABI>
|
||||
struct bucket *buckets = xcalloc(buckets_len, sizeof(struct bucket));
|
||||
|
||||
struct hashmap *h = xmalloc(sizeof(*h));
|
||||
*h = (struct hashmap) {
|
||||
.buckets = buckets,
|
||||
.num_buckets = buckets_len,
|
||||
.exp = EXP_MIN,
|
||||
.entry_comp = entry_comp,
|
||||
.entry_hash = entry_hash,
|
||||
};
|
||||
return h;
|
||||
}
|
||||
|
||||
void
|
||||
hashmap_destroy (
|
||||
struct hashmap *h
|
||||
) {
|
||||
if (h == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
free(h->buckets);
|
||||
free(h);
|
||||
}
|
||||
|
||||
void
|
||||
hashmap_foreach (
|
||||
struct hashmap const *h,
|
||||
hashmap_walk_func *walk_func,
|
||||
void *user_data
|
||||
) {
|
||||
struct bucket *end = h->buckets + h->num_buckets;
|
||||
for (struct bucket *b = h->buckets; b < end; ++b) {
|
||||
void *entry = b->entry;
|
||||
if (entry == NULL) {
|
||||
continue;
|
||||
}
|
||||
walk_func(b->entry, user_data);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
hashmap_entry_delete (
|
||||
struct hashmap *h,
|
||||
void const *entry,
|
||||
long entry_id
|
||||
) {
|
||||
struct bucket *home;
|
||||
unsigned long hop_idx;
|
||||
if (entry_id < 0) {
|
||||
hop_idx = find_entry(h, entry, &home);
|
||||
} else {
|
||||
home = h->buckets + (entry_id >> HOP_IDX_WIDTH);
|
||||
hop_idx = entry_id & ((1 << HOP_IDX_WIDTH) - 1);
|
||||
}
|
||||
BIT_UNSET(home->bits, hop_idx);
|
||||
|
||||
struct bucket *b = home + hop_idx;
|
||||
debug_assert(b->entry == entry);
|
||||
b->entry = NULL;
|
||||
|
||||
size_t buckets_used = --h->num_used;
|
||||
if (h->exp <= EXP_MIN) {
|
||||
return;
|
||||
}
|
||||
// load factor < 0.125
|
||||
if (buckets_used < (h->num_buckets >> 3)) {
|
||||
debug_printf("hashmap_entry_delete(): rehashing (%zu/%zu)",
|
||||
buckets_used, h->num_buckets);
|
||||
xassert(0 == rehash(h, false));
|
||||
}
|
||||
}
|
||||
|
||||
void **
|
||||
hashmap_insert (
|
||||
struct hashmap *h,
|
||||
union hashmap_key key,
|
||||
unsigned long hashcode
|
||||
) {
|
||||
unsigned exp = h->exp;
|
||||
size_t hash_idx = HASH_TO_IDX(hashcode, exp);
|
||||
struct bucket *home = h->buckets + hash_idx;
|
||||
|
||||
int hop_idx = make_room(home, h->buckets + h->num_buckets, exp);
|
||||
if (unlikely(hop_idx < 0)) {
|
||||
debug_printf("hashmap_insert(): rehashing (%zu/%zu)",
|
||||
h->num_used, h->num_buckets - (exp - 1));
|
||||
xassert(0 == rehash(h, true));
|
||||
return hashmap_insert(h, key, hashcode);
|
||||
}
|
||||
BIT_SET(home->bits, hop_idx);
|
||||
|
||||
struct bucket *b = home + hop_idx;
|
||||
b->bits = (b->bits & BUCKET_HOP_MASK(exp)) | (hashcode << exp);
|
||||
|
||||
++h->num_used;
|
||||
return &b->entry;
|
||||
}
|
||||
|
||||
void *
|
||||
hashmap_search (
|
||||
struct hashmap const *h,
|
||||
union hashmap_key key,
|
||||
unsigned long hashcode,
|
||||
unsigned long *entry_id_ptr
|
||||
) {
|
||||
unsigned exp = h->exp;
|
||||
size_t hash_idx = HASH_TO_IDX(hashcode, exp);
|
||||
struct bucket *home = h->buckets + hash_idx;
|
||||
unsigned long hop = home->bits;
|
||||
unsigned long hash_mask = BUCKET_HASH_MASK(exp);
|
||||
|
||||
debug_assert(exp <= EXP_MAX);
|
||||
for (unsigned hop_idx; ; BIT_UNSET(hop, hop_idx)) {
|
||||
hop_idx = count_tz(hop);
|
||||
if (hop_idx >= exp) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct bucket *b = home + hop_idx;
|
||||
if ((b->bits & hash_mask) != (hashcode << exp)) {
|
||||
continue;
|
||||
}
|
||||
void *e = b->entry;
|
||||
debug_assert(e != NULL);
|
||||
if (0 != h->entry_comp(key, e)) {
|
||||
continue;
|
||||
}
|
||||
if (entry_id_ptr != NULL) {
|
||||
*entry_id_ptr = PACK_ID(hash_idx, hop_idx);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
}
|
137
src/hashmap.h
Normal file
137
src/hashmap.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* bookmarkfs/src/hashmap.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_HASHMAP_H_
|
||||
#define BOOKMARKFS_HASHMAP_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct hashmap;
|
||||
|
||||
union hashmap_key {
|
||||
void const *ptr;
|
||||
uint64_t u64;
|
||||
};
|
||||
|
||||
typedef void (hashmap_walk_func) (
|
||||
void *entry,
|
||||
void *user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* Obtain the hashcode for an entry.
|
||||
*
|
||||
* This function is only called during rehash and entry delete.
|
||||
*/
|
||||
typedef unsigned long (hashmap_hash_func) (
|
||||
void const *entry
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if the given key matches an entry in the hashmap.
|
||||
*
|
||||
* Returns 0 if matches, non-zero if not.
|
||||
*/
|
||||
typedef int (hashmap_comp_func) (
|
||||
union hashmap_key key,
|
||||
void const *entry
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a new hashmap with given callback functions.
|
||||
*/
|
||||
struct hashmap *
|
||||
hashmap_create (
|
||||
hashmap_comp_func *entry_comp,
|
||||
hashmap_hash_func *entry_hash
|
||||
);
|
||||
|
||||
void
|
||||
hashmap_destroy (
|
||||
struct hashmap *h
|
||||
);
|
||||
|
||||
/**
|
||||
* Apply the walk_func callback to each entry in hashmap,
|
||||
* passing user_data as its second argument.
|
||||
*/
|
||||
void
|
||||
hashmap_foreach (
|
||||
struct hashmap const *h,
|
||||
hashmap_walk_func *walk_func,
|
||||
void *user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* Search for an entry in the hashmap that matches the given key.
|
||||
*
|
||||
* If the key matches multiple entries,
|
||||
* it is unspecified which one will return.
|
||||
*
|
||||
* If entry_id_ptr is not NULL, on a successful lookup,
|
||||
* it will be set to a value that can be later be passed to
|
||||
* hashmap_entry_delete().
|
||||
*
|
||||
* Returns an entry if found, or NULL if not.
|
||||
*/
|
||||
void *
|
||||
hashmap_search (
|
||||
struct hashmap const *h,
|
||||
union hashmap_key key,
|
||||
unsigned long hashcode,
|
||||
unsigned long *entry_id_ptr
|
||||
);
|
||||
|
||||
/**
|
||||
* Insert an entry into the hashmap.
|
||||
*
|
||||
* Invalidates all entry IDs given by previous hashmap_search() calls.
|
||||
*
|
||||
* Returns a pointer to the inserted entry.
|
||||
* The entry must be set to a non-NULL value using this pointer
|
||||
* prior to any further search/insert/delete calls on this hashmap.
|
||||
*/
|
||||
void **
|
||||
hashmap_insert (
|
||||
struct hashmap *h,
|
||||
union hashmap_key key,
|
||||
unsigned long hashcode
|
||||
);
|
||||
|
||||
/**
|
||||
* Remove an entry from the hashmap.
|
||||
* Undefined behavior if the entry does not exist in hashmap.
|
||||
*
|
||||
* The entry_id argument should either be the value given by the
|
||||
* hashmap_search() or hashmap_insert() function call where the
|
||||
* entry is returned from, or -1 (less efficient).
|
||||
*
|
||||
* Unlike hashmap_insert(), previously entry IDs are not affected.
|
||||
*/
|
||||
void
|
||||
hashmap_entry_delete (
|
||||
struct hashmap *h,
|
||||
void const *entry,
|
||||
long entry_id
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_HASHMAP_H_) */
|
76
src/ioctl.h
Normal file
76
src/ioctl.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* bookmarkfs/src/ioctl.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_IOCTL_H_
|
||||
#define BOOKMARKFS_IOCTL_H_
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#define BOOKMARKFS_IOC_MAGIC_ 0xbf
|
||||
#define BOOKMARKFS_IOC_(rw, ...) _IO##rw(BOOKMARKFS_IOC_MAGIC_, __VA_ARGS__)
|
||||
#define BOOKMARKFS_IOC_RW_(rw, nr, name) \
|
||||
BOOKMARKFS_IOC_(rw, nr, struct bookmarkfs_##name##_data)
|
||||
|
||||
#define BOOKMARKFS_IOC_FSCK_REWIND BOOKMARKFS_IOC_(, 0)
|
||||
#define BOOKMARKFS_IOC_FSCK_NEXT BOOKMARKFS_IOC_RW_(R, 1, fsck)
|
||||
#define BOOKMARKFS_IOC_FSCK_APPLY BOOKMARKFS_IOC_RW_(WR, 2, fsck)
|
||||
#define BOOKMARKFS_IOC_PERMD BOOKMARKFS_IOC_RW_(W, 3, permd)
|
||||
|
||||
enum bookmarkfs_fsck_result {
|
||||
BOOKMARKFS_FSCK_RESULT_END = 0, // must be 0
|
||||
BOOKMARKFS_FSCK_RESULT_NAME_DUPLICATE,
|
||||
BOOKMARKFS_FSCK_RESULT_NAME_BADCHAR,
|
||||
BOOKMARKFS_FSCK_RESULT_NAME_BADLEN,
|
||||
BOOKMARKFS_FSCK_RESULT_NAME_INVALID,
|
||||
};
|
||||
|
||||
/**
|
||||
* Predefined reason codes for BOOKMARKFS_FSCK_RESULT_NAME_INVALID.
|
||||
*/
|
||||
enum {
|
||||
BOOKMARKFS_NAME_INVALID_REASON_DOTDOT = 256,
|
||||
BOOKMARKFS_NAME_INVALID_REASON_NOTUTF8,
|
||||
};
|
||||
|
||||
enum bookmarkfs_permd_op {
|
||||
BOOKMARKFS_PERMD_OP_SWAP,
|
||||
BOOKMARKFS_PERMD_OP_MOVE_BEFORE,
|
||||
BOOKMARKFS_PERMD_OP_MOVE_AFTER,
|
||||
};
|
||||
|
||||
struct bookmarkfs_fsck_data {
|
||||
uint64_t id;
|
||||
uint64_t extra;
|
||||
char name[NAME_MAX + 1];
|
||||
};
|
||||
|
||||
struct bookmarkfs_permd_data {
|
||||
enum bookmarkfs_permd_op op;
|
||||
|
||||
char name1[NAME_MAX + 1];
|
||||
char name2[NAME_MAX + 1];
|
||||
};
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_IOCTL_H_) */
|
279
src/json.c
Normal file
279
src/json.c
Normal file
|
@ -0,0 +1,279 @@
|
|||
/**
|
||||
* bookmarkfs/src/json.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "json.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "prng.h"
|
||||
#include "xstd.h"
|
||||
|
||||
struct dump_ctx {
|
||||
int fd;
|
||||
char *buf;
|
||||
size_t buf_len;
|
||||
size_t data_len;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
static int dump_cb (char const *, size_t, void *);
|
||||
static int write_iov (int, struct iovec *, int);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
dump_cb (
|
||||
char const *buf,
|
||||
size_t buf_len,
|
||||
void *user_data
|
||||
) {
|
||||
struct dump_ctx *ctx = user_data;
|
||||
|
||||
size_t new_len = ctx->data_len + buf_len;
|
||||
if (new_len <= ctx->buf_len) {
|
||||
memcpy(ctx->buf + ctx->data_len, buf, buf_len);
|
||||
ctx->data_len = new_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct iovec bufv[] = {
|
||||
{ .iov_base = ctx->buf, .iov_len = ctx->data_len },
|
||||
{ .iov_base = (char *)buf, .iov_len = buf_len },
|
||||
};
|
||||
if (0 != write_iov(ctx->fd, bufv, 2)) {
|
||||
return -1;
|
||||
}
|
||||
ctx->data_len = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
write_iov (
|
||||
int fd,
|
||||
struct iovec *bufv,
|
||||
int bufcnt
|
||||
) {
|
||||
while (1) {
|
||||
ssize_t nbytes = writev(fd, bufv, bufcnt);
|
||||
if (unlikely(nbytes < 0)) {
|
||||
int err = errno;
|
||||
log_printf("writev(): %s", xstrerror(err));
|
||||
|
||||
switch (err) {
|
||||
case EIO:
|
||||
#ifdef __FreeBSD__
|
||||
case EINTEGRITY:
|
||||
#endif
|
||||
abort();
|
||||
|
||||
case EINTR:
|
||||
continue;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
while ((size_t)nbytes >= bufv->iov_len) {
|
||||
nbytes -= (bufv++)->iov_len;
|
||||
if (--bufcnt == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
bufv->iov_base = (char *)(bufv->iov_base) + nbytes;
|
||||
bufv->iov_len -= nbytes;
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
json_array_search (
|
||||
json_t const *haystack,
|
||||
json_t const *needle
|
||||
) {
|
||||
size_t idx;
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
size_t cnt = json_array_size(haystack);
|
||||
#endif
|
||||
for (idx = 0; needle != json_array_get(haystack, idx); ++idx) {
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
xassert(idx < cnt);
|
||||
#endif
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
int
|
||||
json_dump_file_at (
|
||||
json_t const *json,
|
||||
int dirfd,
|
||||
char const *name,
|
||||
size_t flags
|
||||
) {
|
||||
#define TMPNAME_MAX ( NAME_MAX + 21 ) // strlen(".0123456789abcdef.tmp")
|
||||
char buf[TMPNAME_MAX + 1];
|
||||
|
||||
again: ;
|
||||
int name_len = snprintf(buf, TMPNAME_MAX + 1, "%s.%016" PRIx64 ".tmp",
|
||||
name, prng_rand());
|
||||
if (unlikely(name_len > TMPNAME_MAX || name_len < 0)) {
|
||||
errno = ENAMETOOLONG;
|
||||
goto open_fail;
|
||||
}
|
||||
char const *tmp_name = buf;
|
||||
if (name_len > NAME_MAX) {
|
||||
tmp_name += NAME_MAX - name_len;
|
||||
}
|
||||
|
||||
int fd = openat(dirfd, tmp_name,
|
||||
O_CREAT | O_WRONLY | O_EXCL | O_RESOLVE_BENEATH, 0600);
|
||||
if (fd < 0) {
|
||||
if (unlikely(errno == EEXIST)) {
|
||||
goto again;
|
||||
}
|
||||
|
||||
open_fail:
|
||||
log_printf("openat(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
int status = -1;
|
||||
|
||||
if (0 != json_dumpfd_ex(json, fd, flags)) {
|
||||
goto fail;
|
||||
}
|
||||
if (0 != renameat(dirfd, tmp_name, dirfd, name)) {
|
||||
log_printf("renameat(): %s", xstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
if (unlikely(0 != xfsync(dirfd))) {
|
||||
goto fail;
|
||||
}
|
||||
status = 0;
|
||||
goto end;
|
||||
|
||||
fail:
|
||||
unlinkat(dirfd, tmp_name, 0);
|
||||
|
||||
end:
|
||||
close(fd);
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
json_dumpfd_ex (
|
||||
json_t const *json,
|
||||
int fd,
|
||||
size_t flags
|
||||
) {
|
||||
struct stat stat_buf;
|
||||
if (unlikely(0 != fstat(fd, &stat_buf))) {
|
||||
log_printf("fstat(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
int status = -1;
|
||||
|
||||
struct dump_ctx ctx = {
|
||||
.fd = fd,
|
||||
.buf = xmalloc(stat_buf.st_blksize),
|
||||
.buf_len = stat_buf.st_blksize,
|
||||
};
|
||||
if (0 != json_dump_callback(json, dump_cb, &ctx, flags)) {
|
||||
log_puts("json_dump_callback() failed");
|
||||
goto end;
|
||||
}
|
||||
if (ctx.data_len > 0) {
|
||||
struct iovec buf = {
|
||||
.iov_base = ctx.buf,
|
||||
.iov_len = ctx.data_len,
|
||||
};
|
||||
if (0 != write_iov(fd, &buf, 1)) {
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (unlikely(0 != xfsync(fd))) {
|
||||
goto end;
|
||||
}
|
||||
status = 0;
|
||||
|
||||
end:
|
||||
free(ctx.buf);
|
||||
return status;
|
||||
}
|
||||
|
||||
json_t *
|
||||
json_load_file_at (
|
||||
int dirfd,
|
||||
char const *name,
|
||||
size_t flags
|
||||
) {
|
||||
int fd = openat(dirfd, name, O_RDONLY | O_RESOLVE_BENEATH);
|
||||
if (fd < 0) {
|
||||
log_printf("openat(): %s", xstrerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
json_t *result = NULL;
|
||||
|
||||
struct stat stat_buf;
|
||||
if (unlikely(0 != fstat(fd, &stat_buf))) {
|
||||
log_printf("fstat(): %s", xstrerror(errno));
|
||||
goto end;
|
||||
}
|
||||
void *buf = mmap(NULL, stat_buf.st_size, PROT_READ | PROT_MAX(PROT_READ),
|
||||
MAP_PRIVATE, fd, 0);
|
||||
if (unlikely(buf == MAP_FAILED)) {
|
||||
log_printf("mmap(): %s", xstrerror(errno));
|
||||
goto end;
|
||||
}
|
||||
xassert(0 == posix_madvise(buf, stat_buf.st_size, POSIX_MADV_SEQUENTIAL));
|
||||
|
||||
json_error_t err;
|
||||
// XXX: If the file is truncated before json_loadb() returns,
|
||||
// SIGBUS may be delivered, terminating the process.
|
||||
//
|
||||
// A possible workaround is to siglongjmp() back from a signal handler,
|
||||
// and release any memory allocated from within json_loadb().
|
||||
//
|
||||
// We choose not to implement such hacks, since the common practice
|
||||
// (that Chromium and we ourselves are doing for bookmark files)
|
||||
// is to write to a temporary file and then rename to the target path,
|
||||
// which does not cause the aforementioned problem.
|
||||
result = json_loadb(buf, stat_buf.st_size, flags, &err);
|
||||
if (result == NULL) {
|
||||
log_printf("json_loadb(): %s", err.text);
|
||||
}
|
||||
munmap(buf, stat_buf.st_size);
|
||||
|
||||
end:
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
97
src/json.h
Normal file
97
src/json.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* bookmarkfs/src/json.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_JSON_H_
|
||||
#define BOOKMARKFS_JSON_H_
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
// The following helper macros are useful with literal keys,
|
||||
// since the compiler can optimize out the strlen() call.
|
||||
#define json_object_sget(obj, key) json_object_getn(obj, key, strlen(key))
|
||||
#define json_object_sset(obj, key, value) \
|
||||
json_object_setn_nocheck(obj, key, strlen(key), value)
|
||||
#define json_object_sset_new(obj, key, value) \
|
||||
json_object_setn_new_nocheck(obj, key, strlen(key), value)
|
||||
#define json_sstring(str) json_stringn_nocheck(str, strlen(str))
|
||||
|
||||
#define json_object_foreach_iter(obj, iter) \
|
||||
for (void *iter = json_object_iter(obj); iter != NULL; \
|
||||
iter = json_object_iter_next(obj, iter))
|
||||
|
||||
#define json_object_sset_copy(obj, key, value) \
|
||||
json_object_sset_new(obj, key, json_copy(value))
|
||||
|
||||
/**
|
||||
* Find the offset of `needle` in `haystack`.
|
||||
*
|
||||
* If `haystack` is not an array, or `needle` cannot be found
|
||||
* within `haystack`, function behavior is undefined.
|
||||
*/
|
||||
size_t
|
||||
json_array_search (
|
||||
json_t const *haystack,
|
||||
json_t const *needle
|
||||
);
|
||||
|
||||
/**
|
||||
* Like json_dump_file(), but opens file with openat().
|
||||
*
|
||||
* If `name` is not the last path component,
|
||||
* function behavior is undefined.
|
||||
*
|
||||
* Writes to a temporary file first, so that failing do dump
|
||||
* does not corrupt existing file.
|
||||
*/
|
||||
int
|
||||
json_dump_file_at (
|
||||
json_t const *json,
|
||||
int dirfd,
|
||||
char const *name,
|
||||
size_t flags
|
||||
);
|
||||
|
||||
/**
|
||||
* Like json_dumpfd(), using a buffer to reduce system call overhead.
|
||||
*
|
||||
* NOTE: As of Jansson 2.14, json_dumpfd() does not buffer writes.
|
||||
*/
|
||||
int
|
||||
json_dumpfd_ex (
|
||||
json_t const *json,
|
||||
int fd,
|
||||
size_t flags
|
||||
);
|
||||
|
||||
/**
|
||||
* Like json_load_file(), but opens file with openat().
|
||||
*/
|
||||
json_t *
|
||||
json_load_file_at (
|
||||
int dirfd,
|
||||
char const *name,
|
||||
size_t flags
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_JSON_H_) */
|
76
src/lib.c
Normal file
76
src/lib.c
Normal file
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* bookmarkfs/src/lib.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hash.h"
|
||||
#include "prng.h"
|
||||
#include "version.h"
|
||||
|
||||
int
|
||||
bookmarkfs_lib_init (void)
|
||||
{
|
||||
if (0 != prng_seed(NULL)) {
|
||||
return -1;
|
||||
}
|
||||
hash_seed(prng_rand());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
bookmarkfs_print_lib_version (
|
||||
char const *prefix
|
||||
) {
|
||||
char features[] = "----";
|
||||
uint32_t libver = bookmarkfs_lib_version();
|
||||
if (libver & BOOKMARKFS_FEAT_DEBUG) {
|
||||
features[0] = '+';
|
||||
}
|
||||
if (libver & BOOKMARKFS_FEAT_NATIVE_WATCHER) {
|
||||
features[1] = '+';
|
||||
}
|
||||
if (libver & BOOKMARKFS_FEAT_SANDBOX) {
|
||||
features[2] = '+';
|
||||
}
|
||||
#ifdef __linux__
|
||||
if (libver & BOOKMARKFS_FEAT_SANDBOX_LANDLOCK) {
|
||||
features[3] = '+';
|
||||
}
|
||||
#endif
|
||||
printf("%sbookmarkfs-util %d.%d.%d\n", prefix,
|
||||
bookmarkfs_vernum_to_major(libver),
|
||||
bookmarkfs_vernum_to_minor(libver),
|
||||
bookmarkfs_vernum_to_patch(libver));
|
||||
printf(" %c debug\n", features[0]);
|
||||
printf(" %c native-watcher\n", features[1]);
|
||||
printf(" %c sandbox\n", features[2]);
|
||||
#ifdef __linux__
|
||||
printf(" %c sandbox-landlock\n", features[3]);
|
||||
#endif
|
||||
}
|
39
src/lib.h
Normal file
39
src/lib.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* bookmarkfs/src/lib.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_LIB_H_
|
||||
#define BOOKMARKFS_LIB_H_
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
BOOKMARKFS_INTERNAL
|
||||
int
|
||||
bookmarkfs_lib_init (void);
|
||||
|
||||
BOOKMARKFS_INTERNAL
|
||||
FUNCATTR_COLD
|
||||
void
|
||||
bookmarkfs_print_lib_version (
|
||||
char const *prefix
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_LIB_H_) */
|
54
src/macros.h
Normal file
54
src/macros.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* bookmarkfs/src/macros.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_MACROS_H_
|
||||
#define BOOKMARKFS_MACROS_H_
|
||||
|
||||
#ifndef STRINGIFY
|
||||
# define STRINGIFY(arg) STRINGIFY_IMPL_(arg)
|
||||
# define STRINGIFY_IMPL_(arg) #arg
|
||||
#endif
|
||||
|
||||
#define STR_WITHLEN(str) str, strlen(str)
|
||||
|
||||
#define TENTH_ARG_(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10
|
||||
#define NUM_ARGS(...) TENTH_ARG_(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
|
||||
|
||||
#define CONCAT_1_(f, s, a1) ( f(a1) )
|
||||
#define CONCAT_2_(f, s, a1, a2) ( f(a1) s f(a2) )
|
||||
#define CONCAT_3_(f, s, a1, a2, a3) ( f(a1) s f(a2) s f(a3) )
|
||||
#define CONCAT_4_(f, s, a1, a2, a3, a4) ( f(a1) s f(a2) s f(a3) s f(a4) )
|
||||
#define CONCAT_n_(n) CONCAT_##n##_
|
||||
#define CONCAT(f, s, n, ...) CONCAT_n_(n)(f, s, __VA_ARGS__)
|
||||
|
||||
#define BITWISE_OR(f, ...) CONCAT(f, |, NUM_ARGS(__VA_ARGS__), __VA_ARGS__)
|
||||
|
||||
#define BOOKMARK_FLAG_NAME_(name) BOOKMARKFS_BOOKMARK_##name
|
||||
#define BOOKMARK_FLAG(...) BITWISE_OR(BOOKMARK_FLAG_NAME_, __VA_ARGS__)
|
||||
|
||||
#define BOOKMARKFS_FEATURE_STRING_EX(which, name, off, on) \
|
||||
&(off name on name) \
|
||||
[(sizeof(STRINGIFY(BOOKMARKFS_##which)) == 2) * (sizeof(off name) - 1)]
|
||||
#define BOOKMARKFS_FEATURE_STRING(which, name) \
|
||||
BOOKMARKFS_FEATURE_STRING_EX(which, name, " - ", " + ")
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_MACROS_H_) */
|
249
src/mkfs.c
Normal file
249
src/mkfs.c
Normal file
|
@ -0,0 +1,249 @@
|
|||
/**
|
||||
* bookmarkfs/src/mkfs.c
|
||||
*
|
||||
* Chromium backend for BookmarkFS.
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "frontend_util.h"
|
||||
#include "macros.h"
|
||||
#include "version.h"
|
||||
#include "xstd.h"
|
||||
|
||||
struct mkfs_ctx {
|
||||
struct bookmarkfs_backend_conf backend_conf;
|
||||
|
||||
bookmarkfs_backend_mkfs_func *mkfs_func;
|
||||
void *backend_handle;
|
||||
char const *backend_name;
|
||||
|
||||
struct {
|
||||
unsigned no_mkfs : 1;
|
||||
unsigned print_help : 1;
|
||||
unsigned print_version : 1;
|
||||
} flags;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
static int parse_opts (struct mkfs_ctx *, int, char *[]);
|
||||
static int init_backend (struct mkfs_ctx *);
|
||||
static void print_help (void);
|
||||
static void print_version (void);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
parse_opts (
|
||||
struct mkfs_ctx *ctx,
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
enum {
|
||||
BOOKMARKFS_OPT_BACKEND,
|
||||
BOOKMARKFS_OPT_FORCE,
|
||||
|
||||
BOOKMARKFS_OPT_END_,
|
||||
};
|
||||
|
||||
#define BOOKMARKFS_OPT(name, token) [BOOKMARKFS_OPT_##name] = (token)
|
||||
char const *const opts[] = {
|
||||
BOOKMARKFS_OPT(BACKEND, "backend"),
|
||||
BOOKMARKFS_OPT(FORCE, "force"),
|
||||
|
||||
BOOKMARKFS_OPT(END_, NULL),
|
||||
};
|
||||
|
||||
getopt_foreach(argc, argv, ":o:hV") {
|
||||
case 'o':
|
||||
SUBOPT_START(opts)
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_BACKEND) SUBOPT_HAS_VAL {
|
||||
char const *name = SUBOPT_VAL;
|
||||
if (name[0] == '\0') {
|
||||
log_puts("backend name must not be empty");
|
||||
return -1;
|
||||
}
|
||||
ctx->backend_name = name;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_FORCE) SUBOPT_NO_VAL {
|
||||
ctx->backend_conf.flags |= BOOKMARKFS_BACKEND_MKFS_FORCE;
|
||||
}
|
||||
SUBOPT_OPT_FALLBACK() {
|
||||
char *opt = SUBOPT_STR;
|
||||
if (opt[0] == '@') {
|
||||
bookmarkfs_opts_add(&ctx->backend_conf.opts, opt + 1);
|
||||
} else {
|
||||
return SUBOPT_ERR_BAD_KEY();
|
||||
}
|
||||
}
|
||||
SUBOPT_END
|
||||
|
||||
case 'h':
|
||||
ctx->flags.no_mkfs = 1;
|
||||
ctx->flags.print_help = 1;
|
||||
return 0;
|
||||
|
||||
case 'V':
|
||||
ctx->flags.no_mkfs = 1;
|
||||
ctx->flags.print_version = 1;
|
||||
return 0;
|
||||
|
||||
case ':':
|
||||
log_printf("no value provided for option '-%c'", optopt);
|
||||
return -1;
|
||||
|
||||
case '?':
|
||||
log_printf("invalid option '-%c'", optopt);
|
||||
return -1;
|
||||
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
if (ctx->backend_name == NULL) {
|
||||
log_puts("backend not specified");
|
||||
return -1;
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
if (argc != 1) {
|
||||
if (argc == 0) {
|
||||
log_puts("bookmark filepath must be specified");
|
||||
} else {
|
||||
log_puts("too many arguments");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
argv += optind;
|
||||
ctx->backend_conf.store_path = argv[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_backend (
|
||||
struct mkfs_ctx *ctx
|
||||
) {
|
||||
if (ctx->backend_name == NULL) {
|
||||
if (ctx->flags.print_help) {
|
||||
print_help();
|
||||
} else {
|
||||
print_version();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct bookmarkfs_backend const *impl
|
||||
= bookmarkfs_load_backend(ctx->backend_name, &ctx->backend_handle);
|
||||
if (impl == NULL || ctx->backend_handle == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t flags = BOOKMARKFS_FRONTEND_MKFS;
|
||||
if (ctx->flags.no_mkfs) {
|
||||
if (impl->backend_info != NULL) {
|
||||
if (ctx->flags.print_help) {
|
||||
flags |= BOOKMARKFS_BACKEND_INFO_HELP;
|
||||
} else {
|
||||
flags |= BOOKMARKFS_BACKEND_INFO_VERSION;
|
||||
}
|
||||
impl->backend_info(flags);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (impl->backend_init != NULL) {
|
||||
if (0 != impl->backend_init(flags)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (impl->backend_mkfs == NULL) {
|
||||
log_puts("backend does not implement mkfs");
|
||||
return -1;
|
||||
}
|
||||
ctx->mkfs_func = impl->backend_mkfs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
print_help (void)
|
||||
{
|
||||
puts("Usage: mkfs.bookmarkfs [options] <pathname>\n"
|
||||
"\n"
|
||||
"Common options:\n"
|
||||
" -o backend=<name> Backend used by the filesystem\n"
|
||||
" -o @<key>[=<value>] Backend-specific option\n"
|
||||
" -o force Overwrite existing files\n"
|
||||
"\n"
|
||||
"Other options:\n"
|
||||
" -h Print help message and exit\n"
|
||||
" -V Print version information and exit\n"
|
||||
"\n"
|
||||
"See the mkfs.bookmarkfs(1) manpage for more information,\n"
|
||||
"or run 'info bookmarkfs' for the full user manual.\n"
|
||||
"\n"
|
||||
"Project homepage: <" BOOKMARKFS_HOMEPAGE_URL ">.");
|
||||
}
|
||||
|
||||
static void
|
||||
print_version (void)
|
||||
{
|
||||
printf("mkfs.bookmarkfs (BookmarkFS) %d.%d.%d\n",
|
||||
BOOKMARKFS_VER_MAJOR, BOOKMARKFS_VER_MINOR, BOOKMARKFS_VER_PATCH);
|
||||
puts(BOOKMARKFS_FEATURE_STRING(DEBUG, "debug"));
|
||||
}
|
||||
|
||||
int
|
||||
main (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
int status = EXIT_FAILURE;
|
||||
|
||||
struct mkfs_ctx ctx = {
|
||||
.backend_conf = {
|
||||
.version = BOOKMARKFS_VERNUM,
|
||||
},
|
||||
};
|
||||
if (0 != parse_opts(&ctx, argc, argv)) {
|
||||
goto end;
|
||||
}
|
||||
if (0 != init_backend(&ctx)) {
|
||||
goto end;
|
||||
}
|
||||
if (ctx.mkfs_func != NULL) {
|
||||
if (0 != ctx.mkfs_func(&ctx.backend_conf)) {
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
status = EXIT_SUCCESS;
|
||||
|
||||
end:
|
||||
bookmarkfs_opts_free(ctx.backend_conf.opts);
|
||||
bookmarkfs_unload(ctx.backend_handle);
|
||||
return status;
|
||||
}
|
605
src/mount.c
Normal file
605
src/mount.c
Normal file
|
@ -0,0 +1,605 @@
|
|||
/**
|
||||
* bookmarkfs/src/mount.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "frontend_util.h"
|
||||
#include "fs_ops.h"
|
||||
#include "lib.h"
|
||||
#include "macros.h"
|
||||
#include "prng.h"
|
||||
#include "version.h"
|
||||
#include "xstd.h"
|
||||
|
||||
// The upper and default values are chosen according to Chromium's
|
||||
// `url::kMaxURLChars` and `content::kMaxURLDisplayChars` constants.
|
||||
//
|
||||
// See Chromium source code:
|
||||
// - /url/url_constants.h
|
||||
// - /content/public/common/content_constants.cc
|
||||
#define FILE_MAX_LOWER 1024
|
||||
#define FILE_MAX_UPPER ( 2 * 1024 * 1024 )
|
||||
#define FILE_MAX_DEFAULT ( 32 * 1024 )
|
||||
|
||||
struct mount_ctx {
|
||||
struct bookmarkfs_backend const *backend_impl;
|
||||
void *backend_ctx;
|
||||
void *backend_handle;
|
||||
struct fuse_session *session;
|
||||
};
|
||||
|
||||
struct mount_info {
|
||||
struct bookmarkfs_backend_conf backend_conf;
|
||||
|
||||
char const *backend_name;
|
||||
char const *fs_name;
|
||||
char const *mount_target;
|
||||
|
||||
struct fs_flags fs_flags;
|
||||
size_t file_max;
|
||||
struct fuse_args args;
|
||||
|
||||
struct {
|
||||
unsigned is_foreground : 1;
|
||||
unsigned no_mount : 1;
|
||||
unsigned no_sandbox : 1;
|
||||
unsigned print_help : 1;
|
||||
unsigned print_version : 1;
|
||||
} flags;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
static void destroy_ctx (struct mount_ctx const *);
|
||||
static int enter_sandbox (struct mount_ctx const *,
|
||||
struct mount_info const *);
|
||||
static int init_all (struct mount_ctx *, int, char *[]);
|
||||
static int init_backend (struct mount_ctx *, struct mount_info *);
|
||||
static int init_fuse (struct mount_ctx *, struct mount_info *);
|
||||
static int parse_opts (struct mount_info *, int, char *[]);
|
||||
static void print_help (void);
|
||||
static void print_version (void);
|
||||
static int run_fuse (struct fuse_session *);
|
||||
// Forward declaration end
|
||||
|
||||
static void
|
||||
destroy_ctx (
|
||||
struct mount_ctx const *ctx
|
||||
) {
|
||||
if (ctx->session != NULL) {
|
||||
fuse_remove_signal_handlers(ctx->session);
|
||||
fuse_session_unmount(ctx->session);
|
||||
fuse_session_destroy(ctx->session);
|
||||
}
|
||||
if (ctx->backend_impl != NULL) {
|
||||
ctx->backend_impl->backend_free(ctx->backend_ctx);
|
||||
}
|
||||
bookmarkfs_unload(ctx->backend_handle);
|
||||
}
|
||||
|
||||
static int
|
||||
enter_sandbox (
|
||||
struct mount_ctx const *ctx,
|
||||
struct mount_info const *info
|
||||
) {
|
||||
if (info->flags.no_sandbox) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *backend_ctx = ctx->backend_ctx;
|
||||
int fusefd = fuse_session_fd(ctx->session);
|
||||
struct bookmarkfs_backend_init_resp resp = {
|
||||
.bookmarks_root_id = UINT64_MAX,
|
||||
.tags_root_id = UINT64_MAX,
|
||||
};
|
||||
if (0 != ctx->backend_impl->backend_sandbox(backend_ctx, fusefd, &resp)) {
|
||||
return -1;
|
||||
}
|
||||
debug_puts("sandbox entered");
|
||||
|
||||
fs_init_metadata(resp.bookmarks_root_id, resp.tags_root_id, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_all (
|
||||
struct mount_ctx *ctx,
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
struct mount_info info = {
|
||||
.backend_conf = {
|
||||
.version = BOOKMARKFS_VERNUM,
|
||||
.flags = BOOKMARKFS_BACKEND_READONLY,
|
||||
},
|
||||
.fs_flags = {
|
||||
.accmode = 0700,
|
||||
.readonly = 1,
|
||||
},
|
||||
.file_max = FILE_MAX_DEFAULT,
|
||||
};
|
||||
|
||||
int status = -1;
|
||||
if (0 != parse_opts(&info, argc, argv)) {
|
||||
goto end;
|
||||
}
|
||||
if (0 != init_backend(ctx, &info)) {
|
||||
if (info.flags.no_mount) {
|
||||
status = 0;
|
||||
}
|
||||
goto end;
|
||||
}
|
||||
if (0 != init_fuse(ctx, &info)) {
|
||||
goto end;
|
||||
}
|
||||
if (0 != enter_sandbox(ctx, &info)) {
|
||||
goto end;
|
||||
}
|
||||
status = 0;
|
||||
|
||||
end:
|
||||
bookmarkfs_opts_free(info.backend_conf.opts);
|
||||
fuse_opt_free_args(&info.args);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
init_backend (
|
||||
struct mount_ctx *ctx,
|
||||
struct mount_info *info
|
||||
) {
|
||||
if (info->backend_name == NULL) {
|
||||
if (info->flags.print_help) {
|
||||
print_help();
|
||||
} else {
|
||||
print_version();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct bookmarkfs_backend const *impl
|
||||
= bookmarkfs_load_backend(info->backend_name, &ctx->backend_handle);
|
||||
if (impl == NULL || ctx->backend_handle == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t flags = BOOKMARKFS_FRONTEND_MOUNT;
|
||||
if (info->flags.no_mount) {
|
||||
if (impl->backend_info != NULL) {
|
||||
if (info->flags.print_help) {
|
||||
flags |= BOOKMARKFS_BACKEND_INFO_HELP;
|
||||
} else {
|
||||
flags |= BOOKMARKFS_BACKEND_INFO_VERSION;
|
||||
}
|
||||
impl->backend_info(flags);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 != bookmarkfs_lib_init()) {
|
||||
return -1;
|
||||
}
|
||||
if (impl->backend_init != NULL) {
|
||||
flags |= BOOKMARKFS_BACKEND_LIB_READY;
|
||||
if (0 != impl->backend_init(flags)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
struct bookmarkfs_backend_init_resp resp = {
|
||||
.bookmarks_root_id = UINT64_MAX,
|
||||
.tags_root_id = UINT64_MAX,
|
||||
.bookmark_attrs = "",
|
||||
};
|
||||
if (0 != impl->backend_create(&info->backend_conf, &resp)) {
|
||||
debug_puts("backend_create() failed");
|
||||
return -1;
|
||||
}
|
||||
ctx->backend_impl = impl;
|
||||
ctx->backend_ctx = resp.backend_ctx;
|
||||
info->backend_name = resp.name;
|
||||
info->fs_flags.exclusive = !!(resp.flags & BOOKMARKFS_BACKEND_EXCLUSIVE);
|
||||
info->fs_flags.has_keyword
|
||||
= !!(resp.flags & BOOKMARKFS_BACKEND_HAS_KEYWORD);
|
||||
|
||||
fs_init_backend(impl, resp.backend_ctx);
|
||||
fs_init_metadata(resp.bookmarks_root_id, resp.tags_root_id,
|
||||
resp.bookmark_attrs);
|
||||
fs_init_opts(info->fs_flags, info->file_max);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
init_fuse (
|
||||
struct mount_ctx *ctx,
|
||||
struct mount_info *info
|
||||
) {
|
||||
struct fuse_args *args = &info->args;
|
||||
|
||||
char const *fsname = info->fs_name;
|
||||
if (fsname == NULL) {
|
||||
fsname = info->backend_name;
|
||||
}
|
||||
char const *rw = info->fs_flags.readonly ? "ro": "rw";
|
||||
|
||||
char *buf;
|
||||
xasprintf(&buf, "-odefault_permissions,fsname=%s,%s", fsname, rw);
|
||||
xassert(0 == fuse_opt_add_arg(args, buf));
|
||||
free(buf);
|
||||
|
||||
#define BOOKMARKFS_OP(name) .name = fs_op_##name
|
||||
static struct fuse_lowlevel_ops const ops = {
|
||||
BOOKMARKFS_OP(init),
|
||||
BOOKMARKFS_OP(destroy),
|
||||
BOOKMARKFS_OP(lookup),
|
||||
|
||||
BOOKMARKFS_OP(getattr),
|
||||
BOOKMARKFS_OP(setattr),
|
||||
BOOKMARKFS_OP(rename),
|
||||
BOOKMARKFS_OP(ioctl),
|
||||
|
||||
BOOKMARKFS_OP(setxattr),
|
||||
BOOKMARKFS_OP(getxattr),
|
||||
BOOKMARKFS_OP(listxattr),
|
||||
BOOKMARKFS_OP(removexattr),
|
||||
|
||||
BOOKMARKFS_OP(create),
|
||||
BOOKMARKFS_OP(open),
|
||||
BOOKMARKFS_OP(read),
|
||||
BOOKMARKFS_OP(write),
|
||||
BOOKMARKFS_OP(fsync),
|
||||
BOOKMARKFS_OP(release),
|
||||
BOOKMARKFS_OP(link),
|
||||
BOOKMARKFS_OP(unlink),
|
||||
|
||||
BOOKMARKFS_OP(rmdir),
|
||||
BOOKMARKFS_OP(mkdir),
|
||||
BOOKMARKFS_OP(opendir),
|
||||
BOOKMARKFS_OP(readdir),
|
||||
BOOKMARKFS_OP(readdirplus),
|
||||
BOOKMARKFS_OP(fsyncdir),
|
||||
BOOKMARKFS_OP(releasedir),
|
||||
};
|
||||
struct fuse_session *session
|
||||
= fuse_session_new(args, &ops, sizeof(ops), NULL);
|
||||
if (session == NULL) {
|
||||
log_puts("fuse_session_new() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 != fuse_set_signal_handlers(session)) {
|
||||
log_puts("fuse_set_signal_handlers() failed");
|
||||
goto destroy;
|
||||
}
|
||||
if (0 != fuse_session_mount(session, info->mount_target)) {
|
||||
log_puts("fuse_session_mount() failed");
|
||||
goto destroy;
|
||||
}
|
||||
if (0 != fuse_daemonize(info->flags.is_foreground)) {
|
||||
log_puts("fuse_daemonize() failed");
|
||||
goto unmount;
|
||||
}
|
||||
|
||||
ctx->session = session;
|
||||
fs_init_fuse(session);
|
||||
return 0;
|
||||
|
||||
unmount:
|
||||
fuse_remove_signal_handlers(session);
|
||||
fuse_session_unmount(session);
|
||||
|
||||
destroy:
|
||||
fuse_session_destroy(session);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_opts (
|
||||
struct mount_info *info,
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
enum {
|
||||
// bookmarkfs options
|
||||
BOOKMARKFS_OPT_ACCMODE,
|
||||
BOOKMARKFS_OPT_BACKEND,
|
||||
BOOKMARKFS_OPT_CTIME,
|
||||
BOOKMARKFS_OPT_EOL,
|
||||
BOOKMARKFS_OPT_FILE_MAX,
|
||||
BOOKMARKFS_OPT_NO_LANDLOCK,
|
||||
BOOKMARKFS_OPT_NO_SANDBOX,
|
||||
|
||||
// kernel options
|
||||
BOOKMARKFS_OPT_FSNAME,
|
||||
BOOKMARKFS_OPT_RO,
|
||||
BOOKMARKFS_OPT_RW,
|
||||
|
||||
// ignored options
|
||||
BOOKMARKFS_OPT_ATIME,
|
||||
BOOKMARKFS_OPT_BLKDEV,
|
||||
BOOKMARKFS_OPT_BLKSIZE,
|
||||
BOOKMARKFS_OPT_DEV,
|
||||
BOOKMARKFS_OPT_DIRATIME,
|
||||
BOOKMARKFS_OPT_EXEC,
|
||||
BOOKMARKFS_OPT_FD,
|
||||
BOOKMARKFS_OPT_RELATIME,
|
||||
BOOKMARKFS_OPT_STRICTATIME,
|
||||
BOOKMARKFS_OPT_SUBTYPE,
|
||||
BOOKMARKFS_OPT_SUID,
|
||||
BOOKMARKFS_OPT_NOATIME,
|
||||
BOOKMARKFS_OPT_NODEV,
|
||||
BOOKMARKFS_OPT_NODIRATIME,
|
||||
BOOKMARKFS_OPT_NOEXEC,
|
||||
BOOKMARKFS_OPT_NORELATIME,
|
||||
BOOKMARKFS_OPT_NOSTRICTATIME,
|
||||
BOOKMARKFS_OPT_NOSUID,
|
||||
|
||||
BOOKMARKFS_OPT_END_,
|
||||
};
|
||||
|
||||
#define BOOKMARKFS_OPT(name, token) [BOOKMARKFS_OPT_##name] = (token)
|
||||
char const *const opts[] = {
|
||||
BOOKMARKFS_OPT(ACCMODE, "accmode"),
|
||||
BOOKMARKFS_OPT(BACKEND, "backend"),
|
||||
BOOKMARKFS_OPT(CTIME, "ctime"),
|
||||
BOOKMARKFS_OPT(EOL, "eol"),
|
||||
BOOKMARKFS_OPT(FILE_MAX, "file_max"),
|
||||
BOOKMARKFS_OPT(NO_LANDLOCK, "no_landlock"),
|
||||
BOOKMARKFS_OPT(NO_SANDBOX, "no_sandbox"),
|
||||
|
||||
BOOKMARKFS_OPT(FSNAME, "fsname"),
|
||||
BOOKMARKFS_OPT(RO, "ro"),
|
||||
BOOKMARKFS_OPT(RW, "rw"),
|
||||
|
||||
BOOKMARKFS_OPT(ATIME, "atime"),
|
||||
BOOKMARKFS_OPT(BLKDEV, "blkdev"),
|
||||
BOOKMARKFS_OPT(BLKSIZE, "blksize"),
|
||||
BOOKMARKFS_OPT(DEV, "dev"),
|
||||
BOOKMARKFS_OPT(DIRATIME, "diratime"),
|
||||
BOOKMARKFS_OPT(EXEC, "exec"),
|
||||
BOOKMARKFS_OPT(FD, "fd"),
|
||||
BOOKMARKFS_OPT(RELATIME, "relatime"),
|
||||
BOOKMARKFS_OPT(STRICTATIME, "strictatime"),
|
||||
BOOKMARKFS_OPT(SUBTYPE, "subtype"),
|
||||
BOOKMARKFS_OPT(SUID, "suid"),
|
||||
BOOKMARKFS_OPT(NOATIME, "noatime"),
|
||||
BOOKMARKFS_OPT(NODEV, "nodev"),
|
||||
BOOKMARKFS_OPT(NODIRATIME, "nodiratime"),
|
||||
BOOKMARKFS_OPT(NOEXEC, "noexec"),
|
||||
BOOKMARKFS_OPT(NORELATIME, "norelatime"),
|
||||
BOOKMARKFS_OPT(NOSTRICTATIME, "nostrictatime"),
|
||||
BOOKMARKFS_OPT(NOSUID, "nosuid"),
|
||||
|
||||
BOOKMARKFS_OPT(END_, NULL),
|
||||
};
|
||||
|
||||
char const *fargs_common = "-onoatime,noexec,nosuid,"
|
||||
#ifdef __linux__
|
||||
"nodev,"
|
||||
#endif
|
||||
"subtype=bookmarkfs";
|
||||
struct fuse_args *fargs = &info->args;
|
||||
xassert(0 == fuse_opt_add_arg(fargs, ""));
|
||||
xassert(0 == fuse_opt_add_arg(fargs, fargs_common));
|
||||
|
||||
#define SUBOPT_PARSE_NUM(min, max, result) \
|
||||
do { \
|
||||
long num_ = strtol(SUBOPT_VAL, NULL, 0); \
|
||||
if (num_ < (min) || num_ > (max)) { \
|
||||
return SUBOPT_ERR_BAD_VAL(); \
|
||||
} \
|
||||
(result) = num_; \
|
||||
} while (0)
|
||||
|
||||
getopt_foreach(argc, argv, ":o:FhV") {
|
||||
case 'o':
|
||||
SUBOPT_START(opts)
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_ACCMODE) SUBOPT_HAS_VAL {
|
||||
SUBOPT_PARSE_NUM(0, 0777, info->fs_flags.accmode);
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_BACKEND) SUBOPT_HAS_VAL {
|
||||
char const *name = SUBOPT_VAL;
|
||||
if (name[0] == '\0') {
|
||||
log_puts("backend name must not be empty");
|
||||
return -1;
|
||||
}
|
||||
info->backend_name = name;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_CTIME) SUBOPT_NO_VAL {
|
||||
info->fs_flags.ctime = 1;
|
||||
info->backend_conf.flags |= BOOKMARKFS_BACKEND_CTIME;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_EOL) SUBOPT_NO_VAL {
|
||||
info->fs_flags.eol = 1;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_FILE_MAX) SUBOPT_HAS_VAL {
|
||||
SUBOPT_PARSE_NUM(FILE_MAX_LOWER, FILE_MAX_UPPER, info->file_max);
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_FSNAME) SUBOPT_HAS_VAL {
|
||||
info->fs_name = SUBOPT_VAL;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_NO_LANDLOCK) SUBOPT_NO_VAL {
|
||||
info->backend_conf.flags |= BOOKMARKFS_BACKEND_NO_LANDLOCK;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_NO_SANDBOX) SUBOPT_NO_VAL {
|
||||
info->flags.no_sandbox = 1;
|
||||
info->backend_conf.flags |= BOOKMARKFS_BACKEND_NO_SANDBOX;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_RO) SUBOPT_NO_VAL {
|
||||
info->fs_flags.readonly = 1;
|
||||
info->backend_conf.flags |= BOOKMARKFS_BACKEND_READONLY;
|
||||
}
|
||||
SUBOPT_OPT(BOOKMARKFS_OPT_RW) SUBOPT_NO_VAL {
|
||||
info->fs_flags.readonly = 0;
|
||||
info->backend_conf.flags &= ~BOOKMARKFS_BACKEND_READONLY;
|
||||
}
|
||||
SUBOPT_OPT_FALLBACK() {
|
||||
char *opt = SUBOPT_STR;
|
||||
if (opt[0] == '@') {
|
||||
bookmarkfs_opts_add(&info->backend_conf.opts, opt + 1);
|
||||
} else {
|
||||
xassert(0 == fuse_opt_add_opt(&fargs->argv[1], opt));
|
||||
}
|
||||
}
|
||||
SUBOPT_OPT_DEFAULT() {
|
||||
debug_printf("option '-o %s' ignored", SUBOPT_STR);
|
||||
}
|
||||
SUBOPT_END
|
||||
|
||||
case 'F':
|
||||
info->flags.is_foreground = 1;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
info->flags.print_help = 1;
|
||||
info->flags.no_mount = 1;
|
||||
return 0;
|
||||
|
||||
case 'V':
|
||||
info->flags.print_version = 1;
|
||||
info->flags.no_mount = 1;
|
||||
return 0;
|
||||
|
||||
case ':':
|
||||
log_printf("no value provided for option '-%c'", optopt);
|
||||
return -1;
|
||||
|
||||
case '?':
|
||||
log_printf("invalid option '-%c'", optopt);
|
||||
return -1;
|
||||
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
if (info->backend_name == NULL) {
|
||||
log_puts("backend not specified");
|
||||
return -1;
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
if (argc != 2) {
|
||||
if (argc < 2) {
|
||||
log_puts("mount source and target must be specified");
|
||||
} else {
|
||||
log_puts("too many arguments");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
argv += optind;
|
||||
info->backend_conf.store_path = argv[0];
|
||||
info->mount_target = argv[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
print_help (void)
|
||||
{
|
||||
puts("Usage: mount.bookmarkfs [options] <src> <target>\n"
|
||||
"\n"
|
||||
"Common options:\n"
|
||||
" -o backend=<name> Backend used by the filesystem\n"
|
||||
" -o @<key>[=<value>] Backend-specific option\n"
|
||||
" -o accmode=<mode> File access mode\n"
|
||||
" -o ctime Maintain file change time\n"
|
||||
" -o eol Add a newline to the end of file\n"
|
||||
" -o file_max=<bytes> Max file size limit\n"
|
||||
"\n"
|
||||
"Other options:\n"
|
||||
" -o no_sandbox Disable sandbox\n"
|
||||
#ifdef __linux__
|
||||
" -o no_landlock Disable Landlock features for sandbox\n"
|
||||
#endif
|
||||
"\n"
|
||||
" -F Run in foreground, do not daemonize\n"
|
||||
" -h Print help message and exit\n"
|
||||
" -V Print version information and exit\n"
|
||||
"\n"
|
||||
"See the mount.bookmarkfs(1) manpage for more information,\n"
|
||||
"or run 'info bookmarkfs' for the full user manual.\n"
|
||||
"\n"
|
||||
"Project homepage: <" BOOKMARKFS_HOMEPAGE_URL ">.");
|
||||
}
|
||||
|
||||
static void
|
||||
print_version (void)
|
||||
{
|
||||
printf("mount.bookmarkfs (BookmarkFS) %d.%d.%d\n",
|
||||
BOOKMARKFS_VER_MAJOR, BOOKMARKFS_VER_MINOR, BOOKMARKFS_VER_PATCH);
|
||||
puts(BOOKMARKFS_FEATURE_STRING(DEBUG, "debug"));
|
||||
|
||||
bookmarkfs_print_lib_version("\n");
|
||||
}
|
||||
|
||||
static int
|
||||
run_fuse (
|
||||
struct fuse_session *session
|
||||
) {
|
||||
if (session == NULL) {
|
||||
return 0;
|
||||
}
|
||||
int status = fuse_session_loop(session);
|
||||
if (status != 0) {
|
||||
char const *desc;
|
||||
if (status < 0) {
|
||||
desc = xstrerror(-status);
|
||||
} else {
|
||||
// MT-unsafe
|
||||
desc = strsignal(status);
|
||||
}
|
||||
log_printf("fuse_session_loop(): %s", desc);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
main (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
int status = EXIT_FAILURE;
|
||||
|
||||
struct mount_ctx ctx = { 0 };
|
||||
if (0 != init_all(&ctx, argc, argv)) {
|
||||
goto end;
|
||||
}
|
||||
if (0 != run_fuse(ctx.session)) {
|
||||
goto end;
|
||||
}
|
||||
status = EXIT_SUCCESS;
|
||||
|
||||
end:
|
||||
destroy_ctx(&ctx);
|
||||
return status;
|
||||
}
|
96
src/prng.c
Normal file
96
src/prng.c
Normal file
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* bookmarkfs/src/prng.c
|
||||
*
|
||||
* Non-cryptographic pseudo-random number generator.
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "prng.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/random.h>
|
||||
|
||||
#include "xstd.h"
|
||||
|
||||
// Forward declaration start
|
||||
static uint64_t rotl64 (uint64_t, unsigned);
|
||||
// Forward declaration end
|
||||
|
||||
#ifndef BOOKMARKFS_TLS
|
||||
# error "compiler does not support thread-local storage"
|
||||
#endif
|
||||
static BOOKMARKFS_TLS uint64_t state[4];
|
||||
|
||||
static uint64_t
|
||||
rotl64 (
|
||||
uint64_t val,
|
||||
unsigned n
|
||||
) {
|
||||
return (val << n) | (val >> (64 - n));
|
||||
}
|
||||
|
||||
/**
|
||||
* The xoshiro256++ PRNG.
|
||||
*
|
||||
* See: <https://prng.di.unimi.it/xoshiro256plusplus.c>
|
||||
*/
|
||||
uint64_t
|
||||
prng_rand (void)
|
||||
{
|
||||
uint64_t result = rotl64(state[0] + state[3], 23) + state[0];
|
||||
|
||||
uint64_t tmp = state[1] << 17;
|
||||
state[2] ^= state[0];
|
||||
state[3] ^= state[1];
|
||||
state[1] ^= state[2];
|
||||
state[0] ^= state[3];
|
||||
state[2] ^= tmp;
|
||||
state[3] = rotl64(state[3], 45);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
prng_seed (
|
||||
uint64_t const s[4]
|
||||
) {
|
||||
if (s != NULL) {
|
||||
memcpy(state, s, sizeof(state));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t nbytes = getrandom(state, sizeof(state), 0);
|
||||
if (unlikely(nbytes < 0)) {
|
||||
log_printf("getrandom(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// For reads up to 256 bytes, getrandom() always returns
|
||||
// as many bytes as requested.
|
||||
// This is guaranteed on both Linux and FreeBSD.
|
||||
debug_assert(nbytes == sizeof(state));
|
||||
return 0;
|
||||
}
|
54
src/prng.h
Normal file
54
src/prng.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* bookmarkfs/src/prng.h
|
||||
*
|
||||
* Non-cryptographic pseudo-random number generator.
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_PRNG_H_
|
||||
#define BOOKMARKFS_PRNG_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Returns a pseudo-random 64-bit unsigned integer.
|
||||
* The PRNG should be seeded before calling this function.
|
||||
*
|
||||
* This function is MT-Safe.
|
||||
*/
|
||||
uint64_t
|
||||
prng_rand (void);
|
||||
|
||||
/**
|
||||
* Seed the PRNG with the given values.
|
||||
* If `s` is NULL, the values will be read from /dev/urandom.
|
||||
*
|
||||
* When used in a multi-threaded environment,
|
||||
* each thread should seed the PRNG separately.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure.
|
||||
* If `s` is not NULL, this function never fails.
|
||||
*/
|
||||
int
|
||||
prng_seed (
|
||||
uint64_t const s[4]
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_PRNG_H_) */
|
423
src/sandbox.c
Normal file
423
src/sandbox.c
Normal file
|
@ -0,0 +1,423 @@
|
|||
/**
|
||||
* bookmarkfs/src/sandbox.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "sandbox.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if !defined(BOOKMARKFS_SANDBOX)
|
||||
#elif defined(__linux__)
|
||||
# ifdef BOOKMARKFS_SANDBOX_LANDLOCK
|
||||
# include <linux/landlock.h>
|
||||
# include <sys/syscall.h>
|
||||
# endif
|
||||
# include <sys/mman.h>
|
||||
# include <sys/prctl.h>
|
||||
# include <seccomp.h>
|
||||
# define SANDBOX_IMPL_SECCOMP_LANDLOCK
|
||||
#elif defined(__FreeBSD__)
|
||||
# include <sys/capsicum.h>
|
||||
# include <sys/procctl.h>
|
||||
# define SANDBOX_IMPL_CAPSICUM
|
||||
#else
|
||||
# error "sandbox not implemented on this platform"
|
||||
#endif
|
||||
|
||||
#include "macros.h"
|
||||
#include "xstd.h"
|
||||
|
||||
#ifdef SANDBOX_IMPL_SECCOMP_LANDLOCK
|
||||
|
||||
#define ARG_CMP_(...) (struct scmp_arg_cmp []) { __VA_ARGS__ }
|
||||
#define SCMP_RULE(sys, prio, ...) \
|
||||
{ \
|
||||
.syscall_num = SCMP_SYS(sys), \
|
||||
.priority = prio, \
|
||||
.argc = sizeof(ARG_CMP_(__VA_ARGS__)) / sizeof(struct scmp_arg_cmp), \
|
||||
.argv = ARG_CMP_(__VA_ARGS__), \
|
||||
}
|
||||
#define SCMP_RULE_NOARG(sys, prio) \
|
||||
{ \
|
||||
.syscall_num = SCMP_SYS(sys), \
|
||||
.priority = prio, \
|
||||
}
|
||||
#define SCMP_RULES(rules) rules, sizeof(rules) / sizeof(struct scmp_rule_def)
|
||||
|
||||
struct scmp_rule_def {
|
||||
int syscall_num;
|
||||
int argc;
|
||||
struct scmp_arg_cmp const *argv;
|
||||
uint8_t priority;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
static int add_scmp_rules (scmp_filter_ctx, struct scmp_rule_def const *,
|
||||
size_t);
|
||||
|
||||
#ifdef BOOKMARKFS_SANDBOX_LANDLOCK
|
||||
static int landlock_create_ruleset (struct landlock_ruleset_attr const *,
|
||||
size_t, uint32_t);
|
||||
static int landlock_add_rule (int, enum landlock_rule_type,
|
||||
void const *, uint32_t);
|
||||
static int landlock_restrict_self (int, uint32_t);
|
||||
#endif /* defined(BOOKMARKFS_SANDBOX_LANDLOCK) */
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
add_scmp_rules (
|
||||
scmp_filter_ctx sfctx,
|
||||
struct scmp_rule_def const *rules,
|
||||
size_t rules_cnt
|
||||
) {
|
||||
for (size_t idx = 0; idx < rules_cnt; ++idx) {
|
||||
struct scmp_rule_def const *rule = rules + idx;
|
||||
|
||||
int status = seccomp_rule_add_array(sfctx, SCMP_ACT_ALLOW,
|
||||
rule->syscall_num, rule->argc, rule->argv);
|
||||
if (status < 0) {
|
||||
log_printf("seccomp_rule_add(): %s", xstrerror(-status));
|
||||
return status;
|
||||
}
|
||||
status = seccomp_syscall_priority(sfctx, rule->syscall_num,
|
||||
rule->priority);
|
||||
if (status < 0) {
|
||||
log_printf("seccomp_syscall_priority(): %s", xstrerror(-status));
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef BOOKMARKFS_SANDBOX_LANDLOCK
|
||||
|
||||
static int
|
||||
landlock_create_ruleset (
|
||||
struct landlock_ruleset_attr const *attr,
|
||||
size_t attr_size,
|
||||
uint32_t flags
|
||||
) {
|
||||
return syscall(SYS_landlock_create_ruleset, attr, attr_size, flags);
|
||||
}
|
||||
|
||||
static int
|
||||
landlock_add_rule (
|
||||
int ruleset_fd,
|
||||
enum landlock_rule_type rule_type,
|
||||
void const *rule_attr,
|
||||
uint32_t flags
|
||||
) {
|
||||
return syscall(SYS_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
|
||||
flags);
|
||||
}
|
||||
|
||||
static int
|
||||
landlock_restrict_self (
|
||||
int ruleset_fd,
|
||||
uint32_t flags
|
||||
) {
|
||||
return syscall(SYS_landlock_restrict_self, ruleset_fd, flags);
|
||||
}
|
||||
|
||||
#endif /* defined(BOOKMARKFS_SANDBOX_LANDLOCK) */
|
||||
|
||||
int
|
||||
sandbox_enter (
|
||||
int UNUSED_VAR(fusefd),
|
||||
int dirfd,
|
||||
uint32_t flags
|
||||
) {
|
||||
if (flags & SANDBOX_NOOP) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (unlikely(0 != prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))) {
|
||||
log_printf("prctl(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
scmp_filter_ctx sfctx = seccomp_init(SCMP_ACT_ERRNO(EPERM));
|
||||
xassert(sfctx != NULL);
|
||||
|
||||
int status = seccomp_attr_set(sfctx, SCMP_FLTATR_CTL_NNP, 0);
|
||||
if (unlikely(status < 0)) {
|
||||
log_printf("seccomp_attr_set(): %s", xstrerror(-status));
|
||||
goto free_sfctx;
|
||||
}
|
||||
status = -1;
|
||||
|
||||
pid_t pid = getpid();
|
||||
struct scmp_rule_def const rules_common[] = {
|
||||
// exit
|
||||
SCMP_RULE_NOARG(exit, 0),
|
||||
SCMP_RULE_NOARG(exit_group, 0),
|
||||
|
||||
// signals
|
||||
SCMP_RULE_NOARG(sigaction, 10),
|
||||
SCMP_RULE_NOARG(sigprocmask, 10),
|
||||
SCMP_RULE_NOARG(sigreturn, 10),
|
||||
SCMP_RULE_NOARG(restart_syscall, 10),
|
||||
SCMP_RULE_NOARG(rt_sigaction, 10),
|
||||
SCMP_RULE_NOARG(rt_sigprocmask, 10),
|
||||
SCMP_RULE_NOARG(rt_sigreturn, 10),
|
||||
SCMP_RULE(kill, 0, SCMP_A0_32(SCMP_CMP_EQ, pid)),
|
||||
SCMP_RULE(tgkill, 0, SCMP_A0_32(SCMP_CMP_EQ, pid)),
|
||||
|
||||
// read/write
|
||||
SCMP_RULE_NOARG(lseek, 190),
|
||||
SCMP_RULE_NOARG(pread64, 190),
|
||||
SCMP_RULE_NOARG(pwrite64, 190),
|
||||
SCMP_RULE_NOARG(read, 200),
|
||||
SCMP_RULE_NOARG(write, 200),
|
||||
SCMP_RULE_NOARG(writev, 200),
|
||||
|
||||
// other file operations
|
||||
SCMP_RULE_NOARG(close, 20),
|
||||
SCMP_RULE_NOARG(fallocate, 30),
|
||||
SCMP_RULE_NOARG(fcntl, 100),
|
||||
SCMP_RULE_NOARG(flock, 20),
|
||||
SCMP_RULE_NOARG(fstat, 100),
|
||||
SCMP_RULE_NOARG(fstat64, 100),
|
||||
SCMP_RULE_NOARG(fsync, 30),
|
||||
SCMP_RULE_NOARG(ftruncate, 30),
|
||||
SCMP_RULE_NOARG(getdents64, 100),
|
||||
SCMP_RULE_NOARG(ioctl, 30),
|
||||
|
||||
// memory management
|
||||
SCMP_RULE_NOARG(brk, 20),
|
||||
SCMP_RULE_NOARG(madvise, 30),
|
||||
SCMP_RULE(mmap, 30, SCMP_A2_32(SCMP_CMP_MASKED_EQ, PROT_EXEC, 0)),
|
||||
SCMP_RULE(mmap2, 30, SCMP_A2_32(SCMP_CMP_MASKED_EQ, PROT_EXEC, 0)),
|
||||
SCMP_RULE(mprotect, 30, SCMP_A2_32(SCMP_CMP_MASKED_EQ, PROT_EXEC, 0)),
|
||||
SCMP_RULE_NOARG(mremap, 30),
|
||||
SCMP_RULE_NOARG(msync, 30),
|
||||
SCMP_RULE_NOARG(munmap, 30),
|
||||
|
||||
// current process info
|
||||
SCMP_RULE_NOARG(geteuid, 10),
|
||||
SCMP_RULE_NOARG(geteuid32, 10),
|
||||
SCMP_RULE_NOARG(getegid, 10),
|
||||
SCMP_RULE_NOARG(getegid32, 10),
|
||||
SCMP_RULE_NOARG(getpid, 10),
|
||||
SCMP_RULE_NOARG(gettid, 10),
|
||||
|
||||
// system info
|
||||
SCMP_RULE_NOARG(clock_gettime, 50),
|
||||
SCMP_RULE_NOARG(clock_gettime64, 50),
|
||||
|
||||
// other utils
|
||||
SCMP_RULE_NOARG(futex, 40),
|
||||
SCMP_RULE_NOARG(getrandom, 40),
|
||||
SCMP_RULE_NOARG(poll, 40),
|
||||
SCMP_RULE_NOARG(pselect6, 40),
|
||||
SCMP_RULE_NOARG(pselect6_time64, 40),
|
||||
};
|
||||
if (unlikely(0 != add_scmp_rules(sfctx, SCMP_RULES(rules_common)))) {
|
||||
goto free_sfctx;
|
||||
}
|
||||
|
||||
if (dirfd < 0) {
|
||||
goto apply_seccomp;
|
||||
}
|
||||
struct scmp_rule_def const rules_dir[] = {
|
||||
SCMP_RULE(fanotify_mark, 40, SCMP_A3_32(SCMP_CMP_NE, AT_FDCWD)),
|
||||
SCMP_RULE(fstatat64, 100, SCMP_A0_32(SCMP_CMP_NE, AT_FDCWD)),
|
||||
SCMP_RULE(newfstatat, 100, SCMP_A0_32(SCMP_CMP_NE, AT_FDCWD)),
|
||||
SCMP_RULE(openat, 20, SCMP_A0_32(SCMP_CMP_NE, AT_FDCWD)),
|
||||
};
|
||||
if (unlikely(0 != add_scmp_rules(sfctx, SCMP_RULES(rules_dir)))) {
|
||||
goto free_sfctx;
|
||||
}
|
||||
|
||||
if (flags & SANDBOX_READONLY) {
|
||||
goto do_landlock;
|
||||
}
|
||||
struct scmp_rule_def const rules_dir_extra[] = {
|
||||
SCMP_RULE(renameat, 20, SCMP_A0_32(SCMP_CMP_NE, AT_FDCWD),
|
||||
SCMP_A2_32(SCMP_CMP_NE, AT_FDCWD)),
|
||||
};
|
||||
if (unlikely(0 != add_scmp_rules(sfctx, SCMP_RULES(rules_dir_extra)))) {
|
||||
goto free_sfctx;
|
||||
}
|
||||
|
||||
do_landlock:
|
||||
if (flags & SANDBOX_NO_LANDLOCK) {
|
||||
goto apply_seccomp;
|
||||
}
|
||||
#ifdef BOOKMARKFS_SANDBOX_LANDLOCK
|
||||
int ruleset_version = landlock_create_ruleset(NULL, 0,
|
||||
LANDLOCK_CREATE_RULESET_VERSION);
|
||||
if (unlikely(ruleset_version < 0)) {
|
||||
log_printf("landlock_create_ruleset(): %s", xstrerror(errno));
|
||||
goto free_sfctx;
|
||||
}
|
||||
|
||||
#define LANDLOCK_FS_RIGHT_NAME_(name) LANDLOCK_ACCESS_FS_##name
|
||||
#define LANDLOCK_FS_RIGHT(...) \
|
||||
BITWISE_OR(LANDLOCK_FS_RIGHT_NAME_, __VA_ARGS__)
|
||||
|
||||
uint64_t handled_access = LANDLOCK_FS_RIGHT(EXECUTE) |
|
||||
LANDLOCK_FS_RIGHT(WRITE_FILE, READ_FILE, READ_DIR, REMOVE_DIR) |
|
||||
LANDLOCK_FS_RIGHT(REMOVE_FILE, MAKE_CHAR, MAKE_DIR, MAKE_REG) |
|
||||
LANDLOCK_FS_RIGHT(MAKE_SOCK, MAKE_FIFO, MAKE_BLOCK, MAKE_SYM);
|
||||
switch (ruleset_version) {
|
||||
default:
|
||||
case 3:
|
||||
handled_access |= LANDLOCK_FS_RIGHT(TRUNCATE);
|
||||
// fallthrough
|
||||
case 2:
|
||||
handled_access |= LANDLOCK_FS_RIGHT(REFER);
|
||||
// fallthrough
|
||||
case 1:
|
||||
break;
|
||||
}
|
||||
struct landlock_ruleset_attr const ruleset_attr = {
|
||||
.handled_access_fs = handled_access,
|
||||
};
|
||||
int lrfd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
if (unlikely(lrfd < 0)) {
|
||||
log_printf("landlock_create_ruleset(): %s", xstrerror(errno));
|
||||
goto free_sfctx;
|
||||
}
|
||||
|
||||
uint64_t allowed_access = LANDLOCK_FS_RIGHT(READ_FILE, READ_DIR);
|
||||
if (!(flags & SANDBOX_READONLY)) {
|
||||
allowed_access |= LANDLOCK_FS_RIGHT(WRITE_FILE, REMOVE_FILE, MAKE_REG);
|
||||
if (ruleset_version >= 3) {
|
||||
allowed_access |= LANDLOCK_FS_RIGHT(TRUNCATE);
|
||||
}
|
||||
}
|
||||
enum landlock_rule_type rule_type = LANDLOCK_RULE_PATH_BENEATH;
|
||||
struct landlock_path_beneath_attr const rule_attr = {
|
||||
.allowed_access = allowed_access,
|
||||
.parent_fd = dirfd,
|
||||
};
|
||||
if (unlikely(0 != landlock_add_rule(lrfd, rule_type, &rule_attr, 0))) {
|
||||
log_printf("landlock_add_rule(): %s", xstrerror(errno));
|
||||
goto free_ruleset;
|
||||
}
|
||||
|
||||
if (unlikely(0 != landlock_restrict_self(lrfd, 0))) {
|
||||
log_printf("landlock_restrict_self(): %s", xstrerror(errno));
|
||||
goto free_ruleset;
|
||||
}
|
||||
#else
|
||||
log_printf("landlock is not supported on this build");
|
||||
status = -1;
|
||||
goto free_sfctx;
|
||||
#endif /* defined(BOOKMARKFS_SANDBOX_LANDLOCK) */
|
||||
|
||||
apply_seccomp:
|
||||
status = seccomp_load(sfctx);
|
||||
if (unlikely(status != 0)) {
|
||||
log_printf("seccomp_load(): %s", xstrerror(-status));
|
||||
}
|
||||
|
||||
free_ruleset:
|
||||
close(lrfd);
|
||||
|
||||
free_sfctx:
|
||||
seccomp_release(sfctx);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#endif /* defined(SANDBOX_IMPL_SECCOMP_LANDLOCK) */
|
||||
|
||||
#if defined(SANDBOX_IMPL_CAPSICUM)
|
||||
|
||||
int
|
||||
sandbox_enter (
|
||||
int fusefd,
|
||||
int dirfd,
|
||||
uint32_t flags
|
||||
) {
|
||||
if (flags & SANDBOX_NOOP) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int val = PROC_NO_NEW_PRIVS_ENABLE;
|
||||
if (unlikely(0 != procctl(P_PID, getpid(), PROC_NO_NEW_PRIVS_CTL, &val))) {
|
||||
log_printf("procctl(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (unlikely(0 != cap_enter())) {
|
||||
log_printf("cap_enter(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fusefd >= 0) {
|
||||
cap_rights_t rights;
|
||||
cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT);
|
||||
|
||||
if (unlikely(0 != cap_rights_limit(fusefd, &rights))) {
|
||||
log_printf("cap_rights_limit(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirfd >= 0) {
|
||||
cap_rights_t rights;
|
||||
cap_rights_init(&rights, CAP_LOOKUP, CAP_READ, CAP_FSTAT, CAP_FLOCK,
|
||||
CAP_EVENT, CAP_IOCTL, CAP_MMAP_R);
|
||||
if (!(flags & SANDBOX_READONLY)) {
|
||||
cap_rights_set(&rights, CAP_CREATE, CAP_WRITE, CAP_FSYNC,
|
||||
CAP_FTRUNCATE, CAP_RENAMEAT_SOURCE, CAP_RENAMEAT_TARGET,
|
||||
CAP_UNLINKAT);
|
||||
}
|
||||
|
||||
if (unlikely(0 != cap_rights_limit(dirfd, &rights))) {
|
||||
log_printf("cap_rights_limit(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* defined(SANDBOX_IMPL_CAPSICUM) */
|
||||
|
||||
#ifndef BOOKMARKFS_SANDBOX
|
||||
|
||||
int
|
||||
sandbox_enter (
|
||||
int UNUSED_VAR(fusefd),
|
||||
int UNUSED_VAR(dirfd),
|
||||
uint32_t flags
|
||||
) {
|
||||
if (flags & SANDBOX_NOOP) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_puts("sandbox not implemented");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_SANDBOX) */
|
39
src/sandbox.h
Normal file
39
src/sandbox.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* bookmarkfs/src/sandbox.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_SANDBOX_H_
|
||||
#define BOOKMARKFS_SANDBOX_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SANDBOX_READONLY ( 1u << 0 )
|
||||
#define SANDBOX_NO_LANDLOCK ( 1u << 1 )
|
||||
#define SANDBOX_NOOP ( 1u << 2 )
|
||||
|
||||
int
|
||||
sandbox_enter (
|
||||
int fusefd,
|
||||
int dirfd,
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_SANDBOX_H_) */
|
130
src/uuid.c
Normal file
130
src/uuid.c
Normal file
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* bookmarkfs/src/uuid.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "uuid.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef BOOKMARKFS_BACKEND_CHROMIUM_WRITE
|
||||
# include <nettle/base16.h>
|
||||
#endif
|
||||
|
||||
#include "prng.h"
|
||||
|
||||
#ifdef BOOKMARKFS_BACKEND_CHROMIUM_WRITE
|
||||
|
||||
void
|
||||
uuid_bin2hex (
|
||||
char *restrict out,
|
||||
uint8_t const *restrict in
|
||||
) {
|
||||
size_t const parts[] = { 4, 2, 2, 2, 6 };
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
size_t src_len = parts[i];
|
||||
base16_encode_update(out, src_len, in);
|
||||
|
||||
in += src_len;
|
||||
out += src_len << 1;
|
||||
if (i < 4) {
|
||||
*(out++) = '-';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
uuid_hex2bin (
|
||||
uint8_t *restrict out,
|
||||
char const *restrict in
|
||||
) {
|
||||
struct base16_decode_ctx ctx;
|
||||
base16_decode_init(&ctx);
|
||||
|
||||
size_t const parts[] = { 8, 4, 4, 4, 12 };
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
size_t src_len = parts[i];
|
||||
size_t dest_len;
|
||||
if (!base16_decode_update(&ctx, &dest_len, out, src_len, in)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
out += src_len >> 1;
|
||||
in += src_len;
|
||||
if (i < 4 && *(in++) != '-') {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!base16_decode_final(&ctx)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* !defined(BOOKMARKFS_BACKEND_CHROMIUM_WRITE) */
|
||||
|
||||
/**
|
||||
* This implementation is meant to get rid of the Nettle dependency
|
||||
* when building the Chromium backend without write support.
|
||||
*
|
||||
* NOTE: Result is incompatible with the above Nettle-based implementations.
|
||||
* It also requires the input to be NUL-terminated.
|
||||
*/
|
||||
int
|
||||
uuid_hex2bin (
|
||||
uint8_t *restrict out,
|
||||
char const *restrict in
|
||||
) {
|
||||
uint16_t parts[8];
|
||||
int num_matched = sscanf(in,
|
||||
"%4" SCNx16 "%4" SCNx16 "-%4" SCNx16 "-%4" SCNx16
|
||||
"-%4" SCNx16 "-%4" SCNx16 "%4" SCNx16 "%4" SCNx16,
|
||||
&parts[0], &parts[1], &parts[2], &parts[3],
|
||||
&parts[4], &parts[5], &parts[6], &parts[7]);
|
||||
if (num_matched != 8) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(out, parts, UUID_LEN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* defined(BOOKMARKFS_BACKEND_CHROMIUM_WRITE) */
|
||||
|
||||
void
|
||||
uuid_generate_random (
|
||||
uint8_t *out
|
||||
) {
|
||||
uint64_t const buf[] = { prng_rand(), prng_rand() };
|
||||
memcpy(out, buf, UUID_LEN);
|
||||
|
||||
out[7] &= 0x0f;
|
||||
out[7] |= 0x40;
|
||||
out[8] &= 0x3f;
|
||||
out[8] |= 0x80;
|
||||
}
|
69
src/uuid.h
Normal file
69
src/uuid.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* bookmarkfs/src/uuid.h
|
||||
*
|
||||
* Utility library for manipulating RFC 4122 UUIDs.
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_UUID_H_
|
||||
#define BOOKMARKFS_UUID_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define UUID_LEN 16
|
||||
#define UUID_HEX_LEN 36
|
||||
|
||||
/**
|
||||
* Convert a UUID from binary to hexadecimal representation.
|
||||
*
|
||||
* NOTE: This function does not validate the UUID value.
|
||||
*/
|
||||
void
|
||||
uuid_bin2hex (
|
||||
char *restrict out, // UUID_HEX_LEN
|
||||
uint8_t const *restrict in // UUUD_LEN
|
||||
);
|
||||
|
||||
/**
|
||||
* Convert a UUID from hexadecimal to binary representation.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*
|
||||
* NOTE: Like uuid_bin2hex(), this function does not validate
|
||||
* the UUID value, and only fails on bad input string format.
|
||||
*/
|
||||
int
|
||||
uuid_hex2bin (
|
||||
uint8_t *restrict out, // UUID_LEN
|
||||
char const *restrict in // UUID_HEX_LEN
|
||||
);
|
||||
|
||||
/**
|
||||
* Pseudo-randomly generate a version 4 UUID (binary representation).
|
||||
*
|
||||
* Internally uses the PRNG from the bookmarkfs-util library,
|
||||
* which should be seeded before calling this function.
|
||||
*/
|
||||
void
|
||||
uuid_generate_random (
|
||||
uint8_t *out // UUID_LEN
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_UUID_H_) */
|
46
src/version.c
Normal file
46
src/version.c
Normal file
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* bookmarkfs/src/version.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "version.h"
|
||||
|
||||
uint32_t
|
||||
bookmarkfs_lib_version (void)
|
||||
{
|
||||
uint32_t vernum = BOOKMARKFS_VERNUM;
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
vernum |= BOOKMARKFS_FEAT_DEBUG;
|
||||
#endif
|
||||
#ifdef BOOKMARKFS_SANDBOX
|
||||
vernum |= BOOKMARKFS_FEAT_SANDBOX;
|
||||
#ifdef BOOKMARKFS_SANDBOX_LANDLOCK
|
||||
vernum |= BOOKMARKFS_FEAT_SANDBOX_LANDLOCK;
|
||||
#endif
|
||||
#endif /* defined(BOOKMARKFS_SANDBOX) */
|
||||
#ifdef BOOKMARKFS_NATIVE_WATCHER
|
||||
vernum |= BOOKMARKFS_FEAT_NATIVE_WATCHER;
|
||||
#endif
|
||||
return vernum;
|
||||
}
|
52
src/version.h
Normal file
52
src/version.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* bookmarkfs/src/version.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_VERSION_H_
|
||||
#define BOOKMARKFS_VERSION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BOOKMARKFS_VER_MAJOR 0
|
||||
#define BOOKMARKFS_VER_MINOR 1
|
||||
#define BOOKMARKFS_VER_PATCH 0
|
||||
|
||||
#define bookmarkfs_make_vernum(major, minor, patch) \
|
||||
( ((major) << 16) | ((minor) << 8) | ((patch) << 0) )
|
||||
|
||||
#define BOOKMARKFS_VERNUM \
|
||||
bookmarkfs_make_vernum(BOOKMARKFS_VER_MAJOR, BOOKMARKFS_VER_MINOR, \
|
||||
BOOKMARKFS_VER_PATCH)
|
||||
|
||||
#define bookmarkfs_vernum(vernum) ( ((vernum) << 8 ) >> 8 )
|
||||
#define bookmarkfs_vernum_to_major(vernum) ( ((vernum) >> 16) & 0xff )
|
||||
#define bookmarkfs_vernum_to_minor(vernum) ( ((vernum) >> 8 ) & 0xff )
|
||||
#define bookmarkfs_vernum_to_patch(vernum) ( ((vernum) >> 0 ) & 0xff )
|
||||
|
||||
#define BOOKMARKFS_FEAT_DEBUG ( 1u << 24 )
|
||||
#define BOOKMARKFS_FEAT_NATIVE_WATCHER ( 1u << 25 )
|
||||
#define BOOKMARKFS_FEAT_SANDBOX ( 1u << 26 )
|
||||
#define BOOKMARKFS_FEAT_SANDBOX_LANDLOCK ( 1u << 27 )
|
||||
|
||||
uint32_t
|
||||
bookmarkfs_lib_version (void);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_VERSION_H_) */
|
442
src/watcher.c
Normal file
442
src/watcher.c
Normal file
|
@ -0,0 +1,442 @@
|
|||
/**
|
||||
* bookmarkfs/src/watcher.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "watcher.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if !defined(BOOKMARKFS_NATIVE_WATCHER)
|
||||
#elif defined(__linux__)
|
||||
# include <sys/fanotify.h>
|
||||
# define WATCHER_IMPL_FANOTIFY
|
||||
#elif defined(__FreeBSD__)
|
||||
# include <sys/event.h>
|
||||
# define WATCHER_IMPL_KQUEUE
|
||||
#endif
|
||||
|
||||
#include "sandbox.h"
|
||||
#include "xstd.h"
|
||||
|
||||
#define WATCHER_DEAD ( 1u << 2 )
|
||||
#define WATCHER_IDLE ( 1u << 3 )
|
||||
|
||||
// This value is chosen according to how frequent Chromium saves its bookmarks
|
||||
// (`bookmarks::BookmarkStorage::kSaveDelay`).
|
||||
//
|
||||
// See Chromium source code: /components/bookmarks/browser/bookmark_storage.h
|
||||
#define WATCHER_FALLBACK_POLL_INTERVAL 2500
|
||||
|
||||
struct watcher {
|
||||
pthread_mutex_t mutex;
|
||||
uint32_t flags;
|
||||
|
||||
char const *name;
|
||||
int dirfd;
|
||||
#if defined(WATCHER_IMPL_FANOTIFY)
|
||||
int fanfd;
|
||||
#elif defined(WATCHER_IMPL_KQUEUE)
|
||||
int kqfd;
|
||||
int wfd;
|
||||
#endif
|
||||
|
||||
pthread_cond_t cond;
|
||||
struct stat old_stat;
|
||||
int pipefd[2];
|
||||
pthread_t worker;
|
||||
};
|
||||
|
||||
// Forward declaration start
|
||||
static int impl_init (struct watcher *);
|
||||
static void impl_free (struct watcher *);
|
||||
static int impl_rearm (struct watcher *);
|
||||
static int impl_watch (struct watcher *);
|
||||
static void * worker_loop (void *);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
impl_init (
|
||||
struct watcher *w
|
||||
) {
|
||||
if (w->flags & WATCHER_FALLBACK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(WATCHER_IMPL_FANOTIFY)
|
||||
int fanfd = fanotify_init(FAN_CLOEXEC | FAN_NONBLOCK | FAN_REPORT_FID, 0);
|
||||
if (fanfd < 0) {
|
||||
log_printf("fanotify_init(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
w->fanfd = fanfd;
|
||||
|
||||
#elif defined(WATCHER_IMPL_KQUEUE)
|
||||
int kqfd = kqueuex(KQUEUE_CLOEXEC);
|
||||
if (kqfd < 0) {
|
||||
log_printf("kqueuex(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
struct kevent ev = {
|
||||
.ident = w->pipefd[0],
|
||||
.filter = EVFILT_READ,
|
||||
.flags = EV_ADD,
|
||||
};
|
||||
if (0 != kevent(kqfd, &ev, 1, NULL, 0, NULL)) {
|
||||
close(kqfd);
|
||||
log_printf("kevent(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
w->kqfd = kqfd;
|
||||
w->wfd = -1;
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
impl_free (
|
||||
struct watcher *w
|
||||
) {
|
||||
if (w->flags & WATCHER_FALLBACK) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(WATCHER_IMPL_FANOTIFY)
|
||||
close(w->fanfd);
|
||||
|
||||
#elif defined(WATCHER_IMPL_KQUEUE)
|
||||
close(w->kqfd);
|
||||
if (w->wfd >= 0) {
|
||||
close(w->wfd);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
impl_rearm (
|
||||
struct watcher *w
|
||||
) {
|
||||
if (w->flags & WATCHER_FALLBACK) {
|
||||
goto fallback;
|
||||
}
|
||||
|
||||
#if defined(WATCHER_IMPL_FANOTIFY)
|
||||
uint32_t mask = FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_MODIFY;
|
||||
if (0 != fanotify_mark(w->fanfd, FAN_MARK_ADD, mask, w->dirfd, w->name)) {
|
||||
log_printf("fanotify_mark(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
#elif defined(WATCHER_IMPL_KQUEUE)
|
||||
int wfd = openat(w->dirfd, w->name,
|
||||
O_RDONLY | O_CLOEXEC | O_PATH | O_RESOLVE_BENEATH);
|
||||
if (wfd < 0) {
|
||||
log_printf("openat(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
struct kevent ev = {
|
||||
.ident = wfd,
|
||||
.filter = EVFILT_VNODE,
|
||||
.flags = EV_ADD | EV_ONESHOT,
|
||||
.fflags = NOTE_DELETE | NOTE_EXTEND | NOTE_RENAME | NOTE_WRITE,
|
||||
};
|
||||
if (0 != kevent(w->kqfd, &ev, 1, NULL, 0, NULL)) {
|
||||
close(wfd);
|
||||
log_printf("kevent(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
w->wfd = wfd;
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
fallback:
|
||||
if (0 != fstatat(w->dirfd, w->name, &w->old_stat, 0)) {
|
||||
log_printf("fstatat(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
impl_watch (
|
||||
struct watcher *w
|
||||
) {
|
||||
if (w->flags & WATCHER_FALLBACK) {
|
||||
goto fallback;
|
||||
}
|
||||
int nevs;
|
||||
|
||||
#if defined(WATCHER_IMPL_FANOTIFY)
|
||||
struct pollfd pfds[] = {
|
||||
{ .fd = w->fanfd, .events = POLLIN },
|
||||
{ .fd = w->pipefd[0], .events = POLLIN },
|
||||
};
|
||||
nevs = poll(pfds, 2, -1);
|
||||
if (unlikely(nevs < 0)) {
|
||||
log_printf("poll(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (pfds[1].revents & POLLHUP) {
|
||||
// watcher_destroy() was called
|
||||
return -1;
|
||||
}
|
||||
|
||||
char tmp_buf[4096];
|
||||
ssize_t len = read(w->fanfd, &tmp_buf, sizeof(tmp_buf));
|
||||
if (unlikely(len < 0)) {
|
||||
log_printf("read(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned flags = FAN_MARK_REMOVE;
|
||||
uint32_t mask = FAN_DELETE_SELF | FAN_MOVE_SELF | FAN_MODIFY;
|
||||
if (0 != fanotify_mark(w->fanfd, flags, mask, w->dirfd, w->name)) {
|
||||
// If watched file is deleted, the mark is automatically removed.
|
||||
if (errno != ENOENT) {
|
||||
log_printf("fanotify_mark(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Drain fanotify event queue. There may exist more than one event.
|
||||
do {
|
||||
len = read(w->fanfd, &tmp_buf, sizeof(tmp_buf));
|
||||
} while (len > 0);
|
||||
|
||||
#elif defined(WATCHER_IMPL_KQUEUE)
|
||||
struct kevent ev;
|
||||
nevs = kevent(w->kqfd, NULL, 0, &ev, 1, NULL);
|
||||
if (unlikely(nevs < 0)) {
|
||||
log_printf("kevent(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (unlikely(ev.flags & EV_ERROR)) {
|
||||
log_printf("kevent(): %s", xstrerror(ev.data));
|
||||
return -1;
|
||||
}
|
||||
if ((int)ev.ident == w->pipefd[0]) {
|
||||
debug_assert(ev.flags & EV_EOF);
|
||||
return -1;
|
||||
}
|
||||
close(w->wfd);
|
||||
w->wfd = -1;
|
||||
|
||||
#endif
|
||||
goto end;
|
||||
|
||||
fallback:
|
||||
for (struct stat *old_stat = &w->old_stat; ; ) {
|
||||
struct pollfd pfd = {
|
||||
.fd = w->pipefd[0],
|
||||
.events = POLLIN,
|
||||
};
|
||||
nevs = poll(&pfd, 1, WATCHER_FALLBACK_POLL_INTERVAL);
|
||||
if (nevs != 0) {
|
||||
if (unlikely(nevs < 0)) {
|
||||
log_printf("poll(): %s", xstrerror(errno));
|
||||
} else {
|
||||
debug_assert(pfd.revents & POLLHUP);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct stat new_stat;
|
||||
if (0 != fstatat(w->dirfd, w->name, &new_stat, 0)) {
|
||||
debug_printf("fstatat(): %s", xstrerror(errno));
|
||||
break;
|
||||
}
|
||||
if (new_stat.st_ino != old_stat->st_ino
|
||||
|| new_stat.st_mtim.tv_sec != old_stat->st_mtim.tv_sec
|
||||
|| new_stat.st_mtim.tv_nsec != old_stat->st_mtim.tv_nsec
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
debug_printf("file %s changed", w->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *
|
||||
worker_loop (
|
||||
void *user_data
|
||||
) {
|
||||
struct watcher *w = user_data;
|
||||
|
||||
pthread_mutex_lock(&w->mutex);
|
||||
|
||||
#ifdef BOOKMARKFS_SANDBOX
|
||||
uint32_t sandbox_flags = w->flags >> WATCHER_SANDBOX_FLAGS_OFFSET;
|
||||
if (!(sandbox_flags & SANDBOX_NOOP)) {
|
||||
sandbox_flags |= SANDBOX_READONLY;
|
||||
if (unlikely(0 != sandbox_enter(-1, w->dirfd, sandbox_flags))) {
|
||||
goto end;
|
||||
}
|
||||
debug_puts("worker thread enters sandbox");
|
||||
}
|
||||
#endif /* defined(BOOKMARKFS_SANDBOX) */
|
||||
|
||||
if (0 != impl_rearm(w)) {
|
||||
goto end;
|
||||
}
|
||||
debug_puts("worker ready");
|
||||
while (0 == impl_watch(w)) {
|
||||
w->flags |= WATCHER_IDLE;
|
||||
pthread_cond_wait(&w->cond, &w->mutex);
|
||||
}
|
||||
|
||||
end:
|
||||
w->flags |= (WATCHER_DEAD | WATCHER_IDLE);
|
||||
pthread_mutex_unlock(&w->mutex);
|
||||
debug_puts("worker stops");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct watcher *
|
||||
watcher_create (
|
||||
int dirfd,
|
||||
char const *name,
|
||||
uint32_t flags
|
||||
) {
|
||||
struct watcher *w = xmalloc(sizeof(*w));
|
||||
*w = (struct watcher) {
|
||||
.dirfd = dirfd,
|
||||
.name = name,
|
||||
.pipefd[1] = -1,
|
||||
.flags = flags,
|
||||
};
|
||||
if (flags & WATCHER_NOOP) {
|
||||
return w;
|
||||
}
|
||||
|
||||
// The write end of the pipe must have the close-on-exec flag enabled,
|
||||
// since the calling process may fork-exec, so that poll()-ing the read end
|
||||
// may not POLLHUP when the write end closes.
|
||||
if (unlikely(0 != xpipe2(w->pipefd, O_CLOEXEC))) {
|
||||
goto free_watcher;
|
||||
}
|
||||
if (unlikely(0 != impl_init(w))) {
|
||||
goto close_pipes;
|
||||
}
|
||||
pthread_mutex_init(&w->mutex, NULL);
|
||||
pthread_cond_init(&w->cond, NULL);
|
||||
|
||||
int status = pthread_create(&w->worker, NULL, worker_loop, w);
|
||||
if (unlikely(status != 0)) {
|
||||
log_printf("pthread_create(): %s", xstrerror(-status));
|
||||
goto destroy_impl;
|
||||
}
|
||||
|
||||
return w;
|
||||
|
||||
destroy_impl:
|
||||
impl_free(w);
|
||||
pthread_mutex_destroy(&w->mutex);
|
||||
pthread_cond_destroy(&w->cond);
|
||||
|
||||
close_pipes:
|
||||
close(w->pipefd[0]);
|
||||
close(w->pipefd[1]);
|
||||
|
||||
free_watcher:
|
||||
free(w);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
watcher_destroy (
|
||||
struct watcher *w
|
||||
) {
|
||||
if (w == NULL) {
|
||||
return;
|
||||
}
|
||||
if (w->pipefd[1] < 0) {
|
||||
// WATCHER_NOOP
|
||||
goto end;
|
||||
}
|
||||
close(w->pipefd[1]);
|
||||
|
||||
pthread_mutex_lock(&w->mutex);
|
||||
pthread_cond_signal(&w->cond);
|
||||
pthread_mutex_unlock(&w->mutex);
|
||||
pthread_join(w->worker, NULL);
|
||||
|
||||
pthread_cond_destroy(&w->cond);
|
||||
pthread_mutex_destroy(&w->mutex);
|
||||
impl_free(w);
|
||||
close(w->pipefd[0]);
|
||||
|
||||
end:
|
||||
free(w);
|
||||
}
|
||||
|
||||
int
|
||||
watcher_poll (
|
||||
struct watcher *w
|
||||
) {
|
||||
int status = WATCHER_POLL_NOCHANGE;
|
||||
if (w->pipefd[1] < 0) {
|
||||
// WATCHER_NOOP
|
||||
return status;
|
||||
}
|
||||
if (0 != pthread_mutex_trylock(&w->mutex)) {
|
||||
return status;
|
||||
}
|
||||
if (unlikely(!(w->flags & WATCHER_IDLE))) {
|
||||
// The worker is just being initialized, or yet to return from
|
||||
// pthread_cond_wait() after a previous pthread_cond_signal().
|
||||
debug_puts("worker is drowsy...");
|
||||
goto end;
|
||||
}
|
||||
|
||||
status = WATCHER_POLL_ERR;
|
||||
if (unlikely(w->flags & WATCHER_DEAD)) {
|
||||
log_puts("worker is dead");
|
||||
goto end;
|
||||
}
|
||||
if (0 != impl_rearm(w)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
status = WATCHER_POLL_CHANGED;
|
||||
w->flags &= ~WATCHER_IDLE;
|
||||
pthread_cond_signal(&w->cond);
|
||||
|
||||
end:
|
||||
pthread_mutex_unlock(&w->mutex);
|
||||
return status;
|
||||
}
|
88
src/watcher.h
Normal file
88
src/watcher.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* bookmarkfs/src/watcher.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_WATCHER_H_
|
||||
#define BOOKMARKFS_WATCHER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* A watcher_poll() call returning WATCHER_POLL_ERR
|
||||
* most likely means that the file being watched has gone.
|
||||
*
|
||||
* When the file is back, calling watcher_poll() again should
|
||||
* return WATCHER_POLL_CHANGED, and the watcher should continue to work.
|
||||
*/
|
||||
#define WATCHER_POLL_ERR -1
|
||||
#define WATCHER_POLL_NOCHANGE 0
|
||||
#define WATCHER_POLL_CHANGED 1
|
||||
|
||||
/**
|
||||
* Always use fstat() to detect file change.
|
||||
*/
|
||||
#define WATCHER_FALLBACK ( 1u << 0 )
|
||||
/**
|
||||
* Nothing is performed on the filesystem,
|
||||
* and watcher_poll() always returns WATCHER_POLL_NOCHANGE.
|
||||
*/
|
||||
#define WATCHER_NOOP ( 1u << 1 )
|
||||
|
||||
/**
|
||||
* Bit to shift when applying sandbox flags to the watcher.
|
||||
*/
|
||||
#define WATCHER_SANDBOX_FLAGS_OFFSET 16
|
||||
|
||||
struct watcher;
|
||||
|
||||
/**
|
||||
* Creates a watcher that watches for changes of a single file.
|
||||
*
|
||||
* The `flags` argument is a bit combination of watcher flags
|
||||
* and sandbox flags. The latter is used for
|
||||
* initializing the sandbox for the internal worker thread.
|
||||
*/
|
||||
struct watcher *
|
||||
watcher_create (
|
||||
int dirfd,
|
||||
char const *name,
|
||||
uint32_t flags
|
||||
);
|
||||
|
||||
void
|
||||
watcher_destroy (
|
||||
struct watcher *w
|
||||
);
|
||||
|
||||
/**
|
||||
* Check whether the file associated with the watcher
|
||||
* has changed since the last watcher_poll() call
|
||||
* (or, if not yet called, since watcher initialization).
|
||||
*
|
||||
* Returns one of WATCHER_POLL_*.
|
||||
*/
|
||||
int
|
||||
watcher_poll (
|
||||
struct watcher *w
|
||||
);
|
||||
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_WATCHER_H_) */
|
172
src/xstd.c
Normal file
172
src/xstd.c
Normal file
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* bookmarkfs/src/xstd.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "xstd.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <locale.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void
|
||||
xabort_ (
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
char const *assertion,
|
||||
#endif
|
||||
char const *name,
|
||||
int line
|
||||
) {
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
fprintf(stderr, "%s:%d: assertion failed: %s\n", name, line, assertion);
|
||||
#else
|
||||
fprintf(stderr, "%s:%d: assertion failed\n", name, line);
|
||||
#endif
|
||||
abort();
|
||||
}
|
||||
|
||||
void *
|
||||
xcalloc (
|
||||
size_t nmemb,
|
||||
size_t size
|
||||
) {
|
||||
void *p = calloc(nmemb, size);
|
||||
xassert(p != NULL);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
int
|
||||
xfsync (
|
||||
int fd
|
||||
) {
|
||||
while (unlikely(0 != fsync(fd))) {
|
||||
int err = errno;
|
||||
log_printf("fsync(): %s", xstrerror(err));
|
||||
|
||||
switch (err) {
|
||||
case EIO:
|
||||
#ifdef __FreeBSD__
|
||||
case EINTEGRITY:
|
||||
#endif
|
||||
abort();
|
||||
|
||||
case EINTR:
|
||||
continue;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *
|
||||
xmalloc (
|
||||
size_t size
|
||||
) {
|
||||
void *p = malloc(size);
|
||||
xassert(p != NULL);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
int
|
||||
xpipe2 (
|
||||
int pipefd[2],
|
||||
int flags
|
||||
) {
|
||||
#ifdef HAVE_PIPE2
|
||||
if (0 != pipe2(pipefd, flags)) {
|
||||
log_printf("pipe2(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
#else /* !defined(HAVE_PIPE2) */
|
||||
if (0 != pipe(pipefd)) {
|
||||
log_printf("pipe(): %s", xstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int extra_fdflags = 0;
|
||||
if (flags & O_CLOEXEC) {
|
||||
extra_fdflags |= FD_CLOEXEC;
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
int fd = pipefd[i];
|
||||
|
||||
int fdflags = fcntl(fd, F_GETFD);
|
||||
if (fdflags < 0) {
|
||||
goto fail;
|
||||
}
|
||||
if (0 != fcntl(fd, F_SETFD, fdflags | extra_fdflags)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
log_printf("fcntl(): %s", xstrerror(errno));
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
return -1;
|
||||
|
||||
#endif /* defined(HAVE_PIPE2) */
|
||||
}
|
||||
|
||||
void *
|
||||
xrealloc (
|
||||
void *p,
|
||||
size_t size
|
||||
) {
|
||||
p = realloc(p, size);
|
||||
xassert(p != NULL);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
char const *
|
||||
xstrerror (
|
||||
int err_num
|
||||
) {
|
||||
locale_t loc = uselocale((locale_t)0);
|
||||
xassert(loc != (locale_t)0);
|
||||
|
||||
locale_t loc_copy = loc;
|
||||
if (loc == LC_GLOBAL_LOCALE) {
|
||||
loc_copy = duplocale(loc);
|
||||
xassert(loc_copy != (locale_t)0);
|
||||
}
|
||||
char const *err_str = strerror_l(err_num, loc_copy);
|
||||
|
||||
if (loc == LC_GLOBAL_LOCALE) {
|
||||
freelocale(loc_copy);
|
||||
}
|
||||
return err_str;
|
||||
}
|
167
src/xstd.h
Normal file
167
src/xstd.h
Normal file
|
@ -0,0 +1,167 @@
|
|||
/**
|
||||
* bookmarkfs/src/xstd.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
*
|
||||
* This file is part of BookmarkFS.
|
||||
*
|
||||
* BookmarkFS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* BookmarkFS 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 BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_XSTD_H_
|
||||
#define BOOKMARKFS_XSTD_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "defs.h"
|
||||
|
||||
#ifdef HAVE___BUILTIN_EXPECT
|
||||
# define likely(e) __builtin_expect(!!(e), 1)
|
||||
# define unlikely(e) __builtin_expect(!!(e), 0)
|
||||
#else
|
||||
# define likely(e) (e)
|
||||
# define unlikely(e) (e)
|
||||
#endif /* defined(HAVE__BUILTIN_EXPECT) */
|
||||
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
# define xassert(e) if (unlikely(!(e))) xabort_(#e, FILE_NAME_, __LINE__)
|
||||
#else
|
||||
# define xassert(e) if (unlikely(!(e))) xabort_(FILE_NAME_, __LINE__)
|
||||
#endif
|
||||
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
# define debug_assert(e) xassert(e)
|
||||
# define unreachable() xassert(0)
|
||||
#else /* !defined(BOOKMARKFS_DEBUG) */
|
||||
# define debug_assert(e) if (!(e)) { unreachable(); }
|
||||
# ifdef HAVE___BUILTIN_UNREACHABLE
|
||||
# define unreachable() __builtin_unreachable()
|
||||
# else /* !defined(HAVE__BUILTIN_UNREACHABLE) */
|
||||
# define unreachable()
|
||||
# endif /* defined(HAVE_BUILTIN_UNREACHABLE) */
|
||||
#endif /* defined(BOOKMARKFS_DEBUG) */
|
||||
|
||||
#define log_printf(f, ...) \
|
||||
fprintf(stderr, "%s: " f "\n", FILE_NAME_, __VA_ARGS__)
|
||||
#define log_puts(s) log_printf("%s", s)
|
||||
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
# define debug_printf(f, ...) log_printf("[debug] " f, __VA_ARGS__)
|
||||
# define debug_puts(s) log_puts("[debug] " s)
|
||||
#else
|
||||
# define debug_printf(f, ...)
|
||||
# define debug_puts(s)
|
||||
#endif
|
||||
|
||||
#if defined(__FreeBSD__) || (defined(__linux__) && defined(_GNU_SOURCE))
|
||||
# define xasprintf(strp, ...) xassert(0 <= asprintf(strp, __VA_ARGS__))
|
||||
#else
|
||||
# define xasprintf(strp, ...) \
|
||||
do { \
|
||||
int len_ = snprintf(NULL, 0, __VA_ARGS__); \
|
||||
xassert(len_ >= 0); \
|
||||
*(strp) = xmalloc(len_ + 1); \
|
||||
xassert(len_ == sprintf(*(strp), __VA_ARGS__)); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Prints a message to standard error, and then aborts.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
FUNCATTR_COLD FUNCATTR_NORETURN
|
||||
void
|
||||
xabort_ (
|
||||
#ifdef BOOKMARKFS_DEBUG
|
||||
char const *assertion,
|
||||
#endif
|
||||
char const *name,
|
||||
int line
|
||||
);
|
||||
|
||||
/**
|
||||
* Like calloc(), but never returns NULL.
|
||||
*
|
||||
* On failure, the calling process aborts.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
FUNCATTR_MALLOC FUNCATTR_RETURNS_NONNULL
|
||||
void *
|
||||
xcalloc (
|
||||
size_t nmemb,
|
||||
size_t size
|
||||
);
|
||||
|
||||
/**
|
||||
* Like fsync(), but retries on EINTR, and aborts on EIO.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
int
|
||||
xfsync (
|
||||
int fd
|
||||
);
|
||||
|
||||
/**
|
||||
* Like malloc(), but never returns NULL.
|
||||
*
|
||||
* On failure, the calling process aborts.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
FUNCATTR_MALLOC FUNCATTR_RETURNS_NONNULL
|
||||
void *
|
||||
xmalloc (
|
||||
size_t size
|
||||
);
|
||||
|
||||
/**
|
||||
* Like pipe2(), using pipe()+fcntl() on platforms without pipe2().
|
||||
*
|
||||
* The only supported flag is O_CLOEXEC.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
int
|
||||
xpipe2 (
|
||||
int pipefd[2],
|
||||
int flags
|
||||
);
|
||||
|
||||
/**
|
||||
* Like realloc(), but never returns NULL.
|
||||
*
|
||||
* On failure, the calling process aborts.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
FUNCATTR_RETURNS_NONNULL
|
||||
void *
|
||||
xrealloc (
|
||||
void *p,
|
||||
size_t size
|
||||
);
|
||||
|
||||
/**
|
||||
* Like strerror(), but MT-Safe.
|
||||
*
|
||||
* NOTE: strerror() is MT-Safe in some implementations (e.g. glibc >= 2.32),
|
||||
* but POSIX does not enforce that.
|
||||
*/
|
||||
BOOKMARKFS_INTERNAL
|
||||
FUNCATTR_RETURNS_NONNULL
|
||||
char const *
|
||||
xstrerror (
|
||||
int errnum
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_XSTD_H_) */
|
9
tests/Makefile.am
Normal file
9
tests/Makefile.am
Normal file
|
@ -0,0 +1,9 @@
|
|||
#
|
||||
# Copyright (C) 2024 CismonX <admin@cismon.net>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty, provided the copyright notice and
|
||||
# this notice are preserved. This file is offered as-is, without any warranty.
|
||||
#
|
||||
|
||||
AUTOMAKE_OPTIONS = dejagnu
|
Loading…
Add table
Reference in a new issue