home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frostbyte's 1980s DOS Shareware Collection
/
floppyshareware.zip
/
floppyshareware
/
DOOG
/
CBASE09.ZIP
/
GUIDE.TXT
< prev
next >
Wrap
Text File
|
1989-09-01
|
40KB
|
1,041 lines
CBASE
The C Database Library
Citadel
Brookville, IndianaCopyright (c) 1989 by Citadel. All rights reserved.
Citadel
241 East Eleventh Street
Brookville, IN 47012
317-647-4720
Bulletin Board 317-647-2403
This manual is protected by United States copyright law. No part of it
may be reproduced without the express written permission of Citadel.
Technical Support
Technical support is available between 10 a.m. and 4 p.m. EST. When
calling for technical support, please have ready the following
information:
. product name, version, and serial number
. operating system and version number
. C compiler and version number
. computer brand and model
The Citadel BBS is available 24 hours a day. Users of Citadel
products are encouraged to make use of this resource.
UNIX is a trademark of AT&T. MS-DOS is a trademark of Microsoft
Corporation.
Contents
Chapter 1. Introduction 1
Chapter 2. Basic Database Concepts 3
2.1 The Linked Sequential File
2.2 Inverted Files
2.3 The B-tree
Chapter 3. cbase Library Functions 7
3.1 File Access Functions
3.2 Record Cursor Positioning Functions
3.3 Key Cursor Positioning Functions
3.4 Input/Output Functions
3.5 Lock Functions
Chapter 4. An Example Program 15
4.1 Data Definition
4.2 Header Files
4.3 Opening
4.4 General Programming Guidelines
4.5 Closing
Appendix A. Installation Instructions 21
A1 The blkio Library
A2 The btree Library
A3 The cbase library
A4 The lseq Library
Appendix B. Adding New Data Types 25
Appendix C. Porting to a New Operating System27
Bibliography 29
Chapter 1: Introduction
cbase is a C database file management library. Records may be
accessed both randomly and sequentially through indexes stored in B+-
trees. Records may also be accessed sequentially in the order in which
they are stored. Multiuser access is supported under any operating
system with file locking capabilities. Strict adherence to the ANSI C
standard has been made, making cbase extremely portable. Certain
operations such as file locking, however, are very operating system
dependent. All operating system specific code has been isolated,
though, and porting to a system not currently supported should require
only a very minimal effort.
Many of the operations performed by cbase internally represent
independently useful tools, and the software has been designed with this
in mind. cbase actually comprises four individual libraries, each
complete and independently accessible. Figure 1.1 shows these libraries
and their relationships.
┌─────────────────────────────────┐
│ cbase │
└───────┬─────────────────┬───────┘
┌───────┴───────┐ ┌───────┴───────┐
│ lseq │ │ btree │
└───────┬───────┘ └───────┬───────┘
┌───────┴─────────────────┴───────┐
│ blkio │
└─────────────────────────────────┘
Figure 1.1: cbase and Underlying Libraries
At the foundation of cbase is the blkio (block buffered
input/output) library. blkio is a buffered input/output library very
similar to stdio but based on a file model more appropriate for
structured files such as used in database software. While stdio models
a file as an unstructured stream of characters, blkio models a file as a
collection of blocks made up of fields. All file access and buffering is
done using blkio. All operating system specific code is isolated within
a small portion of this library.
The lseq (linked sequential file) library provides all the facilities
necessary for the creation and manipulation of doubly linked sequential
files. The btree (B-tree) library provides the same for B+-tree files.
The cbase library uses lseq and btree to perform all structured file
management operations. The lseq library is used for record storage and
the btree library for inverted file key storage.
When using a particular library, all operations are performed with
functions provided by that library. No references need be made to
underlying libraries. (There is one exception; see bexit in the blkio
reference manual.) This is not to say that the libraries may not be used
simultaneously.
Chapter 2: Basic Database Concepts
This chapter summarizes the fundamental database concepts used
by cbase. It is intended to be a brief and conveniently accessible
introduction. The information presented here is not necessary for using
cbase and can be easily skipped by one already familiar with topics
named in the section headings.
2.1 The Linked Sequential File
One of the simplest file organizations is the physical sequential
file. In this organization, records are simply written one after the other
and sequential access is done in the order in which the records are
physically stored.
The physical sequential file works very well in cases where the
data is static, but it is extremely inefficient when the data is dynamic.
Consider a file containing 100,000 records stored in sorted order, then
the problem of inserting a new record at the beginning of that file.
This single insertion would result in 100,000 records being moved to
make room at the beginning of the file for the new record.
This identical problem also occurs when large ordered lists are
stored in memory, and the same solution, the linked list, can be used
here (see pp. 106-12 of [HORO76] for an explanation of linked lists in
memory). In a linked list, the record order is determined by pointers
stored with each record. In a singly linked list each record has only a
pointer to the next record and so the file can be traversed in just one
direction. In a doubly linked list, each record has both a next and a
previous pointer, allowing bidirectional access.
2.2 Inverted Files
Assume a data file containing member records for an organization,
and that the record format for this file is:
typedef struct {
long number;
char name[24];
char address[81];
char city[24];
char state[3];
char zip[11];
} member_t;
Assume also that these records are stored in the file in physical
sequential order sorted by the member number, therefore a binary
search may be performed to quickly find a record with a given number
field. The field on which the records are sorted is called the primary
index. The problem arises when a query is made on another field
besides primary index, in which case a search of the entire data file is
required.
The problem of being able to perform queries efficiently on more
than one field can be resolved through the use of inverted files. The
inverted file for the name field, for example, would have entries
containing the name field and record position for each record in the
data file, and these would be sorted by the name field. Fields stored in
inverted files are referred to as secondary indexes.
For a concrete example of an inverted file, just pick up a textbook
and look at the index. The index consists of terms from the text paired
with the pages on which they are located, which is an inverted file
relative to the text, which would correspond to the data file.
2.3 The B-tree
As discussed above, the physical sequential file provides
unacceptable performance for dynamic data. For static data, however, it
provides efficient sequential as well as random access (using a binary
search). The linked sequential file solves the problem of performance
for dynamic data while preserving efficient sequential access, but the
ability to randomly access records is lost. The need to store dynamic
data which may be randomly accessed led to the development of the B-
tree.
As the name implies, the B-tree stores data in a tree structure,
which allows random access. And since the tree nodes are connected
by pointers, the update problem is solved in much the same way as by
the linked sequential file organization. A B-tree does not provide very
efficient sequential access, however. This led to a B-tree variant called
the B+-tree, which provides both efficient random and sequential access.
While the B+-tree does come close to providing the best of all
worlds, it has two important drawbacks. First, there is significantly
more storage overhead for a B+-tree than for a sequential file. Every
entry in a B+-tree is stored in a leaf node, and the rest of the tree is
simply scaffolding used when the tree is searched. Second, each entry
in any type of B-tree must be unique. These shortcomings make the B-
tree file organization inappropriate for data files containing records,
which may be both large and duplicated. But for inverted files, which
generally have shorter entries that are by their very nature unique (the
same term and page number would not be listed twice in an index), the
B+-tree is an ideal choice. Therefore cbase uses the linked sequential
file organization for record storage and B+-trees for inverted files.
A discussion of the inner workings of B-trees is beyond the scope
of this chapter. A good discussion of the B-tree and its major variants
may be found in COME79.
Chapter 3: cbase Library Functions
3.1 File Access Functions
The cbcreate function is used to create a new cbase.
int cbcreate(char *cbname, size_t recsize,
cbfield_t *fields, size_t fldcnt);
cbname points to a character string which is the name of the cbase.
This name is used as the name of the data file which holds the records
in the cbase. recsize specifies the record size to be used. fields
points to an array of structures containing the data definition
information for the cbase. Each structure in the array contains the
definition for one field, the number of fields being specified by
fldcnt. The field definitions must be in the order in which the fields
occur in the record. The field definition structure type cbfield_t is
defined as:
typedef struct { /* field definition */
size_t offset; /* field offset */
size_t size; /* size of field */
int type; /* type of field */
int flags; /* flags */
char filename[FILENAME_MAX];
} cbfield_t;
offset is the location of the field within the record and size is the
size of the field. type specifies the field data type, legal values for
which are shown in Table 3.1. flags values are constructed by
ORing together flags from the following list:
CBFKEY Field is to be a key.
CBFUNIQ Only for use with CBFKEY.
Indicates that key is
constrained to be unique.
FILENAME_MAX is an ANSI C definition indicating the maximum
length of a filename string. It is found in <stdio.h>.
t_char signed character
t_uchar unsigned character
t_short signed short integer
t_ushort unsigned short integer
t_int signed integer
t_uint unsigned integer
t_long signed long integer
t_ulong unsigned long integer
t_float floating point
t_double double precision floating point
t_pointer pointer
t_string character string
t_binary block of binary data (e.g., graphics)
Table 3.1: cbase Data Types
Before a cbase that has been created may be used, it must be
opened using the following function:
cbase_t *cbopen(char *cbname, char *type,
cbfield_t fields, size_t fldcnt);
cbname, fields, and fldcnt serve the same functions as for
cbcreate and should be given the same values as when the cbase
was created. type points to a character string specifying the type of
operations for which the cbase is to be opened (as for the stdio
function fopen). Legal values for type are:
"r" open for reading
"r+" open for update (reading and writing)
cbopen returns a pointer to the open cbase.
The cbsync function causes any buffered data for a cbase to be
written out.
int cbsync(cbase_t *cbp);
The cbase remains open and the buffers retain their contents.
After processing is completed on an open cbase, it must be closed
using the following function:
int cbclose(cbase_t *cbp);
The cbclose function causes any buffered data for the cbase to be
written out, unlocks it, closes it, and frees the cbase pointer.
3.2 Record Cursor Positioning Functions
Each open cbase has a record cursor. At any given time the
record cursor is either positioned on a record in that cbase or on a
special position called null. The record the cursor is on is referred to
as the current record. The operations performed by most cbase
functions are either on or relative to the current record, so the initial
step in a transaction on a cbase is usually to position the record cursor
on the desired record. When accessing the records in a cbase in the
order in which they are stored (described in section 2.1), the following
functions are used to move the record cursor:
int cbrecfirst(cbase_t *cbp);
int cbreclast(cbase_t *cbp);
int cbrecnext(cbase_t *cbp);
int cbrecprev(cbase_t *cbp);
The cbrecfirst function positions the record cursor to the first
record, and cbreclast to the last record. Before calling either of
these functions cbreccnt should be used to test if the cbase is empty.
size_t cbreccnt(cbase_t *cbp);
If the cbase is empty, there is no first or last record and so these
functions would return an error. The cbrecnext function advances
the record cursor to the succeeding record, and cbrecprev retreats it
to the preceding record. In the record ordering, null is located before
the first record and after the last.
There are also functions for saving the current position of the
record cursor and resetting it to that position:
int cbgetrcur(cbase_t *cbp, cbrpos_t *cbrpos_p);
int cbsetrcur(cbase_t *cbp, cbrpos_t *cbrpos_p);
The cbgetrcur function gets the current position of the record cursor
and saves it in the variable pointed to by cbrpos_p. cbrpos_t is
the cbase record position type. cbsetrcur can then be used later to
set the record cursor back to that position. The record cursor can be
positioned on null by passing cbsetrcur the NULL pointer rather
than a pointer to a variable. Other than this special case, cbsetrcur
should only be called with record cursor positions previously saved with
cbgetrcur.
The cbrcursor macro is used to test if the record cursor for a
cbase is positioned on a record or on null.
void *cbrcursor(cbase_t *cbp);
If the record cursor of the cbase pointed to by cbp is positioned on
null, cbrcursor returns the NULL pointer. If it is on a record,
cbrcursor returns a value not equal to the NULL pointer. This
function is useful for loops which need to test when the last (or first)
record has been processed.
3.3 Key Cursor Positioning Functions
Each open cbase also has a key cursor for each key (index)
defined for that cbase. As for the record cursor, a key cursor is either
positioned on a record in that cbase or on null. To access a cbase in
the sort order of a certain key, a key cursor is used instead of the
record cursor. Each key cursor moves independently of the others, but
whenever a key cursor position is set, the record cursor is moved to the
same record. The key cursors are not affected by moving the record
cursor. The following functions are used to move a key cursor:
int cbkeyfirst(cbase_t *cbp, int field);
int cbkeylast(cbase_t *cbp, int field);
int cbkeynext(cbase_t *cbp, int field);
int cbkeyprev(cbase_t *cbp, int field);
These perform as do the corresponding functions for the record cursor.
The following function is used to search for a key of a certain
value:
int cbkeysrch(cbase_t *cbp, int field, void *buf);
buf points to the key value to search for. If a key of that value is not
found, the key (and record) cursor is positioned to the record (possibly
null) which would follow a record with that key value.
Since the key cursors do not automatically follow the record
cursor, the situation sometimes occurs where the record cursor is
positioned to the desired record, but the cursor for the key to be used is
not. The cbkeyalign function is used to align a specified key cursor
with the record cursor.
int cbkeyalign(cbase_t *cbp, int field);
The reason the key cursors are not updated every time the record cursor
moves is not because it would be in any way difficult to do so, but
because this would increase the overhead enormously. And since only
one key cursor is normally used at a time, this extra overhead would
rarely yield anything in return.
As for the record cursor, a key cursor position can be tested using
the following macro:
void *cbkcursor(cbase_t *cbp, int field);
If the key cursor specified by field of the cbase pointed to by cbp is
positioned on null, cbkcursor returns the NULL pointer. If it is on a
record, cbkcursor returns a value not equal to the NULL pointer.
3.4 Input/Output Functions
The functions for reading from a cbase are:
int cbgetr(cbase_t *cbp, void *buf);
int cbgetrf(cbase_t *cbp, int field, void *buf);
where cbp is a pointer to an open cbase and buf points to the storage
area to receive the data read from the cbase. The cbgetr function
reads the current record into buf. cbgetrf reads a field from the
current record into buf.
The functions for inserting a record into a cbase are:
int cbinscur(cbase_t *cbp, void *buf);
int cbinsert(cbase_t *cbp, void *buf);
where buf points to the record to be inserted. The cbinscur
function inserts a record after the current record. cbinsert inserts a
record as the last record in the cbase. In both cases the record cursor
is positioned on the inserted record. Note that the position at which a
record is inserted affects only the record cursor functions and not the
key cursor functions.
The cbdelcur function is used to delete a record.
int cbdelcur(cbase_t *cbp);
The record cursor must first be positioned on the record to delete, then
cbdelcur called to delete the current record.
The cbputr function writes over an existing record, which is
equivalent to deleting a record and inserting a new one in the same
position in the file.
int cbputr(cbase_t *cbp, void *buf);
buf points to the new record contents.
3.5 Lock Functions
Before an open cbase can be accessed, it must be locked. The
function to control the lock status of a cbase is:
int cblock(cbase_t *cbp, int l_type);
where cbp is a pointer to an open cbase and l_type is the type of
lock to place on the cbase. The legal values for l_type are:
CB_RDLCK lock cbase for reading
CB_WRLCK lock cbase for reading and writing
CB_RDLKW lock cbase for reading (wait)
CB_WRLKW lock cbase for reading and writing (wait)
CB_UNLCK unlock cbase
If l_type is CB_RDLCK and the cbase is currently write locked by
another process, or if l_type is CB_WRLCK and the cbase is currently
read or write locked by another process, cblock will fail and set
errno to EAGAIN. For the lock types which wait, cblock will not
return until the lock is available.
A general rule for locking in database applications is to retain
locks for the smallest amount of time possible. Therefore all locks
should normally be released before pausing for user input. Also, a
write lock should not be used when a read lock will do.
The cbgetlck function reports the lock status held by the calling
process on a cbase.
int cbgetlck(cbase_t *cbp);
It returns one of the legal values for the l_type argument in the
cblock function.
Chapter 4: An Example Program
Included with cbase is a small example program to illustrate the
basic use of the library. This program, rolo, is a skeletal program for
storing business cards. To allow it to be compiled without requiring
any additional libraries for displays, and because the purpose of the
program is purely instructional, the program has not been given a real
user interface.
4.1 Data Definition
The first step in writing a program using cbase is to define the
data to be stored. This should be done in a separate header file for
each record type to be used. Listing 4.1 shows rolo.h, the header
for the business card record type used by rolo.
Starting at the top, the first thing to note is the definition of a
macro to prevent multiple includes. This is a general practice
applicable to any header file, cbase or otherwise, whose purpose is to
allow a header to be specified for inclusion multiple times in a file
while ensuring that the definitions within the file are processed only
once. For a header containing nothing but macro definitions, the only
effect of preventing multiple includes will be reduced compile times.
But for a header containing, for instance, type definitions, multiple
includes would produce compiler errors. Notice how the include macro
is related to the header file name; the file name is converted to upper
case and the period replaced by an underscore ('.' is not a valid
character for C identifiers). Be consistent in naming these macros so
you will not accidentally use one elsewhere.
The first cbase definition is the cbase name. This character string
is the name used for the record file. Again, note the relation of the
macro name to the header name; the dot extension is dropped from the
file name and the remaining characters converted to upper case. This
macro is to be used for the cbname argument when calling
cbcreate and cbopen.
#ifndef ROLO_H /* prevent multiple includes*/
#define ROLO_H
/* cbase name */
#define ROLO("rolo.dat")
/* record definition */
typedef struct {
char rl_contact[81]; /* contact name */
char rl_title[81]; /* contact job title */
char rl_company[81]; /* company name */
char rl_addr[4][81]; /* company address */
char rl_phone[11]; /* phone number */
char rl_fax[11]; /* fax number */
char rl_notes[5][81]; /* notes */
} rolo_t;
/* field numbers */
#define RL_CONTACT (1)
#define RL_TITLE (2)
#define RL_COMPANY (3)
#define RL_ADDR (4)
#define RL_PHONE (5)
#define RL_FAX (6)
#define RL_NOTES (7)
#define RL_FLDCNT (7)
/* field definitions */
cbfield_t rl_fields[] = {
{offsetof(rolo_t, rl_contact[0]),
sizeofm(rolo_t, rl_contact),
t_string, CBFKEY | CBFUNIQ, "rl_cont.key"},
{offsetof(rolo_t, rl_title[0]),
sizeofm(rolo_t, rl_title),
t_string, 0, ""},
{offsetof(rolo_t, rl_company[0]),
sizeofm(rolo_t, rl_company),
t_string, CBFKEY, "rl_comp.key"},
{offsetof(rolo_t, rl_addr[0][0]),
sizeofm(rolo_t, rl_addr),
t_string, 0, ""},
{offsetof(rolo_t, rl_phone[0]),
sizeofm(rolo_t, rl_phone),
t_string, 0, ""},
{offsetof(rolo_t, rl_fax[0]),
sizeofm(rolo_t, rl_fax),
t_string, 0, ""},
{offsetof(rolo_t, rl_notes[0][0]),
sizeofm(rolo_t, rl_notes),
t_string, 0, ""}
};
#endif/* #ifdef ROLO_H */
Listing 4.1: rolo.h
Next is the type definition for the record being defined. Each
field in the record (member in the structure) begins with a character
sequence derived from the cbase name, usually two characters followed
by an underscore. This character sequence should be unique across all
the record types used by a given program. The type name to be used
is constructed by dropping the dot extension from the header name and
appending _t to indicate a type definition. This type is for use when
declaring record variables. Variables of this type should be used for
functions such as cbgetr which require a pointer to a record.
Following the record type definition is a list of macros used to
identify a field when calling cbase functions. The names of these
macros are constructed by taking the field names from the record type
definition and converting them to uppercase. This is why the field
names are made unique. Each field name macro must be an integer
indicating the number of the field in the record type definition, starting
at one, not zero. A macro for the total number of fields is also
defined. This macro is to be used for the fldcnt argument when
calling cbcreate and cbopen.
The field definition array specifies, for each field in the record, its
offset in the record, its size, its type, some flags, and a filename to be
used if the field is to be a key. The offsetof macro should be used
to specify the offset. A macro sizeofm (size of member) is defined
in cbase.h to give the size of a member of a structure.
size_t sizeofm(struct_t, member);
The arguments to sizeofm are the same as for offsetof. The size
cannot be calculated from the difference between field offsets because
padding characters may be added between structure members to
maintain proper alignment (see Kernighan, 1988).
flags is used to indicate which fields are keys, and which keys
must be unique. Note that every record type should normally have at
least one unique key. If the field is a key, filename is used as the
name of the inverted file to contain the keys.
4.2 Header Files
Looking now at the source file rolo.c, in addition to the data
definition header rolo.h, the following header files are included:
#include <blkio.h>
#include <cbase.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
The header files listed above are normally included by all programs
using the cbase library. blkio.h is the header for the blkio library.
It is included to provide the definition of the NULL pointer and the
declaration of the function bexit. bexit is for use in place of
exit with any library built upon the blkio library, as cbase is. It
writes out any buffered data for any open block file then calls exit
(see bexit in the blkio reference manual). cbase.h is the cbase
header file containing all the constant and type definitions and function
declarations for using the cbase library. <errno.h> contains the
definition of the errno macro as well as definitions of error code
values. <stdio.h> is the header for the standard buffered
input/output library, and is included for the declarations of functions
such as printf and scanf. <stdlib.h> is included for the
macros EXIT_SUCCESS and EXIT_FAILURE, which are
implementation defined success and failure codes to be used as the
argument to exit. <string.h> is included for declarations of the
string and memory functions (e.g., strcpy and memset).
4.3 Opening
The first step in accessing an existing cbase is to open it. Listing
4.2 shows the code from rolo.c to open the business card cbase.
rolo is opened with a type argument of "r+" to allow both reading
and writing. The other arguments are all taken from rolo.h. On
error cbopen returns the NULL pointer. If the error was that the
named cbase does not exist, errno will be set to ENOENT. Here, if
the rolo cbase did not exist it is created and the program continues as
normal. Note that the cbase must still be opened after it is created.
For this program there is only one cbase, but most programs would
have multiple cbases to open.
/* open cbase */
cbp = cbopen(ROLO, "r+", rl_fields, RL_FLDCNT);
if (cbp == NULL) {
if (errno == ENOENT) {
printf("The card file does not exist.
Creating.\n");
rs = cbcreate(ROLO, sizeof(rolo_t),
rl_fields, RL_FLDCNT);
if (rs == -1) {
fprintf(stderr, "*** Error creating rolo.
Error number %d.\n", errno);
bexit(EXIT_FAILURE);
}
cbp = cbopen(ROLO, "r+", rl_fields,
RL_FLDCNT);
if (cbp == NULL) {
printf("*** Error opening rolo.
Error number %d.\n", errno);
bexit(EXIT_FAILURE);
}
} else {
printf("*** Error opening rolo.
Error number %d.\n", errno);
bexit(EXIT_FAILURE);
}
}
Listing 4.2: Opening a cbase
4.4 General Programming Guidelines
The rolo program contains examples of most of the general
concepts needed to use the cbase library. First is how to use of the
contents of the data definition header files. Second, when using
locking, before the program pauses for user input all locks are released
to allow other processes to access the cbase. Also note that the
functions cbgetrcur and cbsetrcur are not used to reposition the
record cursor after the cbase has been unlocked then locked again.
This is because during the time in which the cbase was unlocked it
could have been modified by another process; the record position saved
before might contain another record or be empty.
4.5 Closing
The last step is to close all open cbases. Listing 4.3 shows this
code from rolo.c.
/* close cbase */
rs = cbclose(cbp);
if (rs == -1) {
printf("*** Error closing rolo.
Error number %d.\n", errno);
bexit(EXIT_FAILURE);
}
Listing 4.3: Closing a cbase
Appendix A: Installation Instructions
The cbase library is distributed in MS-DOS format on two 5¼
360K diskettes or one 3½ 720K diskette. All the files for each library
are contained within a directory bearing the name of that library.
The initial steps for installing each library are the same. First, a
directory on the target disk must be created for each library to receive
the files for that library. Second, the files are copied from each
library's directory on a distribution diskette into the appropriate
directory on the target disk. If an operating system besides MS-DOS is
being used, the systems file converson facilities will need to be used.
For VP/ix under UNIX, for instance, the copy command would be
used with the /u option to convert the files from MS-DOS text format
to UNIX text format.
The remaining installation instructions for each library, which
should be performed in the directory containing that library, are given
below. The batch files provided for MS-DOS installation are written
for Borland Turbo C, and because there is so little uniformity among
MS-DOS C compilers, these will have to be modified for use with
another compiler. The necessary modifications are straightforward; they
are listed at the beginning of the blkio installation batch file. Also, if a
make utility is available, the UNIX makefiles may be adapted.
As mentioned in chapter 1, cbase is written in strict compliance
with the ANSI C standard. But since K&R C compilers are still in
wide use, temporary patches have been made to allow compilation
under both ANSI and K&R C. These alterations are all very minor and
they should cause no inconvenience for the ANSI C user.
Before proceeding to install the libraries, the manx utility should
be compiled and placed in a directory in the path. manx is used to
generate the reference manuals.
A1. The blkio Library
UNIX
1. Install the boolean header file.
$ su
# cp bool.h /usr/include
# ^d
2. Build the blkio library. Examine the makerec file
afterward for warnings.
$ make blkio > makerec
3. Install the blkio library. This will copy the blkio header
file blkio.h to /usr/include and the blkio library archive
to /usr/lib.
$ su
# make install
# ^d
MS-DOS
1. If necessary, modify install.bat (and blkio.rsp) for the
C compiler being used.
2. Install the blkio library.
> install
A2. The btree Library
UNIX
1. First install the blkio library.
2. Build the btree library. Examine the makerec file
afterward for warnings or errors.
$ make btree > makerec
3. Install the btree library. This will copy btree.h to
/usr/include and the btree library archive to
/usr/lib/libbtree.a.
$ su
# make install
# ^d
4. Make the utilities and example programs.
$ make util
MS-DOS
1. First install the blkio library.
2. If necessary, modify install.bat (and btree.rsp) for the
C compiler being used.
3. Install the btree library.
> install
A3. The cbase library
UNIX
1. First install the blkio, btree, and lseq libraries.
2. Build the cbase library. Examine the makerec file
afterward for warnings or errors.
$ make cbase > makerec
3. Install the cbase library. This will copy cbase.h to
/usr/include and the cbase library archive to
/usr/lib/libcbase.a.
$ su
# make install
# ^d
4. Make the utilities and example programs.
$ make util
MS-DOS
1. First install the blkio, btree, and lseq libraries.
2. If necessary, modify install.bat (and cbase.rsp) for the
C compiler being used.
3. Install the cbase library.
> install
A4. The lseq Library
UNIX
1. First install the blkio library.
2. Build the lseq library. Examine the makerec file
afterward for warnings or errors.
$ make lseq > makerec
3. Install the lseq library. This will copy lseq.h to
/usr/include and the lseq library archive to
/usr/lib/liblseq.a.
$ su
# make install
# ^d
4. Make the utilities and example programs.
$ make util
MS-DOS
1. First install the blkio library.
2. If necessary, modify install.bat (and lseq.rsp) for the
C compiler being used.
3. Install the lseq library.
> install
Appendix B: Adding New Data Types
Each cbase data type has an associated function used to compare
two data items, thus defining that type. Pointers to these comparison
functions are stored in an array through which they are accessed by
cbase internally. The field type (e.g., t_int, t_string) is an index
into this array. Therefore to add a new data type to cbase, the
comparison function defining that type must be added as well as a type
name macro defined to specify that type. The steps necessary to add a
new type are as follows:
1. Add the New Comparison Function.
The file cbcmp.c contains each cbase data type's
comparison function. Add the new comparison function
in this file. Comparison functions must be of the format:
int f(const void *p1, const void *p2, size_t n);
p1 and p2 are pointers to the two data items to be compared,
and n is the length of the data items. The value returned
by the function must be less than, equal to, or greater than
zero if the data item pointed to by p1 is less than, equal to,
or greater than, respectively, that pointed to by p2. The C
standard library function memcmp, for example, fills all these
requirements. This function is used only by cbase internally,
and then only through the comparison function pointer array,
so all comparison functions should be declared static.
cbcmp_p is the comparison function pointer array. Add a
pointer to the new function as the new last element in this
array.
2. Add the New Type Name.
In cbase.h, find the list of cbase data type macros
(t_char, t_int, t_string, etc.). These are numbered
sequentially starting at 1. Add the new type following
the last in the list. Subtracting 1 from any of these
macros gives the index into the array cbcmp_p for its
associated comparison function.
3. Increment the Type Count Macro.
cbase_.h is the private header included only by cbase
modules. The macro TYPECNT is defined as the number of
functions in the cbcmp_p array. This must be incremented
by one for each new type added.
Appendix C: Porting to a New Operating System
As stated in chapter 1, while cbase is extremely portable, certain
necessary operations are very operating system independent. All system
specific code is contained in the blkio library and is further isolated to
only two files within that library. The steps necessary to port to a new
system are as follows:
1. The Host Macro.
In blkio_.h, find where the HOST macro is defined.
Add the new system to the list of supported systems, then
change the definition of HOST to the new system.
2. The File Descriptor Type.
In blkio.h, the file descriptor type fd_t is defined
as a union. Look at this union to find the member with the
correct type for the new system. The name of this member
will be needed for steps 3 and 4. If a member with the
correct type is not included in the union, add a new member.
3. Code for File Access.
In buops.c, search for every occurrence of the HOST
macro. Each occurence will resemble the following:
#if HOST == UNIX
.
.
.
#elif HOST == MSDOS
.
.
.
#endif
For each of these, add another #elif for the new operating
system followed by the required code. buops.c contains all
code for opening, closing, seeking, reading, and writing
files. A simple and direct translation to the new system
calls should be all that is required.
4. Code for File Locking.
In lockb.c, do the same as for buops.c. lockb.c
contains the code for file record locking. If the new
operating system does not support file locking lockb.c
does not need to be altered.
Bibliography
COME79 Comer, D. The Ubiquitous B-tree. ACM Computing
Surveys, June 1979.
HORO76 Horowitz, E. and S. Sahni. Fundamentals of Data
Structures. Rockville, MD: Computer Science Press, 1976.
KERN88 Kernighan, B. and D. Ritchie. The C Programming
Language 2nd ed. Englewood Cliffs, NJ: Prentice Hall, 1988.