This interface provides record- or element-oriented sequential read and write access to FLAM5 archives. A FLAM5 archive is a proprietary container format that stores data in compressed, encrypted and/or signed members, organized in a directory. Each member can hold records (with or without length prefix), binary blocks or typed elements.
The interface is deliberately designed as a clean thread-safe API: every function accepts an optional error-trace buffer (pcErr) as its first parameter. If the caller does not need the trace, it passes NULL. The FLAM return code is always the direct function return value. There is no global errno-style state.
Compared to the FLUC byte interface (FLCBYT.h), which provides general-purpose file I/O with format conversion, FLMREC is focused exclusively on FLAM5 archives and exposes archive-level concepts such as members, tables, and member state (metadata).
The archive string passed to siFmrOpenArchive() describes the archive to open, using the same CLP syntax as the FLCL command line. The first keyword determines the access mode:
from(...) or read(...) – open for sequential read (decompression / decryption)to.new(...), to.old(...), to.mod(...) or write(...) – open for write (compression / encryption). new creates a new archive, old appends to an existing one, mod updates an existing archive.Example archive strings:
"from(file='backup.flam')" "from(file='backup.flam' filter.member(name='*.txt'))" "to.new(file='backup.flam' comp.lzip() crypt.aes(key='secret'))" "to.old(file='backup.flam')" "to.mod(file='backup.flam')"
Use siFmrHelp() and siFmrSyntax() with FMR_DECO_ARC (FMR_READ_ARC) or FMR_COMP_ARC (FMR_WRITE_ARC) to retrieve interactive help and syntax information for the archive string.
The format string describes how the data inside a FLAM5 member is presented to the application. It uses the same syntax as the format keyword of the FLCBYT byte interface. When NULL is passed, FORMAT.RECORD() is used by default.
Examples:
"format.record()" "format.record(ccsid='IBM-1141')" "format.element()" "format.bin()" "format.text(method=LF)"
Use siFmrHelp() with FMR_READ_FORMAT or FMR_WRITE_FORMAT to get the complete format string reference.
The state string transports member metadata between the application and the archive. When writing, the state string sets attributes for the next member (original file name, record format, record length, character set, etc.). The syntax is the same as for the FLCBYT state parameter.
Example:
"STATE(ORGF='data.txt' RECF=FB RECL=80)"
These attributes can be set individually with siFmrSetMbrName() and siFmrSetMbrState(), or read back after open with pcFmrGetMbrState().
All functions that can fail return a FLAM return code (see FLMRTC.h). FLMRTC_OK (0) indicates success. Any other value indicates an error or a special condition such as end of file, end of member, or end of table.
Special read conditions (not fatal errors):
The optional pcErr parameter receives a human-readable multi-line error trace (context, messages, and call chain) when an error occurs. If pcErr is NULL the trace is suppressed. The caller must provide a buffer of at least FLMERR_SIZ bytes (32 KB). The trace is only meaningful when the function returned a non-zero code.
Use pcFmrErrorMessage() to map a return code to a short English message.
Example:
char acErr[FLMERR_SIZ];
int siLen = sizeof(acBuf);
int rtc = siFmrGet(acErr, pvHdl, &siLen, acBuf, FLMELM_NOSKIP, NULL);
if (rtc == FLMRTC_EOF) { /- end of data -/ }
else if (rtc != FLMRTC_OK) {
fprintf(stderr, "Error %d: %s\n%s", rtc, pcFmrErrorMessage(rtc), acErr);
}
The standard call sequence for a single archive operation is:
1. siFmrOpen() -- open global session (optional but recommended) 2. siFmrOpenArchive() -- open archive for read or write 3. siFmrSetMbrState() -- set member state (write mode, optional) siFmrGet() / loop -- read records (read mode) siFmrPut() / loop -- write records (write mode) 4. siFmrCloseArchive() -- close archive (commit or rollback) 5. siFmrClose() -- close global session
Calling siFmrOpen() is optional: passing pvGbl == NULL to siFmrOpenArchive() causes FLAM to create and release a private session automatically. However, an explicit siFmrOpen() call is recommended because:
isSys=1 and isStd=1 load system variables and DD:STDENV before the first archive operation, ensuring correct code page and locale settings.Minimal example (write one member):
char acErr[FLMERR_SIZ];
void* pvGbl = NULL;
void* pvHdl = NULL;
/- open global session -/
if (siFmrOpen(acErr, &pvGbl, 1, 1, NULL, NULL) != FLMRTC_OK) { /- handle -/ }
/- open archive -/
if (siFmrOpenArchive(acErr, pvGbl, &pvHdl,
"to.new(store.file(name='out.flam'))", NULL, 0) != FLMRTC_OK) { ... }
/- set member state and write records -/
siFmrSetMbrState(NULL, pvHdl, pcStateFromFcb);
while (more_records)
siFmrPut(acErr, pvHdl, siLen, pcRec, NULL);
/- close archive, then global session -/
siFmrCloseArchive(acErr, pvHdl, TRUE, 0, NULL, NULL);
siFmrClose(pvGbl);
When writing programs that use FLMREC, it can be useful to capture diagnostic messages (warnings, statistics) in memory instead of printing them to the console. Use siFmrOpen() to open a global session with in-memory logging enabled. The log buffer is retrieved with siFmrGetLog() and cleared with siFmrClearLog(). The session is closed with siFmrClose(). The global session handle (pvGbl) is passed to siFmrOpenArchive() to share state (environment, logging) across multiple archive operations. If NULL is passed, each siFmrOpenArchive() call creates its own internal global state that is released on close.
FLMREC is fully thread-safe. All state is held in the handle returned by siFmrOpenArchive(). The optional pcErr buffer must be provided per call from the caller's own stack or a per-thread allocation. No global or static writable variables are used.
To write more than one member to a FLAM5 archive opened with siFmrOpenArchive(), use siFmrNewMember(). This internally performs a partial close of the current member and re-opens a new member within the same archive handle. It is significantly faster than calling siFmrCloseArchive() followed by a new siFmrOpenArchive() with to.old().
Typical multi-member write sequence in pseudo-code:
char acErr[FLMERR_SIZ];
void* pvHdl = NULL;
siFmrOpenArchive(acErr, pvGbl, &pvHdl,
"to.new(file='out.flam')", "format.record()", 0);
siFmrSetMbrName(NULL, pvHdl, "first.dat");
siFmrPut(acErr, pvHdl, siLen, pcRec, NULL); /- ... -/
siFmrNewMember(acErr, pvHdl, NULL, 0, NULL, NULL);
siFmrSetMbrName(NULL, pvHdl, "second.dat");
siFmrPut(acErr, pvHdl, siLen, pcRec, NULL); /- ... -/
siFmrCloseArchive(acErr, pvHdl, TRUE, 0, NULL, NULL);
FLAM5 archives support table-structured data. When the format string references a table schema, the data is organized in named tables of rows. On write, call siFmrSetTabName() before putting rows that belong to a specific table. On read, pcFmrGetTabName() returns the name of the current table. When end-of-table (ENDOFT) is configured in the format string, siFmrGet() and siFmrLocate() return FLMRTC_EOT at the boundary between tables.
When format.element() is used as the format string, each siFmrPut() call submits exactly one serialized element structure of type FlmElmRec0 (defined in FLMDEF.h). The element's matrix type field (uiMat) determines how the archive interprets the element stream and how many elements form one logical row.
Standard formats (matrix types below FLMMAT_TYP_TABELM, e.g. FLMMAT_TYP_STDREC, FLMMAT_TYP_TXTRST, FLMMAT_TYP_XMLELM): The column count per logical row is fixed by the matrix type itself and requires no additional header element. For example, FLMMAT_TYP_TXTRST always carries 2 elements per logical record (the text body and the suppressed trailing-whitespace rest), while all FLMMAT_TYP_STD* and FLMMAT_TYP_DAT* types carry exactly 1 element per logical record. Submitting elements one by one is sufficient for these matrix types.
Table formats (matrix type >= FLMMAT_TYP_TABELM = 0xF0, i.e. isMatTypTab() is true): The column count is dynamic and is communicated through a mandatory header element:
uiTyp field equals FLMELM_TYPTAB_HEADER (0x0A).uiTyp >> 8) carry the column count uiColCnt for the rows that follow.uiColCnt. One logical row consists of exactly uiColCnt consecutive element calls.uiColCnt is updated for subsequent rows.Row alignment at flush boundaries: When siFmrFlush(), siFmrNewMember(), or siFmrCloseArchive() is called, all pending elements must form complete rows. If the accumulated element count is not a multiple of uiColCnt, FLMRTC_ITF is returned and the complete rows that were already accumulated are still written.
Example – write a 3-column table member with two data rows:
/- build and submit the table header element (colCnt=3) -/
FlmElmRec0* psHdr = ...; /- uiMat=FLMMAT_TYP_TABELM, -/
/- uiTyp=FLMELM_TYPTAB_HEADER|(3<<8) -/
siFmrPut(acErr, pvHdl, FLMELMREC0_LENGTH(psHdr), (U08*)psHdr, NULL);
/- submit 6 data elements = 2 complete rows of 3 columns each -/
for (int i = 0; i < 6; i++) {
FlmElmRec0* psElm = ...;
siFmrPut(acErr, pvHdl, FLMELMREC0_LENGTH(psElm), (U08*)psElm, NULL);
}
siFmrCloseArchive(acErr, pvHdl, TRUE, 0, NULL, NULL);
The FLAM environment is loaded by siFmrOpen() before the first archive operation. When siFmrOpenArchive() is called with pvGbl == NULL, the environment is loaded automatically for that call. The most important environment variable is LANG, which controls the default character set used for text conversion. For the full list of FLAM environment variables see the FLCL manual. Additional variable assignments can be injected through the pcEnv parameter of siFmrOpen() without modifying the process environment. On z/OS, system variables and the standard environment file (DD:STDENV) are loaded via the isSys and isStd parameters of siFmrOpen().
When building archive or format strings on EBCDIC systems, the CLP parser must interpret the string bytes correctly. FLAM supports the same critical-punctuation escape sequences and CCSID area markers as the FLCBYT interface (see FLCBYT.h, section CODPGE for full details).
Critical punctuation characters that differ across EBCDIC code pages:
! $ # @ [ \ ] ^ ` { | } ~
Escape sequences for these characters (usable inside CLP strings):
! = &EXC; $ = &DLR; # = &HSH; @ = &ATS;
[ = &SBO; \ = &BSL; ] = &SBC; ^ = &CRT;
` = &GRV; { = &CBO; | = &VBR; } = &CBC;
~ = &TLD;
CCSID area markers allow mixing literal code pages with variable data:
snprintf(acArc, sizeof(acArc),
"to.new(file='&1047;&TLD;/archive.flam')");
snprintf(acArc, sizeof(acArc),
"&1047;to.new(file='&0000;%s;&1047;')", pcPath);
To use empty parameter lists and fixed-width integer types, the language level on z/OS must be at minimum C99. Additionally, long name support and DLL usage must be activated:
LANG(EXTC99),LO,DLL
We recommend establishing application-default runtime options with the CEEUOPT assembler language source program. The recommended runtime options are available as members CEEUOPTx (x=3 for 31-bit, x=6 for 64-bit) in the hlq.FLAM.SRCLIB partitioned data set.
A C sample program SCFMRPUT uses this interface together with the FLUC byte interface (FLCBYT.h) to read an input file and write it as a named member into a FLAM5 archive. On z/OS it can be found in SRCLIBC(SCFMRPUT) with compile and link steps in JOBLIB(SBUILD). On other platforms (Windows, UNIX) the source is located in the 'sample' directory.
COBOL and PL/1 code examples demonstrating archive open, sequential read, multi-member write and close via the FLMRECLB load module stubs are included in FLMRECLB.h (sections COBOLSMP, COBOLWRT, PL1SMP).