|Main index||Section 3||Options|
The read and write APIs each have four layers: a public API layer, a format layer that understands the archive file format, a compression layer, and an I/O layer. The I/O layer is completely exposed to clients who can replace it entirely with their own functions.
In order to provide as much consistency as possible for clients, some public functions are virtualized. Eventually, it should be possible for clients to open an archive or disk writer, and then use a single set of code to select and write entries, regardless of the target.
The client read callback is expected to provide a block of data on each call. A zero-length return does indicate end of file, but otherwise blocks may be as small as one byte or as large as the entire file. In particular, blocks may be of different sizes.
The client skip callback returns the number of bytes actually skipped, which may be much smaller than the skip requested. The only requirement is that the skip not be larger. In particular, clients are allowed to return zero for any skip that they don't want to handle. The skip callback must never be invoked with a negative value.
Keep in mind that not all clients are reading from disk: clients reading from networks may provide different-sized blocks on every request and cannot skip at all; advanced clients may use mmap(2) to read the entire file into memory at once and return the entire file to libarchive as a single block; other clients may begin asynchronous I/O operations for the next block on each request.
A subsequent call to the consume() function advances the read pointer. Note that data returned from a read_ahead() call is guaranteed to remain in place until the next call to read_ahead(). Intervening calls to consume() should not cause the data to move.
Skip requests must always be handled exactly. Decompression handlers that cannot seek forward should not register a skip handler; the API layer fills in a generic skip handler that reads and discards data.
A decompression handler has a specific lifecycle:
|Registration/Configuration||When the client invokes the public support function, the decompression handler invokes the internal __archive_read_register_compression() function to provide bid and initialization functions. This function returns NULL on error or else a pointer to a struct decompressor_t. This structure contains a void * config slot that can be used for storing any customization information.|
The bid function is invoked with a pointer and size of a block of data.
The decompressor can access its config data
element of the
The bid function is otherwise stateless.
In particular, it must not perform any I/O operations.
The value returned by the bid function indicates its suitability for handling this data stream. A bid of zero will ensure that this decompressor is never invoked. Return zero if magic number checks fail. Otherwise, your initial implementation should return the number of bits actually checked. For example, if you verify two full bytes and three bits of another byte, bid 19. Note that the initial block may be very short; be careful to only inspect the data you are given. (The current decompressors require two bytes for correct bidding.)
|Initialize||The winning bidder will have its init function called. This function should initialize the remaining slots of the struct decompressor_t object pointed to by the decompressor element of the archive_read object. In particular, it should allocate any working data it needs in the data slot of that structure. The init function is called with the block of data that was used for tasting. At this point, the decompressor is responsible for all I/O requests to the client callbacks. The decompressor is free to read more data as and when necessary.|
|Satisfy I/O requests||The format handler will invoke the read_ahead, consume, and skip functions as needed.|
|Finish||The finish method is called only once when the archive is closed. It should release anything stored in the data and config slots of the decompressor object. It should not invoke the client close callback.|
|Registration||Allocate your private data and initialize your pointers.|
|Bid||Formats bid by invoking the read_ahead() decompression method but not calling the consume() method. This allows each bidder to look ahead in the input stream. Bidders should not look further ahead than necessary, as long look aheads put pressure on the decompression layer to buffer lots of data. Most formats only require a few hundred bytes of look ahead; look aheads of a few kilobytes are reasonable. (The ISO9660 reader sometimes looks ahead by 48k, which should be considered an upper limit.)|
|Read header||The header read is usually the most complex part of any format. There are a few strategies worth mentioning: For formats such as tar or cpio, reading and parsing the header is straightforward since headers alternate with data. For formats that store all header data at the beginning of the file, the first header read request may have to read all headers into memory and store that data, sorted by the location of the file data. Subsequent header read requests will skip forward to the beginning of the file data and return the corresponding header.|
|Read Data||The read data interface supports sparse files; this requires that each call return a block of data specifying the file offset and size. This may require you to carefully track the location so that you can return accurate file offsets for each read. Remember that the decompressor will return as much data as it has. Generally, you will want to request one byte, examine the return value to see how much data is available, and possibly trim that to the amount you can use. You should invoke consume for each block just before you return it.|
|Skip All Data||The skip data call should skip over all file data and trailing padding. This is called automatically by the API layer just before each header read. It is also called in response to the client calling the public data_skip() function.|
|Cleanup||On cleanup, the format should release all of its allocated memory.|
For example, libarchive's ISO9660 support operates very differently from most ISO9660 readers. The libarchive support utilizes a work-queue design that keeps a list of known entries sorted by their location in the input. Whenever libarchive's ISO9660 implementation is asked for the next header, checks this list to find the next item on the disk. Directories are parsed when they are encountered and new items are added to the list. This design relies heavily on the ISO9660 image being optimized so that directories always occur earlier on the disk than the files they describe.
Depending on the specific format, such approaches may not be possible. The ZIP format specification, for example, allows archivers to store key information only at the end of the file. In theory, it is possible to create ZIP archives that cannot be read without seeking. Fortunately, such archives are very rare, and libarchive can read most ZIP archives, though it cannot always extract as much information as a dedicated ZIP program.
|LIBARCHIVE_INTERNALS (3)||January 26, 2011|
|Main index||Section 3||Options|
|“|| Never write it in C if you can do it in `awk';
Never do it in `awk' if `sed' can handle it;
Never use `sed' when `tr' can do the job;
Never invoke `tr' when `cat' is sufficient;
Avoid using `cat' whenever possible.
|— Taylor's Laws of Programming|