#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 #include #include #include #include #include #include #include #include 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