\input texinfo @c -*-texinfo-*- @setfilename bookmarkfs.info @include version.texi @settitle BookmarkFS User Manual @macro manpage {name, section, url} @uref{\url\,, @code{\name\(\section\)}} @end macro @macro linuxmanpage {name, section} @manpage{\name\, \section\, https://man7.org/linux/man-pages/man\section\/\name\.\section\.html} @end macro @macro freebsdmanpage {name, section} @manpage{\name\, \section\, https://man.freebsd.org/cgi/man.cgi?\name\(\section\)} @end macro @macro posixfuncmanpage {name} @manpage{\name\, 3p, https://pubs.opengroup.org/onlinepubs/9799919799/functions/\name\.html} @end macro @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 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 GNU General Public License, either version 3, or any later version of the license. You should have received a copy of the GNU General Public License along with BookmarkFS. If not, see @uref{https://www.gnu.org/licenses/}. BookmarkFS is @uref{https://savannah.nongnu.org/projects/bookmarkfs, hosted on Savannah}. Write to the @uref{https://savannah.nongnu.org/mail/?group=bookmarkfs, mailing lists} for bug reports, feature requests, and other discussions. @node Porting @section Porting BookmarkFS Currently, BookmarkFS only runs on GNU/Linux and FreeBSD. Although BookmarkFS sticks hard to POSIX and avoids using platform-specific features, porting it to other operating systems is not trivial. The major pitfall is the @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. While OpenBSD does provide a libfuse-compatible library, however, it only covers the high-level API, and BookmarkFS uses the @uref{https://libfuse.github.io/doxygen/fuse__lowlevel_8h.html, low-level API}. For a similar reason, @uref{https://github.com/winfsp/winfsp, WinFsp} won't work if you're trying to port BookmarkFS to Microsoft Windows. @node 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 Permute directory entries (@pxref{Permute Directory Entries}) @item Online filesystem check (@pxref{Online Filesystem Check}) @end itemize @node Sandboxing @section Sandboxing A BookmarkFS backend can be instructed to enter a sandboxed state, where it irrevocably relinquishes most access to the system resources that it's not supposed to touch. For example: @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. Examples of what ``untrusted input'' may include: @itemize @bullet{} @item Bookmark files that are @emph{not} created by the user using a trusted program (e.g. a file obtained from some random person on the internet). @item Filesystem calls from untrusted programs. The program itself may be isolated, but it has a chance to escape the isolated environment if it can exploit BookmarkFS. @end itemize On Linux, sandboxing is achieved using @linuxmanpage{seccomp, 2} and @linuxmanpage{landlock, 7}. On FreeBSD, @freebsdmanpage{capsicum, 4} is used. @node Programs @chapter Programs BookmarkFS ships with programs that provide users with a command-line interface 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 option is backend-defined. @item target Pathname of the directory where the filesystem shall be mounted to (i.e. the ``mountpoint''). @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. @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 @t{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{File Modification/Change Time} @cindex File Modification/Change Time @item -o ctime Maintain file change time, while modification time follows change time. If this option is not provided, maintain file modification time instead. 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 modification time ctime only updates when mtime does. @item 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 cache file attributes, making ctime appear more ``correct'' than what we claim. 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 @t{32768}. @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 process 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 ``access time'' attribute of a bookmark necessarily means ``the last time it was accessed from the browser''. As a bookmark management tool independent 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}). @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}. Options: @table @option @item -o backend=@var{name} The backend used by the filesystem (@pxref{Backends}). If this option is not provided, or @var{name} is empty, performs online fsck. @table @asis @item Online Mode In online mode, fsck is performed on a mounted BookmarkFS filesystem using @code{ioctl()} (@pxref{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 @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}). If this option is not provided, or @var{name} is empty, a built-in handler will be used. @xref{Built-in Filesystem-Check 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}. @item -o type=bookmark|tag|keyword Subsystem type (@pxref{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 (@pxref{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. @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 I/O controls of a BookmarkFS filesystem. @example bookmarkctl @var{subcmd} [@var{args}] @end example Sub-commands: @table @command @item permd Re-arranges the order of the directory entries obtained from @code{readdir()}. @xref{Permute Directory Entries}. @example bookmarkctl permd @var{pathname} @var{op} @var{name1} @var{name2} @end example @table @var @item pathname Path to the directory. @item name1 @item name2 Filename of entries under the directory. @item op Operation to perform on the directory: @table @samp @item swap Exchange the positions of the directory entries represented by @var{name1} and @var{name2}. @item move-before Move the directory entry represented by @var{name1} to the position just @emph{before} the one represented by @var{name2}. @item move-after Move the directory entry represented by @var{name1} to the position just @emph{after} the one represented by @var{name2}. @end table @end table @item fsck Check for errors within a BookmarkFS filesystem. @xref{Filesystem Check}. @example bookmarkctl fsck @var{pathname} @var{op} @end example @table @var @item pathname Path to the directory to perform checks on. @item op Operation to perform on the directory: @table @samp @item list Display a list of errors found under the given directory. Will not recurse into subdirectories. @end table @end table For the full fsck functionalities, @pxref{fsck.bookmarkfs}. @item help Print help text, and then exit. @item version Print version information, and then exit. @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 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{File 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 (@pxref{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 @code{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's 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 makes 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 daemon process. 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. @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 attributes have a @samp{bookmarkfs.} name 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 decides which attributes are available during initialization, and all bookmark files 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}). @item Ordering of directory entries POSIX does not specify the ordering of the directory entries retrieved from the directory stream using @posixfuncmanpage{readdir}. It only guarantees that if an entry is not added or removed from the directory after the most recent call to @posixfuncmanpage{opendir} or @posixfuncmanpage{rewinddir}, that entry is returned once and only once. This allows filesystem implementations to organize directory entries in a more relaxed manner. There could be extra overhead to maintain a predictable ordering of directory entries, since they may not have a linear structure on modern on-disk filesystems (e.g. ext4 uses @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, it is guaranteed to be equivalent to the directory traversal order. New entries are appended to the end; removed entries do not affect the order of other entries. To change the ordering 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 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 enum @code{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 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 enum @code{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}: @table @code @item EACCES Read permission is denied for the directory. @end table @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 enum @code{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 Where @var{$@{name@}} is the backend name, equivalent to value given to the @option{-o backend=@var{name}} option of frontend programs, and @var{$@{shlib_suffix@}} is the common filename extension for shared library files on the current platform (e.g. @file{.so} on GNU/Linux and FreeBSD). @node Firefox @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}. Firefox bookmarks are stored in a SQLite database under the profile directory: @example ~/.mozilla/firefox/@var{$@{profile_name@}}/places.sqlite @end example When mounting the filesystem, this pathname shall be passed as the @var{src} argument (@pxref{mount.bookmarkfs}). Actual path for the profile directories may differ across distributions. Backend name: @samp{firefox}. 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 @uref{https://www.sqlite.org/pragma.html#pragma_locking_mode, @code{locking_mode}} pragma on SQLite. With @option{lock=exclusive}, other process 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 @code{storage.sqlite.exclusiveLock.enabled} browser preference to @code{false}. @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 bookmark root 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 is valid UTF-8, 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. @end table @node Chromium @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}. Chromium bookmarks are stored in a text file (in JSON format) under the profile directory: @example ~/.config/chromium/@var{$@{profile_name@}}/Bookmarks @end example When mounting the filesystem, this pathname shall be passed as the @var{src} argument (@pxref{mount.bookmarkfs}). Actual path for the profile directories may differ across distributions. Backend name: @samp{chromium}. 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. The GUID string has a ``8-4-4-4-12'' format, while all alphabetic characters are 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 Watch for file changes using platform-specific features: @table @asis @item Linux @linuxmanpage{fanotify, 7} is used. 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 @freebsdmanpage{kevent, 2} with @code{EVFILT_VNODE} is used. @end table @item fallback Watch for file changes by checking @code{st_ino} and @code{st_mtim} attributes with @code{fstatat()} periodically. Less efficient than ``native'' implementations, but should work on any POSIX-compatible system. @item none Do not watch for file changes. With @option{watcher=none}, changes on the bookmark storage are not visible to the filesystem. @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 bookmark root 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 is valid UTF-8, 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 @quotation Note This section is work-in-progress. Come back later! @end quotation @node Filesystem-Check Handlers @chapter Filesystem-Check Handlers The filesystem-check handler is responsible for instructing the @command{fsck.bookmarkfs} program on what to do with each invalid bookmark name. 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 Where @var{$@{name@}} is the handler name, equivalent to the value given to the @option{-o handler=@var{name}} option of @command{fsck.bookmarkfs}, and @var{$@{shlib_suffix@}} is the common filename extension for shared library files on the current platform (e.g. @file{.so} on GNU/Linux and FreeBSD). @node Built-in Filesystem-Check Handler @section Built-in Handler The built-in 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 characters into @var{char}. Defaults to @samp{_}. @end table For each bookmark entry, the built-in handler prints a message to standard output explaining why the bookmark name is invalid. 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 @item 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 rename for the current entry. @item e[-] @var{new_name} Change the proposed rename 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 Filesystem-Check Handler @section Tcl-Based Handler The Tcl-based handler allows fsck entries to be handled via Tcl scripting. Handler name: @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::end @item $::bookmarkfs::fsck::result::nameDuplicate @item $::bookmarkfs::fsck::result::nameBadChar @item $::bookmarkfs::fsck::result::nameBadLen @item $::bookmarkfs::fsck::result::nameDotDot @item $::bookmarkfs::fsck::result::nameInvalid @xref{Filesystem-check Result Code} for the meaning of each value. Except for @code{$::bookmarkfs::fsck::result::end}, 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 structure @code{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 readonly 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 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 > $::bookmarkfs::fsck::result::end@} @{ 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 Filesystem-Check Handler API @section Handler API @quotation Note This section is work-in-progress. Come back later! @end quotation @node General Index @appendix General Index @printindex cp @node GNU Free Documentation License @appendix GNU Free Documentation License @include fdl.texi @bye