Technical Capabilities
Having trouble finding what you need?
Get in touch with us, so we can answer your specific questions directly.
Get In Touch

VX/ACMS – Application Control and Management System

Overview

VX/ACMS is a native C++20 reimplementation of HP/VSI ACMS (Application Control and Management System) for Linux. It provides source-compatible task execution for migrated OpenVMS applications: existing TDF / GDF / ADF sources compile through the ADU, existing COBOL and C step procedures run unchanged, and existing calls to ACMS$ services resolve against the same signatures and status codes as on VMS.

VX/ACMS targets the single-server deployment model used by the majority of production ACMS estates. Multi-node clustering and DDTM/XA are v2 scope items.

Component map

VX/ACMS preserves the ACMS component model verbatim. Each VMS process class has an exact Linux counterpart with the same responsibilities; only the IPC substrate changes (POSIX shared memory and UNIX-domain sockets instead of VMS global sections and DECnet links).

VMS processLinux binaryRoleStatus
ACCacmsdRoot daemon. Owns the SQLite control / audit / queue databases; spawns child processes; listens on the operator socket (/run/acms/operator.sock).live
EXCacms-excPer-application task interpreter. Drives IF / WHILE / REPEAT / GOTO / ATOMIC / EXCHANGE / PROCESSING steps; loads task .so files via dlopen; manages workspace pool.live
SPacms-spProcedure server pool. Loads server group .so files; dispatches PROCESSING CALL steps from EXC via UDS; maintains server process pool sizing.live
CPacms-cpPer-submitter FORMS$ / menu process. Bridges terminal I/O through VSI DECforms libvxdf* on Linux. Handles EXCHANGE steps bound to forms.v1 soon
QTIacms-qtiQueued task initiator. Drains the SQLite-backed task queue; submits deferred tasks to EXC; honours priority ordering.v1 soon
ATLacms-atlAudit trail logger. Receives structured log events from all other components via the SWL library; writes to the audit database.live
SWLliblogswlIn-process event log library. Wraps all structured logging; consumed by ACC / EXC / SP / QTI / ATL.live
SI (agent)libacms_si.soAgent-callable surface. Provides ACMS$CALL, ACMS$SIGN_IN, and 10 other implemented services. Linked by submitter programs.live (12 svc)
ADUadu / adu_compileApplication Definition Utility. Parses TDF / GDF / ADF / MDF; resolves CDD$ records; emits C++; compiles to .so.live (MDF v1)
No RR server in v1. The Remote Request (RR) inter-node server is a v2 item. All inter-process communication in v1 is on a single Linux host via UNIX-domain sockets and shared memory.

Workspace model

Workspaces are the shared-memory records that flow between a submitter, the EXC task interpreter, and SP procedure servers. VX/ACMS maps the VMS global section model onto POSIX shm_open / mmap; the layout of each workspace is determined by its CDD record and is verified at install time by matching a layout_hash.

Workspace lifetimes

LifetimeVMS equivalentScope
TASKTask workspaceAllocated at task call entry; released at EXIT TASK or exception unwind.
USERPer-submitter workspaceLives for the duration of the submitter's ACMS session (sign-in to sign-out).
GROUPGroup workspaceShared across all tasks in the same application group; lifetime = application runtime.
SYSTEMSystem workspaceShared system-wide. Includes the three ACMS-maintained system workspaces (ACMS$SELECTION_STRING, ACMS$PROCESSING_STATUS, ACMS$TASK_INFORMATION).

Workspace handle

Every workspace in VX/ACMS is identified by a handle passed through the CBOR IPC frames:

{
  shm_id:  uint32  // POSIX shm name index
  offset:  uint64  // byte offset within the mapping
  length:  uint32  // record byte length
  version: uint16  // layout_hash low word (checked at first access)
}

CDD integration

Record layouts are authoritative in the CDD database (banking.cdd.db) built by vxcdd --add. Both the ADU compiler and the COBOL/C compilers consume CDD views; no hand-maintained struct duplicates. The layout_hash is a deterministic hash of the flattened CDD field list; a mismatch between the task .so and the server .so causes a hard install-time error.

