-*-outline-*-
Overview Documentation for SS Objects

* Introduction

This overview is supplied to assist potential users in determining
whether or not the HP UDF Object Framework is suitable for their
particular application. It is trying to answer the larger question,
"What is the HP UDF Object Framework?" in enough detail for readers to
be confident that they understand the answer.

* HP UDF Object Framework Philosophy

** Description
HP is providing this UDF Object Framework to help bring about true
cross-platform file interchange via the OSTA Universal Disk Format.

The HP UDF Object Framework attempts to abstract out or "hide" as much
as possible of the ugliness of the details of UDF from the user, so
that he may easily build UDF-conforming applications or file systems.

The HP UDF Object Framework is NOT a file system. It is a framework of
objects which may be easily built into a file system. The reason for
this is that it tries to focus on format, rather than functionality,
whereas file systems join the two.

Users add functionality, but use these objects to read and write
metadata structures from and to the disk, and for this achieve UDF
conformance, providing file interchange to their customers.

HP has made a minimum of investment in functionality, focusing instead
upon format, which is the foundation of file interchange and UDF
Conformance. Basic on-disk structures have been abstracted out and
then placed in appropriate collection classes. For example, Directory
entries (DirEntry) are provided along with an associated list and
iterator classes (DirList and DirListIter).

The HP UDF Toolkit has been designed to be the basis for a UDF
implementation, but may also (of course) be used as a reference
implementation, or as the basis for any number of tools to assist
developers in writing their own implementations.

* Infrastructure Objects

Most of the objects in the HP UDF Object Framework are abstractions of
UDF structures. Some, however, are used to do the dirty work of
holding basic information, reporting errors, or providing access to
the storage device. This section describes these auxilliary objects.

** Trace

The Trace class provides a centralized and reasonably versatile
facility for controlling and facilitating basic instrumentation as to
what the code is doing. It was developed very late in the program, and
is for that reason not widely used (yet). Of course, it may be used by
the developer to instrument any new code as well.

It provides three major facilities.

*** Algorithm Tracing

It provides a static which may be used to enable or disable printf()
statements. Static member functions are provided which allow the user
to enable or disable the different types of tracing, as well as
specify the log file.

*** Call Tracing

Local instances may be created which provide tracing of entry and exit
from a scope, such as a method. The entry and exit printf()s are
located in the constructor and destructor, respectively. This is
especially useful in methods with more than one exit point.

*** Error Tracing

It also provides logging of the value of an arbitrary variable upon
exit from a scope. This is especially useful for tracing error values
at the exit from a method.

*** Examples

The file 'udfCore/ms/nsrArc.cpp' uses the Trace facility in almost all
of its capacities. all public methods use the Call and Error Tracing
capabilities, and various key points in complex methods use the
Algorithm Tracing capabilities. to find these, search for 'Trace'.

** Errors

*** Note About Exceptions

Since exceptions are (were) not yet supported on all platforms, the
UDF Object Framework does not use them. However, the code follows some
strict guidelines on how errors are reported and handled, so that
converting over to exceptions will be straightforward when it is done.
For details on how errors are reported and handled, please see the file
'udfDoc/ssDoc/Final/ErrHandl.txt'.

*** Souped-up Enum

All error information is reported using instances of the Error
class. It is normally treated just like an enum, but it also supports
a message() method, which returns a string describing the error in
text.

The Error class contains an enum ErrorCodeType, and uses a static
ErrorTable which associates each one with a string literal, which is
used by the message() method. It supports construction from and
assignment from an enum ErrorCodeType, as well as a cast to the enum
as well as to an int, to simplify checking for non-zero errors.

*** Examples

The following checks for an error in getting the ICBAddress from a
directory entry, and if one occurred, it prints out the error
information and returns.

Error err;  // Local error variable.
dirEnt.getICBAddress(logExt, err);
if (err) {
    printf("getICBAddress() returned error : %s\n",err.message());
    return;
}

** Strings

Strings are simply RogueWave RWCStrings with some additional methods
which we use internally. They are compatible with the standard
'char *' strings from C, except that they follow value semantics,
freeing the user from any direct concerns about memory allocation and
null pointer dereferencing.

The code does not currently use much of the robust functionality of
the RWCString, and it is expected that it could be easily replaced
with the C++ Standard library string class, when it becomes
available. If this is done, only the String class would need to
change.

** ByteArrays

The fundamental buffer construct in the Object Framework is the
ByteArray. It follows value semantics, supporting resizing and
automatic memory allocation and deallocation. It may be cast to a
normal 'unsigned char *'.

