\input texinfo @c -*-texinfo-*- @setfilename bookmarkfs.info @include version.texi @settitle BookmarkFS User Manual @documentlanguage en_US @paragraphindent 0 @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 @macro linuxdoc {name, path} @uref{https://docs.kernel.org/\path\, \name\} @end macro @macro rfcdoc {name, path} @uref{https://datatracker.ietf.org/doc/html/\path\, \name\} @end macro @macro sqlitedoc {name, path} @uref{https://www.sqlite.org/\path\, \name\} @end macro @macro tcldoc {name, path} @uref{https://www.tcl.tk/man/tcl8.6.13/\path\, \name\} @end macro @copying This manual is for BookmarkFS, version @value{VERSION}. @quotation Copyright @copyright{} 2024 CismonX 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 @uref{https://www.gnu.org/licenses/#GPL, GNU General Public License}, either version 3, or any later version of the license. To install BookmarkFS, refer to the @file{INSTALL.md} file under the root directory of the project codebase. BookmarkFS is @uref{https://savannah.nongnu.org/projects/bookmarkfs, hosted on Savannah}, where latest releases, development sources and other information are available. Also see @uref{https://builds.sr.ht/~cismonx/bookmarkfs, builds.sr.ht} for CI build logs. @node Porting BookmarkFS @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 @linuxdoc{FUSE, filesystems/fuse.html} 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. OpenBSD does provide a libfuse-compatible library, however, it only covers the high-level API, while 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. Other notable portability issues: @table @asis @item Sandboxing Not all operating system kernels provide sandboxing mechanisms similar to Linux and FreeBSD. If not supported, operations that require sandboxing should fail. Users should not be provided with a false sense of security. If they wish, they could pass a @option{-o no_sandbox} option to explicitly disable sandboxing. Also @pxref{Sandboxing}. @end table @node Limitations on FreeBSD @section Limitations on FreeBSD Currently, the FreeBSD @freebsdmanpage{fusefs, 5} implementation does not support @code{FUSE_IOCTL}. All custom @freebsdmanpage{ioctl, 2} calls on a FUSE filesystem fail with @code{ENOTTY} without the requests being sent to the FUSE server. Thus, BookmarkFS features that depend on @code{ioctl()} do not work on FreeBSD, which includes: @itemize @bullet{} @item @ref{Permute Directory Entries} @item @ref{Online Filesystem Check} @end itemize Meanwhile, FreeBSD does not support @code{FUSE_READDIRPLUS} and directory entry caching, which makes listing directory entries less efficient. @node Mailing Lists @section Mailing Lists Write to the @uref{https://savannah.nongnu.org/mail/?group=bookmarkfs, mailing lists} for bug reports, feature requests, and other discussions. Please do @emph{not} send feature patches, since BookmarkFS is currently in experimental stage, and is not yet ready for collaboration. Trivial patches, such as bugfix and typo corrections, are okay. For security-related problems, please email directly to the project maintainer. @node Programs @chapter Programs BookmarkFS ships with command-line programs to create, mount, fix, and manage BookmarkFS filesystems. Those programs do not work on their own, and usually require a ``backend'' for low-level functionalities. @xref{Backends}. @node mount.bookmarkfs @section @command{mount.bookmarkfs} The @command{mount.bookmarkfs} program mounts a BookmarkFS filesystem. @example mount.bookmarkfs [@var{options}] @var{src} @var{target} @end example @table @var @item src The bookmark storage, presumably the pathname of a regular file that contains bookmark data. The exact interpretation of this argument is backend-defined. @item target Pathname of the directory to mount the filesystem. @end table Files under the filesystem are assigned ownership according to the effective user ID and group ID of the calling process. On FreeBSD, you may wish to set the @samp{vfs.usermount} @freebsdmanpage{sysctl, 8} to @t{1} for unprivileged mounts. To unmount a BookmarkFS filesystem, run @linuxmanpage{fusermount3, 1} or @linuxmanpage{umount, 8} on @var{target}. The daemon process will automatically dismount the filesystem upon @code{SIGINT} or @code{SIGTERM} receipt, however, it only works in non-sandbox mode. Options: @table @option @item -o backend=@var{name} The backend used by the filesystem (@pxref{Backends}). This option is mandatory. @anchor{Alternative Backend Name} Alternatively, @var{name} could be specified in @samp{@var{lib_path}:@var{sym_name}} format, where: @table @var @item lib_path Presumably the pathname to the backend library. Its exact interpretation is equivalent to the first argument for @posixfuncmanpage{dlopen}. @item sym_name @xref{Backend Symbol Name Format}. @end table @item -o @@@var{key}[=@var{value}] A backend-specific option. This option can be provided multiple times. @item -o accmode=@var{mode} File access mode. Defaults to @samp{0700}. This option applies to both directories and regular files. Execution bits on regular files are masked off. Should be used in combination with @option{-o allow_other} for other users to access the files. @anchor{Last Modification/Change Time} @cindex Last Modification/Change Time @item -o ctime Maintain last status change time instead of last modification time. Usually, a bookmark's ``modification time'' attribute behaves differently from both mtime and ctime. In Chromium, for instance, when a bookmark is renamed, neither itself nor the parent directory changes timestamp accordingly. BookmarkFS do not follow browser behavior here, and instead try to stay compatible with POSIX. Since a bookmark has only one ``modification time'' attribute instead of two, the user has to choose which one to maintain: @table @asis @item Last modification time ctime only updates when mtime does. @item Last status change time ctime updates normally; mtime always updates when ctime does, even if the file content is not modified. This behavior may be inefficient, but makes applications that depend on ctime less fragile. @end table The kernel may automatically update and cache ctime, making it more ``correct'' than what we expect. However, this behavior should not be relied upon. @item -o eol Add a newline (ASCII LF character) to the end of each file. Before writing the file content back to the backend, a trailing newline is automatically removed (if one exists). @item -o file_max=@var{bytes} Max file size limit. Defaults to @samp{32768}. This limit also applies to extended attribute values. @item -o no_sandbox Do not enable sandboxing features (@pxref{Sandboxing}). @anchor{Disabling Landlock} @item -o no_landlock Do not use Landlock for sandboxing. This option is ignored on non-Linux platforms. Without Landlock, sandboxing offers less security. However, Landlock is a rather new feature (requires kernel version 5.13 or later), thus we provide an option to disable it separately. @item -F Stay in the foreground, do not daemonize. @item -h Print help text, and then exit. @item -V Print version and feature information, and then exit. @end table Unrecognized options specified with @option{-o} are passed to libfuse (and subsequently to the kernel, if applicable) as-is. Notable options: @table @option @item -o rw Mount the filesystem read/write. @quotation Warning Always backup the bookmark storage before mounting it read/write, or risk losing your data! @end quotation By default, the filesystem is mounted read-only. This behavior won't change in the future, due to the hackish nature of most BookmarkFS backends. When mounted read/write, other processes must not write to the underlying bookmark storage, otherwise data corruption may occur. @item -o debug Set libfuse log level to @code{FUSE_LOG_DEBUG}. Log messages related to each FUSE request will be printed to standard error. @item -o fsname=@var{name} Name for the filesystem. Defaults to the backend name. This name is equivalent to the @code{fs_spec} field in @linuxmanpage{fstab, 5}, and appears as the @samp{SOURCE} column in @linuxmanpage{findmnt, 8} output. @item -o atime,diratime,relatime These options (and other atime-related ones) are ignored. BookmarkFS only supports @option{noatime} mounts, since the ``last access time'' attribute of a bookmark necessarily means ``the last time it was accessed from the browser''. BookmarkFS should never update that time automatically. Nonetheless, the user is still allowed to explicitly update atime (e.g., with @posixfuncmanpage{futimens}). @item -o auto_unmount Instruct libfuse to fork-exec a helper process, which automatically dismounts the filesystem when the filesystem daemon terminates without unmounting, so that the user don't have to manually dismount the inactive filesystem. See @linuxmanpage{mount.fuse3, 8} for details. This option is helpful when sandboxing is enabled, especially when the @option{-F} option is given, since a sandboxed process itself can neither @linuxmanpage{umount, 2} nor fork-exec. Currently, this option is not available on FreeBSD. @end table @node fsck.bookmarkfs @section @command{fsck.bookmarkfs} The @command{fsck.bookmarkfs} program checks and optionally repairs a BookmarkFS filesystem. @example fsck.bookmarkfs [@var{options}] @var{pathname} @end example Filesystem check on BookmarkFS has a different purpose compared to on-disk filesystems. @xref{Filesystem Check}. Depending on the options specified, filesystem check works either in online or offline mode: @table @asis @item Online Mode In online mode, fsck is performed on a mounted BookmarkFS filesystem using @code{ioctl()}. @xref{Online Filesystem Check}. The @var{pathname} argument refers to the directory to operate on. @item Offline Mode In offline mode, fsck is performed directly on the bookmark storage via the corresponding backend. The @var{pathname} argument is the path to the bookmark storage, equivalent to the @var{src} argument given to @command{mount.bookmarkfs}. Alternatively, @var{pathname} could be specified in @samp{@var{src}:@var{dir}} format, where @var{dir} refers to the directory to operate on, relative to the root directory of the subsystem. @end table Options: @table @option @item -o backend=@var{name} The backend used by the filesystem. @xref{Backends}. Value of @var{name} could be specified in an alternative format. @xref{Alternative Backend Name}. If this option is not provided, or @var{name} is empty, performs online fsck. Otherwise perform offline fsck. @item -o @@@var{key}[=@var{value}] A backend-specific option. This option can be provided multiple times. @item -o handler=@var{name} The handler for resolving errors found during fsck (@pxref{Filesystem-Check Handlers}). Alternatively, @var{name} could be specified in @samp{@var{lib_path}:@var{sym_name}} format, where: @table @var @item lib_path Presumably the pathname to the fsck handler library. Its exact interpretation is equivalent to the first argument for @posixfuncmanpage{dlopen}. @item sym_name @xref{Filesystem-Check Handler Symbol Name Format}. @end table If this option is not provided, or @var{name} is empty, a built-in handler will be used. @xref{Built-in Handler}. @item -o %@var{key}[=@var{value}] A handler-specific option. This option can be provided multiple times. @item -o repair Attempt to repair errors found during fsck. @quotation Warning Always backup the bookmark storage before repairing, or risk losing your data! @end quotation @item -o rl_app=@var{name} Readline application name in interactive mode. Defaults to @samp{fsck.bookmarkfs}. @xref{Conditional Init Constructs,,, readline, GNU Readline Library}. @item -o type=bookmark|tag|keyword Subsystem type (@pxref{Filesystem Hierarchy}). Defaults to @samp{bookmark}. This option is ignored when performing online fsck. @item -i Enable interactive mode. @item -R Perform fsck on subdirectories recursively. This option is ignored when performing fsck on tags or keywords. @item -o no_sandbox Do not enable sandboxing features. @xref{Sandboxing}. @item -o no_landlock Do not use Landlock for sandboxing. This option is ignored on non-Linux platforms. Also @pxref{Disabling Landlock}. @item -h Print help text, and then exit. @item -V Print version and feature information, and then exit. @end table @node mkfs.bookmarkfs @section @command{mkfs.bookmarkfs} The @command{mkfs.bookmarkfs} program creates a new BookmarkFS filesystem. @example mkfs.bookmarkfs [@var{options}] @var{pathname} @end example @table @var @item pathname The underlying bookmark storage for the new filesystem. This option is equivalent to the @var{src} argument for @command{mount.bookmarkfs}. @end table Options: @table @option @item -o backend=@var{name} The backend used by the filesystem (@pxref{Backends}). This option is mandatory. Value of @var{name} could be specified in an alternative format. @xref{Alternative Backend Name}. @item -o @@@var{key}[=@var{value}] A backend-specific option. This option can be provided multiple times. @item -o force If the file referred to by @var{pathname} already exists, overwrite it. @item -h Print help text, and then exit. @item -V Print version and feature information, and then exit. @end table @node bookmarkctl @section @command{bookmarkctl} The @command{bookmarkctl} program is a command-line wrapper for various operations on a BookmarkFS filesystem that cannot be done portably with POSIX utilities alone, including I/O controls and managing extended attributes. @example bookmarkctl @var{subcmd} [@var{args}] @end example Sub-commands: @table @command @item permd Rearranges the order of the directory entries to be returned from future calls to @code{readdir()}. @xref{Permute Directory Entries}. @example bookmarkctl permd [@var{options}] @var{name1} @var{name2} @var{pathname} @end example @table @var @item name1 @itemx name2 Filename of entries under the directory. @item pathname Path to the parent directory. @end table Options: @table @option @item -s Exchange the positions of the directory entries represented by @var{name1} and @var{name2}. @item -b Move the directory entry represented by @var{name1} to the position just @emph{before} the one represented by @var{name2}. @item -a Move the directory entry represented by @var{name1} to the position just @emph{after} the one represented by @var{name2}. @end table The @option{-s}, @option{-b} and @option{-a} options are mutually exclusive. If multiple options are provided, the last one takes effect. @item fsck Displays a list of filesystem errors found under the given directory. @xref{Filesystem Check}. Does not recurse into subdirectories. @example bookmarkctl fsck @var{pathname} @end example @table @var @item pathname Path to the directory to perform checks on. @end table The output shares the same format with the built-in fsck handler. @xref{Filesystem-Check Output Format}. For the full fsck functionalities, @pxref{fsck.bookmarkfs}. @item xattr-list Displays a list of extended attribute names. @xref{Extended Attributes}. @example bookmarkctl xattr-list @var{pathname} @end example @table @var @item pathname Path to the file to obtain extended attribute names. @end table Only the names prefixed with @samp{user.bookmarkfs.} are listed, with the prefix removed. @item xattr-get Displays extended attribute values. @xref{Extended Attributes}. @example bookmarkctl xattr-get [@var{options}] @var{attrname} @var{pathname}... bookmarkctl xattr-get [@var{options}] -a @var{attrname}... @var{pathname} @end example @table @var @item attrname Name of the extended attribute, without the @samp{user.bookmarkfs.} prefix. @item pathname Path to the file to obtain extended attribute values. @end table Options: @table @option @item -b Treat the value as binary, and print it verbatim. If this option is not provided, non-printable characters are replaced with @samp{?}. @item -a Switch to multi-attrname mode, where multiple extended attribute names can be specified instead of multiple files. Also, the corresponding @var{attrname} is printed alongside with the value, instead of @var{pathname}. @item -q Print values only, do not print names. If provided twice, do not print the suffix character after each value. @item -s @var{char} The character separating each name and value. Defaults to the ASCII HT character. @item -n @var{char} The character suffixing each value. Defaults to the ASCII LF character. @end table For the @option{-s} and @option{-n} options, if @var{char} contains multiple characters, the first one is used. If empty, the ASCII NUL character is used. @item xattr-set Update the value of an extended attribute. @xref{Extended Attributes}. @example bookmarkctl xattr-set [@var{options}] @var{attrname} @var{pathname} @end example @table @var @item attrname Name of the extended attribute to update, without the @samp{user.bookmarkfs.} prefix. @item pathname Path to the file to update extended attribute value. @end table Options: @table @option @item -v @var{value} New value for the extended attribute. If this option is not provided, the new value is read from standard input. @end table @item help Print help text, and then exit. @example bookmarkctl help @end example @item version Print version information, and then exit. @example bookmarkctl version @end example @end table @node Filesystem @chapter The Filesystem When a BookmarkFS filesystem is mounted using the @command{mount.bookmarkfs} program, a daemon process acts as a proxy between the kernel (which relays filesystem requests to FUSE requests) and the backend (which manipulates actual bookmark data, @pxref{Backends}), thus providing POSIX (and platform-specific) filesystem API access to bookmarks. BookmarkFS is designed in the hope that web browser bookmarks can be managed flexibly using a combination of existing software, without having to ``reinvent the wheel''. However, like most other pseudo-filesystems, it cannot be considered fully POSIX-compliant. Users should be aware of the limitations when using BookmarkFS. @node Filesystem Hierarchy @section Filesystem Hierarchy BookmarkFS has multiple subsystems. Each one appears as a directory under the mountpoint: @example @var{$@{mountpoint@}}/bookmarks @var{$@{mountpoint@}}/tags @var{$@{mountpoint@}}/keywords @end example If the backend does not support a subsystem, the corresponding directory does not exist. Currently all subsystem definitions are hard-coded within the @command{mount.bookmarkfs} program, and cannot be extended by the backend. @node Bookmarks @subsection Bookmarks The ``bookmarks'' subsystem maintains the hierarchical structure, names, URLs and other information of a bookmark storage. @example @var{$@{mountpoint@}}/bookmarks/@var{$@{bookmark_dir...@}}/@var{$@{bookmark@}} @end example Each bookmark folder name appears as the filename for directory @var{$@{bookmark_dir@}}, and each @var{$@{bookmark@}} is a regular file that refers to a bookmark. The name of a bookmark file is usually the ``bookmark title'', which is the name that appears in the browser's bookmark manager. The content of a bookmark file is usually the URL associated with the bookmark. Not all bookmark names can be represented as a filename. For a bookmark or bookmark folder with an invalid name, the corresponding file is not visible to lookups and @code{readdir()} calls. To deal with such bookmarks, @pxref{Filesystem Check}; or you can instruct the backend to identify bookmarks using GUIDs instead of titles (and then access the titles via extended attributes). Some file attributes are used to represent bookmark metadata: @table @code @item st_ino ID of the bookmark (stored as lower bits). @item st_size Length of the bookmark URL in bytes. Always @t{0} for directories. @item st_atim Last access time of the bookmark. @item st_mtim Last modification time of the bookmark. @xref{Last Modification/Change Time}. @end table Additional information of a bookmark or bookmark folder can be accessed via the extended attributes of the corresponding file, for backends that supports it. @xref{Extended Attributes}. @node Tags @subsection Tags The ``tags'' subsystem maintains a many-to-many mapping between bookmarks and their alternative names. @example @var{$@{mountpoint@}}/tags/@var{$@{tag_dir@}}/@var{$@{bookmark@}} @end example Each tag name appears as the filename for directory @var{$@{tag_dir@}}, and @var{$@{bookmark@}} is a hard link to the bookmark file. A bookmark directory cannot be associated with a tag. If multiple bookmark files with identical names are both associated with a tag, it is unspecified which one appears as an entry for the tag directory. However, consecutive lookups and @code{readdir()}s should produce consistent results for that file, provided that it is not renamed or deleted. Tag files behave differently from traditional hard links. If the original bookmark file is renamed or deleted, it may also change accordingly. It may even link to another file that was previously shadowed. Applications should tread lightly if they wish to cache tag directory entries. To associate a bookmark with a tag, use @posixfuncmanpage{link}: @example c fd = open("tags/gnu/readline", O_CREAT | O_WRONLY, 0600); // Oops, fd == -1, errno == EPERM fd = link("bookmarks/other/readline", "tags/gnu/readline", 0); // OK! @end example Make sure that the two files have identical names, otherwise @code{link()} fails with @code{EPERM}. @node Keywords @subsection Keywords The ``keywords'' subsystem maintains a one-to-one mapping between bookmarks and their alternative names, independent from tag names. @example @var{$@{mountpoint@}}/keywords/@var{$@{keyword_name@}} @end example Each keyword name appears as the filename for regular file @var{$@{keyword_name@}}, which is a hard link to the bookmark file. A bookmark directory cannot be associated with a keyword. To associate a bookmark with a keyword, use @posixfuncmanpage{link} like we do with tags. If the original file is already associated with another keyword, @code{link()} fails with @code{EEXIST}. @node Error Codes @section Error Codes When a filesystem operation fails, the kernel returns an error code for the system call. In addition to common error codes, there are a few more in BookmarkFS: @table @code @item EPERM Attempting to perform an unsupported operation. For example: @itemize @bullet{} @item @code{chmod()}, @code{chown()}, and other operations that make no sense for web browser bookmarks. @item Moving a file across subsystems. @item Creating a bookmark file with a name that is not valid UTF-8 (on Chromium backend). @end itemize @item EIO An unexpected internal error occurred, likely due to a bug in BookmarkFS, or a corruption in the bookmark storage. When this error occurs, a log message describing the situation may be printed to the standard error of the filesystem daemon. Sometimes this error comes from the kernel-side FUSE implementation, and there's no error message. Once this error occurs, behavior of any further operations on the filesystem is undefined. @item ESTALE The file associated with the file descriptor no longer exists. The error may occur when the underlying bookmark storage has been modified by another process (e.g., a web browser) after opening a file. If the filesystem is mounted in exclusive mode, this error should not occur. @xref{Exclusive Mode}. @end table Other BookmarkFS-specific errors may occur. See the corresponding manual section for details. @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 @samp{user.} name prefix on Linux, and should be accessed with @code{EXTATTR_NAMESPACE_USER} on FreeBSD. All attribute names have a @samp{bookmarkfs.} prefix. For example, to get the GUID of a bookmark file (Firefox backend): @example c // Linux len = fgetxattr(fd, "user.bookmarkfs.guid", buf, sizeof(buf)); // FreeBSD len = extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, "bookmarkfs.guid", buf, sizeof(buf)); @end example BookmarkFS does not define any common attributes, neither can users create arbitrary ones. The backend context decides which attributes are available (@pxref{Create Backend Context}), and all bookmark files under it share the same set of attributes. @node Directory Entries @section Directory Entries @table @asis @item The @samp{.} and @samp{..} Entries POSIX consider @samp{.} and @samp{..} as ``special'' filenames, which must refer to the current and parent directory, if they exist. These entries are optional, and BookmarkFS does not support them, for the sake of simplicity. Additionally, bookmarks with such names are hidden from the filesystem until fixed with fsck (@pxref{Filesystem Check}). @anchor{Directory Entry Ordering} @cindex Directory Entry Ordering @item Directory Entry Ordering 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 @linuxdoc{htree, filesystems/ext4/dynamic.html#hash-tree-directories} 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. In BookmarkFS, the directory traversal order is guaranteed to be equivalent to that order. New entries are appended to the end; removed entries do not affect the order of other entries. To change the order of directory entries, @pxref{Permute Directory Entries}. @end table @node Permute Directory Entries @subsection Permute Directory Entries BookmarkFS provides an I/O control for rearranging directory entries: @example c #include int ioctl (int dirfd, BOOKMARKFS_IOC_PERMD, struct bookmarkfs_permd_data const *argp); @end example The @code{bookmarkfs_permd_data} structure is defined as: @example c struct bookmarkfs_permd_data @{ enum bookmarkfs_permd_op op; char name1[NAME_MAX + 1]; char name2[NAME_MAX + 1]; @}; @end example @anchor{Rearrange Operations} The @code{op} field denotes the operation to perform on the directory: @table @code @item BOOKMARKFS_PERMD_OP_SWAP Exchange the positions of the directory entries represented by @code{name1} and @code{name2}. @item BOOKMARKFS_PERMD_OP_MOVE_BEFORE Move the directory entry represented by @code{name1} to the position just @emph{before} the one represented by @code{name2}. @item BOOKMARKFS_PERMD_OP_MOVE_AFTER Move the directory entry represented by @code{name1} to the position just @emph{after} the one represented by @code{name2}. @end table On success, @code{ioctl()} returns @t{0}. Otherwise, it returns @t{-1} and sets @code{errno}: @table @code @item EACCES Write or search permission is denied for the directory. @item EINVAL @code{op} is not one of the values defined in @code{enum bookmarkfs_permd_op}. @item EINVAL @code{name1} or @code{name2} is not a valid filename (e.g., empty string; contains @samp{/} character). @item ENOENT The directory does not contain entries named @code{name1} or @code{name2}. @item ENOTTY The kernel does not support @code{FUSE_IOCTL}. @xref{Limitations on FreeBSD}. @item EPERM The backend does not support rearranging entries for this directory. @end table To ensure that the order change is visible to further @code{readdir()} calls, @code{fsync()} or @code{close()} the directory. @node Filesystem Check @section Filesystem Check On-disk filesystems may suffer from data corruption due to power loss or hardware failures, thus they usually provide a ``filesystem check'' mechanism to detect and fix those problems. As a pseudo-filesystem, BookmarkFS does not check for data integrity, and ``filesystem check'' is given a new purpose: To check if a bookmark name is valid as a filename, and ``repair'' (rename) it if it isn't. A POSIX-compliant filesystem has various restrictions regarding filenames: @itemize @bullet{} @item must not contain @samp{/} characters @item must not be empty or longer than @code{NAME_MAX} @item must not duplicate with another file in the same directory @item @samp{.} and @samp{..} must refer to the current and parent directory @end itemize It is commonplace for bookmark names to not meet such criteria, thus a filesystem check is often necessary when switching to BookmarkFS from another bookmark management software. @node Online Filesystem Check @subsection Online Filesystem Check To perform filesystem check on a mounted BookmarkFS filesystem, use the following I/O controls: @example c #include int ioctl (int dirfd, BOOKMARKFS_IOC_FSCK_NEXT, struct bookmarkfs_fsck_data *argp); int ioctl (int dirfd, BOOKMARKFS_IOC_FSCK_APPLY, struct bookmarkfs_fsck_data *argp); int ioctl (int dirfd, BOOKMARKFS_IOC_FSCK_REWIND); @end example @anchor{Filesystem-Check Data} The @code{bookmarkfs_fsck_data} structure is defined as: @example c struct bookmarkfs_fsck_data @{ uint64_t id; uint64_t extra; char name[NAME_MAX + 1]; @}; @end example Filesystem-check commands: @table @code @item BOOKMARKFS_IOC_FSCK_NEXT Find the next bookmark with invalid name under the directory. @anchor{Filesystem-Check Result Code} @cindex Filesystem-Check Result Code On success, @code{ioctl()} updates @var{argp}, and returns one of the values defined in @code{enum bookmarkfs_fsck_result}: @table @code @item BOOKMARKFS_FSCK_RESULT_END There are no more bookmarks with invalid name under the directory. Fields in @var{argp} have unspecified values. @item BOOKMARKFS_FSCK_RESULT_NAME_DUPLICATE The bookmark name duplicates with another bookmark which appears earlier in the directory stream. Value of @code{extra} is the ID of the other bookmark. @item BOOKMARKFS_FSCK_RESULT_NAME_BADCHAR The bookmark contains a bad character (i.e., the ASCII @samp{/} character). Value of @code{extra} is the byte offset where the bad character first appears in @code{name}. @item BOOKMARKFS_FSCK_RESULT_NAME_BADLEN The bookmark name is either longer than @code{NAME_MAX}, or an empty string. Value of @code{extra} is the length of the bookmark name, and @code{name} is truncated if longer than @code{NAME_MAX}. @item BOOKMARKFS_FSCK_RESULT_NAME_DOTDOT The bookmark name is either @samp{.} or @samp{..}. Value of @code{extra} is unspecified. @end table On failure, @code{ioctl()} returns @t{-1}, and sets @code{errno}. @anchor{Repair Bookmark} @item BOOKMARKFS_IOC_FSCK_APPLY ``Repair'' a bookmark by renaming it. The @code{id} field must be set to a value previously obtained from @code{BOOKMARKFS_IOC_FSCK_NEXT} with the same @var{dirfd}, otherwise the behavior is undefined. The @code{name} field should be set to the new name for the bookmark. The @code{extra} field is unused. On success, @code{ioctl()} updates @var{argp}, and returns one of the values defined in @code{enum bookmarkfs_fsck_result}, like with @code{BOOKMARKFS_IOC_FSCK_NEXT}. Additionally, it may also return: @table @code @item BOOKMARKFS_FSCK_RESULT_NAME_INVALID The new name is not a valid bookmark name. Value of @code{extra} is a backend-specific reason code explaining why the bookmark name is invalid. It may equal to one of the following predefined values: @table @code @item BOOKMARKFS_NAME_INVALID_REASON_NOTUTF8 The name is not valid UTF-8. @end table @end table On failure, @code{ioctl()} returns @t{-1}, and sets @code{errno}: @table @code @item EACCES Write or search permission is denied for the directory. @end table To ensure that the rename is visible to further lookups and @code{readdir()} calls, @code{fsync()} or @code{close()} the directory. @item BOOKMARKFS_IOC_FSCK_REWIND Reset the fsck state for the directory. Further @code{BOOKMARKFS_IOC_FSCK_NEXT} requests will start from the beginning of the directory stream. On success, @code{ioctl()} returns @t{0}. Otherwise, it returns @t{-1}, and sets @code{errno}. @end table Common error codes for all filesystem-check ioctls: @table @code @item ENOTTY The kernel does not support @code{FUSE_IOCTL}. @xref{Limitations on FreeBSD}. @item EPERM The backend does not support fsck for this directory. @end table @node Backends @chapter Backends In BookmarkFS, each backend provides a way to manipulate a certain kind of application bookmarks. Typically, backends are built into shared libraries, and are installed as: @example @var{$@{pkglibdir@}}/backend_@var{$@{name@}}@var{$@{shlib_suffix@}} @end example @table @var @item $@{pkglibdir@} Presumably @file{@var{$@{prefix@}}/lib/bookmarkfs}. @xref{Uniform,, The Uniform Naming Scheme, automake, GNU Automake}. @item $@{name@} The backend name, equivalent to the value given to the @option{-o backend=@var{name}} option of frontend programs. @item $@{shlib_suffix@} The common filename extension for shared library files on the current platform (e.g., @file{.so} on GNU/Linux and FreeBSD). @end table @node Firefox Backend @section Firefox Backend The Firefox backend provides access to the bookmark data of the web browser @uref{https://www.mozilla.org/en-US/firefox/new/, Mozilla Firefox} and its derivatives, notably @uref{https://www.torproject.org/, Tor Browser} and @uref{https://librewolf.net/, Librewolf}. Backend name (for the @option{-o backend} option): @samp{firefox}. Firefox bookmarks are stored in a SQLite database under the profile directory. When mounting the filesystem, this pathname shall be passed as the @var{src} argument (@pxref{mount.bookmarkfs}): @example ~/.mozilla/firefox/@var{$@{profile_name@}}/places.sqlite @end example Actual path for the profile directories may differ across distributions. When in doubt, visit @url{about:profiles} in your browser. Backend-specific options (@command{mount.bookmarkfs} only): @table @option @item filename=title|guid Whether to use the bookmark title or GUID as the bookmark file name. Defaults to @samp{title}. A bookmark GUID is a @rfcdoc{base64url-encoded, rfc4648#section-5} 128-bit string uniquely associated with a bookmark or bookmark folder. When creating a new file: @table @samp @item title The GUID is randomly generated by the backend. @item guid The filename must be a valid GUID, and must not duplicate with other files on the same filesystem, otherwise @code{open()} or @code{mkdir()} fails with @code{EPERM}. Also set the GUID string as the bookmark title. @end table @item lock=exclusive|normal The database connection locking mode for the bookmark storage. Defaults to @samp{normal} when the filesystem is mounted read-only, @samp{exclusive} otherwise. This option corresponds to the @sqlitedoc{@code{locking_mode}, pragma.html#pragma_locking_mode} pragma on SQLite. With @option{lock=exclusive}, other processes cannot access the bookmark storage until the filesystem is dismounted. The Firefox browser holds an exclusive lock on the database by default. If you wish to mount the bookmarks while keeping the browser session open, set the @samp{storage.sqlite.exclusiveLock.enabled} browser preference to @samp{false}. The @option{lock=exclusive} option also enables exclusive mode (@pxref{Exclusive Mode}). @item assume_title_distinct If this options is provided, the backend assumes that bookmark names are distinct under the same bookmark folder. This option is ignored with @option{filename=guid}. This option may improve @code{readdir()} performance, however, making a false assumption results in a directory entry with duplicate names. It is recommended to perform a full filesystem check (@pxref{Filesystem Check}) on the bookmark storage before mounting with this option. @end table If launched from @command{fsck.bookmarkfs}, all backend-specific options are ignored, and always enforces the @option{lock=exclusive} option. Backend-specific options (@command{mkfs.bookmarkfs} only): @table @option @item date_added=@var{timestamp} File creation time for the predefined bookmark directories. Defaults to the current time. Format of @var{timestamp} is equivalent to the @samp{date_added} extended attribute (see below). @end table Extended attributes: @table @samp @item title The bookmark title. Only available with @option{filename=guid}. This value is allowed be set to any string that does not contain a NUL character, and does not have to be valid as a filename. @item guid The bookmark GUID. Only available with @option{filename=title}. @item description An arbitrary text associated with the bookmark. Usually, when bookmarking a page in the browser, this value is set to the @code{content} attribute of the @code{} element whose @code{name} is @samp{description}. @item date_added The bookmark creation time. Value is a decimal integer representing number of microseconds since the Unix epoch. @item keyword The keyword associated with the bookmark. @xref{Keywords}. This attribute is read-only. @end table Notable limitations: @itemize @bullet{} @item If multiple bookmarks share the same URL, changing the last access time of a bookmark may affect that of other bookmarks, due to the way bookmarks are associated with the URLs. @item The bookmark storage (SQLite database file) should be writable even if the BookmarkFS filesystem is mounted read-only, due to the limitations of WAL-mode. See @sqlitedoc{Write-Ahead Logging, wal.html#readonly}. @end itemize @node Chromium Backend @section Chromium Backend The Chromium backend provides access to the bookmark data of the web browser @uref{https://www.chromium.org/Home/, Chromium} and its derivatives, notably @uref{https://github.com/ungoogled-software/ungoogled-chromium, ungoogled-chromium}. Backend name (for the @option{-o backend} option): @samp{chromium}. Chromium bookmarks are stored in a text file (in JSON format) under the profile directory. When mounting the filesystem, this pathname shall be passed as the @var{src} argument (@pxref{mount.bookmarkfs}): @example ~/.config/chromium/@var{$@{profile_name@}}/Bookmarks @end example Actual path for the profile directories may differ across distributions. When in doubt, visit @url{chrome://profile-internals/} in your browser. Backend-specific options (@command{mount.bookmarkfs} only): @table @option @item filename=title|guid Whether to use the bookmark title or GUID as the bookmark file name. Defaults to @samp{title}. A bookmark GUID is a hex-encoded 128-bit string uniquely associated with a bookmark or bookmark folder. It has a ``8-4-4-4-12'' format, with all alphabetic characters in lowercase. For example: @example 0bc5d13f-2cba-5d74-951f-3f233fe6c908 @end example When creating a new file: @table @samp @item title The GUID is randomly generated by the backend. It is guaranteed to be a valid version 4 UUID as specified by @rfcdoc{RFC 4122, rfc4122}. @item guid The filename must be a valid GUID, and must not duplicate with other files on the same filesystem, otherwise @code{open()} or @code{mkdir()} fails with @code{EPERM}. Also set the GUID string as the bookmark title. @end table @item watcher=native|fallback|none The file watcher to use for the bookmark storage. Defaults to @samp{native} when the filesystem is mounted read-only, @samp{none} otherwise. @table @samp @item native @itemx fallback Watch for changes of the bookmark storage, either using a ``native'' implementation using platform-specific features, or a ``fallback'' one which is less efficient but more portable. @xref{File Watcher}. @item none Do not watch for changes of the bookmark storage. External changes are not visible to the BookmarkFS filesystem, and will be lost if the backend writes to the bookmark storage. This option also enables exclusive mode (@pxref{Exclusive Mode}). @end table @end table If launched from @command{fsck.bookmarkfs}, all backend-specific options are ignored, and always enforces the @option{watcher=none} option. Backend-specific options (@command{mkfs.bookmarkfs} only): @table @option @item date_added=@var{timestamp} File creation time for the predefined bookmark directories. Defaults to the current time. Format of @var{timestamp} is equivalent to the @samp{date_added} extended attribute (see below). @end table Extended attributes: @table @samp @item title The bookmark title. Only available with @option{filename=guid}. This value is allowed be set to any UTF-8 string that does not contain a NUL character, and does not have to be valid as a filename. @item guid The bookmark GUID. Only available with @option{filename=title}. @item date_added The bookmark creation time. Value is a decimal integer representing number of microseconds since the Windows @code{FILETIME} epoch (134774 days ahead of the Unix epoch). @end table Notable limitations: @itemize @bullet{} @item Does not scale well with large bookmark storage, since everything is kept in memory, and the entire JSON file has to be parsed whenever a reload is required. @item No support for tags (@pxref{Tags}) and keywords (@pxref{Keywords}), since Chromium does not have such concepts. @end itemize @node Backend API @section Backend API The Backend API specifies how a BookmarkFS backend communicates with a frontend program (e.g., @command{mount.bookmarkfs}). @quotation Warning Currently BookmarkFS is experimental. The Backend API may change drastically without prior notice. @end quotation @anchor{Backend Symbol Name Format} @cindex Backend Symbol Name Format To implement the Backend API, a backend library should expose a symbol with the following name: @example bookmarkfs_backend_@var{$@{name@}} @end example Where @var{$@{name@}} is equivalent to the value given to the @option{-o backend=@var{name}} option of frontend programs. The symbol should name a data object identifier of the following type: @example c #include struct bookmarkfs_backend @{ bookmarkfs_backend_create_func *backend_create; bookmarkfs_backend_destroy_func *backend_destroy; 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_bookmark_check_func *bookmark_check; 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_permute_func *bookmark_permute; bookmarkfs_bookmark_rename_func *bookmark_rename; bookmarkfs_bookmark_set_func *bookmark_set; bookmarkfs_bookmark_sync_func *bookmark_sync; bookmarkfs_cookie_free_func *cookie_free; @}; @end example Each field is a function pointer provided by the backend. Some can be optional and set to @code{NULL}, if the backend does not support the corresponding features. @node General Information @subsection General Information @table @asis @item Multithreading Currently, all BookmarkFS programs are single-threaded, and backend functions are called sequentially. This is an intended design to keep BookmarkFS simple. Multithreading may help with performance in some scenarios, but not much, and we choose not to bother with it. A downside of the this design is that backends should refrain from spending too much time in a function, especially waiting for I/O. Once a backend function blocks, the entire filesystem hangs. Future iterations of the Backend API may introduce event notification mechanisms to improve the situation. Sometimes a backend may wish to use multithreading internally for whatever reason. If it does, a few more precautions should be taken in mind: @itemize @bullet{} @item The @code{SIGINT}, @code{SIGTERM} and @code{SIGHUP} signals should be blocked with @posixfuncmanpage{pthread_sigmask} for the child threads. If a signal is delivered to a child thread, the program may not be terminated promptly, since libfuse relies on @code{EINTR} to break out of the event loop. @end itemize @cindex Backend Context @item Backend Contexts A backend context maintains internal states for manipulating a specific bookmark storage. Each BookmarkFS filesystem mounted by @command{mount.bookmarkfs} is backed by a backend context. The context is created before mount, and destroyed after dismount. Backend functions that operate on a single context accept an argument (usually named @code{backend_ctx}) which refers to the context. @item Callback Functions Some backend functions (e.g., @code{bookmark_get}) accept a function pointer argument, usually named @code{callback}, to be invoked by the backend. It is guaranteed that backend functions are never called from within a callback function. @anchor{Exclusive Mode} @cindex Exclusive Mode @item Exclusive Mode If exclusive mode is enabled for a backend context, modifications to the corresponding bookmark storage should only be performed by calling backend functions on the current context. If the bookmark storage is modified by other means, filesystem and backend behavior is undefined. Frontend programs may perform optimizations based on this assumption. The backend decides whether exclusive mode should be enabled for a context during @code{backend_create} (@pxref{Create Backend Context}), by setting the @code{BOOKMARKFS_BACKEND_EXCLUSIVE} response flag. @item Error Codes Some backend functions return an error code on failure instead of just @code{-1}. For backend functions analogous to the corresponding filesystem calls, the error code should honor POSIX as well as platform-specific conventions. For example: @itemize @bullet{} @item When replacing a non-empty directory with @code{bookmark_rename}, the function should fail with @code{EEXIST} or @code{ENOTEMPTY} as specified in @posixfuncmanpage{rename}. @item When trying to get/set the value of a non-existent extended attribute, the function should fail with @code{ENODATA} on Linux, and @code{ENOATTR} on FreeBSD. @xref{Extended Attributes}. @end itemize Also @pxref{Error Codes} for BookmarkFS-specific error codes. A backend function may also return other error codes which it sees fit, however, they should be chosen carefully. A bad error code may confuse filesystem users or even break applications. @end table @node Initialize Backend @subsection Initialize Backend The @code{backend_init} function is called before any other functions (except for @code{backend_info}). If not @code{NULL}, it is guaranteed to be called exactly once throughout the lifetime of the process. Type of the @code{backend_init} function is defined as: @example c typedef int (bookmarkfs_backend_init_func) ( uint32_t flags ); @end example Function arguments: @table @code @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BACKEND_LIB_READY Indicates that the utility library is already initialized. @xref{The Utility Library}. Some frontend programs use the utility library. If they do, they always initialize it before initializing the backend. The utility library must not be initialized multiple times, otherwise the behavior is undefined. @item BOOKMARKFS_FRONTEND_FSCK @itemx BOOKMARKFS_FRONTEND_MOUNT @itemx BOOKMARKFS_FRONTEND_MKFS Denotes the frontend program that launches the backend. These flags are mutually exclusive. @end table @end table The function should return @t{0} on success, and @t{-1} on error. @node Get Backend Information @subsection Get Backend Information The @code{backend_info} function is called to print information about the backend. It can be @code{NULL}. When this function is called, it should write a human-readable message of the corresponding information to standard output. Type of the @code{backend_info} function is defined as: @example c typedef void (bookmarkfs_backend_info_func) ( uint32_t flags ); @end example Function arguments: @table @code @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BACKEND_INFO_HELP Indicates that the function should print a help message. Mutually exclusive with @code{BOOKMARKFS_BACKEND_INFO_VERSION}. The message should contain a brief description of this backend, as well as a list of backend-specific options. @item BOOKMARKFS_BACKEND_INFO_VERSION Indicates that the function should print a version message. Mutually exclusive with @code{BOOKMARKFS_BACKEND_INFO_HELP}. In addition to the version number, the version message may also contain dependency versions, compile-time options, and other information that can be used to determine a specific version of the backend. @item BOOKMARKFS_FRONTEND_FSCK @itemx BOOKMARKFS_FRONTEND_MOUNT @itemx BOOKMARKFS_FRONTEND_MKFS Denotes the frontend program that launches the backend. These flags are mutually exclusive. @end table @end table @node Create Backend Context @subsection Create Backend Context The @code{backend_create} function is called to create a backend context. It must not be @code{NULL}. Type of the @code{backend_create} function is defined as: @example c typedef int (bookmarkfs_backend_create_func) ( struct bookmarkfs_backend_conf const *conf, struct bookmarkfs_backend_create_resp *resp ); @end example Function arguments: @table @code @item conf Backend configuration items. @anchor{Backend Configuration} The @code{bookmarkfs_backend_conf} structure is defined as: @example c struct bookmarkfs_backend_conf @{ uint32_t version; uint32_t flags; char *store_path; struct bookmarkfs_conf_opt *opts; @}; @end example @table @code @item version Version number of the frontend program, equivalent to the @code{BOOKMARKFS_VERNUM} macro in @file{@var{$@{pkgincludedir@}}/version.h}. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BACKEND_READONLY Indicates that the filesystem is mounted read-only, and the functions that may write to the bookmark storage will not be called for this session. @item BOOKMARKFS_BACKEND_CTIME Indicates that the @option{-o ctime} option is given to @code{mount.bookmarkfs}. @item BOOKMARKFS_BACKEND_NO_SANDBOX Indicates that the @option{-o no_sandbox} option is given to @code{mount.bookmarkfs}. If the backend does not support sandboxing, @code{backend_create} should fail. Otherwise, function @code{backend_sandbox} should be implemented. @xref{Enter Sandbox}. @item BOOKMARKFS_BACKEND_NO_LANDLOCK Indicates that the @option{-o no_landlock} option is given to @code{mount.bookmarkfs}. @item BOOKMARKFS_BACKEND_FSCK_ONLY Indicates that the backend is launched from @command{fsck.bookmarkfs}. Exclusive mode must be enabled for this context (@pxref{Exclusive Mode}), and only the @code{bookmark_lookup}, @code{bookmark_list}, @code{bookmark_check} and @code{bookmark_sync} functions will be called on the bookmark objects. @end table @item store_path Path to the bookmark storage, equivalent to the @var{src} argument passed to @command{mount.bookmarkfs}. The string is guaranteed to be NUL-terminated, and valid throughout the lifetime of the current backend. The function is free to modify the string, however, it must not write past the string boundary. @item opts Linked list of backend-specific options, @code{NULL} if there are none. @anchor{Backend-Specific Options} The @code{bookmarkfs_conf_opt} structure is defined as: @example c struct bookmarkfs_conf_opt @{ char *key; char *val; struct bookmarkfs_conf_opt *next; @}; @end example @table @code @item key @itemx val Key and value of the current option. If the option does not have a value (no @samp{=} character present), @code{val} is @code{NULL}. When not @code{NULL}, the strings are guaranteed to be NUL-terminated, and valid throughout the lifetime of the current backend context. The function is free to modify the strings, however, it must not write past the string boundary. @item next Pointer to the next option, @code{NULL} if there are no more options. @end table The backend must not re-assign the fields in @code{opt}. @end table @item resp Information of the new backend context. The initial content of this argument is unspecified. When the backend context is successfully created, the function should populate this argument with appropriate values. @anchor{Backend-Create Response} The @code{bookmarkfs_backend_create_resp} structure is defined as: @example c struct bookmarkfs_backend_create_resp @{ char const *name; void *backend_ctx; uint64_t bookmarks_root_id; uint64_t tags_root_id; char const *xattr_names; uint32_t flags; @}; @end example @table @code @item name Name of the backend context, used as default value for the @option{-o fsname=@var{name}} option of @command{mount.bookmarkfs}. Must be a NUL-terminated string valid at least until the next function call on this backend context. @item backend_ctx An opaque pointer referring to the backend context. The pointer will be passed to further function calls on this context. @item bookmarks_root_id ID of the bookmarks root directory (i.e., @file{@var{$@{mountpoint@}}/bookmarks}). Must not be greater than @code{BOOKMARKFS_MAX_ID}. If sandboxing is requested, and the ID cannot be determined in a safe way before entering sandbox, the backend may leave this field as-is or set it to @code{UINT64_MAX}. @item tags_root_id ID of the tags root directory (i.e., @file{@var{$@{mountpoint@}}/tags}). Must not be greater than @code{BOOKMARKFS_MAX_ID}. This field should be left as-is or set to @code{UINT64_MAX}, if one of the following conditions is met: @itemize @bullet{} @item Sandboxing is requested, and the ID cannot be determined in a safe way before entering sandbox. @item The backend does not support tags for this context. @xref{Tags}. @end itemize @item xattr_names Names of extended attributes (@pxref{Extended Attributes}) supported for this backend context. Must be a concatenation of NUL-terminated strings valid throughout the lifetime of this backend context. An empty string denotes the end of list. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BACKEND_EXCLUSIVE Indicates that the backend claims exclusive access to the bookmark storage. @xref{Exclusive Mode}. @item BOOKMARKFS_BACKEND_HAS_KEYWORD Indicates that the backend supports keywords for this context. @xref{Keywords}. @end table @end table @end table The function should return @t{0} on success, and @t{-1} on error. @node Destroy Backend Context @subsection Destroy Backend Context The @code{backend_destroy} function is called to destroy a backend context. It must not be @code{NULL}. Upon destruction, the backend should release all system resources associated with this context. Changes made to the bookmark storage should be persisted. Type of the @code{backend_destroy} function is defined as: @example c typedef void (bookmarkfs_backend_destroy_func) ( void *backend_ctx ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @end table @node Enter Sandbox @subsection Enter Sandbox The @code{backend_sandbox} function is called to instruct the backend to enter a sandboxed state where it has limited access to system resources, thereby improving security. @xref{Sandboxing}. This function is only called if the @code{BOOKMARKFS_BACKEND_NO_SANDBOX} flag is @emph{not} set for this context. Considering how sandboxing is usually implemented, it may be complicated or even impractical for multiple backend contexts to enter sandbox separately without affecting other ones. Thus it is guaranteed that, when launched from a frontend program like @command{mount.bookmarkfs}, only one backend context will be created throughout the lifetime of the process if sandboxing is ever needed. Type of the @code{backend_sandbox} function is defined as: @example c typedef int (bookmarkfs_backend_sandbox_func) ( void *backend_ctx, struct bookmarkfs_backend_create_resp *resp ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item resp @xref{Backend-Create Response} for the definition of @code{struct bookmarkfs_backend_create_resp}. Fields in this argument should be left as-is, except for: @table @code @item bookmarks_root_id This field must be set to the ID of the bookmarks root directory, if not already done so in @code{backend_create}. @item tags_root_id This field should be set to the ID of the tags root directory, if not already done so in @code{backend_create}. If the backend does not support tags for this context, this field should be left as-is or set to @code{UINT64_MAX}. @end table @end table The function should return @t{0} on success, and @t{-1} on error. Once this function fails, no further function calls will be performed on the backend except for @code{backend_destroy}. @node Lookup Bookmark @subsection Lookup Bookmark The @code{bookmark_lookup} function is called to obtain the attributes of a bookmark or a bookmark-related object. It must not be @code{NULL}. Type of the @code{bookmark_lookup} function is defined as: @example c typedef int (bookmarkfs_bookmark_lookup_func) ( void *backend_ctx, uint64_t id, char const *name, uint32_t flags, struct bookmarkfs_bookmark_stat *stat_buf ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item id ID of the object to be used for lookup. It could be a bookmark ID or tag ID, depending on the value of @code{flags}. @item name Name of the object to lookup. It could be a bookmark name, tag name or keyword name, depending on the value of @code{flags}. When not @code{NULL}, @code{name} is guaranteed to be a valid filename, which should be used to lookup an object under the directory referred to by @code{id}. Otherwise, the function should operate on the object referred to by @code{id} itself. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BOOKMARK_TYPE_MASK The subsystem to operate on. Equals to one of the following values (after shifting): @table @code @item BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK Lookup a bookmark or bookmark folder. @xref{Bookmarks}. @item BOOKMARKFS_BOOKMARK_TYPE_TAG Lookup a tag, or a bookmark under a tag. @xref{Tags}. @item BOOKMARKFS_BOOKMARK_TYPE_KEYWORD Lookup a keyword. @xref{Keywords}. The value of @code{id} is unspecified. @end table @end table @item stat_buf Attributes of the object to lookup. The initial content of this argument is unspecified. When the object is successfully found, the function should populate this argument with appropriate values. @anchor{Bookmark Attributes} The @code{bookmarkfs_bookmark_stat} structure is defined as: @example c struct bookmarkfs_bookmark_stat @{ uint64_t id; ssize_t value_len; struct timespec atime; struct timespec mtime; @}; @end example @table @code @item id ID of the object. @item value_len Length of the object value in bytes. @t{-1} if the object refers to a directory. @item atime @itemx mtime Last access and modification time of the object. Values should be the time elapsed since the Unix epoch. @end table @end table On success, the function should return @t{0}. Otherwise, it should return a negated @code{errno} indicating the error encountered. @node List Bookmarks @subsection List Bookmarks The @code{bookmark_list} function is called to list the bookmarks or bookmark-related objects under a directory. It must not be @code{NULL}. Type of the @code{bookmark_list} function is defined as: @example c 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 ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item id Object ID of the directory. It could be a bookmark ID or tag ID, depending on the value of @code{flags}. @item off Position of the current @code{bookmark_list} operation, equivalent to the second argument for @posixfuncmanpage{seekdir}. Initially, the value is always @t{0}. Subsequent calls (with the same cookie) use the values obtained from @code{entry->off} of @code{callback} function calls. If not, the position is unspecified. A subsequent call with a @t{0} value indicates @posixfuncmanpage{rewinddir}. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BOOKMARK_LIST_WITHSTAT Indicates that the caller requires the attributes of the objects in the list. @item BOOKMARKFS_BOOKMARK_TYPE_MASK The subsystem to operate on. Equals to one of the following values (after shifting): @table @code @item BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK List bookmarks (and bookmark folders) under a bookmark folder. @xref{Bookmarks}. @item BOOKMARKFS_BOOKMARK_TYPE_TAG List tags, or bookmarks under a tag. @xref{Tags}. @item BOOKMARKFS_BOOKMARK_TYPE_KEYWORD List keywords. @xref{Keywords}. The value of @code{id} is unspecified. @end table @end table @item callback Function to be called for each object found under the directory. If @code{NULL}, @code{bookmark_list} should return immediately without changing current position. Ordering of the objects should follow the ordering of which they appear in the browser. @xref{Directory Entry Ordering}. Type of the @code{callback} function is defined as: @example c typedef int (bookmarkfs_bookmark_list_cb) ( void *user_data, struct bookmarkfs_bookmark_entry const *entry ); @end example Function arguments: @table @code @item user_data Must be equal to the @code{user_data} argument value for the current @code{bookmark_list} call. @item entry Information of this object. The @code{bookmarkfs_bookmark_entry} structure is defined as: @example c struct bookmarkfs_bookmark_entry @{ char const *name; off_t off; struct bookmarkfs_bookmark_stat stat; @}; @end example @table @code @item name Name of the object entry. Must be a valid filename. Objects with invalid name should be exempted from the list. @xref{Filesystem Check}. @item off Position of the current entry, to be used for subsequent calls to @code{bookmark_list}. @item stat Attributes of the object entry. @xref{Bookmark Attributes} for the definition of @code{struct bookmarkfs_bookmark_stat}. If the @code{BOOKMARKFS_BOOKMARK_LIST_WITHSTAT} flag is @emph{not} given, the caller ignores @code{atime} and @code{mtime}, and only use @code{value_len} to determine whether the entry refers to a directory. @end table Values of @code{entry} and @code{name} only need to be valid until the @code{callback} function returns. @end table Return value: @table @asis @item @t{0} Continue to the next entry, if one exists. @item Non-zero value Stop listing entries, and use this value as the return value for the current @code{bookmark_list} call. @end table @item user_data An opaque pointer which should be passed as-is to the @code{callback} function. @item cookie_ptr Pointer to the ``cookie'' for the current @code{bookmark_list} operation. @code{NULL} if not used by the caller. @anchor{Bookmark-List Cookie} @cindex Bookmark-List Cookie The ``cookie'' is an opaque pointer which stores internal states to be used for subsequent operations on the same directory. If the value pointed to by @code{cookie_ptr} is @code{NULL}, it indicates that this is an initial operation. The function should set the value to a pointer which is not @code{NULL}. The cookie obtained from @code{bookmark_list} may be used for @code{bookmark_check}, and vise versa. However, the position of each operation should be maintained separately. The function must not change the cookie value. @end table Return value: @table @asis @item @t{0} All entries in the directory have been listed. @item Negated @code{errno} An error occurred. @item Arbitrary non-zero value The function is terminated by a call to @code{callback} that returns a non-zero value. @end table If returning a negative value, the position of the current @code{bookmark_list} operation is unspecified. @node Get Bookmark Content @subsection Get Bookmark Content The @code{bookmark_get} function is called to get the content or extended attribute value of a bookmark. The ``content'' of a bookmark is equivalent to the content of a regular file on a BookmarkFS filesystem that refers to a bookmark. @xref{Bookmarks}. Type of the @code{bookmark_get} function is defined as: @example c typedef int (bookmarkfs_bookmark_get_func) ( void *backend_ctx, uint64_t id, char const *xattr_name, bookmarkfs_bookmark_get_cb *callback, void *user_data, void **cookie_ptr ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item id ID of the bookmark (could be a bookmark folder if @code{xattr_name} is not @code{NULL}). @item xattr_name Name of an extended attribute. If not @code{NULL}, @code{xattr_name} is guaranteed to be a NUL-terminated string, and the function should get the corresponding extended attribute value instead of the bookmark content. @item callback Function to be called for the required bookmark data. It is guaranteed not to be @code{NULL}. Type of the @code{callback} function is defined as: @example c typedef int (bookmarkfs_bookmark_get_cb) ( void *user_data, void const *value, size_t value_len ); @end example Function arguments: @table @code @item user_data Must be equal to the @code{user_data} argument value of the current @code{bookmark_get} call. @item value Bookmark content (or extended attribute) data. Must not be @code{NULL}. It may contain arbitrary bytes, and only need to be valid until the @code{callback} function returns. @item value_len Length of @code{value} in bytes. @end table The function returns @t{0} on success. Otherwise, the function returns a negated @code{errno} which should be used as the return value for the current @code{bookmark_get} call. @item user_data An opaque pointer which should be passed as-is to the @code{callback} function. @item cookie_ptr Pointer to the ``cookie'' for the current @code{bookmark_get} operation. @code{NULL} if not used by the caller. The “cookie” is an opaque pointer which stores internal states for subsequent operations on the same bookmark (and @code{xattr_name}) to determine whether the corresponding data has changed. If the value pointered to by @code{cookie_ptr} is @code{NULL}, it indicates that this is an initial operation. If the backend supports watching for changes of bookmark content, it may set the value to a pointer which is not @code{NULL}. The function must not change the cookie value. @end table On success, the function should return @t{0}. Otherwise, it should return a negated @code{errno} indicating the error encountered. Notable error codes: @table @code @item EAGAIN Bookmark data has not been changed @emph{externally} since the last call to @code{bookmark_get} with the same cookie. If this error code is returned, @code{callback} must not be called. ``Externally'' means that the modification comes from another process which has access to the bookmark storage. In exclusive mode, this is not expected to happen, and @code{bookmark_list} is always called with a @code{cookie_ptr} of @code{NULL} value. @xref{Exclusive Mode}. If changes cannot be detected in an efficient way, false positives are acceptable. False negatives are also acceptable for a short time duration (preferably no more than a few seconds). @end table @node Free Cookie @subsection Free Cookie When a cookie obtained from @code{bookmark_list}, @code{bookmark_get} or @code{bookmark_check} is no longer used, the @code{cookie_free} function is called. It must not be @code{NULL}. The function should release all system resources associated with this cookie. Type of the @code{cookie_free} function is defined as: @example c typedef void (bookmarkfs_cookie_free_func) ( void *backend_ctx, void *cookie, enum bookmarkfs_cookie_type cookie_type ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item cookie Cookie to be freed. @item cookie_type Type of the cookie. It must be one of the values defined in @code{enum bookmarkfs_cookie_type}: @table @code @item BOOKMARKFS_COOKIE_TYPE_WATCH Indicates that the cookie is obtained from @code{bookmark_get}. @item BOOKMARKFS_COOKIE_TYPE_LIST Indicates that the cookie is obtained from @code{bookmark_list} or @code{bookmark_check}. @end table @end table @node Set Bookmark Content @subsection Set Bookmark Content The @code{bookmark_set} function is called to update the data associated with a bookmark or bookmark-related object. It is never called when the filesystem is mounted read-only. Type of the @code{bookmark_set} function is defined as: @example c typedef int (bookmarkfs_bookmark_set_func) ( void *backend_ctx, uint64_t id, char const *xattr_name, uint32_t flags, void const *val, size_t val_len ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item id ID of the object to update. It could be a bookmark ID or tag ID, depending on the value of @code{flags}. @item xattr_name Name of an extended attribute. If not @code{NULL}, @code{xattr_name} is guaranteed to be a NUL-terminated string, and the function should update the corresponding extended attribute value instead of the bookmark content. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BOOKMARK_SET_ATIME @itemx BOOKMARKFS_BOOKMARK_SET_MTIME Indicates that the function should update the timestamps associated with the object, instead of the bookmark content or extended attribute value. The @code{val} argument points to an array of @code{struct timespec}, the first element refers to the last access time, and the second element refers to the last modification time for the object. If a flag is not set, the corresponding timestamp should be left as-is. Values of @code{xattr_name} and @code{val_len} are unspecified. @item BOOKMARKFS_BOOKMARK_TYPE_MASK The subsystem to operate on. Equals to one of the following values (after shifting): @table @code @item BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK Update the data associated with a bookmark or bookmark folder. @xref{Bookmarks}. @item BOOKMARKFS_BOOKMARK_TYPE_TAG Update the data associated with a tag. @xref{Tags}. @end table @end table @item val Pointer to the new value of the object data. @item val_len Length of the new value in bytes. @end table On success, the function should return @t{0}. Otherwise, it should return a negated @code{errno} indicating the error encountered. The backend does not need to update the last modification time of a bookmark upon bookmark content update, since the caller knows better when the bookmark content is actually modified, and always explicitly updates the mtime after a writeback. However, if the @code{BOOKMARKFS_BACKEND_CTIME} flag is set for this context, the function should automatically update the bookmark mtime to the current time upon extended attribute update. @node Create Bookmark @subsection Create Bookmark The @code{bookmark_create} function is called to create a new bookmark or bookmark-related object. It is never called when the filesystem is mounted read-only. Type of the @code{bookmark_create} function is defined as: @example c 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 ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item parent_id ID of the parent directory to create the new object. It could be a bookmark ID or tag ID, depending on the value of @code{flags}. @item name Name of the new object. It could be a bookmark name, tag name or keyword name, depending on the value of @code{flags}. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BOOKMARK_CREATE_DIR Indicates that the new object to be created should be a directory. If this flag is not set, the new object should be a bookmark. @item BOOKMARKFS_BOOKMARK_TYPE_MASK The subsystem to operate on. Equals to one of the following values (after shifting): @table @code @item BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK Create a bookmark or bookmark folder. @xref{Bookmarks}. @item BOOKMARKFS_BOOKMARK_TYPE_TAG Create a tag, or associate an existing bookmark with a tag. @xref{Tags}. @item BOOKMARKFS_BOOKMARK_TYPE_KEYWORD Associate an existing bookmark with a new keyword. @xref{Keywords}. The value of @code{parent_id} is unspecified. @end table @end table @item stat_buf Attributes of the new object. @xref{Bookmark Attributes} for the definition of @code{struct bookmarkfs_bookmark_stat}. Except for the @code{id} field, the initial content of this argument is unspecified. When the object is successfully found, the function should populate this argument with appropriate values. When associating an existing bookmark with a tag or keyword, the initial value of the @code{id} field refers to the bookmark to be associated. Generally it should be left as-is, but the backend is allowed to re-assign it to the ID of another bookmark (see below for restrictions). @end table On success, the function should return @t{0}. Otherwise, it should return a negated @code{errno} indicating the error encountered. Restrictions for the new object: @table @asis @item New bookmark Bookmark content should be empty if possible. Other reasonable values (e.g., @samp{about:blank}) are also acceptable if the bookmark storage does not allow empty bookmark content. @item New directory Should not contain any entries. @item New association Should appear to be the same object as the associated bookmark, or at least contain the same content. @end table When a new object is successfully created, the function should automatically update the last modification time of the parent directory to the current time. @node Rename Bookmark @subsection Rename Bookmark The @code{bookmark_rename} function is called to rename and/or reparent a bookmark or a bookmark-related object. It is never called when the filesystem is mounted read-only. Type of the @code{bookmark_rename} function is defined as: @example c 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 ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item old_parent_id ID of the parent directory containing the object to be renamed. It could be a bookmark ID or tag ID, depending on the value of @code{flags}. @item old_name Name of the object to be renamed. It could be a bookmark name, tag name or keyword name, depending on the value of @code{flags}. @item new_parent_id ID of the new parent directory for the object. @item new_name New name of the object. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BOOKMARK_RENAME_NOREPLACE Indicates that the function should not replace an existing object. This flag is analogous to the @code{RENAME_NOREPLACE} flag for @linuxmanpage{renameat2}. @item BOOKMARKFS_BOOKMARK_TYPE_MASK The subsystem to operate on. Equals to one of the following values (after shifting): @table @code @item BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK Rename a bookmark. @xref{Bookmarks}. @item BOOKMARKFS_BOOKMARK_TYPE_TAG Rename a tag. @xref{Tags}. The function should fail with @code{EPERM} if either @code{old_parent_id} or @code{new_parent_id} does not refer to the tags root directory, since renaming a tag association makes no sense. @item BOOKMARKFS_BOOKMARK_TYPE_KEYWORD Rename a keyword. @xref{Keywords}. Values of @code{old_parent_id} and @code{new_parent_id} are unspecified. @end table @end table @end table On success, the function should return @t{0}. Otherwise, it should return a negated @code{errno} indicating the error encountered. When replacing an existing object: @itemize @bullet{} @item The two objects should be of the same type (e.g., a bookmark cannot replace a bookmark folder). @item When replacing a directory, the destination object must be empty. @item If two objects are the same, the function should do nothing and return successfully. @end itemize Upon successful completion, the function should automatically update the last modification time of the parent directories. @node Delete Bookmark @subsection Delete Bookmark The @code{bookmark_delete} function is called to remove a bookmark or a bookmark-related object. It is never called when the filesystem is mounted read-only. Type of the @code{bookmark_delete} function is defined as: @example c typedef int (bookmarkfs_bookmark_delete_func) ( void *backend_ctx, uint64_t parent_id, char const *name, uint32_t flags ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item parent_id ID of the parent directory containing the object to be removed. It could be a bookmark ID or tag ID, depending on the value of @code{flags}. @item name Name of the object to be removed. It could be a bookmark name, tag name or keyword name, depending on the value of @code{flags}. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BOOKMARK_DELETE_DIR Indicates that the object to be removed is a directory. If the current backend context is not in exclusive mode (@pxref{Exclusive Mode}), this information may be incorrect. If so, the function should fail with @code{ENOTDIR} or @code{EISDIR}. @item BOOKMARKFS_BOOKMARK_TYPE_MASK The subsystem to operate on. Equals to one of the following values (after shifting): @table @code @item BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK Remove a bookmark or an empty bookmark folder. @xref{Bookmarks}. @item BOOKMARKFS_BOOKMARK_TYPE_TAG Remove a tag, or dissociate a bookmark from a tag. @xref{Tags}. @item BOOKMARKFS_BOOKMARK_TYPE_KEYWORD Remove a keyword. @xref{Keywords}. The value of @code{parent_id} is unspecified. @end table @end table @end table On success, the function should return @t{0}. Otherwise, it should return a negated @code{errno} indicating the error encountered. Upon successful completion, the function should automatically update the last modification time of the parent directory. @node Permute Bookmarks @subsection Permute Bookmarks The @code{bookmark_permute} function is called to rearrange bookmarks or bookmark-related objects under a directory. It is never called when the filesystem is mounted read-only. This function should implement the @code{BOOKMARKFS_IOC_PERMD} I/O control. @xref{Permute Directory Entries}. Type of the @code{bookmark_permute} function is defined as: @example c 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 ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item parent_id ID of the parent directory containing the objects to be rearranged. It could be a bookmark ID or tag ID, depending on the value of @code{flags}. @item op Operation to perform on objects represented by @code{name1} and @code{name2}. @xref{Rearrange Operations} for the meaning of each value. @item name1 @itemx name2 Names of the objects to be rearranged. They could be bookmark names, tag names or keyword names, depending on the value of @code{flags}. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BOOKMARK_TYPE_MASK The subsystem to operate on. Equals to one of the following values (after shifting): @table @code @item BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK Rearrange entries under a bookmark folder. @xref{Bookmarks}. @item BOOKMARKFS_BOOKMARK_TYPE_TAG Rearrange tags or tag associations. @xref{Tags}. @item BOOKMARKFS_BOOKMARK_TYPE_KEYWORD Rearrange keywords. @xref{Keywords}. The value of @code{parent_id} is unspecified. @end table @end table @end table On success, the function should return @t{0}. Otherwise, it should return a negated @code{errno} indicating the error encountered. @node Check Bookmarks @subsection Check Bookmarks The @code{bookmark_check} function is called when a ``filesystem check'' operation is performed on a BookmarkFS directory. @xref{Filesystem Check}. Type of the @code{bookmark_check} function is defined as: @example c typedef int (bookmarkfs_bookmark_check_func) ( void *backend_ctx, uint64_t parent_id, struct bookmarkfs_fsck_data const *fsck_data, uint32_t flags, bookmarkfs_bookmark_check_cb *callback, void *user_data, void **cookie_ptr ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @item parent_id ID of the parent directory containing the objects to be checked. It could be a bookmark ID or tag ID, depending on the value of @code{flags}. @item fsck_data Always @code{NULL} if the filesystem is mounted read-only. Otherwise, a non-@code{NULL} value indicates @code{BOOKMARKFS_IOC_FSCK_APPLY}. @xref{Repair Bookmark}. @xref{Filesystem-Check Data} for the definition of @code{struct bookmarkfs_fsck_data}. Value of each field: @table @code @item id ID of the object to rename. The function should check whether the object is under the directory referred to by @code{parent_id}. If not, it should fail with @code{ENOENT}. If the object ID was not previously passed to the @code{callback} function during a @code{bookmark_check} call with the same cookie, the function may: @itemize @bullet{} @item Fail with an arbitrary error code. @item Perform the rename, even if the original name is a valid filename. @end itemize @item name New name for the object. The string may @emph{not} be NUL-terminated. @item extra Unspecified value. @end table If @code{fsck_data} is not @code{NULL}, the function should only check whether the new name is valid, instead of going through the rest of the directory. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BOOKMARK_TYPE_MASK The subsystem to operate on. Equals to one of the following values (after shifting): @table @code @item BOOKMARKFS_BOOKMARK_TYPE_BOOKMARK Check bookmark and bookmark folder names. @xref{Bookmarks}. @item BOOKMARKFS_BOOKMARK_TYPE_TAG Check tag names. @xref{Tags}. The function should fail with @code{EPERM} if @code{parent_id} does not refer to the tags root directory. @item BOOKMARKFS_BOOKMARK_TYPE_KEYWORD Check keyword names. @xref{Keywords}. The value of @code{parent_id} is unspecified. @end table @end table @item callback Function to be called for each object with invalid name. A @code{NULL} value indicates @code{BOOKMARKFS_IOC_FSCK_REWIND}, in which case the function should reset the position of the current @code{bookmark_check} operation. Type of the @code{callback} function is defined as: @example c typedef int (bookmarkfs_bookmark_check_cb) ( void *user_data, int result, uint64_t id, uint64_t extra, char const *name ); @end example Function arguments: @table @code @item user_data Must be equal to the @code{user_data} argument for the current @code{bookmark_check} call. @item result Should be one of the values defined in @code{enum bookmarkfs_fsck_result} indicating why this name is invalid. @xref{Filesystem-Check Result Code}. @item id @itemx extra @itemx name Equivalent to the corresponding fields in @code{struct bookmarkfs_fsck_data}. @xref{Filesystem-Check Data}. @end table Return value: @table @asis @item @t{0} Continue to the next entry, if one exists. @item Non-zero value Stop listing entries, and use this value as the return value for the current @code{bookmark_check} call. @end table @item user_data An opaque pointer which should be passed as-is to the @code{callback} function. @item cookie_ptr Pointer to the ``cookie'' for the current @code{bookmark_check} operation. It is guaranteed not to be @code{NULL}. Except for the position of operation, this cookie shares the same semantics as the one for @code{bookmark_list}. @xref{Bookmark-List Cookie}. @end table Return value: @table @asis @item @t{0} The operation completes successfully. @item Negated @code{errno} An error occurred. @item Arbitrary non-zero value The function is terminated by a call to @code{callback} that returns a non-zero value. @end table If returning a negative value, the position of the current @code{bookmark_check} operation is unspecified. @node Sync Bookmarks @subsection Sync Bookmarks The @code{bookmark_sync} function is called to persist changes to the bookmark storage. It is never called when the filesystem is mounted read-only. If the bookmark storage is also changed from another process, function behavior is undefined. However, the backend should try not to corrupt the bookmark storage, and prefer changes from the caller, if possible. Type of the @code{bookmark_sync} function is defined as: @example c typedef int (bookmarkfs_bookmark_sync_func) ( void *backend_ctx ); @end example Function arguments: @table @code @item backend_ctx The pointer referring to the backend context. @end table On success, the function should return @t{0}. Otherwise, it should return a negated @code{errno} indicating the error encountered. @node Create Bookmark Storage @subsection Create Bookmark Storage The @code{backend_mkfs} function is called by the @command{mkfs.bookmarkfs} program to create a new bookmark storage. It can be @code{NULL}. The new bookmark storage should be able to be opened directly, by both @code{backend_create} and the application that owns the bookmark storage (typically a web browser). Type of the @code{backend_mkfs} function is defined as: @example c typedef int (bookmarkfs_backend_mkfs_func) ( struct bookmarkfs_backend_conf const *conf ); @end example Function arguments: @table @code @item conf @xref{Backend Configuration} for the definition of @code{struct bookmarkfs_backend_conf}. Some fields should be handled differently from @code{backend_create}: @table @code @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_BACKEND_MKFS_FORCE Indicates that the @option{-o force} option is given to @command{mkfs.bookmarkfs}. @end table @item store_path Path to the bookmark storage to be created, equivalent to the @code{store_path} passed to @code{backend_create}. The function should not overwrite existing bookmark storage, unless the @code{BOOKMARKFS_BACKEND_MKFS_FORCE} flag is given. @end table @end table The function should return @t{0} on success, and @t{-1} on error. @node Filesystem-Check Handlers @chapter Filesystem-Check Handlers The filesystem-check handler instructs the @command{fsck.bookmarkfs} program on what to do with invalid bookmark names. Like backends, filesystem-check handlers are typically built into shared libraries, and are installed as: @example @var{$@{pkglibdir@}}/fsck_handler_@var{$@{name@}}@var{$@{shlib_suffix@}} @end example @table @var @item $@{pkglibdir@} Presumably @file{@var{$@{prefix@}}/lib/bookmarkfs}. @xref{Uniform,, The Uniform Naming Scheme, automake, GNU Automake}. @item $@{name@} The handler name, equivalent to the value given to the @option{-o handler=@var{name}} option of @command{fsck.bookmarkfs}. @item $@{shlib_suffix@} The common filename extension for shared library files on the current platform (e.g., @file{.so} on GNU/Linux and FreeBSD). @end table @node Built-in Handler @section Built-in Handler The built-in filesystem-check handler is linked into the @command{fsck.bookmarkfs} program. It has minimal functionalities, but is capable enough to handle most common fsck scenarios. Handler-specific options: @table @option @item prompt=@var{str} Readline prompt string. Defaults to @samp{% }. This option is ignored in non-interactive mode. @item translit=@var{char} Transliterate bad (@samp{/}) characters into @var{char} (must be a single ASCII character). Defaults to @samp{_}. @end table For each bookmark entry, the built-in handler prints a line to standard output explaining why the bookmark name is invalid. @anchor{Filesystem-Check Output Format} @cindex Filesystem-Check Output Format Output format (with an ASCII HT character separating each item): @example @var{id} @var{why} @var{extra} @var{name} @end example @table @var @item why Name of the result code, without the @samp{BOOKMARKFS_FSCK_RESULT_} prefix. @xref{Filesystem-Check Result Code}. @item id @itemx extra @itemx name Information of this entry. @xref{Filesystem-Check Data}. Integers are printed in decimal format. ASCII control characters in @var{name} are replaced with @samp{?}. @end table When the @option{-o repair} option is given, the handler renames the bookmark according to the following rules: @table @code @item BOOKMARKFS_FSCK_RESULT_NAME_DUPLICATE Append a @samp{_@var{$@{counter@}}} suffix to the name, where @var{$@{counter@}} is a self-incrementing 32-bit integer in decimal format. If appending the suffix would exceed max name length, truncate the name first. @item BOOKMARKFS_FSCK_RESULT_NAME_BADCHAR Transliterate the bad character into another character. @item BOOKMARKFS_FSCK_RESULT_NAME_BADLEN If the name is too long, truncate it to @code{NAME_MAX} bytes. If the name is empty, see below: @item BOOKMARKFS_FSCK_RESULT_NAME_DOTDOT @itemx BOOKMARKFS_FSCK_RESULT_NAME_INVALID Rename to @samp{fsck-@var{$@{id@}}}, where @var{$@{id@}} is the bookmark ID in decimal format. @end table In interactive mode, the handler prompts the user before applying the rename. The user may issue a command to indicate what to do with each entry: @table @command @item p Print the ID and new name of current entry. @item a[-] Apply the proposed new name for the current entry. @item e[-] @var{new_name} Change the proposed new name to @var{new_name} and then apply. @item c Continue to the next entry. @item s[-] Skip current directory. @item S[-] Skip current directory and all subdirectories. @item r[-] Rewind current directory. @item R[-] Rewind all. @item w[-] Save applied changes. @item q Save applied changes and quit. @end table The optional @samp{-} suffix inhibits the default behavior of continuing to the next entry, after the command completes successfully. @node Tcl-Based Handler @section Tcl-Based Handler The @uref{https://www.tcl-lang.org/,, Tcl}-based filesystem-check handler allows fsck entries to be handled via Tcl scripting. Handler name (for the @option{-o handler} option): @samp{tcl}. Handler-specific options: @table @option @item script=@var{path} Path to the Tcl script file. This option is mandatory. The script is evaluated once after interpreter initialization. The evaluation result will be used as the command name for later executions. The following variables are set before script evaluation: @table @code @item $::bookmarkfs::fsck::isInteractive Equals to @t{1} if the @option{-i} option is given to @code{fsck.bookmarkfs}, @t{0} otherwise. @item $::bookmarkfs::fsck::isReadonly Equals to @t{0} if the @option{-o repair} option is given to @code{fsck.bookmarkfs}, @t{1} otherwise. @end table @item unsafe Enable unsafe Tcl interpreter features. Should be used in combination with the @option{-o no_sandbox} option if you wish to access extra system resources (e.g., open local files). Without this option, the Tcl interpreter has limited functionalities as if created with @code{interp create -safe}. See @tcldoc{Safe Interpreters, TclCmd/interp.htm#M45}. @end table Each time @command{fsck.bookmarkfs} finds an entry, or completes a handler-initiated operation, the command returned from the script is executed with a single list argument. The first element of the list is an integer indicating the reason for this handler call: @table @code @item $::bookmarkfs::fsck::result::nameDuplicate @itemx $::bookmarkfs::fsck::result::nameBadChar @itemx $::bookmarkfs::fsck::result::nameBadLen @itemx $::bookmarkfs::fsck::result::nameDotDot @itemx $::bookmarkfs::fsck::result::nameInvalid @xref{Filesystem-Check Result Code} for the meaning of each value. The second element is a list of information about the current entry: @enumerate 0 @item The bookmark ID @item Extra information regarding the invalid name, equivalent to the @code{extra} field in @code{struct bookmarkfs_fsck_data}. @item Name of the bookmark @item ID of the parent directory @end enumerate @item -1 If the command is being executed for the first time, or the previous execution returns @code{$::bookmarkfs::fsck::handler::next}, this value never appears. If the previous execution of the command returns @code{$::bookmarkfs::fsck::handler::userInput}, the second element of the list is a string of user input. Otherwise, this value indicates that the previous operation is successfully performed on the entry. @end table The command should return a list indicating the operation to perform on the entry. First element of the list should be one of the following values: @table @code @item $::bookmarkfs::fsck::handler::next Continue to the next entry. @item $::bookmarkfs::fsck::handler::apply Apply change for the current entry. Not available in read-only mode. Second element of the list should be a string for the new name of the bookmark. @item $::bookmarkfs::fsck::handler::userInput Request for user input. Only available in interactive mode. Second element of the list should be a string for Readline prompt, equivalent to the @option{prompt=@var{str}} option of the built-in handler. @item $::bookmarkfs::fsck::handler::save Save applied changes. @item $::bookmarkfs::fsck::handler::stop Save applied changes and quit. Once this value is returned, the command will never be executed again. @item $::bookmarkfs::fsck::handler::rewind Rewind current directory. @item $::bookmarkfs::fsck::handler::skip Skip current directory. @item $::bookmarkfs::fsck::handler::skipChildren Skip current directory and all subdirectories. @item $::bookmarkfs::fsck::handler::reset Rewind all. @end table Here is an example Tcl script that simply prints each fsck entry (requires the @option{unsafe} option): @example tcl chan configure stdout -encoding utf-8 coroutine whatever apply @{@{@} @{ set args [yield [info coroutine]] while 1 @{ lassign $args why data if @{$why > -1@} @{ lassign $data id extra name parent puts "id: $id, extra: $extra, name: $name, parent: $parent" @} set args [yield [list $::bookmarkfs::fsck::handler::next]] @} @}@} @end example @node Handler API @section Handler API The Filesystem-Check Handler API specifies how a fsck handler communicates with @command{fsck.bookmarkfs}. @quotation Warning Currently BookmarkFS is experimental. The Handler API may change drastically without prior notice. @end quotation @anchor{Filesystem-Check Handler Symbol Name Format} @cindex Filesystem-Check Handler Symbol Name Format To implement the Filesystem-Check Handler API, a fsck handler library should expose a symbol with the following name: @example bookmarkfs_fsck_handler_@var{$@{name@}} @end example Where @var{$@{name@}} is equivalent to the value given to the @option{-o handler=@var{name}} option of @command{fsck.bookmarkfs}. The symbol should name a data object identifier of the following type: @example c #include 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; @}; @end example Each field is a function pointer provided by the fsck handler. Some can be optional and set to @code{NULL}, if the backend does not support the corresponding features. @node Get Handler Information @subsection Get Handler Information If not @code{NULL}, the @code{info} function is called when the user instructs @command{fsck.bookmarkfs} to print information about the handler. When this function is called, it should write a human-readable message of the corresponding information to standard output. Type of the @code{info} function is defined as: @example c typedef void (bookmarkfs_fsck_handler_info_func) ( uint32_t flags ); @end example Function arguments: @table @code @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_FSCK_HANDLER_INFO_HELP @itemx BOOKMARKFS_FSCK_HANDLER_INFO_VERSION Indicates that the function should print a help/version message. These flags are analogous to the corresponding flags in the @code{backend_info} function of the backend API. @xref{Get Backend Information}. @end table @end table @node Create Handler Context @subsection Create Handler Context A filesystem-check handler context maintains internal states for handling fsck entries. To create a handler context, the @code{create} function is called. It must not be @code{NULL}. Type of the @code{create} function is defined as: @example c typedef int (bookmarkfs_fsck_handler_create_func) ( struct bookmarkfs_conf_opt const *opts, uint32_t flags, void **handler_ctx_ptr ); @end example Function arguments: @table @code @item opts Linked list of handler-specific options, @code{NULL} if there are none. Handler-specific option should be processed in the same way as backend-specific options. @xref{Backend-Specific Options}. @item flags A bit array of the following flags: @table @code @item BOOKMARKFS_FSCK_HANDLER_INTERACTIVE Indicates that the @option{-i} option is given to @command{fsck.bookmarkfs}. @item BOOKMARKFS_FSCK_HANDLER_READONLY Indicates that the @option{-o repair} option is @emph{not} given to @command{fsck.bookmarkfs}. @end table @item handler_ctx_ptr Pointer to an opaque pointer referring to the handler context. The initial value of that pointer is unspecified. It should be set by the handler when the handler context is successfully created. @end table The function should return @t{0} on success, and @t{-1} on error. @node Destroy Handler Context @subsection Destroy Handler Context The @code{destroy} function is called to destroy a handler context. It must not be @code{NULL}. Upon destruction, The handler should release all system resources associated with this context. Type of the @code{destroy} function is defined as: @example c typedef void (bookmarkfs_fsck_handler_destroy_func) ( void *handler_ctx ); @end example Function arguments: @table @code @item handler_ctx The pointer referring to the handler context. @end table @node Run Handler @subsection Run Handler Each time @command{fsck.bookmarkfs} finds and entry, or completes a handler-initiated operation, the @code{run} function is called. It must not be @code{NULL}. Type of the @code{run} function is defined as: @example c typedef int (bookmarkfs_fsck_handler_run_func) ( void *handler_ctx, int why, union bookmarkfs_fsck_handler_data *data ); @end example Function arguments: @table @code @item handler_ctx The pointer referring to the handler context. @item why Reason code for this handler call: @table @asis @item Non-negative value The @command{fsck.bookmarkfs} program has found an entry. @xref{Filesystem-Check Result Code} for possible values. However, @code{BOOKMARKFS_FSCK_RESULT_END} is handled by @command{fsck.bookmarkfs} and never appears. @item @t{-1} Indicates that an operation instructed by the previous return value of this function is successfully performed. If @code{run} is being called for the first time, or the previous invocation returns @code{BOOKMARKFS_FSCK_NEXT}, this value never appears. @end table @item data Information for this handler call. The @code{bookmarkfs_fsck_handler_data} union is defined as: @example c union bookmarkfs_fsck_handler_data @{ struct bookmarkfs_fsck_handler_entry entry; char *str; @}; @end example @table @code @item entry Information for the bookmark entry. This field is set when @code{why} is non-negative. The @code{bookmarkfs_fsck_handler_entry} structure is defined as: @example c struct bookmarkfs_fsck_handler_entry @{ uint64_t parent_id; struct bookmarkfs_fsck_data data; @}; @end example @table @code @item parent_id ID of the bookmark folder that contains this bookmark entry. @item data @xref{Online Filesystem Check} for the definition of @code{struct bookmarkfs_fsck_data}. @end table @item str A NUL-terminated string. This field is set to the user input string when @code{why} is @t{-1}, and the previous invocation of @code{run} returns @code{BOOKMARKFS_FSCK_USER_INPUT}. The string is only guaranteed to be valid until the function returns. @end table @end table On success, the function should return one of the following values, indicating the operation to perform: @table @code @item BOOKMARKFS_FSCK_NEXT Continue to the next entry. @item BOOKMARKFS_FSCK_APPLY Apply change for the current entry. Not available in read-only mode. The function should copy the new name for the bookmark to @code{data->entry.data.name}. @item BOOKMARKFS_FSCK_USER_INPUT Request for user input. Only available in interactive mode. The function should set @code{data->str} to a prompt string for Readline. The string must be valid until the next function call on this handler context. @item BOOKMARKFS_FSCK_SAVE Save applied changes. @item BOOKMARKFS_FSCK_STOP Save applied changes and quit. The @code{run} function is guaranteed not to be called again for this context. @item BOOKMARKFS_FSCK_REWIND Rewind current directory. @item BOOKMARKFS_FSCK_SKIP Skip current directory. @item BOOKMARKFS_FSCK_SKIP_CHILDREN Skip current directory and all subdirectories. @item BOOKMARKFS_FSCK_RESET Rewind all. @end table On error, the function should return @t{-1}. @node The Utility Library @chapter The Utility Library The BookmarkFS Utility Library implements various common utility functions. It is used internally by most of BookmarkFS's components, including the backends (@pxref{Backends}) and the @command{mount.bookmarkfs} program. @quotation Warning Currently BookmarkFS is experimental. Functions provided by the utility library may change drastically without prior notice. @end quotation Typically, the library is built into a shared object, and installed as: @example @var{$@{libdir@}}/bookmarkfs_util@var{$@{shlib_suffix@}} @end example @table @var @item $@{libdir@} Presumably @file{@var{$@{prefix@}}/lib}. @xref{Uniform,, The Uniform Naming Scheme, automake, GNU Automake}. @item $@{shlib_suffix@} The common filename extension for shared library files on the current platform (e.g., @file{.so} on GNU/Linux and FreeBSD). @end table @node Hash Function @section Hash Function Non-cryptographic hash function with 64-bit seed and digest, suitable for hash tables. Currently, the @uref{https://xxhash.com/, xxHash} (XXH3) algorithm is used. Functions: @table @code @item hash_seed Updates the seed used by the hash function. @example c #include void hash_seed ( uint64_t s ); @end example With the same seed, the hash function always produces the same digest for the same input. If this function is never called, the hash function bahaves as if it is seeded with all bits zero. @item hash_digest Calculates the digest of the given input. @example c uint64_t hash_digest ( void const *input, size_t len ); @end example @item hash_digestv Like @code{hash_digest()}, but takes input from multiple buffers. @example c uint64_t hash_digestv ( struct iovec const *bufv, int bufcnt ); @end example The function is guaranteed not to modify the buffer data of each @code{struct iovec} object. @item hash_digestcb Like @code{hash_digestv()}, but takes input buffers from a callback function. @example c uint64_t hash_digestcb ( hash_digestcb_func *callback, void *user_data ); @end example Function arguments: @table @code @item callback The function to provide input buffers. It must not be @code{NULL}. Type of the @code{callback} function is defined as: @example c typedef size_t (hash_digestcb_func) ( void *user_data, void const **buf_ptr ); @end example Function arguments: @table @code @item user_data Equals to the @code{user_data} pointer passed to the current @code{hash_digest} function call. @item buf_ptr Unless returning @t{0}, the callback function should store the pointer to an input buffer to the location pointed to by @code{buf_ptr}. The buffer must be valid until the next call to the callback function, or until @code{hash_digestcb()} returns. @end table The callback function should return the number of bytes to be read from the input buffer. It is called continuously until it returns @t{0}. @item user_data An opaque pointer to be passed to the @code{callback} function. @end table @end table @node Hash Tables @section Hash Tables A simple hash table implementation, based on a variant of @uref{https://en.wikipedia.org/wiki/Hopscotch_hashing, Hopscotch Hashing}. Time complexity: @multitable @columnfractions .33 .33 .33 @headitem Operation @tab Average @tab Worst case @item Insert @tab @t{Θ(1)} @tab @t{O(n)} @item Search @tab @t{Θ(1)} @tab @t{O(log n)} @item Delete @tab @t{Θ(1)} @tab @t{O(log n)} @end multitable Functions: @table @code @item hashmap_create Creates a new hash table. @example c #include struct hashmap * hashmap_create ( hashmap_comp_func *entry_comp, hashmap_hash_func *entry_hash ); @end example Function arguments: @table @code @item entry_comp The callback function for checking whether a key matches an entry. Type of @code{entry_comp} is defined as: @example c typedef int (hashmap_comp_func) ( union hashmap_key key, void const *entry ); @end example The function should return @t{0} if the objects match, non-zero if not. @item entry_hash The callback function for obtaining the hashcode for an entry. Type of @code{entry_hash} is defined as: @example c typedef unsigned long (hashmap_hash_func) ( void const *entry ); @end example This function is only called during rehashing, and during @code{hashmap_delete} with a negative @code{entry_id}. @end table Returns an opaque pointer referring to the new hash table. @item hashmap_insert Inserts a new entry into a hash table. @example c void hashmap_insert ( struct hashmap *map, unsigned long hashcode, void *entry ); @end example Function arguments: @table @code @item map The hash table to insert entry into. @item hashcode The hash code for the new entry. @item entry An arbitrary pointer to be associated with the new entry. It must not be @code{NULL}. @end table @item hashmap_search Searches for an entry in a hash table. @example c void * hashmap_search ( struct hashmap const *map, union hashmap_key key, unsigned long hashcode, unsigned long *entry_id_ptr ); @end example Function arguments: @table @code @item map The hash table to search from. @item key An object to identify the entry being searched. Type of the @code{hashmap_key} union is defined as: @example c union hashmap_key @{ void const *ptr; uint64_t u64; @}; @end example For each entry in the hash table with the given @code{hashcode}, the @code{entry_comp} callback function is called with @code{key} as the first argument, until a matching entry is found. @item hashcode The hash code for the entry to search. @item entry_id_ptr If not @code{NULL}, and a matching entry is found, the function sets @code{*entry_id_ptr} to an opaque integer value, which can be used to identify the entry when calling @code{hashmap_delete()}. A subsequent call to @code{hashmap_insert()} and @code{hashmap_delete()} on this hash table invalidates that value. @end table Returns the pointer associated with the entry, or @code{NULL} if no matching entry is found. @item hashmap_delete Removes an entry from a hash table. @example c void hashmap_delete ( struct hashmap *map, void const *entry, long entry_id ); @end example Function arguments: @table @code @item map The hash table to delete entry from. @item entry The entry to be deleted from the hash table. If the hash table does not contain this entry, function behavior is undefined. @item entry_id An opaque integer obtained from @code{hashmap_search()} which identifies the entry to be deleted. If not readily available, @t{-1} should be used. If provided, the delete operation is always of @t{O(1)} time complexity. @end table @item hashmap_foreach Iterates through all entries of a hash table. @example c void hashmap_foreach ( struct hashmap const *map, hashmap_walk_func *walk_func, void *user_data ); @end example Function arguments: @table @code @item map The hash table to traverse. @item walk_func The callback function to be called for each entry. Type of @code{walk_func} is defined as: @example c typedef void (hashmap_walk_func) ( void *entry, void *user_data ); @end example The callback function must not insert to or delete from the current hash table. @item user_data An opaque pointer to be passed to @code{walk_func}. @end table @item hashmap_destroy Destroys a hash table. @example c void hashmap_destroy ( struct hashmap *map ); @end example Function arguments: @table @code @item map The hash table to destroy. If @code{NULL}, this function has no effect. @end table @end table Limitations: @itemize @bullet{} @item Insertion performance is sacrificed in favor of search performance. @item Suffers more from collisions than traditional open addressing. Too many collisions on a single bucket forces a rehash. With a good-quality hash function, the average load factor of a hash table should be around 70%~75% before it must rehash. @item Performance is suboptimal without hardware support for @uref{https://en.wikipedia.org/wiki/Find_first_set, Find First Set}. @item Concurrent writes on the same hash table is MT-Unsafe. @end itemize @node Pseudo-Random Number Generator @section Pseudo-Random Number Generator Non-cryptographic pseudo-random number generator with 64-bit output and 256-bit state. Currently, the @uref{https://prng.di.unimi.it/, xoshiro256++} algorithm is used. Functions: @table @code @item prng_seed Updates the seed used by the PRNG. @example c #include int prng_seed ( uint64_t const s[4] ); @end example Function arguments: @table @code @item s The new seed (i.e., the internal state) for the PRNG. @quotation Note It is recommended to seed the PRNG with values that ``appear to be random'' (have a uniform distribution of bits). A seed with all bits zero (equivalent to never calling @code{prng_seed()}) results in @code{prng_rand()} producing a bad-quality number sequence. @end quotation If @code{NULL}, the seed will be obtained using @linuxmanpage{getrandom, 2}. @end table The function returns @t{0} on success, and @t{-1} on error. If @code{s} is not @code{NULL}, the function never fails. @item prng_rand Returns the next value in the pseudo-random number sequence. @example c uint64_t prng_rand (void); @end example With the same seed, the @code{prng_rand()} function always produces the same number sequence. @quotation Note This function is MT-Unsafe. @end quotation @end table @node Sandboxing @section Sandboxing While entering sandbox, the calling process/thread irrevocably relinquishes most access to the system resources that it's not supposed to touch. For example: @itemize @bullet{} @item access local files other than the bookmark storage @item establish socket connections @item execute other files @end itemize 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. ``Untrusted input'' includes but not limited to: @itemize @bullet{} @item Bookmark storage @emph{not} created by the user with a trusted program (e.g., obtained from some random person on the internet). @item Filesystem calls from untrusted programs. The program may be running in an isolated environment, but it has a chance to escape if BookmarkFS can be exploited. @end itemize The utility library provides a helper function for sandboxing: @example c #include int sandbox_enter ( int dirfd, uint32_t flags ); @end example Function arguments: @table @code @item dirfd File descriptor of a directory to grant access. @code{-1} if not needed. The process may access, modify and create filesystem objects under the directory using the @code{*at()} family of system calls. For other directory file descriptors, it is unspecified whether they can be used in any way. A security-aware program should close such file descriptors before entering sandbox. @item flags A bit array of the following flags: @table @code @item SANDBOX_READONLY Grants read-only access to the directory referred to by @code{dirfd}, instead of read/write access. This option is ignored when @code{dirfd} is @code{-1}. @item SANDBOX_NO_LANDLOCK Do not use Landlock for sandboxing. This option is ignored on non-Linux platforms. @item SANDBOX_NOOP The sandbox does nothing, and always returns successfully. @end table @end table The function should return @t{0} on success, and @t{-1} on error. Despite the return value, the function should not be called again for the calling process/thread. Upon failure, the sandboxing state of the calling process/thread is unspecified. @quotation Note The @code{sandbox_enter()} function is designed to work with BookmarkFS components, and may require some tweaking to be used for other programs. @end quotation Sandboxing is implemented using platform-specific features, and may behave differently across platforms: @table @asis @item Linux Implemented using @linuxmanpage{seccomp, 2} and @linuxmanpage{landlock, 7}. Sandbox applies to the calling thread. @item FreeBSD Implemented using @freebsdmanpage{capsicum, 4}. Sandbox applies to the calling process. @end table Refer to the source code in @file{src/sandbox.c} for implementation details. @node File Watcher @section File Watcher File watchers detect filesystem changes using platform-specific features: @table @asis @item Linux Implemented with @linuxmanpage{fanotify, 7}. Requires kernel version 5.13 or later for unprivileged users. @linuxmanpage{inotify, 7} does not have this limitation, however, it is incompatible with our sandboxing design. @item FreeBSD Implemented with the @code{EVFILT_VNODE} filter of @freebsdmanpage{kevent, 2}. @item Fallback When no platform-specific filesystem watching mechanism is available, periodically checks the @code{st_ino} and @code{st_mtim} attributes of the watched file with @posixfuncmanpage{fstatat}. This approach is less efficient than ``native'' implementations, but should work on any POSIX-compatible system. @end table Functions: @table @code @item watcher_create Creates a new watcher. @example c #include struct watcher * watcher_create ( int dirfd, char const *name, uint32_t flags ); @end example Function arguments: @table @code @item dirfd File descriptor of the directory holding the file to be watched. @item name Name of the file to be watched. @item flags A bit array of the following flags: @table @asis @item @code{WATCHER_FALLBACK} Do not use platform-specific features. @item @code{WATCHER_NOOP} The watcher does nothing, and @code{watcher_poll()} always return @code{-EAGAIN}. Values of @code{dirfd}, @code{name} and other flags are ignored. @item Sandbox Flags Flags for @code{sandbox_enter()}, shifted @code{WATCHER_SANDBOX_FLAGS_OFFSET} bits. The sandbox flags apply to the worker thread used internally by the watcher. @code{SANDBOX_READONLY} is always applied. @xref{Sandboxing}. @end table @end table On success, the function returns a pointer referring to the watcher. On error, it returns @code{NULL}. @item watcher_destroy Stop watching, and release all system resources associated with the watcher. @example c void watcher_destroy ( struct watcher *w ); @end example @item watcher_poll Checks whether the file being watched has changed. This function never blocks. @example c int watcher_poll ( struct watcher *w ); @end example Return value: @table @asis @item @t{0} The file has changed since the last call to @code{watcher_poll()}; if it hasn't been called, since watcher initialization. @item @code{-EAGAIN} The file hasn't changed since the last call to @code{watcher_poll()} that returns @t{0}. If no previous @code{watcher_poll()} calls return @t{0}, it indicates that the watcher is being initialized. @item @code{-ENOENT} The file being watched does not exist. @item @code{-EIO} An internal error has occurred. Further calls to @code{watcher_poll()} on this watcher will always fail. @end table @end table @node General Index @appendix General Index @printindex cp @node GNU Free Documentation License @appendix GNU Free Documentation License @include fdl.texi @bye