41 KiB
Psion SIBO / Workabout MX — File System and DBF Database API Reference
Source: PLIB Reference, chapters Files (ch. 11) and Database Files (ch. 14), with
corroborating structural detail from EPOC O/S System Services, chapter 20 (Database File
Management). Everything below is drawn from those manuals; page-anchored citations are given as
[PLIB p.NNN] / [SysSvc §20]. OCR-garbled items are flagged explicitly.
OCR caveat. The source is scanned OCR text. Identifiers written
p_open, function-name casing, and hex constants have been normalised to their obvious intended form. Where a signature itself is corrupted or truncated in the source, this is called out inline. Do not treat a normalised name as a guarantee of exact header spelling — verify againstp_file.h/p_dbf.hbefore compiling.
Part 1 — The File System
1.1 The file server and file systems (nodes)
- All file operations are performed by a high-priority system process, the file server
(process name
SYS$FSRV). A process must connect to it before use; the C startup module normally does this automatically. Sending a message to the file server without connecting panics with panic number 41. [PLIB p.117] - The file server supports multiple file systems, also called nodes.
p_opencan list them. Three were implemented at the time of writing [PLIB p.117]:LOC::— the local filing system, with the RAM driveM:and SSD drivesA:,B:, … (count depends on hardware).REM::— the remote filing system (present only while connected to a remote file server).ROM::— the ROM filing system for ROM-based files; normally invisible to the user; does not support devices or directories.
- Within
LOC::(andREM::when the remote end is a PC or another SIBO machine), the device and directory structure is MSDOS-compatible. [PLIB p.117]
Media types (SSDs). RAM SSDs (battery-backed static RAM, block-structured, not buffered on SIBO) and Flash SSDs (linked variable-length records). Overwriting or deleting on Flash consumes space that is only reclaimed by reformatting; a byte can be physically overwritten with no storage penalty only if the new value is derived from the old by clearing bits to zero. This is the property the DBF layer exploits. [PLIB pp.118–120, 195]
1.2 File specification structure
A full file specification has the form [PLIB p.120]:
<node><device><dir><name><ext>
Example: LOC::B:\NOTES\OLD\PLANS.TPD, where:
| Component | Meaning | Example |
|---|---|---|
<node> |
file system node | LOC:: |
<device> |
device name | B: |
<dir> |
directory name | \NOTES\OLD\ |
<name> |
file name | PLANS |
<ext> |
extension name | .TPD |
Rules [PLIB p.120]:
- A file specification never exceeds
P_FNAMESIZE(128) bytes including the zero terminator. - The
<node>component is alwaysP_FSYSNAMESIZEbytes long (excluding any zero terminator). - Beyond
<node>and theP_FNAMESIZEtotal, make no assumptions about component sizes; the syntax of<device>/<dir>/<name>/<ext>is owned by the node's file-system code (foreign remote systems can map onto this model, e.g. VMS or Mac paths). Usep_fparse/p_chdirrather than manipulating specs by hand.
Default path. The file server stores a per-client default path (<node><device><dir>) plus a
system-wide default assigned to new clients. Manipulate with:
p_setpth / p_setpthasync (set this process's default), p_getpth (get it),
p_getpthbyid (get another process's), p_setdefaultpath (set the system-wide default).
[PLIB pp.121, 126]
Parsing / directory manipulation.
INT p_fparse(TEXT *name, TEXT *related, TEXT *full, P_FPARSE *perk);— builds a full spec intofull(reserveP_FNAMESIZEbytes;P_FNAMESIZEbytes are always written). Components are taken fromname, thenrelated(may beNULL), then the default path, in that order of precedence. Output is upper-cased.perk(may beNULL) receives aP_FPARSEstruct with the lengths of each component and a wildcard-flags byte (P_PWILD_ANY,P_PWILD_NAME,P_PWILD_EXT).f_fparseis identical but callsp_leaveon error instead of returning it;p_fparseasyncadds a trailingWORD *stat. [PLIB pp.123–124]INT p_chdir(TEXT *src, TEXT *outp, INT mode, TEXT *subdir);— parsesrcand change its directory permode:P_CD_ROOT,P_CD_PARENT, orP_CD_SUBDIR(append zero-terminatedsubdir). ReserveP_FNAMESIZEatoutp. Async formp_chdirasync. [PLIB p.125]
1.3 p_open — modes, formats, and access flags
Channel-based file services all go through p_open:
INT p_open(VOID **ppfcb, TEXT *name, UINT mode);
On success returns 0 and writes the channel handle to *ppfcb (not written on failure). name is
parsed with a NULL related name. [PLIB p.142]
mode is a bitwise-OR of exactly one open mode, exactly one format, and any combination of
access flags.
Open modes (choose exactly one) [PLIB pp.142–143]
| Mode | Behaviour |
|---|---|
P_FOPEN |
Open an existing file. E_FILE_NXIST if it does not exist. Normally used for read access. |
P_FCREATE |
Create a file that must not already exist. E_FILE_EXIST if it does. Requires P_FUPDATE for write. |
P_FREPLACE |
If the file exists, open and truncate to zero length; otherwise create it. Requires P_FUPDATE for write. |
P_FAPPEND |
Same as P_FOPEN but initial position is at end of file so the next write appends. P_FRANDOM not needed; P_FUPDATE needed for write. |
P_FUNIQUE |
Create a unique file, using the passed path as the related path; the unique name is written back to name (reserve P_FNAMESIZE bytes). P_FUPDATE not needed. |
Formats (choose exactly one) [PLIB pp.142, 146–147; §list p.121]
| Format | Meaning |
|---|---|
P_FSTREAM |
Flat binary file. |
P_FSTREAM_TEXT |
Flat binary file, but declares the file is text; on REM:: it makes the remote side present/parse CRLF-terminated records. Locally usually identical to P_FSTREAM; no penalty, potential gain on remote. Prefer this for self-processed text files. |
P_FTEXT |
Record-oriented text file. Implemented as a client-side layer (the TXT: device) over P_FSTREAM_TEXT; data is buffered in that layer, so flushing is needed. |
P_FDIR |
Open a directory-listing channel (list files/subdirectories). |
P_FDEVICE |
Open a device-listing channel (list devices of a node). |
P_FNODE |
Open a node-listing channel (list file systems). |
P_FFORMAT |
Open a device-format channel (LOC:: only at time of writing; OR in P_FLOWDENSITY for dual-density low-density format). |
Access flags (combine as needed) [PLIB p.143]
| Flag | Meaning |
|---|---|
P_FUPDATE |
Write access as well as read. Writing without it → E_FILE_RDONLY. |
P_FRANDOM |
Random access required; needed to use p_seek. Without it, p_seek → E_FILE_INV. Do not specify unless you will seek (the FS may optimise sequential-only access). |
P_FSHARE |
Do not block the file from being re-opened for read access. Without it, a later open → E_FILE_LOCKED. Cannot be combined with P_FUPDATE (shared write is unsupported). |
Sharing rules: any number of processes may open the same file for reading only, provided all
readers specify P_FSHARE. Multiple writers are never allowed. Once open for reading it may be
re-opened for reading but not writing; once open for writing it may not be re-opened at all.
[PLIB p.140]
Examples [PLIB p.140]:
p_open(&fcb, "fred.dat", P_FSTREAM | P_FSHARE); /* read only */
p_open(&fcb, "fred.dat", P_FSTREAM | P_FUPDATE | P_FREPLACE | P_FRANDOM);/* writable */
Selected p_open errors: E_GEN_NOMEMORY, E_GEN_ARG (illegal flag combination),
E_FILE_DEVICE, E_FILE_NOTREADY, E_FILE_EXIST, E_FILE_NXIST, E_FILE_ACCESS,
E_FILE_DIRFULL, E_FILE_PROTECT, E_FILE_FULL, E_FILE_LOCKED, E_FILE_DIR, plus any
p_fparse error. [PLIB pp.143–144]
1.4 Reading and writing streams
INT p_read (VOID *pfcb, VOID *buf, UINT len);
INT p_write(VOID *pfcb, VOID *buf, UINT len);
INT p_close(VOID *pfcb);
p_readreadslenbytes (or the bytes remaining before EOF, whichever is smaller) from the current position; returns the number of bytes read. At EOF, reads 0 bytes and returnsE_FILE_EOF.lenmust not exceedP_FMAXSSIZE(16K). Most efficient in multiples ofP_FBLKSIZE(512) on 512-byte boundaries. Other errors:E_FILE_ABORT,E_FILE_READ. [PLIB p.144]p_writewriteslenbytes at the current position; increments position by bytes written; returns 0 on success.len≤P_FMAXSSIZE. On Flash you may overwrite a single byte (len==1) if the new byte only clears bits (then the modification date is not changed). Errors:E_FILE_FULL,E_FILE_RDONLY,E_FILE_ABORT,E_FILE_WRITE. [PLIB p.144]p_close(NULL)is harmless (returns 0). Close may perform a final buffered write and a date update, so it can returnp_write/p_fdateerrors — but it always closes the channel. To handle failures cleanly,p_iow(pfcb, P_FFLUSH)first.LOC::on SIBO does not buffer written data; EPOC-on-PC does. [PLIB p.143]
p_iow control operations on a binary/text channel [PLIB pp.145–146]:
INT p_iow(VOID *pfcb, P_FFLUSH);— flush buffered data and write the modification date.INT p_iow(VOID *pfcb, P_FSETEOF, ULONG *peof);— set the logical EOF to*peof(requiresP_FUPDATE). Extending pre-allocates storage on block devices; truncating also reduces the current position if needed.INT p_iow(VOID *pfcb, P_FCANCEL);— cancel pending async requests (not actively supported; a no-op even when a request is pending — simulate a cancel instead).
1.5 Seeking
Binary / stream:
INT p_seek(VOID *pfcb, INT sense, LONG *ppos);
sense is one of [PLIB p.144]:
sense |
Meaning |
|---|---|
P_FABS |
set position to *ppos |
P_FEND |
set position to *ppos relative to end-of-file |
P_FCUR |
set position to *ppos relative to current position |
On success the new position is written back to *ppos and 0 returned. E_FILE_INV if the channel
was not opened with P_FRANDOM. A negative resulting position clamps to the start; beyond EOF
clamps to EOF (use P_FSETEOF to extend). Setting *ppos=0 with P_FCUR senses the current
position; with P_FEND senses the file length. f_seek is the p_leave-on-error variant. On
P_FSTREAM_TEXT remote channels, only "seek to 0 (P_FABS)" and "seek to end (P_FEND, rel 0)"
are guaranteed. [PLIB pp.144–145]
Record-oriented text (P_FTEXT): p_seek sets the read position only (writes always go to
EOF), and sense is different [PLIB pp.149]:
sense |
Meaning |
|---|---|
P_FREWIND |
position to the first record (ppos ignored) |
P_FRSENSE |
get the position of the last record read/written |
P_FRSET |
set the record position previously got with P_FRSENSE |
Requires P_FRANDOM (else E_FILE_INV). Some remote systems may not support P_FRSET/P_FRSENSE.
1.6 Text records (P_FTEXT)
Convention: records are terminated by CRLF (CR = 13, LF = 10); a file may optionally end with SUB
(26). On read, the parser also accepts CR, LF, or LFCR as a terminator and treats a SUB as EOF; on
write it appends CRLF only (no SUB). Record content must not exceed P_FMAXRSIZE (256) bytes
and must not contain CR, LF, or SUB. [PLIB pp.146–148]
p_readreturns the record length (bytes written tobuf), positioning to the next record. Iflen< record length, the firstlenbytes are read andE_FILE_RECORDreturned (still advances). Returns 0 on a zero-length record;E_FILE_EOFpast the last record. [PLIB p.148]p_writewrites a record oflenbytes (0 …P_FMAXRSIZE); always appended at EOF; must not contain delimiters. Errors incl.E_FILE_RECORDif oversize. [PLIB p.149]
1.7 Directory, device, and node listing
All three use the open → repeated p_iow(..., P_FREAD, ...) until E_FILE_EOF → close pattern.
- Nodes:
p_open(&ncb, "FIL:" or NULL, P_FNODE); eachp_iow(ncb, P_FREAD, buf, pinfo)writes the next node name (bufcapacityP_FSYSNAMESIZE+1, i.e. 6). Optionalpinfo(P_NINFO*) gets the same info asp_ninfo. [PLIB p.127] - Devices:
p_open(&dcb, node-name, P_FDEVICE);E_GEN_FSYSif the node is bad,E_GEN_NSUPif the node has no devices (e.g.ROM::). Eachp_iow(P_FREAD)writes the next device name (bufcapacityP_FNAMESIZE); trailing argNULL. [PLIB p.128] - Files:
p_open(&dcb, name, P_FDIR);nameis parsed with a wildcard related name (*.*), so a name of""lists the current directory. Eachp_iow(P_FREAD, buf, pinfo)writes the next matching file name (excluding node/device/dir;bufcapacityP_FNAMESIZE). Optionalpinfo(P_INFO*) carries per-file info. A root directory of a PC-based device may return a volume-name entry withP_FAVOLUMEset. [PLIB pp.133–134]
P_INFO (from p_file.h) [PLIB p.134]:
typedef struct {
UWORD version;
UWORD status; /* status bits */
ULONG size; /* size of the file in bytes (end-of-file position) */
ULONG modst; /* system time of last modification (secs since 1970-01-01) */
UBYTE spare[4];
} P_INFO;
status bit fields: P_FAWRITE (not read-only), P_FAMOD (modified), P_FAHIDDEN,
P_FASYSTEM, P_FADIR (directory file), P_FAVOLUME (volume-name directory), P_FATEXT
(recognised text file; LOC:: cannot recognise text files). [PLIB p.134]
1.8 File / device / node information and non-channel operations
INT p_finfo(TEXT *name, P_INFO *pinfo);— info on one file/directory (same fields as aP_FDIRread). Good atomic existence check (E_FILE_NXISTif absent). Asyncp_finfoasync. [PLIB p.135]INT p_testpth(TEXT *dname);— returns 0 if the directory component exists. [PLIB p.135]INT p_ninfo(TEXT *node, P_NINFO *pninfo);— node info.P_NINFO { UWORD version; UWORD type; UWORD formattable; UBYTE spare[26]; }.typeisP_FSYSTYPE_FLATorP_FSYSTYPE_HIER. [PLIB pp.127–128]INT p_dinfo(TEXT *dname, P_DINFO *pdinfo);— device + mounted-medium info.P_DINFO { UWORD version; UWORD mediatype; UWORD removable; ULONG size; ULONG free; UBYTE name[P_VOLUMENAME]; WORD batterystate; UBYTE spare[16]; }. Low byte ofmediatype:P_FMEDIA_UNKNOWN|FLOPPY|HARDDISK|RAM|FLASH|ROM|WRITEPROTECTED; high byte flags incl.P_FMEDIA_COMPRESSIBLE(worth compressing out deleted records — not set for Flash),P_FMEDIA_DYNAMIC,P_FMEDIA_INTERNAL,P_FMEDIA_DUAL_DENSITY,P_FMEDIA_FORMATTABLE. [PLIB pp.129–130]- Other non-channel calls (each with an
…asynctwin):p_rename,p_delete(directory must be empty),p_mkdir(creates intermediate dirs),p_sfstat(set attributes; withP_FAVOLUMEset in the mask, sets/deletes the volume label),p_fdate(set modification date, ≥ 1980-01-01). [PLIB pp.136–139] - Formatting: open with
P_FFORMATthen repeatedlyp_read(first read yields aUWORDtotal count, subsequent reads step the format,E_FILE_EOFwhen done). Aborting early corrupts the medium. [PLIB pp.131–132]
Part 2 — The DBF Database API
2.1 The model
A database file (DBF) is a binary file of typed, variable-length records. Used by MC Diary, Series 3 Database, and OPL data files. DBFs are Flash-friendly: records can be appended, deleted, or replaced in place on a Flash SSD without rewriting the whole file. [PLIB p.195]
Key model properties [PLIB pp.195–197, 204–207]:
- Record type 0 = deleted. Deleting a record only overwrites its 4-bit type field with zero (which merely clears bits — legal on Flash). Therefore deleting does not shrink the file.
- Append-only; updates = erase + append. Updating a record deletes the original and appends the modified version, so an update always moves the record to the end of the file.
- Reclaiming space:
DbfCompress(only on a compressible medium — not Flash) or copy the file record-by-record withDbfCopyFile(deleted records are not copied). - Sparse index (optional): one 4-byte address per sixteenth record, held in a separate
segment (
DBF$nnnn.INX,nnnna 4-hex-digit number from the channel) so it does not consume the app's data space. Enables fast random access and fast backward scans; adjusted on append/delete so it always points to every 16th record. - Read-ahead buffer: each file-server read fills a caller-supplied buffer (typically 4K),
usually pulling in many records; a read for a record already in the buffer just locates it.
Most DBF services may overwrite the buffer. The next read after the buffer is disturbed
re-reads the whole buffer (a performance hit), so any direct modification of the buffer that is
not done via a DBF service must be followed by
DbfTrash. Services guaranteed not to alter the buffer:DbfFlush,DbfVersion,DbfAppend,DbfSense,DbfCount. [PLIB p.196]
Record limit: max 65534 records of any one visible type, numbered 0…65533. Since only one type is visible per open, a file may hold more in aggregate. A file with more than the max (of the visible type) is logically truncated to the max on open. [PLIB p.197]
End-of-file record. Reading past the last record returns E_FILE_EOF; the current record
number (per DbfSense) then becomes last record + 1 — the fictitious end-of-file record.
Reading before the first record (DbfBackRead/DbfFindRead backwards) also gives E_FILE_EOF
with current record number 0. If the file has no records, DbfSense always returns 0. Services
that operate on the current record (DbfEraseRead, DbfUpdate, …) do nothing and return
E_FILE_EOF when positioned on the EOF record. [PLIB p.197]
2.2 The file header
DBFs begin with a 22-byte standard header [PLIB p.196; SysSvc §20]:
| Byte offset | Contents |
|---|---|
| 0–15 | Zero-terminated file signature (all 16 bytes are verified — pad with trailing zeros). |
| 16, 17 | Version of DBF software used to produce the file. |
| 18, 19 | Offset from start of file to the first record. |
| 20, 21 | Minimum version of DBF software required. |
The first-record offset allows an extended header (application-specific data after the standard
header). If none, the value is 22. The first record is always the type-2 field information
record. Version-number format: see DbfVersion. [PLIB p.196; SysSvc §20]
2.3 Records and the record header word
In memory, a record is a DbfRecord (p_dbf.h) [PLIB p.196]:
typedef struct {
UWORD header; /* record header word */
UBYTE data[2]; /* data to be written... (variable length in practice) */
} DbfRecord;
The header word: top 4 bits = record type (0–15); low 12 bits = record length. Maximum record length is 4094 bytes (one less than the theoretical 0xFFF = 4095, so a 4094-byte record plus its 2-byte header fits a 4096-byte buffer). [PLIB p.196; SysSvc §20]
Record types [PLIB pp.196–197; SysSvc §20]:
| Type | Meaning |
|---|---|
| 0 | Deleted record — ignored by all DBF services; never copied by DbfCopyFile. |
| 1 | Standard data record (fields per the field information record). Usually the only visible type. |
| 2 | Field information record (FIR) — must exist and be the first record; later type-2 records ignored. |
| 3 | Descriptive record — optional, file-wide app data (see DbfDescRecordRead/Write). |
| 4–7 | App-specific: copied to a new file, not appended to an existing file, by DbfCopyFile. |
| 8–13 | App-specific: both copied to a new file and appended to an existing file (merged) by DbfCopyFile. |
| 14 | Reserved for voice records. |
| 15 | Reserved for internal use — do not use. |
2.4 The Field Information Record (FIR) and field types
The FIR (type 2) holds up to 32 bytes, one per field, giving each field's type [PLIB p.197; SysSvc §20]:
| Byte value | Field type |
|---|---|
| 0 | Word |
| 1 | Long |
| 2 | Double |
| 3 | String |
| 4–255 | Reserved |
So a data record has at most 32 fields — with one exception: a record that contains only
string fields is not bound by the 32-field limit and may hold any number of fields, subject to
the 4094-byte record cap. Records may contain fewer fields than the FIR lists, provided only
trailing fields are omitted; records with more fields than the FIR are assumed to have the
extra ones as string fields. Only DbfFindRead/DbfFindReadField assume records match the FIR.
[PLIB p.197]
2.5 String fields (leading byte count) and continuation sub-fields
A string field is leading byte-counted text, so a normal string field holds at most 255
characters. Longer strings use continuation sub-fields: the first 254 characters plus a
terminating byte 0x14, stored in a string field whose count byte is 255; the 0x14 +
count-255 signals that the immediately following string field continues the text. This chains
indefinitely, subject only to the 4094-byte record limit. [PLIB p.197]
2.6 The structs
DbfHeader (p_dbf.h) — passed to open [PLIB p.199]:
typedef struct {
UBYTE fileType[DbfHeaderNameSize]; /* 16-byte file signature */
UWORD createVersion; /* software version used to create the file */
UWORD dataStart; /* offset in file of first record (22 if no ext header) */
UWORD needVersion; /* minimum software version needed to handle this file */
UWORD firHeader; /* header word for the field information record */
UBYTE fir[DbfMaxFirLength]; /* the field information record bytes */
} DbfHeader;
- When creating/replacing, pre-fill all elements (header + FIR). There is no gap between the
header and FIR in the struct even when an extended header is used; the file leaves a gap of
dataStart − 22bytes for it. - When opening existing, pre-fill only
fileType(the signature); the rest is filled from the file. All 16 signature bytes are verified — mismatch →E_FILE_INVALID. DbfHeaderNameSize= 16 (implied by "16-byte file signature").DbfMaxFirLengthis the FIR capacity; the FIR maximum length is 32. (Exact numeric value ofDbfMaxFirLengthnot stated verbatim in the source beyond the max-32 rule — verify inp_dbf.h.)
DbfOpenArgs (p_dbf.h) — for DbfQuickOpen [PLIB p.200]:
typedef struct {
VOID **pFcb;
UBYTE *fName;
UINT mode;
DbfHeader *pHead;
} DbfOpenArgs;
DbfRecord — see §2.3.
2.7 The DBF functions
Every DBF function calls p_panic if pFcb is not a valid DBF channel from DbfOpen/
DbfQuickOpen; that clause is omitted per-entry below. Unless noted "may be used on a DBF opened
without an index", the without-index behaviour is called out where the manual specifies it.
Opening / closing / flushing
-
INT DbfOpen(INT *pstate, VOID **pFcb, TEXT *fName, UINT mode, DbfHeader *pHead, UBYTE *pbuffer, UINT len, UINT type);Open a DBF channel.mode= exactly one ofP_FOPEN/P_FCREATE/P_FREPLACE/P_FAPPEND/P_FUNIQUE, optionally OR'd withP_FUPDATEand/orP_FSHARE(other required stream flags are supplied automatically;P_FOPENandP_FAPPENDare treated identically).pbuffer/lenis the caller's read-ahead buffer: len must be 512…16384 (elseE_FILE_RECORD), and must be at least as large as the largest record (4096 is guaranteed sufficient). Only records of typetypeare visible (normally 1; may be 4–14; results undefined for 0/2/3/>14).*pstateselects the open strategy [PLIB pp.198–199]:DbfStateDisabled— open with a full sparse index; returns only when the index is fully built (may take a while).DbfStateOpenNoIndex— open without an index; returns fast, but some services are then disallowed (see each function).DbfStateStart— open with a sparse index incrementally: callDbfOpenrepeatedly, feeding back*pstateeach time, until*pstatebecomesDbfStateStartagain (as printed — the source states the loop terminates when the written-back value isDbfStateStart; this reads like an OCR/spec inconsistency, likely intended to be a distinct "finished" state such asDbfStateDisabled/DbfStateEnd. Flag: verify the terminating state inp_dbf.h.).
After a successful open the current record is 0, so
DbfNextReadreads record 1 andDbfEraseReaderases record 0; useDbfFirstReadto read record 0. Errors: those ofp_open(P_FSTREAM),p_seek,p_read, plusE_FILE_INVALID(bad signature / bad FIR),E_FILE_RECORD(bad buffer length). [PLIB p.199] -
INT DbfQuickOpen(INT *pstate, DbfOpenArgs *pargs, UBYTE *pbuffer, UINT len, UINT type);AsDbfOpenbutpFcb/fName/mode/pHeadare bundled inDbfOpenArgs. Preferred overDbfOpen(more efficient, shorter code);DbfOpenretained for compatibility. [PLIB p.200] -
INT DbfClose(VOID *pFcb);— close the file (returns asp_close); channel is closed even on error. May be used without an index. [PLIB p.200] -
INT DbfFlush(VOID *pFcb);— flush all buffers so modified data is written (returns asp_write). Does not alter the buffer. May be used without an index. [PLIB p.200] -
VOID DbfTrash(VOID *pFcb);— tell the DBF layer the read-ahead buffer was overwritten by the caller and can no longer be relied on. May be used without an index. [PLIB p.200] -
INT DbfCopyDown(VOID *pFcb, UINT offset);— copy the record atoffsetin the buffer to the start of the buffer (and flag the buffer invalid, so noDbfTrashneeded); returns the record length.offsetmust be one previously returned by a read service and the caller must not have written to the buffer since. May be used without an index. [PLIB p.201]
Whole-file operations
-
INT DbfCompress(UINT *pstate, VOID *pFcb);— reclaim space from deleted records if the medium is compressible (otherwise a no-op that still returns 0). After a real compress the current record is the EOF record.*pstate=DbfStateDisabled(blocking) orDbfStateStart(incremental loop, terminating as written back). Should not be used without an index. [PLIB p.201] -
INT DbfCopyFile(UINT *pstate, VOID *pFcb, TEXT *pTargetName, UINT targetMode, UINT type, INT dir);Copy non-deleted records between the current file andpTargetName.targetMode= same options as open (target always opened without an index;P_FUNIQUEwrites back the unique name).typeselects a single type (usually 1) orDbfRecordTypeAllfor all types.dirisDbfCopyFromHandle(current → target; target new file withP_FCREATE/P_FREPLACE/P_FUNIQUE, or append withP_FOPEN/P_FAPPEND) orDbfCopyToHandle(target → current; target sensiblyP_FOPEN). Copying to a new file always copies the FIR (type 2) and the file header (incl. extended header) regardless oftype; appending never copies types 2–7. Appending requires matching signatures and compatible FIRs (identical, or both string-only) elseE_FILE_INVALID.*pstatesupportsDbfStateDisabled,DbfStateStart(est. calls = file size / buffer size + 2), andDbfStateCopyAbort(abort an in-progressDbfStateStartcopy). May be used without an index. Warning: appending can exceed 65534 records with no error. [PLIB pp.201–202] -
INT DbfFileSize(VOID *pFcb, ULONG *pSize);— write the open file's size to*pSize. May be used without an index. [PLIB p.202] -
UINT DbfVersion(VOID);— DBF software version as hexxyyF:x= major (4 bits),yy= minor (8 bits),F= release type A/B/F (Alpha/Beta/Final, 4 bits). E.g.0x110F→ 1.10F. Only the major number gates whether a file can be handled. May be used without an index. [PLIB p.203]
Extended header and descriptive record
INT DbfExtHeaderRead(UINT cont, VOID *pFcb, VOID *buf, UINT len);— read up tolenbytes of the extended header; returns bytes read.cont: 0 = initial read (resets to start of ext header); 1 = continue; use 0 if reading the whole thing in one call.E_FILE_EOFat end. May be used without an index. [PLIB p.202]INT DbfExtHeaderWrite(UINT cont, VOID *pFcb, VOID *buf, UINT len);— symmetric writer. May be used without an index. [PLIB pp.202–203]INT DbfDescRecordRead(VOID *pFcb);— read the descriptive record to offset 0 of the read-ahead buffer; returns its length, orE_FILE_EOFif none, orE_FILE_INVALIDif the file was opened without an index. Sub-records use the same word-header format as main records; ignore (do not delete) unrecognised sub-record types. [PLIB p.203]INT DbfDescRecordWrite(VOID *pFcb, UINT len);— write a descriptive record from aDbfRecordat buffer offset 0 (content starts at offset 2; the leading 2 bytes form the header and are not counted inlen). Any existing descriptive record is erased first (max one per file);len==0just erases it.E_FILE_INVALIDif opened without an index. [PLIB p.203]
Reading records
All read services return the record data length (excluding the 2-byte header) and write the
buffer offset of the DbfRecord (including its header) to *pOffset. The read record becomes
the current record. On E_FILE_EOF, *pOffset is invalid.
INT DbfAbsRead(VOID *pFcb, UINT recnum, UWORD *pOffset);— seek to and read recordrecnum.E_FILE_EOFifrecnum> last record (current → EOF record). May be used without an index. [PLIB p.204]INT DbfAbsReadSense(VOID *pFcb, UINT recnum, UWORD *pOffset, ULONG *pPos);— asDbfAbsReadbut also writes the file position of the record header to*pPos. May be used without an index. [PLIB p.204]INT DbfNextRead(VOID *pFcb, UWORD *pOffset);— read the next record.E_FILE_EOFif already on the last record / no records (current → EOF record). May be used without an index. [PLIB p.204]INT DbfBackRead(VOID *pFcb, UWORD *pOffset);— read the previous record.E_FILE_EOFif on the first record / no records (current → record 0). May be used without an index. [PLIB p.204]INT DbfFirstRead(VOID *pFcb, UWORD *pOffset);— read the first record.E_FILE_EOFif no records of the current type (current → 0 = EOF record). May be used without an index. [PLIB p.205]INT DbfLastRead(VOID *pFcb, UWORD *pOffset);— read the last record.E_FILE_EOFif none. Should not be used without an index. [PLIB p.205]
Appending / erasing / updating
INT DbfAppend(VOID *pFcb, UINT len);— append a record of the current type and lengthlen(data only) to EOF and make it current; returns 0. The record must be at the start of the read-ahead buffer as aDbfRecordincluding its 2-byte header;DbfAppendbuilds the type/length header from those two bytes (not counted inlen).E_GEN_OVERif already 65534 records of the type;E_FILE_RECORDif total (data + 2) exceeds the buffer. Does not alter the buffer. Should not be used without an index. [PLIB p.205]INT DbfEraseRead(INT *pstate, VOID *pFcb, UWORD *pOffset);— erase the current record and read the following one (becomes current); returns its length or negative. TwoE_FILE_EOFcases: (a) already on the EOF record / no records → does nothing; (b) current was the last record → it is erased and current becomes the EOF record. Distinguish viaDbfSense+DbfCount, or by comparingDbfCountbefore/after.*pstate=DbfStateDisabled/DbfStateStart(incremental). Should not be used without an index. [PLIB p.206]INT DbfUpdate(INT *pstate, VOID *pFcb, UINT len);— erase the current record and append a new one of lengthlenfrom the read-ahead buffer, making it current. The new record must be aDbfRecordat the start of the buffer (leading 2 bytes = header, not counted inlen). The old record is not erased until the new one is successfully appended.E_FILE_EOF(no-op) if no records of the current type or if on the EOF record.*pstate=DbfStateDisabled/DbfStateStart. Should not be used without an index. [PLIB p.206] (Note: the manual's prose here says "DbfAppend uses these two bytes" — clearly referring to the same header-construction mechanism.)
Sensing / counting
UINT DbfSense(VOID *pFcb);— return the current record number (the EOF record number — 0 if empty, else count+1 — if a preceding call returnedE_FILE_EOF). Does not alter the buffer. May be used without an index. [PLIB p.209]UINT DbfCount(VOID *pFcb);— return the number of records of the currently visible type without changing the current record. Does not alter the buffer. Should not be used without an index. [PLIB p.209]
Finding by content
-
INT DbfFindRead(UINT *pstate, VOID *pFcb, VOID *pBuffer, UINT len, UINT findMode, UINT nStrings, UWORD *pOffset);Match wildcard text atpBufferof lengthlen(≤ 255) against the firstnStringsstring fields of each record, starting at the current record; returns the matching record's data length or a negative error. Equivalent toDbfFindReadFieldwithstartStr = 0.nStrings == DbfFindAllStringssearches all string fields to end-of-record. The FIR is used to type the first 32 fields; fields beyond that are assumed strings. On match, that record becomes current and its buffer offset is written to*pOffset; on no match,E_FILE_EOFand current becomes the first record (backward search) or the EOF record (forward search). May be used without an index exceptDbfFindLast(unpredictable). Panics iffindModeis malformed. [PLIB pp.208–209] -
DbfFindReadField(...)— same asDbfFindReadbut with an extrastartStrargument: match starts at string field numberstartStr(0 = first string field). Available only in EPOC ≥ 3.18. OCR FLAG: the signature is garbled/truncated in the source. The printed fragment reads:… UINT nStrings, UWORD *pOffset, UINT startStr);(line lacks the return type,pstate,pFcb,pBuffer,len,findMode). By analogy withDbfFindReadthe full signature is almost certainly:/* RECONSTRUCTED — verify against p_dbf.h before use */ INT DbfFindReadField(UINT *pstate, VOID *pFcb, VOID *pBuffer, UINT len, UINT findMode, UINT nStrings, UWORD *pOffset, UINT startStr);[PLIB pp.207–208]
findMode is an OR of three parts [PLIB pp.207–208]:
- Max match length in any one string field (0…255; 255 = no truncation) occupies the low part
of
findMode. - Direction / start:
DbfFindForwards,DbfFindBackwards,DbfFindFirst,DbfFindLast. - Case:
DbfFindCaseIndependentorDbfFindCaseDependent.
Finding across continuation sub-fields (only needed if strings may exceed 255 chars): OR
0x1400 into len (whose base value cannot exceed 255); set the length component of findMode to
255 and OR in 0x4000; only case-independent matching is allowed (OR DbfFindCaseIndependent);
then OR in the direction flag as usual. [PLIB p.208]
No per-field getter/setter exists. The DBF API deals in whole records only. There is no function to read or write an individual Word/Long/Double/String field. The application must pack and unpack field bytes by hand into/out of the
DbfRecord.dataarea, following the FIR field order and the string leading-byte-count convention. The find services are the only ones that interpret field structure, and only for string matching.
2.8 Error names (DBF)
E_FILE_EOF (past end / before start), E_FILE_INVALID (bad signature / FIR / no-index misuse),
E_FILE_RECORD (bad buffer length or record too big for buffer), E_GEN_OVER (65534-record limit
hit on append). DbfClose/DbfFlush propagate p_close/p_write errors; most read services
propagate p_seek/p_read errors. [PLIB pp.199, 205–206] The System Services manual names the
same conditions EofErr, InvalidFileErr, and the panics PanicDbf1 (bad handle) / PanicDbf2
(bad offset). [SysSvc §20]
Part 3 — Worked recipe: create schema → append → read back
Assembled strictly from the manual's prose (PLIB ch. 14). Field packing is done by hand because there is no per-field API. Illustrative — variable names/details are the author's; the API calls, struct layout, and offsets are per the manual.
Schema for the example: two fields — field 0 = Long (FIR byte 1), field 1 = String (FIR byte
3).
Step 1 — Create the file with header + FIR
Pre-fill a DbfHeader completely (creating/replacing requires the full header and FIR;
p.199). The FIR is itself a type-2 record, so firHeader is a record header word: top 4 bits =
type 2, low 12 bits = FIR length (here 2). Provide a read-ahead buffer of 4096 (guaranteed
sufficient; p.199) and open with visible type = 1.
#include <p_dbf.h>
UBYTE buf[4096]; /* read-ahead buffer, 512..16384; >= largest record */
VOID *fcb;
INT state;
DbfHeader h;
DbfOpenArgs args;
/* --- header --- */
p_bfill(&h.fileType[0], DbfHeaderNameSize, 0); /* zero the 16-byte signature */
p_scpy(&h.fileType[0], "MYAPPDatabase"); /* pad remainder stays 0 (p.196) */
h.createVersion = DbfVersion(); /* p.203 */
h.dataStart = 22; /* no extended header (p.196) */
h.needVersion = DbfVersion();
/* --- FIR (type 2, length 2): field0=Long(1), field1=String(3) --- */
h.firHeader = (2 << 12) | 2; /* top 4 bits type, low 12 len */
h.fir[0] = 1; /* Long (p.197) */
h.fir[1] = 3; /* String (p.197) */
args.pFcb = &fcb;
args.fName = (UBYTE *)"MYDATA.DBF";
args.mode = P_FCREATE | P_FUPDATE; /* create new, writable (Files ch)*/
args.pHead = &h;
state = DbfStateDisabled; /* build index, blocking (p.199) */
if (DbfQuickOpen(&state, &args, &buf[0], sizeof(buf), 1) < 0)
/* handle error (p_fparse / p_open / E_FILE_* errors) */;
Step 2 — Pack a record by hand and append it
A record to be appended must sit at the start of the buffer as a DbfRecord: 2-byte header
then data; DbfAppend builds the header from those two bytes and len is the data length only
(p.205). Pack the Long field (its byte layout is the app's responsibility), then the String field
as a leading count byte followed by its characters (p.197).
DbfRecord *rec = (DbfRecord *)&buf[0];
UBYTE *d = &rec->data[0]; /* data starts at buffer offset 2 */
UINT len = 0;
LONG idNum = 42;
TEXT *nameStr = "Widget";
UINT nameLen = p_slen(nameStr); /* <= 255 for a plain string field */
/* field 0: Long (4 bytes, app-defined byte order) */
p_bcpy(&d[len], &idNum, sizeof(LONG)); len += sizeof(LONG);
/* field 1: String = leading byte count + chars (p.197) */
d[len++] = (UBYTE)nameLen;
p_bcpy(&d[len], nameStr, nameLen); len += nameLen;
/* header bytes are set by DbfAppend from rec->header; type is the current type (1) */
if (DbfAppend(fcb, len) < 0) /* appends, becomes current record (p.205) */
/* handle E_GEN_OVER / E_FILE_RECORD / p_write errors */;
Step 3 — Read the record back and unpack
After open the current record is 0 (p.199). Reads return the data length and write the
DbfRecord offset (header included) into *pOffset. Read the first record, then unpack in FIR
order. (DbfCopyDown first if you intend to edit in the buffer — p.201/SysSvc §20.)
UWORD off;
INT rlen = DbfFirstRead(fcb, &off); /* read record 0 (p.205) */
if (rlen >= 0) {
DbfRecord *r = (DbfRecord *)&buf[off];
UBYTE *p = &r->data[0];
LONG gotId;
UBYTE slen;
TEXT gotName[256];
p_bcpy(&gotId, &p[0], sizeof(LONG)); /* field 0: Long */
p += sizeof(LONG);
slen = *p++; /* field 1: count byte, then chars (p.197) */
p_bcpy(&gotName[0], p, slen);
gotName[slen] = 0;
/* gotId == 42, gotName == "Widget" */
}
DbfFlush(fcb); /* ensure written data is on the medium (p.200) */
DbfClose(fcb); /* p.200 */
Notes tied to the model:
- To update the record, pack a new
DbfRecordat buffer offset 0 and callDbfUpdate(&state, fcb, len); the record is deleted and re-appended at EOF (p.206). - To delete, position on it (e.g.
DbfAbsRead) and callDbfEraseRead(&state, fcb, &off)(p.206); the file does not shrink untilDbfCompress(non-Flash) or aDbfCopyFilerebuild (p.195). DbfFindRead/DbfFindReadFieldcan locate a record by matching a wildcard against its string fields (p.207–209); numeric fields are not searchable by these services.