TASK ARGUMENTS copy boundary. When a submitter passes workspaces to EXC as TASK ARGUMENTS, the contents are copied across the submitter/EXC process split. All other workspace access (EXC ↔ SP) is zero-copy shared memory.

System workspaces

NameContent
ACMS$SELECTION_STRINGTask selector passed by the submitter at ACMS$CALL time.
ACMS$PROCESSING_STATUSStatus returned from the most recent PROCESSING step.
ACMS$TASK_INFORMATIONSubmitter identity, task name, call flags; populated by EXC at task entry.

TDL: Task Definition Language

TDL is the language you use to define ACMS tasks, task groups, and applications. The ADU compiler processes four TDL file types: TDF (tasks), GDF (groups / server definitions), ADF (applications), and MDF (menus). All four compile unchanged through VX/ACMS.

Task Definition File (TDF)

A TDF defines one or more tasks. Each task declares its workspaces, task arguments, and a BLOCK of steps. A minimal task:

REPLACE TASK FUNDS_TRANSFER
    WORKSPACES ARE
        ACCOUNT_WKSP,
        TRANSFER_WKSP,
        AUDIT_WKSP;

    TASK ARGUMENTS ARE
        ACCOUNT_WKSP WITH ACCESS MODIFY,
        AUDIT_WKSP   WITH ACCESS WRITE;

    BLOCK
        EXCEPTION HANDLER
            MOVE "FAILED" TO AUDIT_WKSP.MSG;
            EXIT TASK;

        PROCESSING CALL READ_ACCOUNT
            USING ACCOUNT_WKSP;

        IF ACCOUNT_WKSP.BALANCE < TRANSFER_WKSP.AMOUNT THEN
            MOVE "NSF" TO AUDIT_WKSP.MSG;
            EXIT TASK;
        END IF;

        PROCESSING CALL DEBIT_ACCOUNT
            USING ACCOUNT_WKSP, TRANSFER_WKSP;
    END BLOCK;
END DEFINITION;

Step types

StepPurpose
PROCESSING CALL name USING wksp, …Dispatch to a server procedure in an SP process. The named procedure must appear in the GDF.
EXCHANGE USING wksp WRITING form …Submit a FORMS$ exchange step; data flows through a CP process to the terminal.
ATOMIC BEGIN … END ATOMICBegin a SQLite-backed ATOMIC bracket (DDTM shape preserved). All enclosed steps are within one transaction context.
GET ERROR MESSAGECopy the current ACMS exception text into a workspace field.
MOVE value TO wksp.fieldAssign a literal or workspace field to another workspace field inside EXC (no server dispatch).
IF … THEN … ELSE … END IFConditional based on workspace field comparison.
WHILE condition DO … END WHILELoop while condition holds.
REPEAT … END REPEATUnconditional loop; terminated by EXIT LOOP.
GOTO labelUnconditional branch to a labelled step within the same BLOCK.
EXIT TASKTerminate the task normally.

Group Definition File (GDF)

A GDF defines a server group: which server image (.so) to load, which procedures it exports, and lifecycle hooks:

REPLACE SERVER GROUP BANKING_SERVERS
    PROCEDURE SERVER IMAGE IS "/opt/acms/servers/banking_servers.so";
    INITIALIZATION PROCEDURE IS BANKING_INIT;
    TERMINATION PROCEDURE IS   BANKING_TERM;
    CANCEL PROCEDURE IS         BANKING_CANCEL;
    PROCEDURES ARE
        READ_ACCOUNT,
        DEBIT_ACCOUNT,
        LOG_AUDIT;
END DEFINITION;

Application Definition File (ADF)

An ADF registers tasks and server groups under a named ACMS application. It maps task selectors (used at ACMS$CALL time) to TDF-defined tasks and GDF-defined server groups:

REPLACE APPLICATION BANKING
    TASKS ARE
        FUNDS_TRANSFER     USING TASK GROUP BANKING_TASKS;
    SERVER GROUPS ARE
        BANKING_SERVERS;
