Merge ~epics-core/epics-base/+git/make:rpath-origin into ~epics-core/epics-base/+git/epics-base:7.0

Proposed by mdavidsaver
Status: Merged
Approved by: Andrew Johnson
Approved revision: 784d619bdee4653f8412f664b7338fe42f67e047
Merged at revision: 02bec52f0a966381a43149e685afb28b68fb2939
Proposed branch: ~epics-core/epics-base/+git/make:rpath-origin
Merge into: ~epics-core/epics-base/+git/epics-base:7.0
Diff against target: 1096 lines (+673/-194)
21 files modified
.travis.yml (+1/-1)
configure/CONFIG_BASE (+2/-0)
configure/CONFIG_COMMON (+2/-0)
configure/CONFIG_SITE (+9/-1)
configure/RULES_BUILD (+7/-0)
configure/os/CONFIG.Common.linuxCommon (+2/-0)
modules/database/src/std/softIoc/softMain.cpp (+204/-192)
modules/libcom/src/misc/unixFileName.h (+22/-0)
modules/libcom/src/osi/Makefile (+1/-0)
modules/libcom/src/osi/os/Darwin/osdgetexec.c (+50/-0)
modules/libcom/src/osi/os/Linux/osdgetexec.c (+54/-0)
modules/libcom/src/osi/os/WIN32/osdgetexec.c (+52/-0)
modules/libcom/src/osi/os/WIN32/osiFileName.h (+22/-0)
modules/libcom/src/osi/os/cygwin32/osiFileName.h (+22/-0)
modules/libcom/src/osi/os/default/osdgetexec.c (+14/-0)
modules/libcom/src/osi/os/freebsd/osdgetexec.c (+68/-0)
modules/libcom/src/osi/os/solaris/osdgetexec.c (+30/-0)
modules/libcom/test/Makefile (+5/-0)
modules/libcom/test/testexecname.c (+24/-0)
src/tools/Makefile (+2/-0)
src/tools/makeRPath.py (+80/-0)
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
Martin Konrad (community) c++ part only Approve
mdavidsaver Approve
Review via email: mp+359132@code.launchpad.net

Description of the change

Change to automate the creation of relative associations between shared libraries and executables (via. -rpath $ORIGIN) on Linux and other ELF targets. Also add epicsGetExecDir() to fetch the directory containing the executable which launched the running process.

These two combine to allow a complete built tree to be moved (relocated), or accessed from a different absolute path (eg. different NFS mount point).

eg.

cat <<EOF > configure/CONFIG_SITE.local
LINKER_USE_RPATH=ORIGIN
EOF
make INSTALL_LOCATION=$PWD/usr1
mv usr1 usr2
./usr2/bin/linux-x86_64/softIoc -d some.db

This also works for downstream modules, even in cases like P4P where the install path is customized.

When working with a complex install tree, the new $(LINKER_ORIGIN_ROOT) would be changed to point to eg. "$(TOP)/.." with sub-directories for Base and various modules. This ensures that linking to system libraries outside of this tree remain absolute.

To post a comment you must log in.
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

This change adds a small python script. I could not find a GNU Make equivalent for os.path.relpath(). So this MR will likely have to wait until someone translates this into perl.

The epicsGetExecDir() implementations for solaris and freebsd have not been tested. As both of these targets use ELF, $ORIGIN should work there. However, I have no plans to tests on either of these targets and will remove these implementations if they are a barrier to acceptance.

Also, to be clear. This is not a change to the default behavior. Unless specifically required with LINKER_USE_RPATH=ORIGIN linking continues to be done with absolute rpath.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

In the course of testing this I've come across two gcc/binutils bugs related to $ORIGIN. Both have been fixed, but will remain an issue for older toolchains.

Prior to binutils 2.28 -rpath $ORIGIN entries weren't correctly used to locate library dependencies. That is, when 'ld' needs to find a library on which an explicitly specified library depends.

Practically speaking this is a problem with the 'calc' module which has optional dependencies on sscan and seq. Downstream modules like streamdevice link against 'calc' without mentioning 'sscan' and 'pv'. This is apparently even stricter than is necessary for single pass static linking.

A partial workaround without side effects is to also include an absolute '-rpath-link' for every relative '-rpath'.

https://sourceware.org/bugzilla/show_bug.cgi?id=16936

I've also seen that even older gcc (circa RHEL5) seem to expand '$' variables internally before passing to 'ld'. So preventing expansion with single quotes like -Wl,-rpath,'$ORIGIN' isn't enough. It is necessary to escape once. -Wl,-rpath,'\$ORIGIN'

I haven't able to find a ticket on this one.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I've been testing this idea for the past ~3 months in the context of a mini distribution 'https://github.com/mdavidsaver/build-epics'. This script produces a tar file of binaries (and source) which can be unpacked into an arbitrary location.

Revision history for this message
Andrew Johnson (anj) wrote :

Core group: See Notes below. ANJ to test on Solaris, and rewrite makeRPath into Perl.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Updated. Now possible to give multiple values for LINKER_ORIGIN_ROOT. This allows for more complicated build environments where some libraries are in their final locations while some are in a staging area.

review: Approve
Revision history for this message
Andrew Johnson (anj) wrote :

Still reviewing. You completely rewrote softMain.cpp since I last looked at this, and I do intend to convert makeRPath into Perl.

review: Needs Information (hold for anj)
Revision history for this message
Andrew Johnson (anj) wrote :

Videocon 9/17: Merge is acceptable in principle after a review from RL.

Revision history for this message
Martin Konrad (info-martin-konrad) wrote :

I actually found time to review the C++ part. Looks straight forward to me. Thumbs up for printing the usage information that was hidden in a comment before.

review: Approve (c++ part only)
Revision history for this message
Andrew Johnson (anj) wrote :

