O
n the BeOS there are two user-level APIs to access files and directories. The BeOS supports the POSIX file I/O API, which provides the standard notions of path names and file descriptors. There are some extensions to this API to allow access to attributes, indices, and queries. We will only discuss the standard POSIX API briefly and spend more time on the extensions. The other API to access files on the BeOS is the C++ Storage Kit. The C++ API is a full-class hierarchy and is intended to make C++ programmers feel at home. We will spend most of this chapter discussing the C++ API. However, this chapter is not intended to be a programming manual. (For more specifics of the functions mentioned in this chapter, refer to theBe Developer’s Guide.)11.1
The POSIX API and C Extensions
All the standard POSIX file I/O calls, such asopen(),read(),write(),dup(), close(),fopen(),fprintf(), and so on, work as expected on the BeOS. The POSIX calls that operate directly on file descriptors (i.e.,open(),read(), etc.) are direct kernel calls. The model of file descriptors provided by the kernel directly supports the POSIX model for file descriptors. Although there were pressures from some BeOS developers to invent new mechanisms for file I/O, we decided not to reinvent the wheel. Even the BeOS C++ API uses file de- scriptors beneath its C++ veneer. The POSIX model for file I/O works well, and we saw no advantages to be gained by changing that model.
Attribute Functions
The C interface to attributes consists of eight functions. The first four func- tions provide a way to enumerate the attributes associated with a file. A file can have any number of attributes, and the list of attributes associated with a file is presented as an attribute directory. The API to access the list of attributes associated with a file is nearly identical to the POSIX directory functions (opendir(),readdir(), etc.):
DIR *fs_open_attr_dir(char *path); struct dirent *fs_read_attr_dir(DIR *dirp); int fs_rewind_attr_dir(DIR *dirp); int fs_close_attr_dir(DIR *dirp);
The similarity of this API to the POSIX directory API makes it immediately usable by any programmer familiar with the POSIX API. Our intent here and elsewhere was to reuse concepts that programmers were already familiar with. Each named entry returned byfs read attr dir() corresponds to an attribute of the file referred to by the path given tofs open attr dir().
The next four functions provide access to individual attributes. Again, we stuck with notions familiar to POSIX programmers. The first routine returns more detailed information about a particular attribute:
int fs_stat_attr(int fd, char *name, struct attr_info *info); The function fills in theattr info structure with the type and size of the named attribute.
Of note here is the style of API chosen: to identify an attribute of a file, a programmer must specify the file descriptor of the file that the attribute is associated with and the name of the attribute. This is the style for the rest of the attribute functions as well. As noted in Chapter 10, making attributes into full-fledged file descriptors would have made removing files considerably more complex. The decision not to treat attributes as file descriptors reflects itself here in the user-level API where an attribute is always identified by providing a file descriptor and a name.
The next function removes an attribute from a file: int fs_remove_attr(int fd, char *name);
After this call the attribute no longer exists. Further, if the attribute name is indexed, the file is removed from the associated index.
The next two functions provide the I/O interface to reading and writing attributes:
ssize_t fs_read_attr(int fd, char *name, uint32 type,
off_t pos, void *buffer, size_t count); ssize_t fs_write_attr(int fd, char *name, uint32 type,
1 1 . 1 T H E P O S I X A P I A N D C E X T E N S I O N S
187
The API follows closely what we’ve described in the lower levels. Each at- tribute has a name, a type, and data associated with the name. The file system can use the type code to determine if it is possible to index the attribute. The fs write attr()creates the named attribute if it does not exist. These two functions round out the interface to attributes from the POSIX-style API.
Index Functions
The interface to the indexing features is only provided by a simple C language interface. There is no corresponding C++ API to the indexing routines. This is not a reflection on our language preference but rather is a realization that little would have been gained by writing a C++ wrapper for these routines.
The indexing API provides routines to iterate over the list of indices on a volume, and to create and delete indices. The routines to iterate over the list of indices on a volume are
DIR *fs_open_index_dir(dev_t dev); struct dirent *fs_read_index_dir(DIR *dirp); int fs_rewind_index_dir(DIR *dirp); int fs_close_index_dir(DIR *dirp);
Again, the API is quite similar to the POSIX directory functions. The fs open index dir() accepts a dev t argument, which is how the vnode layer knows which volume to operate on. The entries returned from fs read index dir() provide the name of each index. To obtain more information about the index, the call is
int fs_stat_index(dev_t dev, char *name, struct index_info *info); Thefs stat index()call returns a stat-like structure about the named index. The type, size, modification time, creation time, and ownership of the index are all part of theindex infostructure.
Creating an index is done with
int fs_create_index(dev_t dev, char *name, int type, uint flags); This function creates the named index on the volume specified. Theflags argument is unused at this time but may specify additional options in the future. The index has the data type indicated by the type argument. The supported types are
integer (signed/unsigned, 32-/64-bit) float
double string
A file system could allow other types, but these are the data types that BFS supports (currently the only file system to support indexing on the BeOS is BFS).
The name of the index should correspond to the name of an attribute that will be added to files. After the file system creates the index, all files that have an attribute added whose name matches the name (and type) of this index will also have the attribute value added to the index.
Deleting an index is almost too easy:
int fs_remove_index(dev_t dev, char *name);
After callingfs remove index()the index is deleted and is no more. Deleting an index is a serious operation because once the index is deleted, the infor- mation contained in the index cannot be easily re-created. Deleting an index that is still needed can interfere with the correct operation of programs that need the index. There is little that can be done to protect against someone inadvertently deleting an index, so no interface aside from a command-line utility (that calls this function) is provided to delete indices.
Query Functions
A query is an expression about the attributes of files such asname = foo or MAIL:from != [email protected]. The result of a query is a list of files that match the expression. The obvious style of API for iterating over the list of files that match is the standard directory-style API:
DIR *fs_open_query(dev_t dev, char *query, uint32 flags); struct dirent *fs_read_query(DIR *dirp);
int fs_close_query(DIR *dirp);
Although the API seems embarrassingly simple, it interfaces to a very power- ful mechanism. Using a query, a program can use the file system as a database to locate information on criteria other than its fixed location in a hierarchy.
Thefs open query() argument takes a device argument indicating which volume to perform the query on, a string representing the query, and a (cur- rently unused)flagsargument. The file system uses the query string to find the list of files that match the expression. Each file that matches is returned by successive calls to fs read query(). Unfortunately the information re- turned is not enough to get the full path name of the file. The C API is lacking in this regard and needs a function to convert adirentstruct into a full path name. The conversion from adirentto a full path name is possible in the BeOS C++ API, although it is not on most versions of Unix.
The C API for queries also does not support live queries. This is unfortu- nate, but the mechanism to send updates to live queries is inherently C++ based. Although wrappers could be provided to encapsulate the C++ code,
1 1 . 1 T H E P O S I X A P I A N D C E X T E N S I O N S
189
there was not sufficient motivation to do so. The C interface to queries was written to support primitive test applications during the debugging phase (be- fore the C++ API was coded) and to allow access to extended BFS features from C programs. Further work to make the C interface to queries more useful will probably be done in the future.
Volume Functions
This final group of C language interfaces provides a way to find out the device-id of a file, iterate over the list of available device-ids, and obtain in- formation about the volume represented by a device-id. The three functions are
dev_t dev_for_path(char *path);
int fs_stat_dev(dev_t dev, fs_info *info); dev_t next_dev(int32 *pos);
The first function,dev for path(), returns the device-id of the volume that contains the file referred to bypath. There is nothing special about this call; it is just a convenience call that is a wrapper around the POSIX function stat().
Thefs stat dev()function returns information about the volume identi- fied by the device-id specified. The information returned is similar to a stat structure but contains fields such as the total number of blocks of the device, how many are used, the type of file system on the volume, and flags indicat- ing what features the file system supports (queries, indices, attributes, etc.). This is the function used to get the information printed by a command-line tool likedf.
The next dev() function allows a program to iterate over all device-ids. Thepos argument is a pointer to an integer, which should be initialized to zero before the first call to next dev(). When there are no more device-ids to return, next dev() returns an error code. Using this routine, it is easy to iterate over all the mounted volumes, get their device-ids, and then do something for or with that volume (e.g., perform a query, get the volume info of the volume, etc.).
POSIX API and C Summary
The C APIs provided by the BeOS cover all the standard POSIX file I/O, and the extensions have a very POSIX-ish feel to them. The desire to keep the API familiar drove the design of the extension APIs. The functions provided allow C programs to access most of the features provided by the BeOS with a minimum of fuss.
BEntryList BStatable BDataIO
BQuery BNode BEntry BPositionIO BPath
BDirectory
BFile BSymLink
Figure 11-1 The BeOS C++ Storage Kit class hierarchy.
11.2
The C++ API
The BeOS C++ API for manipulating files and performing I/O suffered a trau- matic birthing process. Many forces drove the design back and forth be- tween the extremes of POSIX-dom and Macintosh-like file handling. The API changed many times, the class hierarchy mutated just as many times, and with only two weeks to go before shipping, the API went through one more spasmodic change. This tumultuous process resulted from trying to appeal to too many different desires. In the end it seemed that no one was particularly pleased. Although the API is functional and not overly burden- some to use, each of the people involved in the design would have done it slightly differently, and some parts of the API still seem quirky at times. The difficulties that arose were never in the implementation but rather in the design: how to structure the classes and what features to provide in each.
This section will discuss the design issues of the class hierarchy and try to give a flavor for the difficulty of designing a C++ API for file access.
The Class Hierarchy
Figure 11-1 shows the C++ Storage Kit class hierarchy. All three of the base classes are pure virtual classes. That is, they only define the base level of features for all of their derived classes, but they do not implement any of the features. A program would never instantiate any of these classes directly; it would only instantiate one of the derived classes. TheBPathclass stands on its own and can be used in the construction of other objects in the main hierarchy. Our description of the class hierarchy focuses on the relationships of the classes and their overall structure instead of the programming details.