507 lines
19 KiB
Plaintext
507 lines
19 KiB
Plaintext
|
===============================================================================
|
||
|
LIBISOFS DEVELOPMENT TUTORIAL
|
||
|
===============================================================================
|
||
|
|
||
|
Creation date: 2008-Jan-27
|
||
|
Author: Vreixo Formoso
|
||
|
_______________________________________________________________________________
|
||
|
|
||
|
This is a little tutorial of how to use libisofs library for application
|
||
|
development.
|
||
|
|
||
|
Contents:
|
||
|
---------
|
||
|
|
||
|
1. Introduction
|
||
|
1.1 Library initialization
|
||
|
1.2 Image context
|
||
|
1.3 Error reporting
|
||
|
2. Creating an image
|
||
|
2.1 Image tree manipulation
|
||
|
2.2 Set the write options
|
||
|
2.3 Obtaining a burn_source
|
||
|
3. Image growing and modification
|
||
|
3.1 Growing vs Modification
|
||
|
3.2 Image import
|
||
|
3.3 Generating a new image
|
||
|
4. Bootable images
|
||
|
5. Advanced features
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
1. Introduction
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
[TODO some lines about refcounts]
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
1.1. Library initialization
|
||
|
|
||
|
Before any usage of the library, you have to call
|
||
|
|
||
|
iso_init()
|
||
|
|
||
|
in the same way, when you have finished using the library, you should call
|
||
|
|
||
|
iso_finish()
|
||
|
|
||
|
to free all resources reserved by the library.
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
1.2. Image context
|
||
|
|
||
|
Libisofs is image-oriented, the core of libisofs usage is the IsoImage object.
|
||
|
Thus, the first you need to do is to get your own IsoImage object:
|
||
|
|
||
|
IsoImage *my_image;
|
||
|
iso_image_new("NEW DISC", &my_image);
|
||
|
|
||
|
An IsoImage is a context for image creation. It holds the files that will be
|
||
|
added to image, other related information and several options to customize
|
||
|
the behavior of libisofs when working with such Image. i.e., an IsoImage is
|
||
|
a context for libisofs operations. As such, you can work with several image
|
||
|
contexts at a time.
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
1.3. Error reporting
|
||
|
|
||
|
In libisofs error reporting is done in two ways: with the return value of
|
||
|
the functions and with the message queue.
|
||
|
|
||
|
Error codes are negative numbers, defined in "libisofs.h" header. An
|
||
|
error code is associated with a given severity, either "DEBUG", "UPDATE",
|
||
|
"NOTE", "HINT", "WARNING", "SORRY", "FAILURE" and "FATAL". For the meaning
|
||
|
of each severity take a look at private header "libiso_msgs.h". Errors
|
||
|
reported by function return value are always "FAILURE" or "FATAL". Other kind
|
||
|
of errors are only reported with the message queue. You can get the severity
|
||
|
of any error message with iso_error_get_severity() function.
|
||
|
|
||
|
First of all, most libisofs functions return an integer. If such integer is
|
||
|
a negative number, it means the function has returned an error. The error code
|
||
|
and its severity is encoded in the return value (take a look at error codes in
|
||
|
libisofs.h header).
|
||
|
|
||
|
Additionally, libisofs reports most of its errors in a message queue. Error
|
||
|
messages on that queue can be printed directly to stderr or programmatically
|
||
|
retrieved. First of all, you should set the severity threshold over which an
|
||
|
error is printed or enqueued, with function:
|
||
|
|
||
|
iso_set_msgs_severities()
|
||
|
|
||
|
Errors enqueued can be retrieved with function:
|
||
|
|
||
|
iso_obtain_msgs()
|
||
|
|
||
|
Together with the code error, a text message and its severity, this function
|
||
|
also returns the image id. This is an identifier that uniquely identifies a
|
||
|
given image context. You can get the identifier of each IsoImage with the
|
||
|
|
||
|
iso_image_get_msg_id()
|
||
|
|
||
|
and that way distinguish what image has issued the message.
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
2. Creating an Image
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
An image is built from a set of files that you want to store together in an
|
||
|
ISO-9660 volume. We call the "iso tree" to the file hierarchy that will be
|
||
|
written to image. The image context, IsoImage, holds that tree, together with
|
||
|
configuration options and other properties of the image, that provide info
|
||
|
about the volume (such as the identifier, author, etc...).
|
||
|
|
||
|
All configuration options and volume properties are set by its corresponding
|
||
|
setters (iso_image_set_volset_id(), iso_image_set_publisher_id()...)
|
||
|
|
||
|
To create an image, you have to follow the following steps:
|
||
|
|
||
|
* Obtain the image context.
|
||
|
See "1.2 Image context" for details of how to obtain the IsoImage.
|
||
|
* Set the desired properties
|
||
|
* Prepare the iso tree with the files you want to add to image.
|
||
|
See "2.1 Image tree manipulation" for details
|
||
|
* Select the options for image generation.
|
||
|
See "2.2 Set the write options"
|
||
|
* Get the burn_source used to actually write the image.
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
2.1 Image tree manipulation
|
||
|
|
||
|
libisofs maintains in memory a file tree (usually called the iso tree), that
|
||
|
represents the files and directories that will be written later to image. You
|
||
|
are allowed to make whatever changes you want to that tree, just like you do
|
||
|
to any "real" filesystem, before actually write it to image.
|
||
|
|
||
|
Unlike other ISO-9660 mastering tools, you have full control over the file
|
||
|
hierarchy that will be written to image, via the libisofs API. You can add
|
||
|
new files, create any file in image, change its name, attributes, etc The iso
|
||
|
tree behaves just like any other POSIX filesystem.
|
||
|
|
||
|
The root of the iso tree is created automatically when the IsoImage is
|
||
|
allocated, and you can't replace it. To get a reference to it you can use the
|
||
|
function:
|
||
|
|
||
|
iso_image_get_root()
|
||
|
|
||
|
* Iso tree objects
|
||
|
|
||
|
Each file in the image or iso tree is represented by an IsoNode instance. In
|
||
|
the same way a POSIX filesystem has several file types (regular files,
|
||
|
directories, symlinks...), the IsoNode has several subtypes:
|
||
|
|
||
|
IsoNode
|
||
|
|
|
||
|
---------------------------------
|
||
|
| | | |
|
||
|
IsoDir IsoFile IsoSymlink IsoSpecial
|
||
|
|
||
|
where
|
||
|
|
||
|
- IsoDir represents a directory
|
||
|
- IsoFile represents a regular file
|
||
|
- IsoSymlink represents a symbolic linke
|
||
|
- IsoSpecial represents any other POSIX file, i.e. block and character
|
||
|
devices, FIFOs, sockets.
|
||
|
|
||
|
You can obtain the concrete type of an IsoNode with the iso_node_get_type()
|
||
|
function.
|
||
|
|
||
|
Many libisofs functions take or return an IsoNode. Many others, however,
|
||
|
require an specific type. You can safety cast any subtype to an IsoNode
|
||
|
object. In the same way, after ensuring you are dealing with the correct
|
||
|
subtype, you can downcast a given IsoNode to the specific subtype.
|
||
|
|
||
|
IsoDir *dir;
|
||
|
IsoNode *node;
|
||
|
|
||
|
node = (IsoNode*) dir;
|
||
|
|
||
|
if (iso_node_get_type(node) == LIBISO_DIR) {
|
||
|
dir = (IsoDir*) node;
|
||
|
...
|
||
|
}
|
||
|
|
||
|
or with the provided macros:
|
||
|
|
||
|
IsoDir *dir;
|
||
|
IsoNode *node;
|
||
|
|
||
|
node = ISO_NODE(dir);
|
||
|
|
||
|
if (ISO_NODE_IS_DIR(node)) {
|
||
|
dir = ISO_DIR(node);
|
||
|
...
|
||
|
}
|
||
|
|
||
|
* Adding files to the image
|
||
|
|
||
|
Files can be added to the image or iso tree either as new files or as files
|
||
|
from the filesystem.
|
||
|
|
||
|
In the first case, files are created directly on the image. They do not
|
||
|
correspond to any file in the filesystem. Provided functions are:
|
||
|
|
||
|
- iso_tree_add_new_dir()
|
||
|
- iso_tree_add_new_symlink()
|
||
|
- iso_tree_add_new_special()
|
||
|
|
||
|
On the other side, you can add local files to the image, either with the
|
||
|
|
||
|
iso_tree_add_node()
|
||
|
|
||
|
or with
|
||
|
|
||
|
iso_tree_add_dir_rec().
|
||
|
|
||
|
The first is intended to add a single file, while the last can be used to add,
|
||
|
recursively, a full directory (see below for details).
|
||
|
|
||
|
It is important to note that libisofs doesn't store any kind of link between
|
||
|
the IsoNode and the filesystem file it was created from. The above functions
|
||
|
just initialize a newly created IsoNode with the attributes of a given file in
|
||
|
the filesystem. After that, you can move the original file, change its
|
||
|
attributes or even delete it. The IsoNode in the image tree remains with the
|
||
|
original attributes. One exception to this rule are the contents of a regular
|
||
|
file. Libisofs does not make any copy of those contents until they're actually
|
||
|
written to image. Thus, you shouldn't modify, move or delete regular files
|
||
|
after adding them to the IsoImage.
|
||
|
|
||
|
|
||
|
* Recursive directory addition.
|
||
|
|
||
|
One common use case is to add a local directory to the image. While this can
|
||
|
be done with iso_tree_add_node(), handling the addition of directory children
|
||
|
in the application, libisofs provides a function suitable for this case:
|
||
|
|
||
|
iso_tree_add_dir_rec()
|
||
|
|
||
|
that takes care of adding all files inside a directory, recursing on directory
|
||
|
children. By default, this function adds all children. However, it is usual
|
||
|
that you don't want really this. For example, you may want to exclude some
|
||
|
kind of files (backup files, application sockets,...). Libisofs provides
|
||
|
several functions to customize the behavior of that function:
|
||
|
|
||
|
- iso_tree_set_follow_symlinks()
|
||
|
- iso_tree_set_ignore_hidden()
|
||
|
- iso_tree_set_ignore_special()
|
||
|
- iso_tree_add_exclude()
|
||
|
|
||
|
* Operations on iso tree
|
||
|
|
||
|
[TODO briefly explain how to add node, change attributes, ...]
|
||
|
|
||
|
* Replace mode
|
||
|
|
||
|
[TODO]
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
2.2 Set the write options
|
||
|
|
||
|
Once you have prepared the iso tree, it is time to select the options for the
|
||
|
image writing.
|
||
|
|
||
|
These options affect the characteristics of the filesystem to create in the
|
||
|
image, but also can control how libisofs generates the image.
|
||
|
|
||
|
First of all you have to get an instance of IsoWriteOpts, with the function
|
||
|
|
||
|
iso_write_opts_new()
|
||
|
|
||
|
The several options available can be classified in:
|
||
|
|
||
|
- Extensions to add to the ISO-9660 image:
|
||
|
|
||
|
iso_write_opts_set_rockridge()
|
||
|
iso_write_opts_set_joliet()
|
||
|
iso_write_opts_set_iso1999()
|
||
|
|
||
|
RockRidge is highly recommended, in fact you should use it in all image. Joliet
|
||
|
is needed if you want to use your images in Windows system. Nowadays,
|
||
|
ISO-9660:1999 is no much useful, so in most cases you don't want such
|
||
|
extension.
|
||
|
|
||
|
- ISO-9660 options:
|
||
|
|
||
|
iso_write_opts_set_iso_level()
|
||
|
iso_write_opts_set_omit_version_numbers()
|
||
|
iso_write_opts_set_allow_deep_paths()
|
||
|
iso_write_opts_set_allow_longer_paths()
|
||
|
iso_write_opts_set_max_37_char_filenames()
|
||
|
iso_write_opts_set_no_force_dots()
|
||
|
iso_write_opts_set_allow_lowercase()
|
||
|
iso_write_opts_set_allow_full_ascii()
|
||
|
|
||
|
These control the options for the ISO-9660 filesystem. In most cases you won't
|
||
|
care about them, as it is the RockRidge or Joliet extensions what determine the
|
||
|
properties of the files once the image is mounted.
|
||
|
|
||
|
- File attributes options
|
||
|
|
||
|
iso_write_opts_set_replace_mode()
|
||
|
iso_write_opts_set_default_dir_mode()
|
||
|
iso_write_opts_set_default_file_mode()
|
||
|
iso_write_opts_set_default_uid()
|
||
|
iso_write_opts_set_default_gid()
|
||
|
iso_write_opts_set_replace_timestamps()
|
||
|
iso_write_opts_set_default_timestamp()
|
||
|
iso_write_opts_set_always_gmt()
|
||
|
|
||
|
They allow to set default attributes for files in image, despite of the real
|
||
|
attributes of the file on the local filesystem.
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
2.3 Obtaining a burn_source
|
||
|
|
||
|
Finally, you get the burn_source used to write the image with the function:
|
||
|
|
||
|
iso_image_create_burn_source()
|
||
|
|
||
|
The returned burn_source is suitable for using with libburn, to directly burn
|
||
|
the image to a disc. Alternatively, you can use burn_source read() to get
|
||
|
the image contents (for example, to write them to a file, pipe...).
|
||
|
|
||
|
Before creating the burn_source, libisofs computes the size of the image, so
|
||
|
the get_size() function of the burn_source always returns the final image
|
||
|
size. It also starts a writing thread. All the operations needed to generate
|
||
|
the image are done by this thread, including read the original files contents.
|
||
|
The image is writing to a FIFO buffer, from which the burn_source will read.
|
||
|
The size of the buffer can be set in advanced with a property of the
|
||
|
IsoWriteOpts struct:
|
||
|
|
||
|
iso_write_opts_set_fifo_size()
|
||
|
|
||
|
You can get the state of the buffer in any moment, with the function:
|
||
|
|
||
|
iso_ring_buffer_get_status()
|
||
|
|
||
|
You can also cancel the writer thread at any time, with the cancel() function
|
||
|
of the burn_source.
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
3. Image growing and modification
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
3.1 Growing vs Modification
|
||
|
|
||
|
Libisofs is not restricted only to create new images. It can also be used to
|
||
|
modify existing images. It supports two kind of image modifications, that we
|
||
|
have called image growing and image modification:
|
||
|
|
||
|
Image modification consists in generating a new image, based on the contents
|
||
|
of an existing image. In this mode, libisofs takes an image, the users modifies
|
||
|
its contents (adding new files, removing files, changing their names...), and
|
||
|
finally libisofs generates a completely new image.
|
||
|
|
||
|
On the other side, image growing is similar, with the difference that the new
|
||
|
image is dependent on the other, i.e., it refers to files of the other image.
|
||
|
Thus, it can't be mounted without the old image. The purpose of this kind of
|
||
|
images is to increment or add files to a multisession disc. The new image only
|
||
|
contains the new files. Old files are just references to the old image blocks.
|
||
|
|
||
|
The advantage of the growing approach is that the generated image is smaller,
|
||
|
as only the new files are written. This mode is suitable when you just want to
|
||
|
add some files to a very big image, or when dealing with write-once media, such
|
||
|
as CD-R. Both the time and space needed for the modification is much less than
|
||
|
with normal image modify.
|
||
|
|
||
|
The main problem of growing is that the new image needs to be recorded together
|
||
|
with the old image, in order to be mountable. The total size of the image
|
||
|
(old + new) is bigger (even much bigger) than a completely new image. So, if
|
||
|
you plan to distribute an image on Internet, or burn it to a disc, generate a
|
||
|
completely new image is usually a better alternative.
|
||
|
|
||
|
To be able to mount a grown image, the OS needs to now you have appended new
|
||
|
data to the original image. In multisession media (such as CD-R), the new data
|
||
|
is appended as a new session, so the OS can identify this and mount the image
|
||
|
propertly. However, when dealing with non-multisession media (such as DVD+RW)
|
||
|
or plain .iso files, the new data is just appended at the end of the old image,
|
||
|
and the OS has no way to know that the appended data is in fact a "new
|
||
|
session". The method introduced by Andy Polyakov in growisofs can be used in
|
||
|
those cases. It consists in overwrite the volume descriptors of the old image
|
||
|
with a new ones that refer to the newly appended contents.
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
3.2 Image import
|
||
|
|
||
|
The first thing you need to do in order to modify or grow an image is to import
|
||
|
it, with the function:
|
||
|
|
||
|
iso_image_import()
|
||
|
|
||
|
It takes several arguments.
|
||
|
|
||
|
First, the image context, an IsoImage previously obtained with iso_image_new().
|
||
|
In most cases you will want to use an empty image. However, if you have already
|
||
|
added files to the image, they will be removed and replaced with the contents
|
||
|
of the image being imported.
|
||
|
|
||
|
The second parameter is an IsoDataSource instance. It abstracts the original
|
||
|
image, and it is used by libisofs to access its contents. You are free to
|
||
|
implement your own data source to access image contents. However, libisofs has
|
||
|
a simple implementation suitable for reading images on the local filesystem,
|
||
|
that can be used for import both .iso files and inserted media, via the block
|
||
|
device and POSIX functions. You can get it with
|
||
|
|
||
|
iso_data_source_new_from_file()
|
||
|
|
||
|
The third parameter of iso_image_import() is a pointer to an IsoReadOpts
|
||
|
struct. It holds the options for image reading. You get it with:
|
||
|
|
||
|
iso_read_opts_new()
|
||
|
|
||
|
and after calling iso_image_import() you should free it with
|
||
|
|
||
|
iso_read_opts_free()
|
||
|
|
||
|
Some options are related to select what extensions to read. Default options
|
||
|
are suitable for most users.
|
||
|
|
||
|
iso_read_opts_set_no_rockridge()
|
||
|
iso_read_opts_set_no_joliet()
|
||
|
iso_read_opts_set_no_iso1999()
|
||
|
iso_read_opts_set_preferjoliet()
|
||
|
|
||
|
If RockRidge extensions are not present, many files attributes can't be
|
||
|
obtained. In those cases libisofs uses default values. You have options to
|
||
|
configure what default values to use.
|
||
|
|
||
|
iso_read_opts_set_default_uid()
|
||
|
iso_read_opts_set_default_gid()
|
||
|
iso_read_opts_set_default_permissions()
|
||
|
|
||
|
If the original image has been created in another system with a different
|
||
|
charset, you may want to use:
|
||
|
|
||
|
iso_read_opts_set_input_charset()
|
||
|
|
||
|
to specify the encoding of the file names on image.
|
||
|
|
||
|
Finally, to import multisession images, you should tell libisofs that it must
|
||
|
read the last session. For that, you must set the block where the last session
|
||
|
starts:
|
||
|
|
||
|
iso_read_opts_set_start_block()
|
||
|
|
||
|
The last parameter for iso_image_import(), optional, is a pointer that will
|
||
|
be filled with a library-allocated IsoReadImageFeatures, that lets you access
|
||
|
some information about the image: size, extensions used,...
|
||
|
|
||
|
[TODO: explain that iso_image_import uses dir rec options]
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
3.3 Generating a new image
|
||
|
|
||
|
After importing the image, the old image tree gets loaded. You are free to
|
||
|
make any changes to it: add new files, remove files, change names or
|
||
|
attributes... Refer to "2.1 Image tree manipulation" for details.
|
||
|
|
||
|
When it is ready, you can now create the new image. The process is the same as
|
||
|
explained in "2.2 Set the write options" and "2.3 Obtaining a burn_source".
|
||
|
However, there are some write options that should be taken into account.
|
||
|
|
||
|
First of all, you must select whether you want to grow or modify the image
|
||
|
(read "3.1 Growing vs Modification" for details). You must call
|
||
|
|
||
|
iso_write_opts_set_appendable()
|
||
|
|
||
|
An appendable image leads to image growing, and a non-appendable image leads
|
||
|
to a completelly new image (modification). An appendable image will be appended
|
||
|
after the old image (in a new session, for example). Thus, in those cases, the
|
||
|
first block of the image is not 0. You should set the correct lba of the first
|
||
|
block with:
|
||
|
|
||
|
iso_write_opts_set_ms_block()
|
||
|
|
||
|
That is usually the "Next Writable Address" on a multisession media, and a
|
||
|
value slightly greater than the old image size on .iso files or DVD+RW media.
|
||
|
You can obtain the old image size with the iso_read_image_features_get_size()
|
||
|
function.
|
||
|
|
||
|
In this last case (i.e., on a non multisession media), you will need to
|
||
|
overwrite the volume descriptors of the old image with the new ones. To do
|
||
|
this you need:
|
||
|
|
||
|
- Allocate a buffer of at least 64 KiBs.
|
||
|
- Initialize it with the first 64 KiBs of the original image.
|
||
|
- Pass the buffer to libisofs with the iso_write_opts_set_overwrite_buf()
|
||
|
option.
|
||
|
- After appending the new image, you have to overwrite the first 64 KiBs of
|
||
|
the original image with the new content of the buffer.
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
4. Bootable images
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
|
||
|
-------------------------------------------------------------------------------
|
||
|
5. Advanced features
|
||
|
-------------------------------------------------------------------------------
|
||
|
|
||
|
|