FLMREC-API
FLAM Record Interface Version: 5.2.1-42969
FLAM Record Interface

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).

Archive string

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.

Format 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.

State string

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().

Error handling

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):

  • FLMRTC_EOF – end of the current member's data stream
  • FLMRTC_EOM – end of member (more members may follow in the archive)
  • FLMRTC_EOT – end of table (only relevant with table-formatted data)

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);
}

Recommended API lifecycle

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:

  • On z/OS, isSys=1 and isStd=1 load system variables and DD:STDENV before the first archive operation, ensuring correct code page and locale settings.
  • A shared session passed to multiple siFmrOpenArchive() calls avoids repeated environment loading and shares log state.
  • Errors during environment initialization are caught explicitly.

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);

Logging

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.

Thread safety

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.

Writing multiple members

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);

Table support

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.

Element interface (write mode)

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:

  • The low byte of the element's uiTyp field equals FLMELM_TYPTAB_HEADER (0x0A).
  • The upper bits (uiTyp >> 8) carry the column count uiColCnt for the rows that follow.
  • A header element MUST be submitted before the first data element of each format (including the very first row of the member).
  • After the header, data elements must arrive in exact multiples of uiColCnt. One logical row consists of exactly uiColCnt consecutive element calls.
  • A new header element signals a format change. All pending data rows (which must be complete) are flushed before the new header is processed, and 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);

Environment variables

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().

Special EBCDIC code page support

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);

Hints for z/OS

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.

Sample programs

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).