Made online change work on ur20

This commit is contained in:
2026-10-07 21:54:01 -06:00
parent 739347c4da
commit bc27b9bb27
10 changed files with 219 additions and 2 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7
View File
@@ -1,3 +1,7 @@
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "atmb_boot_link.h"
#include "../src/atom_bridge.h" #include "../src/atom_bridge.h"
/***************************************************************************** /*****************************************************************************
* Copyright: Copyright CODESYS Development GmbH * Copyright: Copyright CODESYS Development GmbH
@@ -38,6 +42,9 @@ DLL_DECL int CDECL ComponentEntry(INIT_STRUCT *pInitStruct)
s_pfCMCallHook = pInitStruct->pfCMCallHook; s_pfCMCallHook = pInitStruct->pfCMCallHook;
s_pfCMRegisterClass = pInitStruct->pfCMRegisterClass; s_pfCMRegisterClass = pInitStruct->pfCMRegisterClass;
s_pfCMCreateInstance = pInitStruct->pfCMCreateInstance; s_pfCMCreateInstance = pInitStruct->pfCMCreateInstance;
/* Best effort only: never make component startup depend on this. */
atmb_ensure_boot_link();
return ERR_OK; return ERR_OK;
} }
+109
View File
@@ -0,0 +1,109 @@
#ifndef ATMB_BOOT_LINK_H
#define ATMB_BOOT_LINK_H
/* Linux-only boot-project pathname workaround. Include in CmpFrame.c only,
* with _GNU_SOURCE defined before all system headers.
* No PLC memory, communication state, worker thread or cyclic work is involved.
*/
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <syslog.h>
#include <unistd.h>
#include <stdint.h>
static void atmb_ensure_boot_link(void)
{
/* ComponentEntry is called at module initialization, not on each scan.
* Guard against repeated entry; failure is deliberately not retried.
*/
static int attempted;
const char *name = "libCmpAtomModbus.so";
const char *problem = NULL;
Dl_info info;
struct stat source, existing;
char *module_path = NULL;
int directory = -1;
int error = 0;
int saved_errno = errno;
if (attempted)
return;
attempted = 1;
/* The component function address identifies this loaded ELF object. Using
* code rather than a local data symbol also works with runtimes that do not
* expose local data addresses through dladdr().
*/
if (!dladdr((void *)(uintptr_t)&atmb_ensure_boot_link, &info) || !info.dli_fname) {
problem = "cannot locate loaded module";
goto finish;
}
module_path = realpath(info.dli_fname, NULL);
if (!module_path) {
error = errno;
problem = "cannot resolve loaded module pathname";
goto finish;
}
if (stat(module_path, &source) != 0) {
error = errno;
problem = "cannot inspect loaded module file";
goto finish;
}
if (!S_ISREG(source.st_mode)) {
problem = "loaded module pathname is not a regular file";
goto finish;
}
/* Boot creation was observed looking up the bare filename relative to
* runtime cwd. Anchor this directory with an FD; never change cwd.
*/
directory = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (directory < 0) {
error = errno;
problem = "cannot open runtime working directory";
goto finish;
}
if (fstatat(directory, name, &existing, AT_SYMLINK_NOFOLLOW) == 0) {
struct stat resolved;
if (fstatat(directory, name, &resolved, 0) == 0 &&
resolved.st_dev == source.st_dev &&
resolved.st_ino == source.st_ino)
goto finish; /* Correct link, hard link, or the source itself. */
problem = "existing file or link left untouched; check boot-project path";
goto finish;
}
if (errno != ENOENT) {
error = errno;
problem = "cannot inspect boot-project pathname";
goto finish;
}
/* symlinkat fails on EEXIST: even a concurrent creator cannot be replaced.
* Keep this link on shutdown; boot-project creation may still need it.
*/
if (symlinkat(module_path, directory, name) != 0) {
error = errno;
problem = "cannot create boot-project link";
goto finish;
}
syslog(LOG_INFO, "CmpAtomModbus: created boot link %s -> %s in runtime cwd",
name, module_path);
finish:
if (problem) {
if (error)
syslog(LOG_WARNING, "CmpAtomModbus: %s: %s (errno=%d); Modbus startup continues",
problem, strerror(error), error);
else
syslog(LOG_WARNING, "CmpAtomModbus: %s; Modbus startup continues", problem);
}
if (directory >= 0)
close(directory);
free(module_path);
errno = saved_errno;
}
#endif
+7 -2
View File
@@ -1,8 +1,8 @@
# Generated from supplied ExtensionSDK; build with target Linux toolchain. # Generated from supplied ExtensionSDK; build with target Linux toolchain.
SDKDIR ?= ../../ExtensionSDK SDKDIR ?= ../../ExtensionSDK
MAJORVER=0 MAJORVER=0
MINORVER=4 MINORVER=5
CMPVERSION=0x00050000 CMPVERSION=0x00050001
# Local development ID; check no other component uses this ID. # Local development ID; check no other component uses this ID.
CMPID=0x2001 CMPID=0x2001
DOS2UNIX=dos2unix DOS2UNIX=dos2unix
@@ -10,4 +10,9 @@ M4=m4
CC ?= gcc CC ?= gcc
# The SDK otherwise includes CmpFrame in its component identity wildcard. # The SDK otherwise includes CmpFrame in its component identity wildcard.
override CSRC := CmpAtomModbus.c override CSRC := CmpAtomModbus.c
# dladdr locates this module; -ldl also supports glibc older than 2.34.
LDLIBS += -ldl
include $(SDKDIR)/makefile include $(SDKDIR)/makefile
# Recompile the startup hook when the link helper changes.
$(OUTDIR)/CmpFrame.o: atmb_boot_link.h
Binary file not shown.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
atom
ADAM-CODESYS
4648
639270284382256389
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Linux host integration tests for the real dladdr/symlinkat helper; needs cc."""
import ctypes
import os
from pathlib import Path
import subprocess
import sys
import tempfile
HEADER = Path(__file__).resolve().parents[1] / 'component-source/component'
CASES = ['missing', 'correct', 'file', 'wrong_link', 'dangling', 'same_directory', 'unwritable', 'missing_source']
def child(case, base):
base = Path(base)
module = base / 'PlcLogic/libCmpAtomModbus.so'
lib = ctypes.CDLL(str(module), use_errno=True)
lib.warning_count.restype = ctypes.c_int
lib.info_count.restype = ctypes.c_int
os.chdir(base)
link = base / 'libCmpAtomModbus.so'
if case == 'correct':
link.symlink_to('PlcLogic/libCmpAtomModbus.so')
elif case == 'file':
link.write_text('do not replace me')
elif case in ('wrong_link', 'dangling'):
if case == 'wrong_link':
(base / 'other').write_text('other file')
link.symlink_to('other')
elif case == 'same_directory':
os.chdir(module.parent)
elif case == 'missing_source':
module.unlink()
elif case == 'unwritable':
# sysfs cannot accept arbitrary regular symlinks, even as root.
os.chdir('/sys')
link = Path('/sys/libCmpAtomModbus.so')
assert not os.path.lexists(link)
ctypes.set_errno(123)
lib.run_check()
assert ctypes.get_errno() == 123, 'caller errno changed'
warnings = lib.warning_count()
infos = lib.info_count()
lib.run_check()
assert (warnings, infos) == (lib.warning_count(), lib.info_count()), 'retried or logged twice'
if case == 'missing':
assert link.is_symlink() and link.resolve() == module
assert (warnings, infos) == (0, 1)
elif case == 'correct':
assert os.readlink(link) == 'PlcLogic/libCmpAtomModbus.so'
assert (warnings, infos) == (0, 0)
elif case == 'same_directory':
assert module.is_file() and not module.is_symlink()
assert (warnings, infos) == (0, 0)
else:
assert (warnings, infos) == (1, 0)
if case == 'file':
assert link.read_text() == 'do not replace me'
elif case in ('wrong_link', 'dangling'):
assert os.readlink(link) == 'other'
else:
assert not os.path.lexists(link)
print('PASS', case)
if len(sys.argv) == 3:
child(sys.argv[1], sys.argv[2])
else:
with tempfile.TemporaryDirectory(prefix='atmb-link-test-') as temp:
root = Path(temp)
root.chmod(0o755)
harness = root / 'harness.c'
harness.write_text('''#define _GNU_SOURCE
#define syslog test_syslog
#include "atmb_boot_link.h"
static int warnings, infos;
void test_syslog(int priority, const char *format, ...) {
(void)format;
if (priority == LOG_WARNING) ++warnings;
if (priority == LOG_INFO) ++infos;
}
void run_check(void) { atmb_ensure_boot_link(); }
int warning_count(void) { return warnings; }
int info_count(void) { return infos; }
''')
output = root / 'test.so'
subprocess.run([os.environ.get('CC', 'cc'), '-std=c99', '-Wall', '-Wextra', '-Werror',
'-fPIC', '-shared', '-I', str(HEADER), str(harness), '-ldl', '-o', str(output)], check=True)
for case in CASES:
base = root / case
(base / 'PlcLogic').mkdir(parents=True)
(base / 'PlcLogic/libCmpAtomModbus.so').write_bytes(output.read_bytes())
subprocess.run([sys.executable, __file__, case, str(base)], check=True)
base.chmod(0o755)