Group 10/4: No issues.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/.travis.yml b/.travis.yml
index d6130c7..e7b1ed5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,7 +15,7 @@ addons:
15script:15script:
16 - .ci/travis-build.sh16 - .ci/travis-build.sh
17env:17env:
18 - CMPLR=gcc18 - CMPLR=gcc EXTRA=LINKER_USE_RPATH=ORIGIN
19 - CMPLR=clang19 - CMPLR=clang
20 - CMPLR=gcc STATIC=YES20 - CMPLR=gcc STATIC=YES
21 - CMPLR=clang STATIC=YES21 - CMPLR=clang STATIC=YES
diff --git a/configure/CONFIG_BASE b/configure/CONFIG_BASE
index 963b83a..0b857c2 100644
--- a/configure/CONFIG_BASE
+++ b/configure/CONFIG_BASE
@@ -45,6 +45,8 @@ FULLPATHNAME = $(PERL) $(TOOLS)/fullPathName.pl
45TAPTOJUNIT = $(PERL) $(TOOLS)/tap-to-junit-xml.pl45TAPTOJUNIT = $(PERL) $(TOOLS)/tap-to-junit-xml.pl
46GENVERSIONHEADER = $(PERL) $(TOOLS)/genVersionHeader.pl $(QUIET_FLAG) $(QUESTION_FLAG)46GENVERSIONHEADER = $(PERL) $(TOOLS)/genVersionHeader.pl $(QUIET_FLAG) $(QUESTION_FLAG)
4747
48MAKERPATH = $(PYTHON) $(TOOLS)/makeRPath.py
49
48#---------------------------------------------------------------50#---------------------------------------------------------------
49# tools for installing libraries and products51# tools for installing libraries and products
50INSTALL = $(PERL) $(TOOLS)/installEpics.pl $(QUIET_FLAG)52INSTALL = $(PERL) $(TOOLS)/installEpics.pl $(QUIET_FLAG)
diff --git a/configure/CONFIG_COMMON b/configure/CONFIG_COMMON
index d3a6696..6f9a073 100644
--- a/configure/CONFIG_COMMON
+++ b/configure/CONFIG_COMMON
@@ -38,6 +38,8 @@ BUILD_ARCHS = $(EPICS_HOST_ARCH) $(CROSS1) $(CROSS2)
38# otherwise override this in os/CONFIG_SITE.<host_arch>.Common38# otherwise override this in os/CONFIG_SITE.<host_arch>.Common
39PERL = perl -CSD39PERL = perl -CSD
4040
41PYTHON = python
42
41#-------------------------------------------------------43#-------------------------------------------------------
42# Check configure/RELEASE file for consistency44# Check configure/RELEASE file for consistency
43CHECK_RELEASE_YES = checkRelease45CHECK_RELEASE_YES = checkRelease
diff --git a/configure/CONFIG_SITE b/configure/CONFIG_SITE
index b657f5b..c46703f 100644
--- a/configure/CONFIG_SITE
+++ b/configure/CONFIG_SITE
@@ -169,10 +169,18 @@ EPICS_SITE_VERSION =
169GCC_PIPE = NO169GCC_PIPE = NO
170170
171# Set RPATH when linking executables and libraries.171# Set RPATH when linking executables and libraries.
172# Must be either YES or NO. If you set this to NO you must also provide a172# Must be either YES, NO, or ORIGIN. If you set this to NO you must also provide a
173# way for Base executables to find their shared libraries when they are173# way for Base executables to find their shared libraries when they are
174# run at build-time, e.g. set the LD_LIBRARY_PATH environment variable.174# run at build-time, e.g. set the LD_LIBRARY_PATH environment variable.
175# ORIGIN is a feature of the ELF executable format used by Linux, freebsd, and solaris.
175LINKER_USE_RPATH = YES176LINKER_USE_RPATH = YES
176177
178# Only used when LINKER_USE_RPATH=ORIGIN
179# The build time root(s) of the relocatable tree (separate multiple w/ ':').
180# Linking to libraries under any root directory will be relative.
181# Linking to libraries outside of this root will be absolute.
182# All root directories are considered to be the same.
183LINKER_ORIGIN_ROOT = $(INSTALL_LOCATION)
184
177# Overrides for the settings above may appear in a CONFIG_SITE.local file185# Overrides for the settings above may appear in a CONFIG_SITE.local file
178-include $(CONFIG)/CONFIG_SITE.local186-include $(CONFIG)/CONFIG_SITE.local
diff --git a/configure/RULES_BUILD b/configure/RULES_BUILD
index 2a78a96..3306fb3 100644
--- a/configure/RULES_BUILD
+++ b/configure/RULES_BUILD
@@ -196,6 +196,13 @@ ifeq ($(EPICS_HOST_ARCH),$(T_A))
196 $(info Warning: RELEASE file consistency checks have been disabled)196 $(info Warning: RELEASE file consistency checks have been disabled)
197endif197endif
198198
199# $(FINAL_DIR) signals eventual install locations to makeRPath script
200$(TESTPRODNAME): FINAL_DIR=.
201$(PRODNAME): FINAL_DIR=$(INSTALL_BIN)
202$(TESTSHRLIBNAME): FINAL_DIR=.
203$(SHRLIBNAME): FINAL_DIR=$(INSTALL_SHRLIB)
204$(LOADABLE_SHRLIBNAME): FINAL_DIR=$(INSTALL_SHRLIB)
205
199#---------------------------------------------------------------206#---------------------------------------------------------------
200# The order of the following rules is207# The order of the following rules is
201# VERY IMPORTANT !!!!208# VERY IMPORTANT !!!!
diff --git a/configure/os/CONFIG.Common.linuxCommon b/configure/os/CONFIG.Common.linuxCommon
index 965de09..e8e9ab3 100644
--- a/configure/os/CONFIG.Common.linuxCommon
+++ b/configure/os/CONFIG.Common.linuxCommon
@@ -25,11 +25,13 @@ STATIC_LDLIBS_YES= -Wl,-Bdynamic
2525
26# Set runtime path for shared libraries if USE_RPATH=YES and STATIC_BUILD=NO26# Set runtime path for shared libraries if USE_RPATH=YES and STATIC_BUILD=NO
27SHRLIBDIR_RPATH_LDFLAGS_YES_NO = $(SHRLIB_DEPLIB_DIRS:%=-Wl,-rpath,%)27SHRLIBDIR_RPATH_LDFLAGS_YES_NO = $(SHRLIB_DEPLIB_DIRS:%=-Wl,-rpath,%)
28SHRLIBDIR_RPATH_LDFLAGS_ORIGIN_NO = $(shell $(MAKERPATH) -O '\$$ORIGIN' -F $(FINAL_DIR) -R $(LINKER_ORIGIN_ROOT) $(SHRLIB_DEPLIB_DIRS))
28SHRLIBDIR_LDFLAGS += \29SHRLIBDIR_LDFLAGS += \
29 $(SHRLIBDIR_RPATH_LDFLAGS_$(LINKER_USE_RPATH)_$(STATIC_BUILD))30 $(SHRLIBDIR_RPATH_LDFLAGS_$(LINKER_USE_RPATH)_$(STATIC_BUILD))
3031
31# Set runtime path for products if USE_RPATH=YES and STATIC_BUILD=NO32# Set runtime path for products if USE_RPATH=YES and STATIC_BUILD=NO
32PRODDIR_RPATH_LDFLAGS_YES_NO = $(PROD_DEPLIB_DIRS:%=-Wl,-rpath,%)33PRODDIR_RPATH_LDFLAGS_YES_NO = $(PROD_DEPLIB_DIRS:%=-Wl,-rpath,%)
34PRODDIR_RPATH_LDFLAGS_ORIGIN_NO = $(shell $(MAKERPATH) -O '\$$ORIGIN' -F $(FINAL_DIR) -R $(LINKER_ORIGIN_ROOT) $(PROD_DEPLIB_DIRS))
33PRODDIR_LDFLAGS += \35PRODDIR_LDFLAGS += \
34 $(PRODDIR_RPATH_LDFLAGS_$(LINKER_USE_RPATH)_$(STATIC_BUILD))36 $(PRODDIR_RPATH_LDFLAGS_$(LINKER_USE_RPATH)_$(STATIC_BUILD))
3537
diff --git a/modules/database/src/std/softIoc/softMain.cpp b/modules/database/src/std/softIoc/softMain.cpp
index 8400a65..bc945c8 100644
--- a/modules/database/src/std/softIoc/softMain.cpp
+++ b/modules/database/src/std/softIoc/softMain.cpp
@@ -9,225 +9,237 @@
99
10/* Author: Andrew Johnson Date: 2003-04-08 */10/* Author: Andrew Johnson Date: 2003-04-08 */
1111
12/* Usage:12#include <iostream>
13 * softIoc [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf]13#include <string>
14 * [-m macro=value,macro2=value2] [-d file.db]14#include <list>
15 * [-x prefix] [st.cmd]15#include <stdexcept>
16 *
17 * If used the -D option must come first, and specify the
18 * path to the softIoc.dbd file. The compile-time install
19 * location is saved in the binary as a default.
20 *
21 * Usage information will be printed if -h is given, then
22 * the program will exit normally.
23 *
24 * The -S option prevents an interactive shell being started
25 * after all arguments have been processed.
26 *
27 * Previous versions accepted a -s option to cause a shell
28 * to be started; this option is still accepted but ignored
29 * since a command shell is now started by default.
30 *
31 * Access Security can be enabled with the -a option giving
32 * the name of the configuration file; if any macros were
33 * set with -m before the -a option was given, they will be
34 * used as access security substitution macros.
35 *
36 * Any number of -m and -d arguments can be interspersed;
37 * the macros are applied to the following .db files. Each
38 * later -m option causes earlier macros to be discarded.
39 *
40 * The -x option loads the softIocExit.db with the macro
41 * IOC set to the string provided. This database contains
42 * a subroutine record named $(IOC):exit which has its field
43 * SNAM set to "exit". When this record is processed, the
44 * subroutine that runs will call epicsExit() with the value
45 * of the field A determining whether the exit status is
46 * EXIT_SUCCESS if (A == 0.0) or EXIT_FAILURE (A != 0.0).
47 *
48 * A st.cmd file is optional. If any databases were loaded
49 * the st.cmd file will be run *after* iocInit. To perform
50 * iocsh commands before iocInit, all database loading must
51 * be performed by the script itself, or by the user from
52 * the interactive IOC shell.
53 */
54
55#include <stddef.h>
56#include <stdlib.h>
57#include <stddef.h>
58#include <string.h>
59#include <stdio.h>
6016
17#include <epicsGetopt.h>
61#include "registryFunction.h"18#include "registryFunction.h"
62#include "epicsThread.h"19#include "epicsThread.h"
63#include "epicsExit.h"20#include "epicsExit.h"
64#include "epicsStdio.h"21#include "epicsStdio.h"
22#include "epicsString.h"
65#include "dbStaticLib.h"23#include "dbStaticLib.h"
66#include "subRecord.h"24#include "subRecord.h"
67#include "dbAccess.h"25#include "dbAccess.h"
68#include "asDbLib.h"26#include "asDbLib.h"
69#include "iocInit.h"27#include "iocInit.h"
70#include "iocsh.h"28#include "iocsh.h"
29#include "osiFileName.h"
71#include "epicsInstallDir.h"30#include "epicsInstallDir.h"
7231
73extern "C" int softIoc_registerRecordDeviceDriver(struct dbBase *pdbbase);32extern "C" int softIoc_registerRecordDeviceDriver(struct dbBase *pdbbase);
7433
75#define DBD_FILE EPICS_BASE "/dbd/softIoc.dbd"34#ifndef EPICS_BASE
76#define EXIT_FILE EPICS_BASE "/db/softIocExit.db"35// so IDEs knows EPICS_BASE is a string constant
36# define EPICS_BASE "/"
37# error -DEPICS_BASE required
38#endif
7739
78const char *arg0;40#define DBD_BASE "dbd/softIoc.dbd"
79const char *base_dbd = DBD_FILE;41#define EXIT_BASE "db/softIocExit.db"
80const char *exit_db = EXIT_FILE;42#define DBD_FILE_REL "../../" DBD_BASE
43#define EXIT_FILE_REL "../../" EXIT_BASE
44#define DBD_FILE EPICS_BASE "/" DBD_BASE
45#define EXIT_FILE EPICS_BASE "/" EXIT_BASE
8146
47namespace {
8248
83static void exitSubroutine(subRecord *precord) {49static void exitSubroutine(subRecord *precord) {
84 epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE);50 epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE);
85}51}
8652
87static void usage(int status) {53void usage(const char *arg0, const std::string& base_dbd) {
88 printf("Usage: %s [-D softIoc.dbd] [-h] [-S] [-a ascf]\n", arg0);54 std::cout<<"Usage: "<<arg0<<
89 puts("\t[-m macro=value,macro2=value2] [-d file.db]");55 " [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf]\n"
90 puts("\t[-x prefix] [st.cmd]");56 "[-m macro=value,macro2=value2] [-d file.db]\n"
91 puts("Compiled-in path to softIoc.dbd is:");57 "[-x prefix] [st.cmd]\n"
92 printf("\t%s\n", base_dbd);58 "\n"
93 epicsExit(status);59 " -D <dbd> If used, must come first. Specify the path to the softIoc.dbdfile."
60 " The compile-time install location is saved in the binary as a default.\n"
61 "\n"
62 " -h Print this mesage and exit.\n"
63 "\n"
64 " -S Prevents an interactive shell being started.\n"
65 "\n"
66 " -s Previously caused a shell to be started. Now accepted and ignored.\n"
67 "\n"
68 " -a <acf> Access Security configuration file. Macro substitution is\n"
69 " performed.\n"
70 "\n"
71 " -m <MAC>=<value>,... Set/replace macro definitions used by subsequent -d and\n"
72 " -a.\n"
73 "\n"
74 " -d <db> Load records from file (dbLoadRecords). Macro substitution is\n"
75 " performed.\n"
76 "\n"
77 " -x <prefix> Load softIocExit.db. Provides a record \"<prefix>:exit\".\n"
78 " Put 0 to exit with success, or non-zero to exit with an error.\n"
79 "\n"
80 "Any number of -m and -d arguments can be interspersed; the macros are applied\n"
81 "to the following .db files. Each later -m option causes earlier macros to be\n"
82 "discarded.\n"
83 "\n"
84 "A st.cmd file is optional. If any databases were loaded the st.cmd file will\n"
85 "be run *after* iocInit. To perform iocsh commands before iocInit, all database\n"
86 "loading must be performed by the script itself, or by the user from the\n"
87 "interactive IOC shell.\n"
88 "\n"
89 "Compiled-in path to softIoc.dbd is:\n"
90 "\t"<<base_dbd.c_str()<<"\n";
94}91}
9592
9693void errIf(int ret, const std::string& msg)
97int main(int argc, char *argv[])
98{94{
99 char *dbd_file = const_cast<char*>(base_dbd);95 if(ret)
100 char *macros = NULL;96 throw std::runtime_error(msg);
101 char xmacro[PVNAME_STRINGSZ + 4];97}
102 int startIocsh = 1; /* default = start shell */98
103 int loadedDb = 0;99void lazy_dbd(const std::string& dbd_file) {
104 100 static bool loaded;
105 arg0 = strrchr(*argv, '/');101 if(loaded) return;
106 if (!arg0) {102 loaded = true;
107 arg0 = *argv;103
108 } else {104 errIf(dbLoadDatabase(dbd_file.c_str(), NULL, NULL),
109 ++arg0; /* skip the '/' */105 std::string("Failed to load DBD file: ")+dbd_file);
110 }106 std::cout<<"dbLoadDatabase(\""<<dbd_file<<"\")\n";
111 107
112 --argc, ++argv;
113
114 /* Do this here in case the dbd file not available */
115 if (argc>0 && **argv=='-' && (*argv)[1]=='h') {
116 usage(EXIT_SUCCESS);
117 }
118
119 if (argc>1 && **argv=='-' && (*argv)[1]=='D') {
120 dbd_file = *++argv;
121 argc -= 2;
122 ++argv;
123 }
124
125 if (dbLoadDatabase(dbd_file, NULL, NULL)) {
126 epicsExit(EXIT_FAILURE);
127 }
128
129 softIoc_registerRecordDeviceDriver(pdbbase);108 softIoc_registerRecordDeviceDriver(pdbbase);
109 std::cout<<"softIoc_registerRecordDeviceDriver(pdbbase)\n";
130 registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);110 registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);
111}
131112
132 while (argc>1 && **argv == '-') {113} // namespace
133 switch ((*argv)[1]) {114
134 case 'a':115int main(int argc, char *argv[])
135 if (macros) asSetSubstitutions(macros);116{
136 asSetFilename(*++argv);117 try {
137 --argc;118 std::string dbd_file(DBD_FILE),
138 break;119 exit_file(EXIT_FILE),
139 120 macros, // scratch space for macros (may be given more than once)
140 case 'd':121 xmacro;
141 if (dbLoadRecords(*++argv, macros)) {122 bool interactive = true;
142 epicsExit(EXIT_FAILURE);123 bool loadedDb = false;
143 }124
144 loadedDb = 1;125 // attempt to compute relative paths
145 --argc;126 {
146 break;127 std::string prefix;
147 128 char *cprefix = epicsGetExecDir();
148 case 'h':129 if(cprefix) {
149 usage(EXIT_SUCCESS);130 try {
150 131 prefix = cprefix;
151 case 'm':132 free(cprefix);
152 macros = *++argv;133 } catch(...) {
153 --argc;134 free(cprefix);
154 break;135 throw;
155 136 }
156 case 'S':137 }
157 startIocsh = 0;138
158 break;139 dbd_file = prefix + DBD_FILE_REL;
159 140 exit_file = prefix + EXIT_FILE_REL;
160 case 's':141 }
161 break;142
162 143 int opt;
163 case 'x':144
164 epicsSnprintf(xmacro, sizeof xmacro, "IOC=%s", *++argv);145 while ((opt = getopt(argc, argv, "ha:d:m:Ssx:")) != -1) {
165 if (dbLoadRecords(exit_db, xmacro)) {146 switch (opt) {
166 epicsExit(EXIT_FAILURE);147 case 'h': /* Print usage */
167 }148 usage(argv[0], dbd_file);
168 loadedDb = 1;149 epicsExit(0);
169 --argc;150 return 0;
170 break;151 default:
171 152 usage(argv[0], dbd_file);
172 default:153 std::cerr<<"Unknown argument: -"<<char(opt)<<"\n";
173 printf("%s: option '%s' not recognized\n", arg0, *argv);154 epicsExit(2);
174 usage(EXIT_FAILURE);155 return 2;
175 }156 case 'a':
176 --argc;157 lazy_dbd(dbd_file);
177 ++argv;158 if (!macros.empty()) {
178 }159 if(asSetSubstitutions(macros.c_str()))
179 160 throw std::bad_alloc();
180 if (argc>0 && **argv=='-') {161 std::cout<<"asSetSubstitutions(\""<<macros<<"\")\n";
181 switch((*argv)[1]) {162 }
182 case 'a':163 if(asSetFilename(optarg))
183 case 'd':164 throw std::bad_alloc();
184 case 'm':165 std::cout<<"asSetFilename(\""<<optarg<<"\")\n";
185 case 'x':166 break;
186 printf("%s: missing argument to option '%s'\n", arg0, *argv);167 case 'd':
187 usage(EXIT_FAILURE);168 lazy_dbd(dbd_file);
188 169 errIf(dbLoadRecords(optarg, macros.c_str()),
189 case 'h':170 std::string("Failed to load: ")+optarg);
190 usage(EXIT_SUCCESS);171 std::cout<<"dbLoadRecords(\""<<optarg<<"\"";
191 172 if(!macros.empty())
192 case 'S':173 std::cout<<", \""<<macros<<"\"";
193 startIocsh = 0;174 std::cout<<")\n";
194 break;175 loadedDb = true;
195 176 break;
196 case 's':177 case 'm':
197 break;178 macros = optarg;
198 179 break;
199 default:180 case 'S':
200 printf("%s: option '%s' not recognized\n", arg0, *argv);181 interactive = false;
201 usage(EXIT_FAILURE);182 break;
202 }183 case 's':
203 --argc;184 break; // historical
204 ++argv;185 case 'x':
205 }186 lazy_dbd(dbd_file);
206 187 xmacro = "IOC=";
207 if (loadedDb) {188 xmacro += optarg;
208 iocInit();189 errIf(dbLoadRecords(exit_file.c_str(), xmacro.c_str()),
209 epicsThreadSleep(0.2);190 std::string("Failed to load: ")+exit_file);
210 }191 loadedDb = true;
211 192 break;
212 /* run user's startup script */193 }
213 if (argc>0) {194 }
214 if (iocsh(*argv)) epicsExit(EXIT_FAILURE);195
215 epicsThreadSleep(0.2);196 lazy_dbd(dbd_file);
216 loadedDb = 1; /* Give it the benefit of the doubt... */197
217 }198 if(optind<argc) {
218 199 // run script
219 /* start an interactive shell if it was requested */200 // ignore any extra positional args (historical)
220 if (startIocsh) {201
221 iocsh(NULL);202 std::cout<<"# Begin "<<argv[optind]<<"\n";
222 } else {203 errIf(iocsh(argv[optind]),
223 if (loadedDb) {204 std::string("Error in ")+argv[optind]);
224 epicsThreadExitMain();205 std::cout<<"# End "<<argv[optind]<<"\n";
225 } else {206
226 printf("%s: Nothing to do!\n", arg0);207 epicsThreadSleep(0.2);
227 usage(EXIT_FAILURE);208 loadedDb = true; /* Give it the benefit of the doubt... */
228 }209 }
210
211 if (loadedDb) {
212 std::cout<<"iocInit()\n";
213 iocInit();
214 epicsThreadSleep(0.2);
215 }
216
217 if(interactive) {
218 std::cout.flush();
219 std::cerr.flush();
220 if(iocsh(NULL)) {
221 epicsExit(1);
222 return 1;
223 }
224
225 } else {
226 if (loadedDb) {
227 epicsThreadExitMain();
228
229 } else {
230 usage(argv[0], dbd_file);
231 std::cerr<<"Nothing to do!\n";
232 epicsExit(1);
233 return 1;
234 }
235 }
236
237 epicsExit(0);
238 return 0;
239
240 }catch(std::exception& e){
241 std::cerr<<"Error: "<<e.what()<<"\n";
242 epicsExit(2);
243 return 2;
229 }244 }
230 epicsExit(EXIT_SUCCESS);
231 /*Note that the following statement will never be executed*/
232 return 0;
233}245}
diff --git a/modules/libcom/src/misc/unixFileName.h b/modules/libcom/src/misc/unixFileName.h
index 36e818c..9d7af25 100644
--- a/modules/libcom/src/misc/unixFileName.h
+++ b/modules/libcom/src/misc/unixFileName.h
@@ -14,7 +14,29 @@
14#ifndef unixFileNameH14#ifndef unixFileNameH
15#define unixFileNameH15#define unixFileNameH
1616
17#include <shareLib.h>
18
19#ifdef __cplusplus
20extern "C" {
21#endif
22
17#define OSI_PATH_LIST_SEPARATOR ":"23#define OSI_PATH_LIST_SEPARATOR ":"
18#define OSI_PATH_SEPARATOR "/"24#define OSI_PATH_SEPARATOR "/"
1925
26/** Return the absolute path of the current executable.
27 @returns NULL or the path. Caller must free()
28 */
29epicsShareFunc
30char *epicsGetExecName(void);
31
32/** Return the absolute path of the directory containing the current executable.
33 @returns NULL or the path. Caller must free()
34 */
35epicsShareFunc
36char *epicsGetExecDir(void);
37
38#ifdef __cplusplus
39}
40#endif
41
20#endif /* unixFileNameH */42#endif /* unixFileNameH */
diff --git a/modules/libcom/src/osi/Makefile b/modules/libcom/src/osi/Makefile
index ecbf4c2..0352e9f 100644
--- a/modules/libcom/src/osi/Makefile
+++ b/modules/libcom/src/osi/Makefile
@@ -123,6 +123,7 @@ Com_SRCS += osdMonotonic.c
123Com_SRCS += osdProcess.c123Com_SRCS += osdProcess.c
124Com_SRCS += osdNetIntf.c124Com_SRCS += osdNetIntf.c
125Com_SRCS += osdMessageQueue.c125Com_SRCS += osdMessageQueue.c
126Com_SRCS += osdgetexec.c
126127
127Com_SRCS += devLibVME.c128Com_SRCS += devLibVME.c
128Com_SRCS += devLibVMEOSD.c129Com_SRCS += devLibVMEOSD.c
diff --git a/modules/libcom/src/osi/os/Darwin/osdgetexec.c b/modules/libcom/src/osi/os/Darwin/osdgetexec.c
129new file mode 100644130new file mode 100644
index 0000000..4e4961c
--- /dev/null
+++ b/modules/libcom/src/osi/os/Darwin/osdgetexec.c
@@ -0,0 +1,50 @@
1
2#include <string.h>
3#include <stdlib.h>
4
5#include <mach-o/dyld.h>
6
7#define epicsExportSharedSymbols
8#include <osiFileName.h>
9
10char *epicsGetExecName(void)
11{
12 uint32_t max = 64u;
13 char *ret = NULL;
14
15 while(1) {
16 char *temp = realloc(ret, max);
17 if(!temp) {
18 /* we treat alloc failure as terminal */
19 free(ret);
20 ret = NULL;
21 break;
22 }
23 ret = temp;
24
25 /* cf. "man 3 dyld" */
26 if(_NSGetExecutablePath(ret, &max)==0) {
27 /* max left unchanged */
28 ret[max-1] = '\0';
29 break;
30 }
31 /* max has been updated with required size */
32 }
33
34 /* TODO: _NSGetExecutablePath() doesn't follow symlinks */
35
36 return ret;
37}
38
39char *epicsGetExecDir(void)
40{
41 char *ret = epicsGetExecName();
42 if(ret) {
43 char *sep = strrchr(ret, '/');
44 if(sep) {
45 /* nil the charactor after the / */
46 sep[1] = '\0';
47 }
48 }
49 return ret;
50}
diff --git a/modules/libcom/src/osi/os/Linux/osdgetexec.c b/modules/libcom/src/osi/os/Linux/osdgetexec.c
0new file mode 10064451new file mode 100644
index 0000000..cba5d78
--- /dev/null
+++ b/modules/libcom/src/osi/os/Linux/osdgetexec.c
@@ -0,0 +1,54 @@
1
2#include <string.h>
3#include <stdlib.h>
4#include <unistd.h>
5#include <limits.h>
6
7#define epicsExportSharedSymbols
8#include <osiFileName.h>
9
10char *epicsGetExecName(void)
11{
12 size_t max = PATH_MAX;
13 char *ret = NULL;
14 ssize_t n;
15
16 while(1) {
17 char *temp = realloc(ret, max);
18 if(!temp) {
19 /* we treat alloc failure as terminal */
20 free(ret);
21 ret = NULL;
22 break;
23 }
24 ret = temp;
25
26 n = readlink("/proc/self/exe", ret, max);
27 if(n == -1) {
28 free(ret);
29 ret = NULL;
30 break;
31 } else if(n < max) {
32 /* readlink() never adds a nil */
33 ret[n] = '\0';
34 break;
35 }
36
37 max += 64;
38 }
39
40 return ret;
41}
42
43char *epicsGetExecDir(void)
44{
45 char *ret = epicsGetExecName();
46 if(ret) {
47 char *sep = strrchr(ret, '/');
48 if(sep) {
49 /* nil the charactor after the / */
50 sep[1] = '\0';
51 }
52 }
53 return ret;
54}
diff --git a/modules/libcom/src/osi/os/WIN32/osdgetexec.c b/modules/libcom/src/osi/os/WIN32/osdgetexec.c
0new file mode 10064455new file mode 100644
index 0000000..a46ce50
--- /dev/null
+++ b/modules/libcom/src/osi/os/WIN32/osdgetexec.c
@@ -0,0 +1,52 @@
1
2#include <string.h>
3#include <stdlib.h>
4#include <windows.h>
5
6#define epicsExportSharedSymbols
7#include <osiFileName.h>
8
9char *epicsGetExecName(void)
10{
11 size_t max = 128;
12 char *ret = NULL;
13 DWORD n;
14
15 while(1) {
16 char *temp = realloc(ret, max);
17 if(!temp) {
18 /* we treat alloc failure as terminal */
19 free(ret);
20 ret = NULL;
21 break;
22 }
23 ret = temp;
24
25 n = GetModuleFileName(NULL, ret, max);
26 if(n == 0) {
27 free(ret);
28 ret = NULL;
29 break;
30 } else if(n < max) {
31 ret[n] = '\0';
32 break;
33 }
34
35 max += 64;
36 }
37
38 return ret;
39}
40
41char *epicsGetExecDir(void)
42{
43 char *ret = epicsGetExecName();
44 if(ret) {
45 char *sep = strrchr(ret, '\\');
46 if(sep) {
47 /* nil the charactor after the / */
48 sep[1] = '\0';
49 }
50 }
51 return ret;
52}
diff --git a/modules/libcom/src/osi/os/WIN32/osiFileName.h b/modules/libcom/src/osi/os/WIN32/osiFileName.h
index 6ff0308..ced745f 100644
--- a/modules/libcom/src/osi/os/WIN32/osiFileName.h
+++ b/modules/libcom/src/osi/os/WIN32/osiFileName.h
@@ -15,7 +15,29 @@
15#ifndef osiFileNameH15#ifndef osiFileNameH
16#define osiFileNameH16#define osiFileNameH
1717
18#include <shareLib.h>
19
20#ifdef __cplusplus
21extern "C" {
22#endif
23
18#define OSI_PATH_LIST_SEPARATOR ";"24#define OSI_PATH_LIST_SEPARATOR ";"
19#define OSI_PATH_SEPARATOR "\\"25#define OSI_PATH_SEPARATOR "\\"
2026
27/** Return the absolute path of the current executable.
28 @returns NULL or the path. Caller must free()
29 */
30epicsShareFunc
31char *epicsGetExecName(void);
32
33/** Return the absolute path of the directory containing the current executable.
34 @returns NULL or the path. Caller must free()
35 */
36epicsShareFunc
37char *epicsGetExecDir(void);
38
39#ifdef __cplusplus
40}
41#endif
42
21#endif /* osiFileNameH */43#endif /* osiFileNameH */
diff --git a/modules/libcom/src/osi/os/cygwin32/osiFileName.h b/modules/libcom/src/osi/os/cygwin32/osiFileName.h
index 6d7fd6e..1e77990 100644
--- a/modules/libcom/src/osi/os/cygwin32/osiFileName.h
+++ b/modules/libcom/src/osi/os/cygwin32/osiFileName.h
@@ -14,7 +14,29 @@
14#ifndef osiFileNameH14#ifndef osiFileNameH
15#define osiFileNameH15#define osiFileNameH
1616
17#include <shareLib.h>
18
19#ifdef __cplusplus
20extern "C" {
21#endif
22
17#define OSI_PATH_LIST_SEPARATOR ";"23#define OSI_PATH_LIST_SEPARATOR ";"
18#define OSI_PATH_SEPARATOR "\\"24#define OSI_PATH_SEPARATOR "\\"
1925
26/** Return the absolute path of the current executable.
27 @returns NULL or the path. Caller must free()
28 */
29epicsShareFunc
30char *epicsGetExecName(void);
31
32/** Return the absolute path of the directory containing the current executable.
33 @returns NULL or the path. Caller must free()
34 */
35epicsShareFunc
36char *epicsGetExecDir(void);
37
38#ifdef __cplusplus
39}
40#endif
41
20#endif /* osiFileNameH */42#endif /* osiFileNameH */
diff --git a/modules/libcom/src/osi/os/default/osdgetexec.c b/modules/libcom/src/osi/os/default/osdgetexec.c
21new file mode 10064443new file mode 100644
index 0000000..0bec9ea
--- /dev/null
+++ b/modules/libcom/src/osi/os/default/osdgetexec.c
@@ -0,0 +1,14 @@
1#include <stdlib.h>
2
3#define epicsExportSharedSymbols
4#include <osiFileName.h>
5
6char *epicsGetExecName(void)
7{
8 return NULL;
9}
10
11char *epicsGetExecDir(void)
12{
13 return NULL;
14}
diff --git a/modules/libcom/src/osi/os/freebsd/osdgetexec.c b/modules/libcom/src/osi/os/freebsd/osdgetexec.c
0new file mode 10064415new file mode 100644
index 0000000..39c0a16
--- /dev/null
+++ b/modules/libcom/src/osi/os/freebsd/osdgetexec.c
@@ -0,0 +1,68 @@
1
2#include <string.h>
3#include <stdlib.h>
4#include <unistd.h>
5#include <limits.h>
6
7#define epicsExportSharedSymbols
8#include <osiFileName.h>
9
10char *epicsGetExecName(void)
11{
12 size_t max = PATH_MAX;
13 char *ret = NULL;
14 ssize_t n;
15
16 while(1) {
17 char *temp = realloc(ret, max);
18 if(!temp) {
19 /* we treat alloc failure as terminal */
20 free(ret);
21 ret = NULL;
22 break;
23 }
24 ret = temp;
25
26 n = readlink("/proc/curproc/file", ret, max);
27 if(n == -1) {
28 free(ret);
29 ret = NULL;
30 break;
31 } else if(n < max) {
32 /* readlink() never adds a nil */
33 ret[n] = '\0';
34 break;
35 }
36
37 max += 64;
38 }
39
40 if(!ret) {
41 int mib[4];
42 mib[0] = CTL_KERN;
43 mib[1] = KERN_PROC;
44 mib[2] = KERN_PROC_PATHNAME;
45 mib[3] = -1;
46
47 ret = malloc(max);
48 if(ret) {
49 sysctl(mib, 4, ret, &cb, NULL, 0);
50 /* TODO: error check */
51 }
52 }
53
54 return ret;
55}
56
57char *epicsGetExecDir(void)
58{
59 char *ret = epicsGetExecName();
60 if(ret) {
61 char *sep = strrchr(ret, '/');
62 if(sep) {
63 /* nil the charactor after the / */
64 sep[1] = '\0';
65 }
66 }
67 return ret;
68}
diff --git a/modules/libcom/src/osi/os/solaris/osdgetexec.c b/modules/libcom/src/osi/os/solaris/osdgetexec.c
0new file mode 10064469new file mode 100644
index 0000000..ff9739c
--- /dev/null
+++ b/modules/libcom/src/osi/os/solaris/osdgetexec.c
@@ -0,0 +1,30 @@
1
2#include <string.h>
3#include <stdlib.h>
4
5#define epicsExportSharedSymbols
6#include <osiFileName.h>
7
8char *epicsGetExecName(void)
9{
10 const char *raw = getexecname();
11 char *ret = NULL;
12 /* manpage says getexecname() might return a relative path. we treat this as an error */
13 if(raw[0]=='/') {
14 ret = strdup(raw);
15 }
16 return ret;
17}
18
19char *epicsGetExecDir(void)
20{
21 char *ret = epicsGetExecName();
22 if(ret) {
23 char *sep = strrchr(ret, '/');
24 if(sep) {
25 /* nil the charactor after the / */
26 sep[1] = '\0';
27 }
28 }
29 return ret;
30}
diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile
index f2e495a..4a5e457 100755
--- a/modules/libcom/test/Makefile
+++ b/modules/libcom/test/Makefile
@@ -232,6 +232,11 @@ osiSockTest_SRCS += osiSockTest.c
232testHarness_SRCS += osiSockTest.c232testHarness_SRCS += osiSockTest.c
233TESTS += osiSockTest233TESTS += osiSockTest
234234
235TESTPROD_HOST += testexecname
236testexecname_SRCS += testexecname.c
237# no point in including in testHarness. Not implemented for RTEMS/vxWorks.
238TESTS += testexecname
239
235ifeq ($(BUILD_CLASS),HOST)240ifeq ($(BUILD_CLASS),HOST)
236ifneq ($(OS_CLASS),WIN32)241ifneq ($(OS_CLASS),WIN32)
237# This test can only be run on a build host, and is broken on Windows242# This test can only be run on a build host, and is broken on Windows
diff --git a/modules/libcom/test/testexecname.c b/modules/libcom/test/testexecname.c
238new file mode 100644243new file mode 100644
index 0000000..87be847
--- /dev/null
+++ b/modules/libcom/test/testexecname.c
@@ -0,0 +1,24 @@
1
2#include <string.h>
3
4#include <epicsUnitTest.h>
5#include <testMain.h>
6
7#include <osiFileName.h>
8
9MAIN(testexecname)
10{
11 testPlan(1);
12
13 {
14 char *buf = epicsGetExecName();
15 if(!buf) {
16 testSkip(1, "epicsGetExecName() not available for this target");
17 } else {
18 char *loc = strstr(buf, "testexecname");
19 testOk(!!loc, "Find \"testexecname\" in \"%s\"", buf);
20 }
21 }
22
23 return testDone();
24}
diff --git a/src/tools/Makefile b/src/tools/Makefile
index 0df1797..0d0c011 100644
--- a/src/tools/Makefile
+++ b/src/tools/Makefile
@@ -39,6 +39,8 @@ PERL_SCRIPTS += tap-to-junit-xml.pl
39PERL_SCRIPTS += useManifestTool.pl39PERL_SCRIPTS += useManifestTool.pl
40PERL_SCRIPTS += genVersionHeader.pl40PERL_SCRIPTS += genVersionHeader.pl
4141
42PERL_SCRIPTS += makeRPath.py
43
42HTMLS = style.css44HTMLS = style.css
43HTMLS += EPICS/Getopts.html45HTMLS += EPICS/Getopts.html
44HTMLS += EPICS/Path.html46HTMLS += EPICS/Path.html
diff --git a/src/tools/makeRPath.py b/src/tools/makeRPath.py
45new file mode 10064447new file mode 100644
index 0000000..000b8b4
--- /dev/null
+++ b/src/tools/makeRPath.py
@@ -0,0 +1,80 @@
1#!/usr/bin/env python
2
3from __future__ import print_function
4
5import sys
6import os
7from collections import OrderedDict # used as OrderedSet
8
9from argparse import ArgumentParser
10
11if os.environ.get('EPICS_DEBUG_RPATH','')=='YES':
12 sys.stderr.write('%s'%sys.argv)
13
14P = ArgumentParser(description='''Compute and output -rpath entries for each of the given paths.
15 Paths under --root will be computed as relative to --final .''',
16epilog='''
17eg. A library to be placed in /build/lib and linked against libraries in
18'/build/lib', '/build/module/lib', and '/other/lib' would pass:
19
20 "makeRPath.py -F /build/lib -R /build /build/lib /build/module/lib /other/lib"
21which prints "-Wl,-rpath,$ORIGIN/. -Wl,-rpath,$ORIGIN/../module/lib -Wl,-rpath,/other/lib"
22''')
23P.add_argument('-F','--final',default=os.getcwd(), help='Final install location for ELF file')
24P.add_argument('-R','--root',default='', help='Root(s) of relocatable tree. Separate with :')
25P.add_argument('-O', '--origin', default='$ORIGIN')
26P.add_argument('path', nargs='*')
27args = P.parse_args()
28
29# eg.
30# target to be installed as: /build/bin/blah
31#
32# post-install will copy as: /install/bin/blah
33#
34# Need to link against:
35# /install/lib/libA.so
36# /build/lib/libB.so
37# /other/lib/libC.so
38#
39# Want final result to be:
40# -rpath $ORIGIN/../lib -rpath /other/lib \
41# -rpath-link /build/lib -rpath-link /install/lib
42
43fdir = os.path.abspath(args.final)
44roots = [os.path.abspath(root) for root in args.root.split(':') if len(root)]
45
46# find the root which contains the final location
47froot = None
48for root in roots:
49 frel = os.path.relpath(fdir, root)
50 if not frel.startswith('..'):
51 # final dir is under this root
52 froot = root
53 break
54
55if froot is None:
56 sys.stderr.write("makeRPath: Final location %s\nNot under any of: %s\n"%(fdir, roots))
57 sys.exit(1)
58
59output = OrderedDict()
60for path in args.path:
61 path = os.path.abspath(path)
62
63 for root in roots:
64 rrel = os.path.relpath(path, root)
65 if not rrel.startswith('..'):
66 # path is under this root
67
68 # some older binutils don't seem to handle $ORIGIN correctly
69 # when locating dependencies of libraries. So also provide
70 # the absolute path for internal use by 'ld' only.
71 output['-Wl,-rpath-link,'+path] = True
72
73 # frel is final location relative to enclosing root
74 # rrel is target location relative to enclosing root
75 path = os.path.relpath(rrel, frel)
76 break
77
78 output['-Wl,-rpath,'+os.path.join(args.origin, path)] = True
79
80print(' '.join(output))

Subscribers

People subscribed via source and target branches