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