It is very simple, and could in the future be changed to be based upon
the RWCString, since that is more efficient, and can handle embedded
Nulls.

*** Examples

ByteArray buf(4096);

if (buf.length() < 10240) {
    buf.length(10240);
}
read((char *)buf,err);

** ByteArrayRefs

A ByteArrayRef is a simple class which references a portion of a
ByteArray or other buffer. It contains a pointer to a ByteArray, plus
an offset and a length. It does no allocation or freeing, but simply
points to a portion of a byte array. It is easily constructed from
either a ByteArray or a 'char *', and may be cast to an
'unsigned char *'. 

A ByteArrayRef is a required parameter for the write() and read()
methods of the Device object. It enables users to work with portions
of larger buffers (if desired), and prevents the direct passing around
of pointers to ByteArrays.

*** Examples

ByteArray buf(1024);

ByteArrayRef all_of_buf(buf);
ByteArrayRef first_512_of_buf(buf,0,512);
ByteArrayRef last_512_of_buf(buf,512,512);

ssdev.read(5,1,first_512_of_buf,err);
ssdev.write(5,1,first_512_of_buf,err);

** Devices

Drives have been abstracted into an object which supports a very
simple set of basic disk operations. Toolkit users are expected to
supply their own device object for each platform. They must be derived
from the SSDevice class in udfCore/devs/nsrASSdv.h. HP has provided an
example device class for HP-UX in udfCore/devs/nsrRSSdv.h. It is built
on top of the disk character device driver.

Unique optical media features such as WORM and a separate erase pass
are supported via the isWORM(), erase(), and writeWOE() methods.

*** Examples

DriveDevice mydev("/dev/rdsk/c2010s0");
if (err = mydev.error()) {
    printf("Device construction error: %s\n",err.message());
}
if (mydev.isWORM()) {
    printf(" -- WORM media loaded.\n");
} else {
    printf(" -- Rewritable media loaded.\n");
}
printf("Sector size is %d.\n", mydev.sectorSize());

// Now read the first 1K

ByteArray buf(1024);
mydev.read(buf,0,1024);  // Automatically constructs a BARef

* UDF Abstractions

** Basic Description

The goal of the HP UDF Object Framework was to provide abstractions of
the different on-disk structures required by UDF, making them easier
to implement, and hiding as much of their detail as possible.

Fundamentally, These objects are simply structures with fields to fill
in or read. However, wherever possible, fields which have mandatory
contents are automatically filled in, and in some cases, multiple
related structures have been joined together into a much simpler
single object which is much easier to use and keep consistent.

All all of the objects support either a toISO() and fromISO() pair of
methods which translate the in-core representation to and from its
on-disk UDF-compliant form in a data buffer, or a toDisk() and
fromDisk() pair of methods, which actually write or read the in-core
object to and from the device.

Each object provides methods to access and/or modify its different
fields, and many objects are supported in a list, so that collections
of them may be easily examined and/or modified.

This section describes each of these fundamental UDF objects, in an
order where there are the fewest forward references possible. That is,
each successive object should be able to be described in terms of the
preceding objects alone (hopefully).

** PhysExtent

The PhysExtent class is the container of contiguous physical space on
the disk. It consists of a starting sector and its length in
sectors. Thus, all PhysExtents are a multiple of a sector in length
(in contrast to the LogExtent class). It is associated with the UDF
'Extent Descriptor' or 'extent_ad', so it must never be longer than
2^30 bytes long.

Multiple PhysExtents may be held in a PhysExtentList, which allows
access to any individual PhysExtent, by simple index, as well as
through a PhysExtentListIter iterator. PhysExtentLists can hold
non-contiguous PhysExtents, and provide basic append() and remove()
functions, as well as methods which return the total number of
sectors, bytes, and extents in the list.

Each PhysExtent is associated with a specific Physical Volume, and
contains a pointer to the one upon which it resides. Partitions
consist of a single PhysExtent, describing its location on the
surface.

The Device methods are propagated through the PhysExtent, so that to
write to the beginning of a PhysExtent, the user simply calls the
PhysExtent's write() method.

*** Examples

// This erases sectors 10 through 14 on the PhysicalVolume
// pointed to by pvp

PhysExtent ext(pvp,10,5);
ext.erase(err);
if(err) {
    printf("PhysExtent::erase() error: %s\n",err.message());
}

** SingleSurfaceVolume

The SingleSurfaceVolume object is provided to hide all of the
different volume structures from the user. Essentially, it hides all
of part 3 of ISO/IEC 13346. Users simply construct one with an
SSDevice as the constructor argument, and then call fromDisk(). If no
error is returned, the SingleSurfaceVolume object has chased all of
the different structures down and is able to return the volume label
information it found, as well as the root ICB Address (rootICBAddr())
and information on the free space tables or bitmaps
(spacrResourcep()).

