diff --git a/distro/Dockerfile.bootable b/distro/Dockerfile.bootable index 4be10b72c..c99cc5d96 100644 --- a/distro/Dockerfile.bootable +++ b/distro/Dockerfile.bootable @@ -15,7 +15,7 @@ # Contains only: STARTUP.EXE (/init), # INITIALIZE.EXE, kernel modules, config, # and SYS$MANAGER files. All other binaries -# (DCL, LOGIN, HELP, LNMD) must be on the +# (DCL, LOGIN, HELP) must be on the # system disk (/vms). # # STARTUP.EXE (ovmx_init) is /init — no busybox, no shell scripts. @@ -94,7 +94,6 @@ RUN mkdir -p /initramfs-fat/dev /initramfs-fat/proc /initramfs-fat/sys \ cp build-static/bin/DCL.EXE /initramfs-fat/vms/SYS0/SYSCOMMON/SYSEXE/ && \ cp build-static/bin/LOGINOUT.EXE /initramfs-fat/vms/SYS0/SYSCOMMON/SYSEXE/ && \ cp build-static/bin/HELP.EXE /initramfs-fat/vms/SYS0/SYSCOMMON/SYSEXE/ && \ - cp build-static/bin/VMSLNMD.EXE /initramfs-fat/vms/SYS0/SYSCOMMON/SYSEXE/ && \ cp build-static/bin/AUTHORIZE.EXE /initramfs-fat/vms/SYS0/SYSCOMMON/SYSEXE/ 2>/dev/null ; \ cp build-static/bin/MAIL.EXE /initramfs-fat/vms/SYS0/SYSCOMMON/SYSEXE/ 2>/dev/null ; \ cp build-static/bin/MONITOR.EXE /initramfs-fat/vms/SYS0/SYSCOMMON/SYSEXE/ 2>/dev/null ; \ @@ -112,7 +111,7 @@ RUN mkdir -p /initramfs-fat/dev /initramfs-fat/proc /initramfs-fat/sys \ # -- SLIM initramfs ------------------------------------------------------------ # Bootstrap-only: STARTUP.EXE + INITIALIZE.EXE + kernel modules + config. -# All other binaries (DCL, LOGIN, HELP, LNMD) must be on the system disk. +# All other binaries (DCL, LOGIN, HELP) must be on the system disk. # Use this after the system disk has been installed via the fat initramfs. RUN mkdir -p /initramfs-slim/dev /initramfs-slim/proc /initramfs-slim/sys \ /initramfs-slim/tmp /initramfs-slim/var /initramfs-slim/mnt \ diff --git a/distro/rootfs/vms/SYS0/SYSCOMMON/SYSMGR/OVMX.CONF b/distro/rootfs/vms/SYS0/SYSCOMMON/SYSMGR/OVMX.CONF index 9875719d3..b40d85571 100644 --- a/distro/rootfs/vms/SYS0/SYSCOMMON/SYSMGR/OVMX.CONF +++ b/distro/rootfs/vms/SYS0/SYSCOMMON/SYSMGR/OVMX.CONF @@ -15,6 +15,3 @@ DEFAULT_PROTECTION=S:RWED,O:RWED,G:RE,W: # Enable VMS-style file versioning FILE_VERSIONING=1 - -# Logical name daemon socket path -LNM_SOCKET=/tmp/ovmx/lnm.sock diff --git a/docs/architecture.md b/docs/architecture.md index 59eede6e9..c6142179b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -33,7 +33,7 @@ Layer 4 ─ User Interface Layer 3 ─ File Services Record management, VMS filesystem, logical names - [vmsrms, vmsfs, vmslnm + vmslnmd daemon] + [vmsrms, vmsfs, vmslnm] Layer 2 ─ VMS Runtime System services and RTL @@ -57,7 +57,9 @@ libvmssys (freestanding, static only) │ │ │ └── libvms (+ pthread, math) │ │ - │ ├── vmslnm (+ pthread) ──→ vmslnmd daemon + │ ├── vmslnm (+ pthread) — per-process tables only; + │ │ no daemon (deleted, vms-a4b); executive-resident + │ │ placement is an open ruling, see vms-ln0 │ │ │ │ │ └── vmsfs │ │ │ @@ -93,8 +95,7 @@ docker build -f Dockerfile.bootable -o dist . ├── Load: vms.ko, vmsfs.ko ├── Generate /etc/passwd, /etc/group from sysuaf.dat └── exec /sbin/init (ovmx_init) - ├── Start vmslnmd - ├── Load system logicals + ├── (no logical name daemon — deleted, vms-a4b; VMS has no such process) ├── Execute STARTUP.COM └── Launch login shell (vmsdcl) ``` @@ -135,7 +136,7 @@ User types command via SSH | libvmssys | `src/libvmssys/vms_runtime_init.c` | `vmssys.h` | libvmssys.a | | vmsprocess | `src/vmsprocess/vms_pcb.c` | `include/vms/process.h` | libvmsprocess | | libvms | `src/libvms/syssvc/sys_qio.c` | `include/starlet.h` | libvms | -| vmslnm | `src/vmslnm/lnm_table.c` | `include/vms/logical.h` | libvmslnm, vmslnmd | +| vmslnm | `src/vmslnm/lnm_table.c` | `include/vms/logical.h` | libvmslnm | | vmsfs | `src/vmsfs/vmsfs_translate.c` | `include/vmsfs/filespec.h` | libvmsfs | | vmsrms | `src/vmsrms/rms_core.c` | `include/rms/rms.h` | librms | | vmsdcl | `src/vmsdcl/dcl_main.c` | `include/dcl/context.h` | vmsdcl | diff --git a/docs/building.md b/docs/building.md index b8242ae2f..f876a7eba 100644 --- a/docs/building.md +++ b/docs/building.md @@ -45,7 +45,7 @@ cmake --build build -j$(nproc) **Outputs** in `build/`: - Libraries: `libvms.so`, `libvmsprocess.so`, `libvmslnm.so`, `libvmsfs.so`, `librms.so`, `libvmssys.a` -- Executables: `vmsdcl`, `vmslnmd`, `vms_login`, `vms_help`, `vms_ssh_auth`, `ovmx_init` +- Executables: `vmsdcl`, `vms_login`, `vms_help`, `vms_ssh_auth`, `ovmx_init` - Tests: `test_vmssys_*`, integration tests ## Static Build diff --git a/docs/design-authenticity-roadmap.md b/docs/design-authenticity-roadmap.md index e2166afeb..98a79a9d8 100644 --- a/docs/design-authenticity-roadmap.md +++ b/docs/design-authenticity-roadmap.md @@ -179,8 +179,10 @@ of *every file open*. An ioctl per translation is a syscall round trip. LNM may mapping (the `MAP_SHARED` known-image DB is the in-tree precedent) or kernel-side storage with a per-process cache plus invalidation. Decide LNM separately from the rest. -**Scope:** tracked as **`vms-6b8`**. The `lnm.sock` daemon (already loads `SYLOGICALS.CONF`; nothing -connects to it) and the `MAP_SHARED` known-image DB are the two userspace precedents. +**Scope:** tracked as **`vms-6b8`**. The `MAP_SHARED` known-image DB is the surviving userspace +precedent. The other one, the `lnm.sock` daemon, was deleted by `vms-a4b`: it loaded +`SYLOGICALS.CONF` into a private table that nothing connected to, and VMS has no logical name +server process to model it on. ### 2.2 Re-tiering: rank by tell-probability, not by feature completeness @@ -325,7 +327,8 @@ the logical rather than regressing to a compiled-in greeting. **Logical names are themselves an authenticity surface** (`vms-d37`, blocks A4). Wiring the banner to `SYS$WELCOME` exposed that OVMX logical-name tables are **per-process**: `lnm_get_manager()` builds an -in-process table, and nothing but the daemon itself reads `SYLOGICALS.CONF`. Demonstrated across two +in-process table, and nothing but the (since-deleted) daemon ever read `SYLOGICALS.CONF`. +Demonstrated across two DCL processes: ``` diff --git a/docs/design-image-activation.md b/docs/design-image-activation.md index 75f8443df..e521cc71f 100644 --- a/docs/design-image-activation.md +++ b/docs/design-image-activation.md @@ -328,10 +328,10 @@ Priority 1: Known Image Database (VMS$KNOWN_IMAGES.DAT) | Priority 2: Hardcoded SYS$SHARE fallback | /vms/SYS0/SYSCOMMON/SYSLIB/ - | Always available, even before VMSLNMD starts + | Always available, even before the Known Image DB exists | This is the chicken-and-egg breaker | -Priority 3: SYS$SHARE logical name (after VMSLNMD) +Priority 3: SYS$SHARE logical name (once the tables are executive-resident) | Translate SYS$SHARE via vmslnm API | Search result directory for SONAME | Only available after logical name daemon is running @@ -346,6 +346,13 @@ Not found: %IMGACT-F-IMGNOTFND error ### Chicken-and-Egg: VMSLNMD Bootstrap +> **SUPERSEDED (`vms-a4b`, 2026-07-30).** `VMSLNMD.EXE` no longer exists. OpenVMS has no logical +> name server process — the tables are executive-resident — so the daemon was deleted rather than +> kept as a bootstrap step. Everything below describing VMSLNMD's place in the boot sequence is +> **historical**. The resolution it describes still holds and is now the *whole* story: IMGACT.EXE +> breaks the circularity with its hardcoded `SYS$SHARE` fallback path, and no daemon is involved. +> Where the executive-resident tables end up living is ruled by `vms-ln0` / built by `vms-d37`. + The logical name daemon (`VMSLNMD.EXE`) is itself a dynamically linked binary that requires image activation. But IMGACT.EXE normally uses logical names (SYS$SHARE) to find shareable images. This creates a circular dependency. diff --git a/src/imgact/imgact.c b/src/imgact/imgact.c index b647eafe5..c9479a39d 100644 --- a/src/imgact/imgact.c +++ b/src/imgact/imgact.c @@ -619,9 +619,9 @@ static void known_db_shutdown(void) * Priority 1: Known Image Database -- O(1) mmap'd hash lookup, no * filesystem search at all on a hit (bead vms-30d). * Priority 2: hardcoded SYS$SHARE fallback (vms-913.2; always available, - * even before the Known Image DB exists or VMSLNMD starts). - * Priorities 3 (SYS$SHARE logical name via VMSLNMD) and 4 (ELF RPATH) are - * not yet implemented. */ + * even before the Known Image DB exists). + * Priorities 3 (SYS$SHARE logical name, once the logical name tables are + * executive-resident -- vms-a4b) and 4 (ELF RPATH) are not yet implemented. */ static struct obj *load_needed(const char *soname) { struct obj *existing = find_loaded(soname); diff --git a/src/imgact/test/verify_imgact_cmake.sh b/src/imgact/test/verify_imgact_cmake.sh index 00604b204..d8e1c47ec 100755 --- a/src/imgact/test/verify_imgact_cmake.sh +++ b/src/imgact/test/verify_imgact_cmake.sh @@ -45,7 +45,7 @@ readelf -dW "$BIN/STARTUP.EXE" 2>/dev/null | grep -q NEEDED && fail "STARTUP.EXE echo " ok" echo "== executables: PT_INTERP=IMGACT.EXE, DT_HASH ==" -for exe in DCL.EXE LOGINOUT.EXE VMSLNMD.EXE; do +for exe in DCL.EXE LOGINOUT.EXE HELP.EXE; do [ -f "$BIN/$exe" ] || fail "$exe not built" readelf -lW "$BIN/$exe" | grep -q "$IMGACT_PATH" \ || fail "$exe PT_INTERP != $IMGACT_PATH" diff --git a/src/imgact/test/verify_imgact_cmake_x86_64.sh b/src/imgact/test/verify_imgact_cmake_x86_64.sh index 261ee6e42..65b287ab5 100755 --- a/src/imgact/test/verify_imgact_cmake_x86_64.sh +++ b/src/imgact/test/verify_imgact_cmake_x86_64.sh @@ -58,7 +58,7 @@ $READELF -dW "$BIN/STARTUP.EXE" 2>/dev/null | grep -q NEEDED && fail "STARTUP.EX echo " ok" echo "== executables: PT_INTERP=IMGACT.EXE, DT_HASH ==" -for exe in DCL.EXE LOGINOUT.EXE VMSLNMD.EXE; do +for exe in DCL.EXE LOGINOUT.EXE HELP.EXE; do [ -f "$BIN/$exe" ] || fail "$exe not built" $READELF -lW "$BIN/$exe" | grep -q "$IMGACT_PATH" \ || fail "$exe PT_INTERP != $IMGACT_PATH" diff --git a/src/libvms/include/ovmx_layout.h b/src/libvms/include/ovmx_layout.h index dbd4f1bd0..997cd52a6 100644 --- a/src/libvms/include/ovmx_layout.h +++ b/src/libvms/include/ovmx_layout.h @@ -58,6 +58,10 @@ #define VMS_SYLOGIN_PATH "SYS$MANAGER:SYLOGIN.COM" #define VMS_STARTUP_PATH "SYS$MANAGER:STARTUP.COM" #define VMS_HELPLIB_PATH "SYS$HELP:HELPLIB.HLP" +/* Reserved: zero readers as of vms-a4b (its only reader, lnm_daemon.c, + * was deleted). Not wired to anything until the executive-resident + * logical name work lands — see vms-ln0 (gated, operator sign-off + * pending) and vms-d37. Do not add a reader without that ruling. */ #define VMS_LNM_CONF_PATH "SYS$MANAGER:SYLOGICALS.CONF" #define VMS_SSH_HOST_KEY "SYS$MANAGER:SSH_HOST_RSA_KEY" @@ -67,7 +71,6 @@ #define VMS_DCL_PATH "SYS$SYSTEM:DCL.EXE" #define VMS_LOGINOUT_PATH "SYS$SYSTEM:LOGINOUT.EXE" -#define VMS_LNMD_PATH "SYS$SYSTEM:VMSLNMD.EXE" #define VMS_SSHD_PATH "SYS$SYSTEM:VMSSSHD.EXE" #define VMS_AUTHORIZE_PATH "SYS$SYSTEM:AUTHORIZE.EXE" #define VMS_INITIALIZE_PATH "SYS$SYSTEM:INITIALIZE.EXE" diff --git a/src/ovmx_init/ovmx_init.c b/src/ovmx_init/ovmx_init.c index e7e1b4957..96316d38a 100644 --- a/src/ovmx_init/ovmx_init.c +++ b/src/ovmx_init/ovmx_init.c @@ -32,7 +32,6 @@ #include "ovmx_layout.h" #include "ovmx_identity.h" -#define LNM_SOCKET_PATH "/tmp/ovmx/lnm.sock" #define SYSDISK_DEV "/dev/vda" #define INITRAMFS_BACKUP "/tmp/initramfs_vms" @@ -465,7 +464,7 @@ static void provision_symlinks(void) /* [SYS0.SYSCOMMON.SYSEXE] executables */ const char *exes[] = { "LOGINOUT.EXE", "DCL.EXE", "HELP.EXE", "AUTHORIZE.EXE", - "MAIL.EXE", "MONITOR.EXE", "VMSSSHD.EXE", "VMSLNMD.EXE", + "MAIL.EXE", "MONITOR.EXE", "VMSSSHD.EXE", "STARTUP.EXE", NULL }; for (int i = 0; exes[i]; i++) { @@ -564,47 +563,6 @@ static void install_system_blkdev(void) printf("%%STARTUP-I-INSTALLED, system installation complete\n"); } -/* - * Try to start the logical name daemon. - * Returns the child PID on success, -1 if vmslnmd not found. - */ -static pid_t start_lnm_daemon(void) -{ - char lnmd_path[512]; - vms_to_linux(VMS_LNMD_PATH, lnmd_path, sizeof(lnmd_path)); - struct stat st; - if (stat(lnmd_path, &st) != 0) { - return -1; /* vmslnmd not available */ - } - - pid_t pid = fork(); - if (pid == 0) { - /* Child: exec vmslnmd */ - execl(lnmd_path, "vmslnmd", (char *)NULL); - _exit(1); - } - return pid; -} - -/* - * Wait for the LNM daemon socket to appear (up to timeout_ms). - */ -static int wait_for_lnm_socket(int timeout_ms) -{ - struct stat st; - int elapsed = 0; - int interval = 50000; /* 50ms */ - - while (elapsed < timeout_ms * 1000) { - if (stat(LNM_SOCKET_PATH, &st) == 0) { - return 1; /* Socket appeared */ - } - usleep((useconds_t)interval); - elapsed += interval; - } - return 0; /* Timed out */ -} - /* * Try to start the SSH daemon for remote access. * Returns the child PID on success, -1 if sshd not found. @@ -664,7 +622,7 @@ static void run_startup(void) /* * Display VMS-style boot banner. */ -static void display_boot_banner(int lnm_started, int sshd_started) +static void display_boot_banner(int sshd_started) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); @@ -683,10 +641,6 @@ static void display_boot_banner(int lnm_started, int sshd_started) tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(ts.tv_nsec / 10000000)); - if (lnm_started) { - printf("%%STDRV-I-LNMSTART, logical name daemon started\n"); - } - if (sshd_started) { printf("%%STDRV-I-SSHSTART, SSH remote access started on port 22\n"); } @@ -765,29 +719,24 @@ int main(void) * without overwriting existing files. */ provision_seed_files(); - /* Step 3: Start logical name daemon */ - int lnm_started = 0; - pid_t lnm_pid = start_lnm_daemon(); - if (lnm_pid > 0) { - lnm_started = wait_for_lnm_socket(2000); - } - - /* Step 4: Run STARTUP.COM */ + /* Step 3: Run STARTUP.COM. + * There is no logical name daemon to start first: on VMS the logical name + * tables are executive-resident, not a service (vms-a4b). */ run_startup(); - /* Step 5: Start SSH daemon */ + /* Step 4: Start SSH daemon */ int sshd_started = 0; pid_t sshd_pid = start_sshd(); if (sshd_pid > 0) { sshd_started = 1; } - /* Step 6: Boot banner */ - display_boot_banner(lnm_started, sshd_started); + /* Step 5: Boot banner */ + display_boot_banner(sshd_started); fflush(stdout); fflush(stderr); - /* Step 7: Login loop (only if stdin is a terminal) */ + /* Step 6: Login loop (only if stdin is a terminal) */ char loginout_path[512], dcl_path[512]; vms_to_linux(VMS_LOGINOUT_PATH, loginout_path, sizeof(loginout_path)); vms_to_linux(VMS_DCL_PATH, dcl_path, sizeof(dcl_path)); @@ -893,10 +842,6 @@ int main(void) kill(sshd_pid, SIGTERM); waitpid(sshd_pid, NULL, 0); } - if (lnm_pid > 0) { - kill(lnm_pid, SIGTERM); - waitpid(lnm_pid, NULL, 0); - } return 0; } diff --git a/src/vmslink/mk_vmslnm_shr.sh b/src/vmslink/mk_vmslnm_shr.sh index ebf34b224..acd13f3d9 100755 --- a/src/vmslink/mk_vmslnm_shr.sh +++ b/src/vmslink/mk_vmslnm_shr.sh @@ -21,8 +21,7 @@ # the simplest migration so far: no PT_TLS, no --use LIBVMSSYS$SHR (no vms$/vms_kif_ # freestanding syscall imports), just a plain shareable over DECC$SHR. # -# Composition: the 4 vmslnm library translation units (NOT lnm_daemon.c, which is a -# separate VMSLNMD.EXE executable), compiled -fPIC musl: +# Composition: the 4 vmslnm library translation units, compiled -fPIC musl: # lnm_table.c lnm_translate.c lnm_client.c lnm_defaults.c # Compile flags mirror the proven lib-shareable pattern (mk_vmsprocess_shr.sh): # -fPIC -O2 -ffreestanding -fno-builtin -fno-stack-protector -mno-outline-atomics diff --git a/src/vmslnm/CMakeLists.txt b/src/vmslnm/CMakeLists.txt index 8a0ddee16..c637383de 100644 --- a/src/vmslnm/CMakeLists.txt +++ b/src/vmslnm/CMakeLists.txt @@ -33,18 +33,8 @@ if(OVMX_IMGACT) target_link_libraries(vmslnm_static PUBLIC pthread) endif() -# Daemon executable -add_executable(lnm_daemon_exe lnm_daemon.c) -target_include_directories(lnm_daemon_exe PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${VMS_INCLUDE_DIR} - ${VMS_FS_INCLUDE_DIR} -) -target_link_libraries(lnm_daemon_exe PRIVATE vmslnm vmsfs pthread) -target_compile_definitions(lnm_daemon_exe PRIVATE LNM_DAEMON_MAIN) -set_target_properties(lnm_daemon_exe PROPERTIES - OUTPUT_NAME "VMSLNMD" - SUFFIX ".EXE" -) - -install(TARGETS lnm_daemon_exe RUNTIME DESTINATION ${VMS_SYSTEM_DIR}) +# There is no logical name daemon. On OpenVMS the logical name tables live in +# the executive; there is no server process to start and no socket to connect +# to. VMSLNMD.EXE was an OVMX invention with zero clients product-wide, so it +# is gone rather than reduced (vms-a4b). The executive-resident tables that +# replace it are tracked by vms-ln0 / vms-d37. diff --git a/src/vmslnm/lnm_client.c b/src/vmslnm/lnm_client.c index 19d4a1f61..8380f7a10 100644 --- a/src/vmslnm/lnm_client.c +++ b/src/vmslnm/lnm_client.c @@ -2,9 +2,14 @@ * lnm_client.c - Logical Name Client Library * * Provides the public API for logical name operations. - * Currently uses in-process tables with a global static manager. - * Will be extended to talk to the daemon via Unix domain socket - * for system-wide logicals in a future iteration. + * Currently uses in-process tables with a global static manager, so + * SYSTEM/GROUP/JOB tables are per-process, not per-system — a process + * cannot see a logical name DEFINE/SYSTEM'd by another process. On real + * OpenVMS these tables are executive-resident (LNM$SYSTEM_TABLE); there + * is no daemon to talk to. Placement (ioctl-per-translation vs. a + * mmap'd shared arena behind /dev/vms) is an open operator ruling — + * see vms-ln0 (gated) and vms-d37 (blocked twin). Do not add a + * socket/daemon client here; that architecture was deleted (vms-a4b). */ #include diff --git a/src/vmslnm/lnm_daemon.c b/src/vmslnm/lnm_daemon.c deleted file mode 100644 index 53db69cf9..000000000 --- a/src/vmslnm/lnm_daemon.c +++ /dev/null @@ -1,415 +0,0 @@ -/* - * lnm_daemon.c - Logical Name Daemon - * - * Provides system-wide logical name state via a Unix domain socket. - * When compiled with LNM_DAEMON_MAIN, runs as a standalone daemon. - * Otherwise, the file is included in the library build but the - * main() entry point is omitted. - * - * Protocol (newline-delimited text commands): - * CREATE - * DELETE
- * TRANSLATE
- * ENUMERATE
- * - * Responses: - * OK -- success - * ERR -- failure with VMS status code - * ENTRY -- for ENUMERATE, one per entry - * END -- end of ENUMERATE results - * - * Configuration: - * Reads /etc/ovmx/sylogicals.conf on startup (one "name value" per line). - * Listens on /tmp/ovmx/lnm.sock. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "vms/logical.h" -#include "ssdef.h" - -#include "ovmx_layout.h" -#include "vmsfs/device.h" -#include "vmsfs/filespec.h" -#define LNM_SOCKET_PATH "/tmp/ovmx/lnm.sock" -#define LNM_CONF_PATH VMS_LNM_CONF_PATH -#define LNM_LINE_MAX 1024 -#define LNM_VMS_ROOT SYSDISK_MOUNT - -#ifdef LNM_DAEMON_MAIN - -static volatile int daemon_running = 1; - -/* Read-write lock protecting the logical name manager. - * TRANSLATE/ENUMERATE take the read lock; CREATE/DELETE take the write lock. */ -static pthread_rwlock_t lnm_rwlock = PTHREAD_RWLOCK_INITIALIZER; - -static void handle_signal(int sig) -{ - (void)sig; - daemon_running = 0; -} - -/* - * Enumeration callback used by the ENUMERATE command. - * Writes one "ENTRY name value\n" line per logical name to the client fd. - */ -struct enum_ctx { - int fd; -}; - -static int enum_callback(const char *name, const lnm_entry_t *entry, void *ctx) -{ - struct enum_ctx *ec = (struct enum_ctx *)ctx; - char line[LNM_LINE_MAX]; - const char *value = ""; - - if (entry->num_translations > 0) - value = entry->translations[0].value; - - int n = snprintf(line, sizeof(line), "ENTRY %s %s\n", name, value); - if (n > 0) - (void)write(ec->fd, line, (size_t)n); - - return 0; /* continue */ -} - -/* - * Parse and execute a single command from a client. - */ -static void process_command(lnm_manager_t *mgr, int client_fd, char *cmd) -{ - char response[LNM_LINE_MAX]; - char *saveptr = NULL; - - /* Trim trailing newline/carriage return */ - size_t len = strlen(cmd); - while (len > 0 && (cmd[len - 1] == '\n' || cmd[len - 1] == '\r')) - cmd[--len] = '\0'; - - if (len == 0) - return; - - char *verb = strtok_r(cmd, " \t", &saveptr); - if (!verb) - return; - - if (strcasecmp(verb, "CREATE") == 0) { - char *table = strtok_r(NULL, " \t", &saveptr); - char *name = strtok_r(NULL, " \t", &saveptr); - /* The rest of the line is the value (may contain spaces) */ - char *value = saveptr; - - if (!table || !name || !value || *value == '\0') { - snprintf(response, sizeof(response), "ERR %08X\n", SS$_BADPARAM); - } else { - /* Trim leading whitespace from value */ - while (*value == ' ' || *value == '\t') - value++; - pthread_rwlock_wrlock(&lnm_rwlock); - uint32_t status = lnm_create(mgr, table, name, value, - 0, LNM_MODE_USER); - pthread_rwlock_unlock(&lnm_rwlock); - if ($VMS_STATUS_SUCCESS(status)) - snprintf(response, sizeof(response), "OK\n"); - else - snprintf(response, sizeof(response), "ERR %08X\n", status); - } - (void)write(client_fd, response, strlen(response)); - - } else if (strcasecmp(verb, "DELETE") == 0) { - char *table = strtok_r(NULL, " \t", &saveptr); - char *name = strtok_r(NULL, " \t", &saveptr); - - if (!table || !name) { - snprintf(response, sizeof(response), "ERR %08X\n", SS$_BADPARAM); - } else { - pthread_rwlock_wrlock(&lnm_rwlock); - uint32_t status = lnm_delete(mgr, table, name, LNM_MODE_USER); - pthread_rwlock_unlock(&lnm_rwlock); - if ($VMS_STATUS_SUCCESS(status)) - snprintf(response, sizeof(response), "OK\n"); - else - snprintf(response, sizeof(response), "ERR %08X\n", status); - } - (void)write(client_fd, response, strlen(response)); - - } else if (strcasecmp(verb, "TRANSLATE") == 0) { - char *table = strtok_r(NULL, " \t", &saveptr); - char *name = strtok_r(NULL, " \t", &saveptr); - - if (!table || !name) { - snprintf(response, sizeof(response), "ERR %08X\n", SS$_BADPARAM); - } else { - char result[LNM_MAX_VALUE + 1]; - uint16_t result_len = 0; - uint32_t attrs = 0; - pthread_rwlock_rdlock(&lnm_rwlock); - uint32_t status = lnm_translate(mgr, table, name, - result, sizeof(result), - &result_len, &attrs); - pthread_rwlock_unlock(&lnm_rwlock); - if ($VMS_STATUS_SUCCESS(status)) - snprintf(response, sizeof(response), "OK %s\n", result); - else - snprintf(response, sizeof(response), "ERR %08X\n", status); - } - (void)write(client_fd, response, strlen(response)); - - } else if (strcasecmp(verb, "ENUMERATE") == 0) { - char *table = strtok_r(NULL, " \t", &saveptr); - - if (!table) { - snprintf(response, sizeof(response), "ERR %08X\n", SS$_BADPARAM); - (void)write(client_fd, response, strlen(response)); - } else { - struct enum_ctx ctx; - ctx.fd = client_fd; - pthread_rwlock_rdlock(&lnm_rwlock); - uint32_t status = lnm_enumerate(mgr, table, enum_callback, &ctx); - pthread_rwlock_unlock(&lnm_rwlock); - if (!$VMS_STATUS_SUCCESS(status)) { - snprintf(response, sizeof(response), "ERR %08X\n", status); - (void)write(client_fd, response, strlen(response)); - } - /* Send end-of-enumeration marker */ - (void)write(client_fd, "END\n", 4); - } - - } else { - snprintf(response, sizeof(response), "ERR %08X\n", SS$_IVSSRQ); - (void)write(client_fd, response, strlen(response)); - } -} - -/* Thread context for client handler */ -struct client_ctx { - int fd; - lnm_manager_t *mgr; -}; - -/* - * Client handler thread. - * Reads newline-delimited commands and dispatches them. - */ -static void *handle_client(void *arg) -{ - struct client_ctx *ctx = (struct client_ctx *)arg; - int client_fd = ctx->fd; - lnm_manager_t *mgr = ctx->mgr; - free(ctx); - - char buf[LNM_LINE_MAX]; - ssize_t nread; - char linebuf[LNM_LINE_MAX]; - size_t linepos = 0; - - while ((nread = read(client_fd, buf, sizeof(buf) - 1)) > 0) { - buf[nread] = '\0'; - - /* Accumulate into line buffer, process complete lines */ - for (ssize_t i = 0; i < nread; i++) { - if (buf[i] == '\n') { - linebuf[linepos] = '\0'; - process_command(mgr, client_fd, linebuf); - linepos = 0; - } else if (linepos < sizeof(linebuf) - 1) { - linebuf[linepos++] = buf[i]; - } - } - } - - close(client_fd); - return NULL; -} - -/* - * Load system logicals from a configuration file. - * - * File format: one logical per line, "NAME VALUE" separated by whitespace. - * Lines starting with '#' are comments. Blank lines are ignored. - */ -static void load_config(lnm_manager_t *mgr, const char *conf_path) -{ - FILE *fp = fopen(conf_path, "r"); - if (!fp) { - /* Not an error -- config file is optional */ - return; - } - - char line[LNM_LINE_MAX]; - while (fgets(line, sizeof(line), fp)) { - /* Strip newline */ - size_t len = strlen(line); - while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) - line[--len] = '\0'; - - /* Skip blank lines and comments */ - char *p = line; - while (*p == ' ' || *p == '\t') - p++; - if (*p == '\0' || *p == '#') - continue; - - /* Parse "NAME VALUE" */ - char *name = strtok(p, " \t"); - char *value = strtok(NULL, "\n"); - if (!name || !value) - continue; - - /* Trim leading whitespace from value */ - while (*value == ' ' || *value == '\t') - value++; - - if (*value == '\0') - continue; - - lnm_create(mgr, LNM_SYSTEM_TABLE, name, value, - LNM_ATTR_TERMINAL, LNM_MODE_EXEC); - } - - fclose(fp); -} - -/* - * Ensure the socket directory exists. - */ -static int ensure_socket_dir(const char *socket_path) -{ - /* Extract directory from socket path */ - char dir[256]; - strncpy(dir, socket_path, sizeof(dir) - 1); - dir[sizeof(dir) - 1] = '\0'; - - char *slash = strrchr(dir, '/'); - if (slash) { - *slash = '\0'; - if (mkdir(dir, 0755) < 0 && errno != EEXIST) - return -1; - } - return 0; -} - -int main(int argc, char *argv[]) -{ - (void)argc; - (void)argv; - - printf("OVMX Logical Name Daemon starting...\n"); - - /* Initialize the logical name manager */ - lnm_manager_t *mgr = lnm_init(); - if (!mgr) { - fprintf(stderr, "Failed to initialize logical name manager\n"); - return 1; - } - - /* Bootstrap device table before setting up logicals */ - vmsfs_device_add(SYSDISK_DEVICE, SYSDISK_MOUNT); - - /* Set up default VMS system logicals */ - lnm_setup_defaults(mgr, LNM_VMS_ROOT); - - /* Load additional system logicals from configuration file */ - char lnm_conf_linux[1024]; - vmsfs_to_linux_path(LNM_CONF_PATH, lnm_conf_linux, sizeof(lnm_conf_linux)); - load_config(mgr, lnm_conf_linux); - - /* Ensure socket directory exists */ - if (ensure_socket_dir(LNM_SOCKET_PATH) < 0) { - fprintf(stderr, "Failed to create socket directory: %s\n", - strerror(errno)); - lnm_shutdown(mgr); - return 1; - } - - /* Remove stale socket file */ - unlink(LNM_SOCKET_PATH); - - /* Create Unix domain socket */ - int server_fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (server_fd < 0) { - perror("socket"); - lnm_shutdown(mgr); - return 1; - } - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, LNM_SOCKET_PATH, sizeof(addr.sun_path) - 1); - - if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - perror("bind"); - close(server_fd); - lnm_shutdown(mgr); - return 1; - } - - if (listen(server_fd, 16) < 0) { - perror("listen"); - close(server_fd); - lnm_shutdown(mgr); - return 1; - } - - /* Set up signal handlers for clean shutdown */ - signal(SIGINT, handle_signal); - signal(SIGTERM, handle_signal); - signal(SIGPIPE, SIG_IGN); - - printf("Logical Name Daemon listening on %s\n", LNM_SOCKET_PATH); - - while (daemon_running) { - int client_fd = accept(server_fd, NULL, NULL); - if (client_fd < 0) { - if (errno == EINTR) - continue; - perror("accept"); - break; - } - - /* - * Allocate a context block containing the client fd and - * a pointer to the manager for the handler thread. - */ - struct client_ctx *ctx = malloc(sizeof(struct client_ctx)); - if (!ctx) { - close(client_fd); - continue; - } - ctx->fd = client_fd; - ctx->mgr = mgr; - - pthread_t thread; - if (pthread_create(&thread, NULL, handle_client, ctx) != 0) { - perror("pthread_create"); - close(client_fd); - free(ctx); - continue; - } - pthread_detach(thread); - } - - printf("Logical Name Daemon shutting down...\n"); - - close(server_fd); - unlink(LNM_SOCKET_PATH); - lnm_shutdown(mgr); - - printf("Logical Name Daemon stopped.\n"); - return 0; -} - -#endif /* LNM_DAEMON_MAIN */