END DEFINITION;

TDL keyword reference

ACCESS
ADF
AND
APPLICATION
ATOMIC
BACKUP
BEGIN
BLOCK
CALL
CANCEL
CLEAR
DEFINE
DELETE
DO
ELSE
END
EXCEPTION
EXCHANGE
EXIT
FOR
FROM
GDF
GET
GOTO
GROUP
HANDLER
IF
IMAGE
IN
INITIALIZATION
IS
LOOP
MDF
MESSAGE
MODIFY
MOVE
NOT
ON
OR
PROCEDURE
PROCESSING
READ
REPEAT
REPLACE
SERVER
TASK
TDF
TERMINATION
THEN
TO
USING
WHILE
WITH
WORKSPACES
WRITE

ACMS$ services

Submitter programs link against libacms_si.so and call ACMS$ services to sign in, submit tasks, and sign out. VX/ACMS implements the 12 services used across production estates; the full VMS-compatible signature and status code set is preserved.

ServicePurposeStatus
ACMS$SIGN_INAuthenticate the submitter and open an ACMS session. Validates against the PAM stack (or the internal rights store). Returns a session handle.live
ACMS$SIGN_OUTTerminate the submitter's ACMS session and release all USER-lifetime workspaces.live
ACMS$CALLSubmit a task by application name and task selector. Blocks (or returns immediately with ACMS$_PENDING if non-blocking) until the task completes.live
ACMS$START_CALLAsynchronous task submission. Pairs with ACMS$WAIT_CALL for the completion rendezvous.live
ACMS$WAIT_CALLBlock until a previously ACMS$START_CALL-submitted task completes.live
ACMS$CANCEL_CALLCancel an outstanding ACMS$START_CALL submission before it has been dispatched by EXC.live
ACMS$RAISE_STEP_EXCEPTIONRaise an exception from inside a server step procedure. The EXC EXCEPTION HANDLER fires at the nearest enclosing BLOCK.live
ACMS$SET_SELECTION_STRINGPre-populate the ACMS$SELECTION_STRING system workspace before a ACMS$CALL.live
ACMS$GET_TASK_INFORead the current task's name, submitter identity, and call flags from ACMS$TASK_INFORMATION.live
ACMS$EXCHANGE_IOPerform a forms-exchange I/O step from a server procedure (advanced use). Requires CP process.v1 soon (CP)
ACMS$QUEUE_TASKEnqueue a task for deferred execution by QTI. Returns immediately; task runs when QTI dispatches it.surface live, QTI v1
ACMS$GET_ERROR_MESSAGERetrieve the text of the most recent ACMS exception. Corresponds to the GET ERROR MESSAGE TDL step when called from a server procedure.live
Status codes. VX/ACMS uses the same 32-bit ACMS status codes as VMS: ACMS_NORMAL (1), ACMS$_NOSUCHAPP, ACMS$_STEP_ERR, etc. A full table of status codes is available in Appendix A of the Programmer's Reference Manual.

Calling ACMS$CALL from C

/* Minimal ACMS$CALL from C: funds transfer */
#include <acms_si.h>

acms_session_t  sess;
acms_status_t   rc;

/* sign in */
rc = ACMS$SIGN_IN("operator", &sess);
if (rc != ACMS_NORMAL) exit(1);

/* populate TASK ARGUMENTS workspaces via shm handle */
memcpy(ws_account.shm_ptr, &account_rec, sizeof(account_rec));
memcpy(ws_transfer.shm_ptr, &transfer_rec, sizeof(transfer_rec));

/* call task */
rc = ACMS$CALL(&sess, "BANKING", "FUNDS_TRANSFER",
               ws_array, 2, 0);
if (rc != ACMS_NORMAL) handle_error(rc);

ACMS$SIGN_OUT(&sess);

Server programming

Step procedures are compiled into server group .so files and loaded by the SP process via dlopen. VX/ACMS preserves the VMS server module ABI so that existing COBOL and C procedure source compiles and runs without modification.

Server module ABI (C / C++)