Similarly, to initialize a disk, the initDisk() method is invoked. Other
methods allow the user to specify the partition's location on the disk
and its size, as well as the locations and sizes of the other
structures. If none of these are called, a default layout of the disk
is used.

*** Internals

Several low-level abstractions of UDF structures are instantiated by
the SingleSurfaceVolume, but are hidden (most of the time) from the
user. Fundamentally, these objects are very simple representations of
their corresponding UDF structures.

Unlike the UDF structures, these objects contain or point to each
other corresponding to how they are logically related. For example,
each Partition contains a PhysExtent which points to the
PhysicalVolume upon which it resides, and also points to 
the LogicalVolume which uses it. The LogicalVolume points back to the
Partition, and also points to the FileSet it contains.

(WEB DIAGRAM GOES HERE)

Currently, all objects within the scope of the SingleSurfaceVolume are
instantiated either on initDisk() or fromDisk(), and free()ed when the
SingleSurfaceVolume is destroyed.

*** Examples

// This program prints the volume label information and the root ICB
// address.
main()
{
    Error tstError;
    DriveDevice    ddev(str);
    if ( tstError = ddev.error() ) {
        printf("\n Error instantiating DriveDevice: %s\n",tstError.message());
        return -1;
    }
    
    SingleSurfaceVolume vol(&ddev);

    vol.fromDisk(tstError);
    if ( tstError ) {
        printf("\n Error: %s\n",tstError.message());
        return -1;
    }
    
    printf("Volume ID is     '%s'\n",(CONST_CHAR_PTR)vol.physVolumeID());
    printf("Volume Set ID is '%s'\n",(CONST_CHAR_PTR)vol.volumeSetID());

    LogExtent rootICBExtent = vol.rootICBAddr(tstError);
    if ( tstError ) {
        printf("\n Error: %s\n",tstError.message());
        return -1;
    }
    printf(" ((Root is Block %lu )) ",(UINT32)rootICBExtent.startBlock());
    
    return 0;
}

** LogExtent

The PhysExtent and LogExtent are the sole objects which hold
information about disk space. Unlike the PhysExtent, the LogExtent
holds information about space inside of a partition, where block
addresses are used, rather than sector addresses.

LogExtents correspond to the UDF Short and Long Allocation
Descriptors, or the 'short_ad' and 'long_ad'.

