Generally speaking, the package tries to somewhat imitate a typical Unix filesystem structure.
The source code for an executable program is located in bin/<program-name>/,
while the source code for a library is located in lib/<library-name>/,
without the typical lib prefix.
Shared header files for the libraries are in include/. So far, a header
sub-directory is only used for libsquashfs, since those headers are somewhat
more numerous and are installed on the system in the same sub-directory.
If a binary program comes with a man page, the man page is located at the same
location as the program source (i.e. bin/<program-name>/<program-name>.1).
Extra documentation (like this file) is located in the doc directory, and
source code for example programs which are not installed is in extras.
Unit tests for the libraries are in tests/<library-name>, with a lib prefix
and tests for programs are in tests/<program-name>.
To achieve loose coupling, core functionality is implemented by libraries in a reasonably generic way, and may in-turn make use of other libraries to implement their functionality.
To the extent possible, the actual application programs are merely frontends for the underlying libraries.
The following diagram tries to illustrate how the libraries are stacked:
_______________________________________
| |
| Application Programs |
|_______________________________________|
____________________________
| |
| libcommon |
__________|____________________________|
| | | |
| libtar | libfstree | |
|__________|_______ | libsqfs |
| | | |
| libfstream | | |
|__________________|______|_____________|
| | |
| libcompat | libutil |
|_________________________|_____________|
At the bottom, libutil contains common helper functions and container
data structures (dynamic array, hash table, rb-tree, et cetera) used by
both libsqfs and the application programs.
The libcompat library contains fallback implementations for OS library
functions that might not be available everywhere (e.g. POSIX stuff missing
on Windows or GNU stuff missing on BSD).
The libfstream library implements stream based I/O abstraction, i.e. it has
an abstract data structure for a non-seek-able read-only input streams and
write-only output streams. It has concrete implementations wrapping the
underlying OS functionality, as well as stream-compressor based implementations
that wrap an existing interface instance.
On top of libfstream, the libtar library is implemented. It supports
simple reading & decoding of tar headers from an input stream, as well as
generating and writing tar headers to an output stream and supports various
extensions (e.g. GNU, SCHILY). Thanks to the libfstream compressor wrappers,
it supports transparent compression/decompression of tar headers and data.
The libfstree library contains functionality related to managing a
and manipulating a hierarchical filesystem tree. It can directly parse the
description format for gensquashfs or scan a directory.
The libsqfs (actually libsquashfs) library implements the bulk of the
squashfs reading/writing functionality. It is built as a shared library and
is installed on the target system along with the application programs and a
bunch of public headers.
Finally, libcommon contains miscellaneous stuff shared between the
application programs, such as common CLI handling code, some higher level
utilities, higher level wrappers around libsqfs for some tool specific
tasks.
The application programs and the static libraries are GPL licensed,
while libsquashfs is licensed under the LGPL. Because the code
of libutil is compiled into libsquashfs, it also needs to be under
the LGPL and can only contain 3rd party code under a compatible license.
Furthermore, since the LZO compressor library is GPL licensed, libsquashfs
cannot use it directly and thus does not support LZO compression. Instead,
the libcommon library contains an implementation of the libsquashfs
compressor interface that uses the LZO library, so the application
programs do support LZO, but the library doesn't.
All symbols exported from libsquashfs must start with a sqfs_ prefix.
Likewise, all data structures and typedefs in the public header use this prefix
and macros use an SQFS_ prefix, in order to prevent namespace pollution.
The sqfs/predef.h header contains a macro called SQFS_API for marking
exported symbols. Whether the symbols are imported or exported, depends on
the presence of the SQFS_BUILDING_DLL macro.
To mark symbols as explicitly not exported (required on some platforms), the
macro SQFS_INTERNAL is used (e.g. on all libutil functions to keep
the internal).
An additional SQFS_INLINE macro is provided for inline functions declared
in headers.
The public headers of libsquashfs also must not include any headers of the
other libraries, as they are not installed on the target system.
However, somewhat contradictory to the diagtram, a number of the libraries
outlined above need declarations, typedefs and macros from sqfs/predef.h
and simply include thta header.
Anybody who has done C programming to a reasonable degree should be familiar
with the technique. An interface is basically a struct with function pointers
where the first argument is a pointer to the instance (this pointer).
Single inheritance basically means making the base struct the first member of the extended struct. A pointer to the extended object can be down cast to a pointer to the base struct and used as such.
To the extent possible, concrete implementations are made completely opaque and only have a factory function to instantiate them, for a more loose coupling.
The sqfs/predef.h defines and typedefs a sqfs_object_t structure, which
is at the bottom of the inheritance hierarchy.
It contains two function pointers destroy and copy. The former destroys and
frees the object itself, the later creates an exact copy of the object.
The copy callback may be NULL, if creating copies is not applicable for a
particular type of object.
For convenience, two inline helpers sqfs_destroy and sqfs_copy are provided
that cast a void pointer into an object pointer and call the respecive
callbacks. The later also checks if the callback is NULL.
While most code in libsquashfs works with objects that have a destroy hook,
some functions return pointers to data blobs or dumb structures that have been
allocated with malloc and expect the caller to free them again.
This turned out to be a design issue, since the shared library could in theory end up being linked against a different C runtime than the application using it. On Unix like systems this would require a rather freakish circumstances, but on Windows this actually happens fairly easily.
As a result, a sqfs_free function was added to libsquashfs to expose access
to the free function of the libraries run-time. All new code
using libsquashfs should use that function, but to maintain backward
compatibility with existing code, the library has to continue using regular
malloc at those places, so programs that currently work with a simple free
continue to work in the future.