Each server group .so must export an acms_server_module_v1 struct:

typedef struct {
    uint32_t           version;        /* = ACMS_SERVER_MODULE_VERSION (1) */
    const char        *group_name;     /* must match GDF SERVER GROUP name */
    acms_proc_entry_t *procedures;     /* null-terminated array */
    acms_vfn_t         on_init;        /* INITIALIZATION PROCEDURE, may be NULL */
    acms_vfn_t         on_term;        /* TERMINATION PROCEDURE, may be NULL */
    acms_vfn_t         on_cancel;      /* CANCEL PROCEDURE, may be NULL */
} acms_server_module_v1;

Each procedure entry maps a name string to a function pointer:

typedef struct {
    const char    *name;              /* must match GDF PROCEDURES ARE name */
    acms_proc_fn_t fn;               /* unsigned int fn(WKSP *a, WKSP *b, …) */
} acms_proc_entry_t;

/* Procedure signature: one pointer per workspace declared USING */
unsigned int READ_ACCOUNT(ACCOUNT_WKSP_t *acct) {
    /* read account record: field access via struct layout */
    if (acct->balance < 0) return ACMS$_INSUFFICIENT_FUNDS;
    return ACMS_NORMAL;
}

COBOL step procedures

COBOL procedures use the VMS-shape signature: workspace records appear as BY REFERENCE arguments in the PROCEDURE DIVISION USING clause, and the return status is conveyed through RETURN-CODE (not a function return value):

IDENTIFICATION DIVISION.
PROGRAM-ID. READ-ACCOUNT.
DATA DIVISION.
WORKING-STORAGE SECTION.
LINKAGE SECTION.
    01 ACCOUNT-WKSP.
       05 ACCOUNT-ID   PIC X(16).
       05 BALANCE       PIC S9(12)V99 COMP-3.
       05 STATUS-MSG    PIC X(40).
PROCEDURE DIVISION USING ACCOUNT-WKSP.
    EXEC SQL
        SELECT BALANCE INTO :ACCOUNT-WKSP.BALANCE
        FROM ACCOUNTS WHERE ID = :ACCOUNT-WKSP.ACCOUNT-ID
    END-EXEC.
    IF SQLCODE <> 0
        MOVE "NOT FOUND" TO ACCOUNT-WKSP.STATUS-MSG
        MOVE ACMS-STEP-ERROR TO RETURN-CODE
    ELSE
        MOVE ACMS-NORMAL TO RETURN-CODE
    END-IF.
    STOP RUN.
layout_hash enforcement. The layout_hash in the workspace handle must match the hash compiled into the server .so. A mismatch is caught at install time by acmsctl install, not at runtime. Recompile both the task and server .so after any CDD record change.

Exception handling from server code

Call ACMS$RAISE_STEP_EXCEPTION to propagate a step-level error back to the EXC EXCEPTION HANDLER without using a C++ exception or a non-zero return value directly:

unsigned int DEBIT_ACCOUNT(ACCOUNT_WKSP_t *acct,
                            TRANSFER_WKSP_t *xfr)
{
    if (xfr->amount > acct->balance) {
        ACMS$RAISE_STEP_EXCEPTION(ACMS$_INSUFFICIENT_FUNDS,
                                  "Balance too low");
        return ACMS_NORMAL; /* control never reaches here */
    }
    acct->balance -= xfr->amount;
    return ACMS_NORMAL;
}

Build pipeline

The ADU (Application Definition Utility) converts TDL sources to native Linux shared objects through a four-step pipeline. All steps are exposed through the adu_compile command-line tool.

Pipeline steps

StepCommandInputOutput
1. CDD compilevxcdd --add <name>.cdoCDD object definition (.cdo)CDD database (*.cdd.db)
2. GDF compileadu_compile server.gdf --cdd *.cdd.dbGroup definition file + CDD databaseserver_group.cpp → server_group.so
3. TDF compileadu_compile task.tdf --gdf server.gdf --compileTask definition file + GDFtask_module.cpp → task.so
4. Installacmsctl install application <APP>ADF + compiled .so filesApplication registered in control.db; EXC/SP notified.

