Files

110 lines
3.5 KiB
C

#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