VX/MSG - OpenVMS Message Compiler
Overview
VX/MSG is a native reimplementation of the OpenVMS message compiler for Linux. It reads OpenVMS message-definition files (.msg) and compiles each message into an indexed message database that the runtime reads through SYS$GETMSG, so migrated applications keep the same OpenVMS message calls and message codes without source changes.
The compiler preserves the OpenVMS message-code layout (facility, message number, and severity) and reproduces the SYS$MESSAGE:MESSAGES.SYS file structure that VMS applications expect. It can also emit 3GL constant sources, so application code continues to reference message symbols by name exactly as it did on OpenVMS. Full compatibility with SYS$GETMSG, SYS$FAO, and SYS$FAOL means messages are retrieved and formatted at runtime with no changes to calling code.
<!-- Webflow embed (min). Source: msg-page-technical/msg-tech-body-embed-dark.html → python3 website/embeds/minify_webflow_embed.py … -->
<div class="s7-tech-embed" data-vxmsgd-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="architecture">Architecture and compilation pipeline</h2><p>
VX/MSG is a small, single-pass compiler, not a long-running service. It reads an
OpenVMS-style message-definition file (<code>.msg</code>) and, in its default mode,
writes each message as a fixed-size record into an indexed database that the VX/RT
runtime reads back through <code>SYS$GETMSG</code>. There is no server process to
administer: compilation happens once per build, and lookups happen inside the migrated
application. The table maps each OpenVMS message-system component to its VX/MSG
counterpart.
</p><div class="table-wrapper"><table><thead><tr><th>OpenVMS component</th><th>VX/MSG counterpart</th><th>Role</th><th>Status</th></tr></thead><tbody><tr><td>MESSAGE (message compiler)</td><td><code>msg</code></td><td>Parses <code>.msg</code> facilities, symbols, text, and qualifiers and compiles them into the indexed message database.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SYS$MESSAGE:MESSAGES.SYS</code></td><td>Indexed (ISAM) database via VX/RMS</td><td>The on-disk message store, keyed by message code, that the runtime reads at lookup time.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SYS$GETMSG</code></td><td>VX/RT <code>SYS$GETMSG</code></td><td>Looks a message up by its 32-bit code at run time and returns the text plus argument metadata.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SYS$FAO</code> / <code>SYS$FAOL</code></td><td>VX/RT formatting services</td><td>Substitute FAO directives (for example <code>!UL</code>, <code>!AS</code>) in the retrieved text at run time.</td><td><span class="ac-live">live</span></td></tr><tr><td>Shared message image</td><td><code>msgsec</code></td><td>Flattens the indexed database into a memory-mappable image (<code>messages.shr</code>) for a fast lookup path.</td><td><span class="ac-live">live</span></td></tr><tr><td>3GL message constants</td><td><code>msg -c</code> / <code>-h</code> / <code>-f</code></td><td>Emit C, C header, or FORTRAN constant sources so application code references message symbols by name.</td><td><span class="ac-live">live</span></td></tr></tbody></table></div><h6>Data flow</h6><p>The <strong>compile-time</strong> flow runs offline, once per build:</p><style>.s7-tech-embed .process-flow {margin: 1.5rem 0;padding: 16px;border: 1px solid var(--s7t-border);border-radius: 10px;background: rgba(255, 255, 255, 0.01);}.s7-tech-embed .process-stages,.s7-tech-embed .process-branches {display: grid;gap: 10px;}.s7-tech-embed .process-stages {grid-template-columns: repeat(3, minmax(0, 1fr));}.s7-tech-embed .process-branches {grid-template-columns: repeat(2, minmax(0, 1fr));margin-top: 10px;}.s7-tech-embed .process-stage {min-width: 0;padding: 15px;border-top: 3px solid var(--s7t-accent);border-radius: 6px;background: var(--s7t-surface2);color: var(--s7t-text);}.s7-tech-embed .process-branch,.s7-tech-embed .process-output {border: 1px solid var(--s7t-note-bd);border-top-width: 3px;background: var(--s7t-note-bg);}.s7-tech-embed .process-number,.s7-tech-embed .process-label {display: block;margin-bottom: 10px;font-family: var(--s7t-mono);color: var(--s7t-accent);font-size: 0.76em;font-weight: 700;letter-spacing: 0.08em;text-transform: uppercase;}.s7-tech-embed .process-stage strong,.s7-tech-embed .process-stage span:last-child {display: block;}.s7-tech-embed .process-stage strong {margin-bottom: 4px;color: var(--s7t-text);}.s7-tech-embed .process-stage span:last-child {color: var(--s7t-muted);font-size: 0.9em;line-height: 1.45;}@media (max-width: 800px) {.s7-tech-embed .process-stages {grid-template-columns: 1fr;}}@media (max-width: 600px) {.s7-tech-embed .process-branches {grid-template-columns: 1fr;}}</style><div class="process-flow" aria-label="VX/MSG compile-time flow"><div class="process-stages"><div class="process-stage"><span class="process-number">Compile 01</span><strong>messages.msg</strong><span>Message definitions enter the compiler on standard input.</span></div><div class="process-stage"><span class="process-number">Compile 02</span><strong>msg compiler</strong><span>Parses facilities, symbols, severities, text, and qualifiers.</span></div><div class="process-stage"><span class="process-number">Compile 03</span><strong>Select output mode</strong><span>The default database path and optional source-generation path remain separate.</span></div></div><div class="process-branches" aria-label="Compiler output branches"><div class="process-stage process-branch"><span class="process-label">Default output</span><strong>SYS$MESSAGE:MESSAGES.SYS</strong><span>Writes the indexed message database as <code>.dat</code> and <code>.idx</code> files.</span></div><div class="process-stage process-branch"><span class="process-label">-c, -h, or -f output</span><strong>Language constants</strong><span>Writes C, C header, or FORTRAN constant source to standard output.</span></div></div></div><p>The <strong>run-time</strong> flow happens inside the migrated application:</p><div class="process-flow" aria-label="VX/MSG runtime lookup and formatting flow"><div class="process-stages"><div class="process-stage"><span class="process-number">Runtime 01</span><strong>MESSAGES.SYS</strong><span>The compiled indexed database is the runtime message source.</span></div><div class="process-stage"><span class="process-number">Runtime 02</span><strong>msgsec -f</strong><span>Flattens the indexed records into <code>messages.shr</code>.</span></div><div class="process-stage"><span class="process-number">Runtime 03</span><strong>Memory map</strong><span>The runtime maps <code>messages.shr</code> for fast indexed access.</span></div><div class="process-stage"><span class="process-number">Runtime 04</span><strong>SYS$GETMSG(code)</strong><span>Looks up the code and returns the matching message text and metadata.</span></div><div class="process-stage"><span class="process-number">Runtime 05</span><strong>SYS$FAO or SYS$FAOL</strong><span>Substitutes directives such as <code>!UL</code> and <code>!AS</code>.</span></div><div class="process-stage process-output"><span class="process-number">Runtime 06</span><strong>Formatted line</strong><span>The migrated application receives the final display text.</span></div></div></div><div class="callout"><strong>No runtime rebuild step.</strong> The runtime reads the message database fresh at
lookup time, so a recompiled <code>MESSAGES.SYS</code> takes effect on the next run with no
relink of the calling application.
</div><h2 id="msg-format">The .msg message-definition format</h2><p>
A <code>.msg</code> file is line-oriented and uses the same syntax as the OpenVMS message
source it replaces. The scanner recognises four line shapes: comments, control words
(leading <code>.</code>), symbol names, and message text with optional per-message
qualifiers. Existing OpenVMS message sources compile through VX/MSG without editing.
</p><h6>Control words</h6><div class="table-wrapper"><table><thead><tr><th>Control word</th><th>Effect</th></tr></thead><tbody><tr><td><code>.FACILITY name, num [/SYSTEM] [/PREFIX=xxx]</code></td><td>Starts a facility and resets numbering. A user facility number is OR'd with the user-supplied bit unless <code>/SYSTEM</code> is given; <code>/PREFIX</code> sets the symbol prefix.</td></tr><tr><td><code>.BASE n</code> (or <code>.BASE ^Xhhh</code>)</td><td>Sets the next message number. A user facility adds the user base offset; <code>/SYSTEM</code> facilities use the number exactly. The hex form uses a <code>^X</code> prefix.</td></tr><tr><td><code>.SEVERITY name</code></td><td>Sets the default severity for following messages: <code>SUCCESS</code>, <code>INFORMATIONAL</code>, <code>WARNING</code>, <code>ERROR</code>, or <code>FATAL</code> / <code>SEVERE</code>.</td></tr><tr><td><code>.IDENT text</code></td><td>Stores an ident string for the module.</td></tr><tr><td><code>.TITLE text</code></td><td>Stores a title string for the module.</td></tr><tr><td><code>.PAGE</code></td><td>Page break. No effect on the compiled output.</td></tr><tr><td><code>.END</code></td><td>End-of-file marker.</td></tr></tbody></table></div><h6>Symbols, message text, and qualifiers</h6><p>
A symbol name at the start of a line names the message. Message text follows in one of two
delimiter styles: double quotes (<code>"...text..."</code>) or angle brackets
(<code><...text...></code>), with angle brackets supporting nesting. A trailing
<code> - </code> continues a definition onto the next line, so a symbol, its qualifiers, and
its text can span several lines. Per-message qualifiers refine a single message:
</p><div class="table-wrapper"><table><thead><tr><th>Qualifier</th><th>Meaning</th></tr></thead><tbody><tr><td><code>/FAO_COUNT=n</code></td><td>Number of FAO substitution arguments the message text expects.</td></tr><tr><td><code>/USER=n</code></td><td>A user-supplied value stored alongside the message.</td></tr><tr><td><code>/IDENTIFICATION=x</code></td><td>Accepted for source compatibility.</td></tr><tr><td><code>/SUCCESS /INFORMATIONAL /WARNING /ERROR /FATAL /SEVERE</code></td><td>Override the default severity for this one message.</td></tr></tbody></table></div><h6>Example</h6><p>
A user facility for a funds-transfer service, with FAO directives left in the text for the
runtime to substitute through <code>SYS$FAO</code>:
</p>
<pre><span class="kw">.facility</span> BANKING, 2048 <span class="kw">/PREFIX=</span>BANK$_
<span class="kw">.base</span> 0
<span class="kw">.severity</span> ERROR
INSUFFUNDS <span class="str"><insufficient funds in account !UL></span> <span class="kw">/FAO_COUNT=</span>1
ACCTLOCKED <span class="str"><account !AS is locked></span> <span class="kw">/FAO_COUNT=</span>1
XFERDONE <span class="str"><transfer of !UL cents completed></span> <span class="kw">/FAO_COUNT=</span>1 <span class="kw">/SUCCESS</span></pre>
<p>
Here <code>!UL</code> (unsigned longword) and <code>!AS</code> (counted ASCII string) are VMS
FAO directives. VX/MSG stores the message text verbatim and records the argument count from
<code>/FAO_COUNT</code>, leaving the substitution itself to <code>SYS$FAO</code> and
<code>SYS$GETMSG</code> at run time.
</p><h2 id="message-codes">Message codes and the output record</h2><p>
VX/MSG reproduces the OpenVMS 32-bit message-code layout exactly, so codes compiled on
Linux match the values applications and callers already expect. Each code is assembled from
the facility number, the message index (base plus sequence within the facility), and the
severity:
</p>
<pre>code = (facility << 16) | ((base + sequence) << 3) | severity</pre>
<p>
Bits 0 to 2 hold the severity, bits 3 to 15 hold the message index, and bits 16 to 27 hold
the facility number. A user facility (one declared without <code>/SYSTEM</code>) carries the
user-supplied bit in its facility word. The severity encoding matches OpenVMS:
</p><div class="table-wrapper"><table><thead><tr><th>Severity</th><th>Value</th></tr></thead><tbody><tr><td>WARNING</td><td><code>0</code></td></tr><tr><td>SUCCESS</td><td><code>1</code></td></tr><tr><td>ERROR</td><td><code>2</code></td></tr><tr><td>INFORMATIONAL</td><td><code>3</code></td></tr><tr><td>FATAL / SEVERE</td><td><code>4</code></td></tr></tbody></table></div><h6>The message record</h6><p>
Each message is written to the database as a fixed-size record holding the code, the
facility number, argument metadata, and the facility, symbol, and message-text strings:
</p>
<pre><span class="kw">typedef struct</span> {
U32 msgnum; <span class="cmt">/* code with severity masked off */</span>
U32 facnum;
<span class="kw">unsigned char</span> optinfo[4]; <span class="cmt">/* [0]=severity [1]=FAO count [2]=user [3]=reserved */</span>
<span class="kw">char</span> facnam[32];
<span class="kw">char</span> msgnam[64];
<span class="kw">char</span> message[256];
} MESSAGE_RECORD;</pre>
<div class="callout"><strong>Severity is masked out of the key.</strong> The ISAM key is the message code with
the low three severity bits cleared, and the actual severity is preserved in
<code>optinfo[0]</code>. A lookup by code therefore matches regardless of the severity a
caller passes, matching OpenVMS behaviour. Duplicate keys update the existing record rather
than failing the build.
</div><h2 id="cli">Command-line interface</h2><p>
The compiler reads a <code>.msg</code> file from standard input (or from a filename argument)
and selects one of two behaviours. With no source-generation flag it populates the indexed
message database. With a source-generation flag it writes a 3GL constant source to standard
output and does not touch the database; the two modes are mutually exclusive.
</p><div class="table-wrapper"><table><thead><tr><th>Flag</th><th>Effect</th></tr></thead><tbody><tr><td><code>-q</code> / <code>--quiet</code></td><td>Suppress the startup banner.</td></tr><tr><td><code>-c</code></td><td>Emit a C source file with a linkable constant per message.</td></tr><tr><td><code>-h</code></td><td>Emit a C header file with a <code>#define</code> per message.</td></tr><tr><td><code>-f</code></td><td>Emit a FORTRAN source file with a <code>PARAMETER</code> constant per message.</td></tr><tr><td><code>-n</code></td><td>Emit a C table of <code>{code, FAO_COUNT}</code> pairs for messages that take arguments.</td></tr><tr><td><code>-p</code></td><td>Preserve the original case of symbol names.</td></tr><tr><td><code>-l</code></td><td>Lowercase symbol names.</td></tr><tr><td><code>-s</code></td><td>Emit GNU-ld <code>--defsym</code> linker constants (used with the FORTRAN path).</td></tr></tbody></table></div><p>Flags can be bundled, so <code>msg -qc</code> is a quiet C-source generation run.</p><h6>Generated constant shapes</h6><p>The source-generation modes emit one constant per message symbol:</p><div class="table-wrapper"><table><thead><tr><th>Mode</th><th>Output form</th></tr></thead><tbody><tr><td>C source (<code>-c</code>)</td><td><code>int NAME = 0x<code>;</code>, guarded for VMS globalvalue linkage.</td></tr><tr><td>C header (<code>-h</code>)</td><td><code>#define NAME 0x<code></code>, with a comment naming the facility and severity.</td></tr><tr><td>FORTRAN (<code>-f</code>)</td><td><code>PARAMETER NAME = '<code>'x</code>, plus a matching <code>PARAMETER</code> per facility.</td></tr></tbody></table></div><h6>Worked example</h6>
<pre><span class="cmt"># compile the message source into the indexed database</span>
msg -q banking.msg
<span class="cmt"># populates SYS$MESSAGE:MESSAGES.SYS (.dat + .idx)</span>
<span class="cmt"># generate a C header of message constants for the application build</span>
msg -h < banking.msg > bank_messages.h
<span class="cmt"># generate FORTRAN constants</span>
msg -f < banking.msg > bank_messages.for</pre>
<h2 id="runtime-lookup">Runtime lookup and files on disk</h2><p>
At run time the migrated application calls <code>SYS$GETMSG</code> with a message code, and
VX/RT resolves it against the compiled database. The default database is
<code>SYS$MESSAGE:MESSAGES.SYS</code>, where the logical name <code>SYS$MESSAGE</code>
resolves to the message directory under the VX/RT release. The indexed store is two files:
</p><div class="table-wrapper"><table><thead><tr><th>File</th><th>Contents</th></tr></thead><tbody><tr><td><code>MESSAGES.SYS.dat</code></td><td>The message records: codes, facility and symbol names, argument metadata, and message text.</td></tr><tr><td><code>MESSAGES.SYS.idx</code></td><td>The index over message codes used for fast lookup.</td></tr><tr><td><code>messages.shr</code></td><td>Optional memory-mappable image built by <code>msgsec -f</code> for a faster lookup path.</td></tr></tbody></table></div><h6>Lookup path</h6><p><code>SYS$GETMSG</code> prefers the memory-mapped <code>messages.shr</code> image when it is
present and falls back to the indexed database otherwise. If neither is available it returns
default placeholder text, and an unknown code returns the standard
<code>SS$_MSGNOTFND</code> status, so callers see well-defined behaviour rather than a crash.
The argument metadata returned to the caller matches what the compiler wrote: the FAO
argument count and the user value travel with the message text.
</p><h6>Viewing the database</h6><p>
Because the store mirrors the OpenVMS <code>SYS$MESSAGE</code> layout, compiled messages can
be inspected from VX/DCL just as on OpenVMS, which is useful for verifying a message-source
compile during a migration:
</p>
<pre>$ <span class="kw">TYPE/PAGE</span> SYS$MESSAGE:MESSAGES.SYS</pre>
<h2 id="compatibility">Compatibility and language support</h2><p>
VX/MSG targets source-level compatibility with the OpenVMS message system: existing
<code>.msg</code> sources compile unchanged, message codes keep their original layout, and
the runtime message calls behave as they did on OpenVMS. The matrix below summarises what is
supported today.
</p><div class="table-wrapper"><table><thead><tr><th>Capability</th><th>Status</th></tr></thead><tbody><tr><td>Compile <code>.msg</code> to indexed <code>MESSAGES.SYS</code> (<code>.dat</code> / <code>.idx</code>)</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SYS$GETMSG</code>, <code>SYS$FAO</code>, <code>SYS$FAOL</code> retrieval and formatting</td><td><span class="ac-live">live</span></td></tr><tr><td>C source constants (<code>-c</code>)</td><td><span class="ac-live">live</span></td></tr><tr><td>C header <code>#define</code>s (<code>-h</code>)</td><td><span class="ac-live">live</span></td></tr><tr><td>FORTRAN constants (<code>-f</code>)</td><td><span class="ac-live">live</span></td></tr><tr><td>FAO-count table (<code>-n</code>) and FORTRAN linker constants (<code>-s</code>)</td><td><span class="ac-live">live</span></td></tr><tr><td>Symbol-case control (<code>-p</code> / <code>-l</code>)</td><td><span class="ac-live">live</span></td></tr><tr><td>Memory-mapped shared image (<code>msgsec</code>)</td><td><span class="ac-live">live</span></td></tr><tr><td>View compiled messages from VX/DCL</td><td><span class="ac-live">live</span></td></tr></tbody></table></div><h6>Message constants for application code</h6><p>
Applications that referenced message symbols by name on OpenVMS continue to do so on Linux.
VX/MSG generates constant sources for C, C headers, and FORTRAN, so C and C++ callers include
the generated header and FORTRAN callers include the generated <code>PARAMETER</code>
definitions. Each constant carries the same message code the runtime resolves, keeping the
compile-time symbol and the run-time lookup in step.
</p><div class="callout"><strong>Migration posture.</strong> VX/MSG preserves the message-code layout, the
<code>SYS$MESSAGE</code> file structure, and the <code>SYS$</code> retrieval calls, so
message handling migrates with minimal changes to application code.
</div><h2 id="quickref">Quick reference</h2><h6>Control words</h6><div class="kw-grid"><div>.FACILITY</div><div>.BASE</div><div>.SEVERITY</div><div>.IDENT</div><div>.TITLE</div><div>.PAGE</div><div>.END</div></div><h6>Qualifiers</h6><div class="kw-grid"><div>/SYSTEM</div><div>/PREFIX=</div><div>/FAO_COUNT=n</div><div>/USER=n</div><div>/IDENTIFICATION=</div><div>/SUCCESS</div><div>/INFORMATIONAL</div><div>/WARNING</div><div>/ERROR</div><div>/FATAL</div><div>/SEVERE</div></div><h6>Severity values</h6><div class="kw-grid"><div>WARNING = 0</div><div>SUCCESS = 1</div><div>ERROR = 2</div><div>INFORMATIONAL = 3</div><div>FATAL / SEVERE = 4</div></div><h6>Compiler flags</h6><div class="kw-grid"><div>-q / --quiet</div><div>-c</div><div>-h</div><div>-f</div><div>-n</div><div>-p</div><div>-l</div><div>-s</div></div><h6>Runtime services</h6><div class="kw-grid"><div>SYS$GETMSG</div><div>SYS$FAO</div><div>SYS$FAOL</div></div><h6>Common commands</h6>
<pre><span class="cmt"># compile to the indexed message database</span>
msg -q messages.msg
<span class="cmt"># generate constants</span>
msg -c < messages.msg > constants.c
msg -h < messages.msg > constants.h
msg -f < messages.msg > constants.for
<span class="cmt"># view compiled messages via VX/DCL</span>
$ TYPE/PAGE SYS$MESSAGE:MESSAGES.SYS</pre>
</div>