adu_compile flags

FlagPurpose
--cdd <path>Path to the CDD database (required for workspace resolution).
--gdf <path>Path to the compiled GDF (required for TDF step resolution).
--compileInvoke the system C++ compiler on the generated .cpp and link the .so.
--output-dir <dir>Write generated files to this directory (default: same as input).
--cxx <compiler>C++ compiler binary (default: g++).
--cxxflags <flags>Additional flags forwarded to the C++ compiler.
--verbosePrint resolver and emitter trace to stderr.
--dry-runParse and resolve; do not emit C++ or compile.

Full build example

# 1: compile the CDD record repository
vxcdd --add banking.cdo

# 2: compile the server group
adu_compile banking_servers.gdf \
    --cdd banking.cdd.db \
    --compile \
    --output-dir build/

# 3: compile all task definitions
adu_compile funds_transfer.tdf enquiry.tdf audit_report.tdf \
    --gdf banking_servers.gdf \
    --cdd banking.cdd.db \
    --compile \
    --output-dir build/

# 4: install the application
acmsctl install application BANKING \
    --adf banking.adf \
    --task-dir build/ \
    --server-dir build/

Incremental rebuild

Re-run only the steps for the files you changed. If you modify a CDD record, you must recompile both the task .so and the server .so that reference that workspace; the layout_hash check at install time will catch any mismatch. Use acmsctl reload application BANKING to hot-reload an updated application without restarting acmsd.