LogExtents may also have a non block-multiple length, since the length
they hold is a byte count, rather than a number of sectors. Like
PhysExtents, they are limited to 2^30 bytes in length. The infoLen()
method provides the number of bytes the LogExtent contains, while
numBlocks() provides how many blocks are in it, and blockMultLen()
provides the same information in bytes. NOTE that for a given extent,
its blockMultLen() - infoLen() must be less than its blockSize() (this
is directly derived from ISO/IEC13346.

Each LogExtent contains a pointer to the Partition within which it
resides. This provides a path from each LogExtent to the device
holding it, and so allows the user to access the device directly
through the LogExtent, using read(), write() or other device-related
methods.

*** Extent Types

Each LogExtent has an associated type, which correspond to the ones
available in UDF and ISO/IEC 13346, but are more cleanly divided from
each other.

  AD Type (UDF - ISO/IEC 13346)  UDF Object Framework

  Unallocated and not Recorded   Imaginary
  Allocated and not Recorded     Unerased
                                 Erased
  Allocated and Recorded         Recorded
  
When erasure or writing are performed on LogExtents, they change their
type accordingly. For example, erase() sets the type to Erased, and
write() sets it to Recorded. Also, according to the standard, any
unrecorded LogExtent should act as though it were all zeros inside of
the read() method.

They hold the type of space they represent, and write
supports changing types, as well as the optional erase pass if it
is of type ERASED. Read, on the other hand, supports zeros from
all non-recorded extents.

*** LogExtentList and LogExtentListIter

Logically contiguous but physically non-contiguous disk space is
represented in a LogExtentList. All LogExtents contained in one are
individually accessible via the at() method, as well as via the
LogExtentListIter. Note that the data space of a file consists of a
single LogExtentList.

The infoLen(), blockMultLen() and almost all other LogExtent methods are
supported by the LogExtentList, and it is expected that it will be
much more commonly used than the bare LogExtent class. Note that, per
the ISO standard, only the last LogExtent in the LogExtentList may be
non block-multiple. The infoLen() may be changed, but within the
identical constraints as the LogExtent itself - only a partial block
may be reflected in the blockMultLen() which is not counted in the
infoLen().

*** PackedLogExtentList

A derived PackedLogExtentList automatically coalesces adjacent
LogExtents when they are joined to the list. For example, if single
adjacent blocks are consecutively appended to a LogExtentList, the
result will be a list with a single LogExtent in it.

*** Manipulators

Very simple methods are provided which help to chop up LogExtentLists
and put them back together again. They provide full versatility for
users needing to read or write an arbitrary piece of an arbitrarily
fragmented file, for example.

The first part of a LogExtentList may be extracted via the HeadOf() or
HeadXOf() methods. These set their LogExtentList non-destructively to
the first N bytes of the supplied LogExtentList. HeadOf() sets it to
the first part of the LogExtentList including the block into which the
supplied offset points. For example, new.HeadOf(old,5,err) sets new to
a LogExtentList containing the first block only of old. HeadXOf(), on
the other hand, excludes the block containing the supplied
offset. Thus, new.HeadXOf(old.5,err) sets new to an empty
LogExtentList (unless of course, the blockSize() is less than 5 bytes
long!

The other end of the list may be extracted using the TailOf() and
TailXOf() methods, which have similar naming conventions as the
HeadOf() and HeadXOf() methods. One siginificant difference is that
the all LogExtentLists must begin on a block boundary, so TailOf()
starts at the beginning of the block containing the offset, and
TailXOf() starts at the beginning of the block following the one
containing the offset.

More details about these is provided in the inline documentation, as
well as in the file udfDoc/ssDoc/Final/HeadTail.txt. Note that
obtaining the middle of a LogExtentList is as simple as taking the
head of the tail or vise versa.

LogExtentLists are grown using either the append() method, for adding
a single LogExtent, or the concat() method, which adds another
LogExtentList to the LogExtentList.

*** Examples

**** Reading a file in 5K chunks
**** Appending to a file

// *** This is NOT COMPLETED ***
// This appends the supplied buf (a ByteArray) to the end of
// file (a LogExtentList)

// First, grab last block of file in LEL last
LogExtentList last(file.logVolume());
last.TailOf(file,file.infoLen(),err); // block containing last byte

// Next, create a buffer a block long
ByteArray oneBlockBuf(file.blockSize());

// Then, create a ByteArrayRef pointing to only the valid bytes in the
// ByteArray.
ByteArrayRef lastPart(oneBlockBuf,0,
                      file.blockMultLen() - file.infoLen());

// Now, Read the last part of the file in.
last.read(lastPart,err); // places information in oneBlockBuf.

// Overlay the first part of the supplied buffer into the last part of
// the buffer.
ByteArrayRef firstPart(oneBlockBuf,
                       file.blockMultLen() - file.infoLen(),
                       file.infoLen());


**** Overwriting a piece in the middle of a file, which spans X extents.

**** Packing a LogExtentList

// The following coalesces lel, which starts out in any state,
// but ends up entirely coalesced.
{
    PackedLogExtentList plel(logVol) = lel;
    lel = plel;
}

**** Creating a sparse file.

// Writes dataExt, a LogExtent holding the non-zero data for the file
// to the location 1000K into the file, without chewing up any disk
// space for the first 1000K.

LogExtent imagExt(1000*1024);
LogExtentList file(logVol);
file.append(imagExt);
file.append(dataExt);

** SpaceResource

Whereas LogExtents represent physically contiguous disk space, and
LogExtentLists represent logically contiguous but possibly physically
discontiguous disk space, a SpaceResource represent a pool of disk
space which has no guaranteed contiguity in either realm.  In general,
SpaceResources hold free space.

In UDF, free space is associated with each partition. The free space
in a partition may be either erased or unerased (The ISO terms are
'Unallocated' and 'Freed', respectively.) This free space may be held
in either a bitmap form, where one bit is alotted to each sector, or
in Table form, where the free space is recorded in a list of
Allocation Descriptors, just like the body of a file. Note that ISO
allows both or either erased and unerased space for each partition,
but does not allow mixing of Bitmaps and Tables in the same
partition. Also note that UDF does not allow bitmaps on WORM media
because of its gross inefficiency.

Also in UDF, space which has been allocated to a file, but which has
not been recorded and is not inside its body is held in the tail of
its Allocation Descriptors. The UDF Object Framework encapsulates this
space inside a SpaceResource as well.

*** Common SpaceResource Methods

All SpaceResources are derived from the abstract base class
SpaceResource. The space available is returned by availBytes(), and
maxExtLen() returns the size of the largest contiguous space
available. Freeing space to it is done via putLogExtent() and
putLogExtentList(). Allocating contiguous space is done via
takeLogExtent(), while possibly discontiguous space is available via
the takeLogExtentList() method. In addition, all SpaceResources have
methods to indicate which type of space they contain, etc.

*** LELSpaceResource

The LELSpaceResource (for 'LogExtentListSpaceResource') holds both
erased and unerased LogExtents together, and is used for the tail of
each file's Allocation Descriptors. It may also be used in any other
situation where a temporary SpaceResource is desired, as it is not as
closely associated with a specific on-disk structure.

*** ISOSpaceResource

The ISOSpaceResource class is derived from the SpaceResource class,
but adds methods which tie it to the disk. The initWithNewSpace()
method accepts a LogExtentList, and is used for initializing a
disk. The initFromDisk() method reads all of its free space from what
is on the disk in the free space table structure. The syncToDisk()
method makes sure that the contents of the on-disk structure matches
the image in memory.

*** TableSpaceResource

The TableSpaceResource class is derived from the ISOSpaceResource
class, and supports the on-disk free space table structure. It works
by holding all of the free space in memory in an LELSpaceResource, and
writing it all back out to disk when the syncToDisk() method is
invoked. 

*** BitmapSpaceResource

The BitmapSpaceResource class is also derived from the
ISOSpaceResource class, and supports the ISO free space bitmap
structure. It never holds all of the free space information in memory,
but rather swaps pages of the bitmap in and out of memory in order to
support the allocation and freeing of space.

*** MasterSpaceResource

The MasterSpaceResource class is the most powerful and easy-to-use
ISOSpaceResource. It diverts the space freed to it to the appropriate
erased or unerased SpaceResource, and keeps track of whether or not
Bitmaps or Tables are used. Users may wish to modify its allocation
and/or deallocation algorithms, since they are provided from HP with
very simple policies.

When reading an existing UDF disk, it is constructed with only a
pointer to a Partition object, gotten from the SingleSurfaceVolume. It
determines from the Partition the types of SpaceResources to
instantiate, and then constructs them, setting itself up to
appropriately divert all SpaceResource calls. It also supports
initializing free space structures in a simplified fashion.

*** Examples

**** Defragmenting a file
**** convert Table to bitmap
**** check two SpaceResources for double-allocation and orphan space.

** Integrity

The integrity of a UDF disk is fundamentally whether its on-disk
structures are in a consistent state or not - whether they are "clean"
or "dirty". This is used at mount time to tell whether or not the
volume should be allowed to be mounted. Besides holding the integrity
of the volume, UDF holds some free space and total space information
in this structure, as well as some other pieces of information needed
at mount time, and available at unmount time.

The Integrity object is an abstraction of the Logical Volume Integrity
Descriptor UDF structure.

The SingleSurfaceVolume object instantiates one of these on a
fromDisk(), but the user is free to write it back and re-read it
independently of the SingleSurfaceVolume. It has its own toDisk() and
fromDisk() methods for this.

** DirEntry and DirList

In UDF, directories are stored as files containing the names and node
addresses of files and subdirectories. A single name and address pair
is called a File Identifier Descriptor. File Identifier Descriptors
are variable in length (because the names are variable) and are packed
together.

Since File Identifier Descriptors are packed and variable in length,
UDF disallows sorting them (- it doesn't do any good, since you cannot
to binary searches...). Also, if a file or subdirectory is given a new
name which is not exactly the same length as before, it cannot be
updated in-place. Instead, the old name's entry must be marked as
deleted, and the new name must be appended at the end. Alternatively,
the entire directory from the point of modification to the end must be
repacked.

*** DirEntry

The HP UDF Object Framework abstractions of File Identifier
Descriptors is the class DirEntry. Each DirEntry has an associated
type -- it is either a parent directory, a subdirectory, or a
file. DirEntries may be tagged as hidden (to support the common DOS H
attribute) and/or deleted. Methods are provided for all of these, as
well as the two fundamental ones, name() and set/getICBAddress(). To
help ensure proper packing, an ISOLen() method is provided as well,
which returns the number of bytes the entire associated File
Identifier Descriptor will or would occupy. Finally, toISO() and
fromISO() methods are supported as well, which translate between the
in-memory and on-disk forms.

*** DirList

The DirList is a linked list of DirEntry objects. It is associated
with a directory itself, and provides its own toISO() and fromISO()
methods. Since directories may be arbitrarily large, a DirList may
hold a smaller portion of a directory at a time if desired. This
can help performance and is nevertheless a requirement if virtual
memory is not available.

Since directories are held inside of files, the reading and/or writing
of directories is expected to be done by the user in the same way as
reading and/or writing of files. Thus, DirList does not support a
toDisk() or fromDisk() method, but rather toISO() and fromISO(). These
methods simply call the appropriate toISO() or fromISO() on each
member of the list. These write or read the File Identifier
Descriptors into or from the supplied buffer, which must be separately
written to or read from the surface of the disk.

These methods support starting offsets and return a residue, since
File Identifier Descriptors are not typically aligned with block
boundaries, but buffers, and certainly read and write operations,
are. Using these features, pages of a directory are read into a
buffer, translated to DirLists, and then inspected or searched. Then,
if desired, they are modified, translated back to File Identifier
Descriptors in the buffer, and written back out.

Note that since the File Identifier Descriptors are not aligned with
block boundaries, the pages must overlap by a block, and the offsets
and residues used to begin a new DirList exactly where the previous
DirList left off. The LogExtentList manipulation methods work very
well for this approach.

*** DirListIter

An iterator is provided to make traversing the DirList simpler.

*** Examples

**** Printing out the root directory (one window)

See udfAncil/ssTest/rootPrnt.cpp.

**** Printing out any root directory (10K chunks)
**** Printing out deleted entries in a given directory.
**** Creating a new Directory entry. (hidden)

** PathElement and PathElementList

UDF supports the notion of symbolic links. These are files which
contain a path to another file or directory. The operating system is
expected to follow the new path in order to find the file or
directory. UDF (ISO, really) has set aside a file type for symbolic
link, which means that the data space of the file contains the path to
follow. ISO defined the format of the path to be platform-independent,
so that it contains neither forward nor backward slashes, for
example.

The UDF structure for the path is called a Path Component. Path
Components are packed together in a way similar to directories. Each
Path Component contains a type, and an optional string. The most
common types are parent, root, and a normal Path Component, with an
associated string.

The UDF Object Framework abstraction of the Path Component is the Path
Element. It holds the identical information to the Path Component, but
may be easily contained inside of a list. the PathElementList contains
a singly-linked list of PathElement types, and does not support
translation to and from ISO. Instead, it is expected that the user
will manually fill in a PathComponent type with the contents of a
PathElement.

*** NOTE

Additional functionality was not needed at the time this work was
discontinued, so this was never cleaned up and made easy to use.

*** Examples

**** print out a symbolic link
**** Follow a symbolic link
**** create a symbolic link (incl setting attr to symlink)

** Attributes

In UDF, information about a file is held in three different
areas. First, the directory holds its name and whether it is
"hidden". Secondly, the common attributes are held in the File Entry
(sometimes referred to as the "ICB"). Finally, attributes unique to a
given platform or implementation are held in what are called "Extended
Attributes".

In the HP UDF Object Framework, the common attributes are encapsulated
in the Attributes class. The Attributes class is essentially a bridge
between UDF and system-specific representations of common
attributes. It is essentially a compact struct but it setter and
getter methods for UDF as well as Unix attribute representations. It
may be easily extended to support any system's representation(s) of
the attribute information.

Users MAY provide an entirely different "bridge" structure for this
purpose. The Object Framework allows this, so long as the UDF methods
are supported, toFileEntry() and fromFileEntry().

*** C Compatible

The Attributes class is compatible with C compilers also. No virtual
functions are used, so that this may be treated just like a c struct
if desired. The C face of Attributes is a struct with public data
members, with constants defined for masks and bit positions, instead
of the much more convenient C++ methods.

*** File Types and Link Count

The Attributes class contains the type of the file as an enum, along
with the link count of the file. In order to conform to UDF or ISO,
the link count must be set to the number of File Identifier
Descriptors which point to the it. This works out to be as follows,
for all files and directories, including root:

    for files:       1 + numHardLinks
    for directories: 1 + numSubDirs

*** Information Length Not Included

The Attributes class does NOT contain the length of the file. This IS
a true UDF attribute, but in the Object Framework, that information is
held in the LogExtentList for the file. That way, if any extent
manipulation occurs, the information length of the file (and number of
blocks recorded, which is also in UDF) are automatically updated.

*** Examples

**** chown, chgrp, chmod
**** set SYSTEM bit
**** print out file times and attributes (using print() methods?)

** Node

*** UDF Structures

In UDF, most of the information about a file is stored in a structure
called an 'Information Control Block', or ICB. The ICB consists of one
or more entries, and in the simplest case (i.e., not WORM), a single
entry, which describes the file. This structure is called a 'File
Entry'.

NOTE about ICB Addresses:

    Because on WORM a single ICB may contain multiple entries, an ICB
    Address is defined as and extent. That is, whenever a structure
    has an 'ICBAddress' field, it is a 'short_ad' or 'long_ad', which
    has a block address PLUS a length. Thus, Node Addresses are all a
    single LogExtent, rather than a simple block number.

The File Entry contains all the attributes, plus information about
where the file's extents are located, in the form of Allocation
Descriptors. If the file has more extents than can be described in the
File Entry, these are continued in a separate UDF structure called an
'Allocation Extent Descriptor'. If more than one is required, they are
simply chained together, each one being referenced by the one
preceding it.

The HP UDF Object Framework encapsulates both of these in the Node
class. The node class forms the hub of all information about a file,
except that stored in its DirEntry. Fundamentally, it provides access
to the file's Attributes, and to its LogExtentList. It supports the
standard toDisk() and fromDisk() methods which translate between its
in-memory and on-disk images. 

*** File Data and Preallocated Space

UDF File Entries support the concept of embedded data. This means that
instead of holding Allocation Descriptors which describe where the
extents of the file reside, the actual file data is recorded inside of
the File Entry. The node supports this via the embeddedData() method,
which provides access to a ByteArray held inside the Node. Its size
and contents may be easily inspected and/or modified.

The data space of larger files is available via the LogExtentList
reference returned from the dataEL() method. This LogExtentList may be
freely modified by the user, directly affecting the data space of the
file. Note that Unrecorded LogExtents inside this LogExtentList
constitute preallocated space for the file, and will return all zeros
if read. These extents are represented in the body of the Allocation
Descriptors for the File Entry (and potentially Allocation Extent
Descriptors).

Other preallocated space associated with the file is available via the
reserveLELSR() method which returns a reference to an
LELSpaceResource. The extents contained in it are represented in
the tail of the Allocation Descriptors for the File Entry (and
potentially Allocation Extent Descriptors).

*** Attributes and Extended Attributes

The Node contains an instance of the Attributes class, which may be
accessed via the attributes() method. It may be freely inspected
and/or modified by the user.

Extended Attributes are stored in EA Spaces. Each Node is associated
with up to two EA Spaces. The first is an EA Space inside of the File
Entry, available via the embeddedEASpace() method. It works similarly
to the embedded data, and consists of a ByteArray stored inside of the
Node class. The other EA Space is essentially its own file, pointed to
by the File Entry. That EA Space has its own Node, whose address is
available via the EANodeAddr() method.

Thus, files with larger Extended Attributes have two Node instances
associated with them. The primary Node is pointed to by the DirEntry
for the file, and the other (for the EAs) is pointed to by the primary
node. This EA Node is just like any other node, except that its
fileType is file_ExtendedAttributes. Thus, the normal fromDisk()
and toDisk() methods are used to inspect or modify it.

Note that the EA Node supports all of the normal methods, but that
ONLY its dataEL() and reserveLELSR() methods matter. No permissions or
other attributes are valid for it. Thus, the toDisk() method handles
it as a special case, filling in the appropriate values for these
fields.

*** Descriptor Space Budgeting

The variable-length components of the Node are the embeddedData(),
embeddedEASpace(), dataEL(), and reserveLELSR(). Note that the first
two MUST fit inside of the File Entry, while the other two may be
overflowed into Allocation Extent Descriptors. Thus, it is possible
for the user to specify more space for either or both of the embedded
structures than can be recorded on disk in a compliant fashion.

In order to enable the user to control exactly what gets embedded, and
make sure that his Node is represented in a compliant fashion, the
Node works the following way.

1. All ADs (except for one) are automatically shifted from the File
   Entry into Allocation Extent Descriptors to make room for either
   embedded space. Thus, the embedded information has precedence over
   the non-embedded.

2. A descriptorBytesAvailable() method is provided which returns a
   signed number of bytes not used in the File Entry. Whenever this
   returns a negative value, the two types of embedded space are
   taking up more room than is allowed. This may be used to perform
   appropriate descriptor space budgeting, and the user may decide
   that the embedded data or the embedded EA Space needs to be booted
   out.

3. The toDisk() method sets its error argument if it cannot fit all
   the requested information onto disk in a conforming fashion. This
   ensures that users with bugs in their budgeting will not write
   non-conforming disks without at least getting an error.

*** Space Taken Up By Descriptors

The location for recording Nodes is deferred to the latest possible
time. It is provided as an argument to the toDisk() method. Thus, any
given Node has no specific location held inside of it. The user must
keep track of where it has been written in order to read it back. This
is clean because each Node must be "pointed to" or referenced by
another UDF structure anyway, and that structure's abstraction must
hold that address. For example, the DirEntry contains a LogExtent
which is the Node's address.

The LogExtent supplied to the Node at toDisk() and fromDisk() is
location where its File Entry is recorded. Since the Node also handles
Allocation Extent Descriptors, more space may be required for it to be
recorded, and that space may vary with each Node. In order to properly
address this, the fromDisk() method holds the space taken up by the
Allocation Extent Descriptors inside of the Node. Its toDisk() method,
then, requires a Space Resource as an argument, so that it can
allocate more space if needed, or potentially free the space it did
not need. The space taken up by the Allocation Extent Descriptors is
available to the user via the diskSpace() method.

Thus, in order to write a new file, the user must allocate space for
the data space of the file, and optionally preallocate space to be
held in the Node's reserveLELSR(), allocate space for the Node, and
then supply a SpaceResource to its toDisk() method. In order to free
all of its space, it must conversely free all of the preallocated
space and all of the data space, then free the space returned by the
diskSpace() method, and finally free the space associated with the
Node Address. Thus, the data space, preallocated space, and
dataSpace() are all within the scope of the Node, while the user must
keep track of the Node Address.

*** Examples

**** Reading a file, given its ICBAddress.
**** Truncating a file, including updating its modification time.
**** Preallocating space to a file (erased/unerased/mixed)

** Extended Attributes

Extended Attributes are the ISO-defined and UDF-defined structures
associated with a file or directory which do not fit inside the common
attribute collection held in a File Entry. This includes attributes
such as alternative times, specifications for device files, and other
OS-specific file attributes and extended attributes.

*** UDF Structures

Extended Attribute Spaces are found both inside of the File Entry (see
Node under embeddedEASpace()) and inside of the data space of an
associated file (see Node under EAICBAddr()).

Each EA Space consists of a header, followed by a packed stream of
individual Extended Attributes. Each Extended Attribute either has a
constant length for its type or contains its length, and has
predefined fields to hold its logical information.

ISO divides the Extended Attributes into three categories - Those
it defines in ISO/IEC 13346, Implementation Use EAs, and Application
Use EAs, and in any given EA Space, the EAs must be in that order. The
header structure contains offsets to the different types of EAs.

UDF defines several new EAs to hold information from the Operating
Systems it supports. These are specially-marked Implementation Use
EAs, and must therefore be placed in the middle segment of the EA
Space. UDF places additional restrictions on how these must be ordered
as well. The UDF-defined EAs must precede any other Implementation Use
EAs. Also, all Implementation Use EAs which are block in length or
longer must be placed after those shorter than a block. These longer
EAs must also be block-aligned. That is, they must begin and end on
block boundaries. This is so that each long EA may be extended or
shortened without requiring relocation or re-packing of the EAs which
follow it.

When all of these constraints are taken into account, it is clear that
Extended Attributes represent a significant amount of complexity in
the format. The UDF Object Framework provides classes to do the
packing and unpacking of EAs, as well as classes which interpret the
structure of each UDF-defined EA.

*** Extended Attribute Classes

The Different ISO and UDF Extended Attributes work in the same way as
the rest of the UDF descriptors. That is, they are fundamentally
structs, but support both a toISO() and a fromISO() method, which
translate between the in-memory and on-disk representations. Also, the
data members are typically protected, with public setter/getter
methods.

They share a common interface for setting their lengths and how much
padding that follows them. However, that interface is used only by the
packing and unpacking objects discussed below.

(CARY: DOES IT MAKE SENSE TO SAY ANY MORE HERE?)

*** EAInspector

The EAInspector class provides a way for the EA packing and unpacking
code to find out all that it needs about a given EA.

(CARY: FILL THIS IN HERE. YOU WILL PROBABLY WANT DIFFERENT HEADINGS,
 WHICH IS FINE...)

*** EASpace
*** EAViewItem and EAView


*** Examples

**** Locate the MacFinderInfo in a node (if there)
**** Set the CreationTime (?) for a file, placing it in the
     embeddedEASpace.
**** Append 1000 bytes to a MacResourceFork 31KB long, leaving ALL
     other EAs alone.

* (REFERENCE)Checklist for SSLIB documentation

** Dependencies documentation (should exist somewhere)
*** RWTools.h++ 6.1 (or higher?)
*** No templates or exceptions used
** Revisions to and hooks into Previous HTML documents on MSLIB.

* (REFERENCE) Inline Documentation

** Classes .h
** Methods .cpp and .h(inline)
** HTML from these ...

** Conformance Considerations