Generated C++ is first-class source. Check build/*.cpp into version control alongside your TDF sources. A git diff of the generated files tells you precisely what semantic changes an ADU version bump or TDF edit produced.

Quick reference

TDF step summary

StepSyntax (abbreviated)EXC action
PROCESSING CALLPROCESSING CALL proc USING wksp, …Dispatch to SP; workspace handles passed by reference.
EXCHANGEEXCHANGE USING wksp WRITING form TO wkspRoute through CP; FORMS$TRANSCEIVE called.
MOVEMOVE "lit" TO wksp.fieldAssign literal or field to workspace field in-process.
ATOMIC BEGINATOMIC BEGIN … END ATOMICOpen SQLite transaction bracket; all steps inside are atomic.
GET ERROR MESSAGEGET ERROR MESSAGE INTO wksp.fieldCopy current exception text to workspace.
IF / ELSE / END IFIF wksp.field op value THEN … END IFConditional; evaluated by EXC interpreter.
WHILE / END WHILEWHILE condition DO … END WHILELoop until condition false.
REPEAT / END REPEATREPEAT … EXIT LOOP … END REPEATUnconditional loop.
GOTOGOTO labelJump to labelled step within BLOCK.
EXIT TASKEXIT TASKNormal task termination.
EXCEPTION HANDLEREXCEPTION HANDLER steps; END HANDLERCatch any step exception in the enclosing BLOCK.

ACMS$ service quick reference

ServiceOne-linerv1?
ACMS$SIGN_INOpen session; authenticate submitter.✓
ACMS$SIGN_OUTClose session; release USER workspaces.✓
ACMS$CALLSynchronous task submission.✓
ACMS$START_CALLAsynchronous task submission.✓
ACMS$WAIT_CALLWait for async task completion.✓
ACMS$CANCEL_CALLCancel pending async submission.✓
ACMS$RAISE_STEP_EXCEPTIONRaise exception from server step.✓
ACMS$SET_SELECTION_STRINGPre-populate selection string workspace.✓
ACMS$GET_TASK_INFORead task identity from system workspace.✓
ACMS$EXCHANGE_IOForms exchange I/O from server code.v1 soon
ACMS$QUEUE_TASKEnqueue task for deferred QTI dispatch.partial
ACMS$GET_ERROR_MESSAGERetrieve exception text string.✓

acmsctl operator commands

CommandPurpose
acmsctl install application APP --adf app.adfRegister application; verify layout hashes; notify EXC/SP.
acmsctl reload application APPHot-reload updated .so files without restarting acmsd.
acmsctl start application APPActivate application for task submission.
acmsctl stop application APPQuiesce application; drain in-flight tasks.
acmsctl call APP TASKTest-call a task from the operator shell.
acmsctl show application APPPrint task list, server group status, active submitters.
acmsctl show statisticsAggregate call counts, step latency percentiles, queue depth.

Key file paths (default layout)

PathContents
/opt/acms/bin/acmsd, acms-exc, acms-sp, acms-qti, acms-atl, acmsctl
/opt/acms/lib/libacms_si.so, liblogswl.so
/opt/acms/applications/Installed task and server .so files per application
/var/lib/acms/control.db, audit.db, queue.db
/run/acms/UNIX-domain sockets: operator.sock, per-EXC / per-SP sockets
/etc/acms/applications.confBoot-time application list; paths override acmsctl install defaults

Frequently Asked Questions

Curious about how Sector7 can facilitate your application migration? Explore our FAQs for expert insights.

What is VX/ACMS and how does it help modernize OpenVMS ACMS applications?

VX/ACMS is a native C++20 reimplementation of HP/VSI ACMS for Linux. It delivers source-compatible task execution for migrated OpenVMS applications: your existing TDF, GDF, and ADF sources compile through the ADU, existing COBOL and C step procedures run unchanged, and calls to ACMS$ services resolve against the same signatures and status codes as on VMS. VX/ACMS targets the single-server deployment model used by most production ACMS estates.

Does VX/ACMS maintain the original ACMS step-based processing model?

Yes. VX/ACMS preserves the ACMS component model and step-driven task interpretation. The EXC task interpreter drives the same step types you already use: PROCESSING CALL, EXCHANGE, ATOMIC, IF, WHILE, REPEAT, GOTO, MOVE, and EXCEPTION HANDLER. Workspaces still flow between submitters, EXC, and procedure servers through the familiar TASK, USER, GROUP, and SYSTEM lifetimes. EXCHANGE steps need the DECforms (CP) integration, which is still in progress.

Which TDL file types does VX/ACMS support for migration?

The ADU compiler processes the standard TDL file types: TDF for tasks, GDF for server groups and procedure definitions, and ADF for applications. Existing TDF, GDF and ADF sources compile unchanged through VX/ACMS. MDF (menu definition files) support arrives in v1 with the CP integration. CDD record definitions that describe workspace layouts are compiled separately via vxcdd and consumed by both the ADU and your COBOL or C compilers.

Does VX/ACMS require rewriting existing COBOL and C step procedures?

No. VX/ACMS preserves the VMS server module ABI so that existing procedure source compiles and runs without modification. COBOL step procedures keep their VMS-shape BY REFERENCE workspace signatures with status returned through RETURN-CODE, and C or C++ procedures export the same acms_server_module_v1 struct expected by the SP procedure server. Server group .so files are loaded by dlopen at runtime, matching the GDF definitions you already maintain.

Which ACMS$ services does VX/ACMS implement?

VX/ACMS implements the 12 ACMS$ services used across production estates, including ACMS$SIGN_IN, ACMS$SIGN_OUT, ACMS$CALL, ACMS$START_CALL, ACMS$WAIT_CALL, ACMS$CANCEL_CALL, and ACMS$RAISE_STEP_EXCEPTION. Submitter programs link against libacms_si.so and call these services with the same VMS-compatible signatures and 32-bit status codes – ACMS_NORMAL, ACMS$_NOSUCHAPP, ACMS$_STEP_ERR, and the rest of the standard set.

How does the VX/ACMS migration build pipeline work at a high level?

Migration follows a four-step pipeline exposed through adu_compile and acmsctl. First, vxcdd builds your CDD database from .cdo record definitions. Next, GDF sources compile to server group .so files, then TDF sources compile to task .so files that reference the GDF. Finally, acmsctl install application registers the ADF and compiled .so files in the control database, with layout_hash checks catching any workspace mismatch before tasks can run.

Transform Your Legacy Software Today!

Get In Touch
Unlock the potential of your legacy software with our expert migration services.