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
1diff --git a/.travis.yml b/.travis.yml
2index d6130c7..e7b1ed5 100644
3--- a/.travis.yml
4+++ b/.travis.yml
5@@ -15,7 +15,7 @@ addons:
6 script:
7 - .ci/travis-build.sh
8 env:
9- - CMPLR=gcc
10+ - CMPLR=gcc EXTRA=LINKER_USE_RPATH=ORIGIN
11 - CMPLR=clang
12 - CMPLR=gcc STATIC=YES
13 - CMPLR=clang STATIC=YES
14diff --git a/configure/CONFIG_BASE b/configure/CONFIG_BASE
15index 963b83a..0b857c2 100644
16--- a/configure/CONFIG_BASE
17+++ b/configure/CONFIG_BASE
18@@ -45,6 +45,8 @@ FULLPATHNAME = $(PERL) $(TOOLS)/fullPathName.pl
19 TAPTOJUNIT = $(PERL) $(TOOLS)/tap-to-junit-xml.pl
20 GENVERSIONHEADER = $(PERL) $(TOOLS)/genVersionHeader.pl $(QUIET_FLAG) $(QUESTION_FLAG)
21
22+MAKERPATH = $(PYTHON) $(TOOLS)/makeRPath.py
23+
24 #---------------------------------------------------------------
25 # tools for installing libraries and products
26 INSTALL = $(PERL) $(TOOLS)/installEpics.pl $(QUIET_FLAG)
27diff --git a/configure/CONFIG_COMMON b/configure/CONFIG_COMMON
28index d3a6696..6f9a073 100644
29--- a/configure/CONFIG_COMMON
30+++ b/configure/CONFIG_COMMON
31@@ -38,6 +38,8 @@ BUILD_ARCHS = $(EPICS_HOST_ARCH) $(CROSS1) $(CROSS2)
32 # otherwise override this in os/CONFIG_SITE.<host_arch>.Common
33 PERL = perl -CSD
34
35+PYTHON = python
36+
37 #-------------------------------------------------------
38 # Check configure/RELEASE file for consistency
39 CHECK_RELEASE_YES = checkRelease
40diff --git a/configure/CONFIG_SITE b/configure/CONFIG_SITE
41index b657f5b..c46703f 100644
42--- a/configure/CONFIG_SITE
43+++ b/configure/CONFIG_SITE
44@@ -169,10 +169,18 @@ EPICS_SITE_VERSION =
45 GCC_PIPE = NO
46
47 # Set RPATH when linking executables and libraries.
48-# Must be either YES or NO. If you set this to NO you must also provide a
49+# Must be either YES, NO, or ORIGIN. If you set this to NO you must also provide a
50 # way for Base executables to find their shared libraries when they are
51 # run at build-time, e.g. set the LD_LIBRARY_PATH environment variable.
52+# ORIGIN is a feature of the ELF executable format used by Linux, freebsd, and solaris.
53 LINKER_USE_RPATH = YES
54
55+# Only used when LINKER_USE_RPATH=ORIGIN
56+# The build time root(s) of the relocatable tree (separate multiple w/ ':').
57+# Linking to libraries under any root directory will be relative.
58+# Linking to libraries outside of this root will be absolute.
59+# All root directories are considered to be the same.
60+LINKER_ORIGIN_ROOT = $(INSTALL_LOCATION)
61+
62 # Overrides for the settings above may appear in a CONFIG_SITE.local file
63 -include $(CONFIG)/CONFIG_SITE.local
64diff --git a/configure/RULES_BUILD b/configure/RULES_BUILD
65index 2a78a96..3306fb3 100644
66--- a/configure/RULES_BUILD
67+++ b/configure/RULES_BUILD
68@@ -196,6 +196,13 @@ ifeq ($(EPICS_HOST_ARCH),$(T_A))
69 $(info Warning: RELEASE file consistency checks have been disabled)
70 endif
71
72+# $(FINAL_DIR) signals eventual install locations to makeRPath script
73+$(TESTPRODNAME): FINAL_DIR=.
74+$(PRODNAME): FINAL_DIR=$(INSTALL_BIN)
75+$(TESTSHRLIBNAME): FINAL_DIR=.
76+$(SHRLIBNAME): FINAL_DIR=$(INSTALL_SHRLIB)
77+$(LOADABLE_SHRLIBNAME): FINAL_DIR=$(INSTALL_SHRLIB)
78+
79 #---------------------------------------------------------------
80 # The order of the following rules is
81 # VERY IMPORTANT !!!!
82diff --git a/configure/os/CONFIG.Common.linuxCommon b/configure/os/CONFIG.Common.linuxCommon
83index 965de09..e8e9ab3 100644
84--- a/configure/os/CONFIG.Common.linuxCommon
85+++ b/configure/os/CONFIG.Common.linuxCommon
86@@ -25,11 +25,13 @@ STATIC_LDLIBS_YES= -Wl,-Bdynamic
87
88 # Set runtime path for shared libraries if USE_RPATH=YES and STATIC_BUILD=NO
89 SHRLIBDIR_RPATH_LDFLAGS_YES_NO = $(SHRLIB_DEPLIB_DIRS:%=-Wl,-rpath,%)
90+SHRLIBDIR_RPATH_LDFLAGS_ORIGIN_NO = $(shell $(MAKERPATH) -O '\$$ORIGIN' -F $(FINAL_DIR) -R $(LINKER_ORIGIN_ROOT) $(SHRLIB_DEPLIB_DIRS))
91 SHRLIBDIR_LDFLAGS += \
92 $(SHRLIBDIR_RPATH_LDFLAGS_$(LINKER_USE_RPATH)_$(STATIC_BUILD))
93
94 # Set runtime path for products if USE_RPATH=YES and STATIC_BUILD=NO
95 PRODDIR_RPATH_LDFLAGS_YES_NO = $(PROD_DEPLIB_DIRS:%=-Wl,-rpath,%)
96+PRODDIR_RPATH_LDFLAGS_ORIGIN_NO = $(shell $(MAKERPATH) -O '\$$ORIGIN' -F $(FINAL_DIR) -R $(LINKER_ORIGIN_ROOT) $(PROD_DEPLIB_DIRS))
97 PRODDIR_LDFLAGS += \
98 $(PRODDIR_RPATH_LDFLAGS_$(LINKER_USE_RPATH)_$(STATIC_BUILD))
99
100diff --git a/modules/database/src/std/softIoc/softMain.cpp b/modules/database/src/std/softIoc/softMain.cpp
101index 8400a65..bc945c8 100644
102--- a/modules/database/src/std/softIoc/softMain.cpp
103+++ b/modules/database/src/std/softIoc/softMain.cpp
104@@ -9,225 +9,237 @@
105
106 /* Author: Andrew Johnson Date: 2003-04-08 */
107
108-/* Usage:
109- * softIoc [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf]
110- * [-m macro=value,macro2=value2] [-d file.db]
111- * [-x prefix] [st.cmd]
112- *
113- * If used the -D option must come first, and specify the
114- * path to the softIoc.dbd file. The compile-time install
115- * location is saved in the binary as a default.
116- *
117- * Usage information will be printed if -h is given, then
118- * the program will exit normally.
119- *
120- * The -S option prevents an interactive shell being started
121- * after all arguments have been processed.
122- *
123- * Previous versions accepted a -s option to cause a shell
124- * to be started; this option is still accepted but ignored
125- * since a command shell is now started by default.
126- *
127- * Access Security can be enabled with the -a option giving
128- * the name of the configuration file; if any macros were
129- * set with -m before the -a option was given, they will be
130- * used as access security substitution macros.
131- *
132- * Any number of -m and -d arguments can be interspersed;
133- * the macros are applied to the following .db files. Each
134- * later -m option causes earlier macros to be discarded.
135- *
136- * The -x option loads the softIocExit.db with the macro
137- * IOC set to the string provided. This database contains
138- * a subroutine record named $(IOC):exit which has its field
139- * SNAM set to "exit". When this record is processed, the
140- * subroutine that runs will call epicsExit() with the value
141- * of the field A determining whether the exit status is
142- * EXIT_SUCCESS if (A == 0.0) or EXIT_FAILURE (A != 0.0).
143- *
144- * A st.cmd file is optional. If any databases were loaded
145- * the st.cmd file will be run *after* iocInit. To perform
146- * iocsh commands before iocInit, all database loading must
147- * be performed by the script itself, or by the user from
148- * the interactive IOC shell.
149- */
150-
151-#include <stddef.h>
152-#include <stdlib.h>
153-#include <stddef.h>
154-#include <string.h>
155-#include <stdio.h>
156+#include <iostream>
157+#include <string>
158+#include <list>
159+#include <stdexcept>
160
161+#include <epicsGetopt.h>
162 #include "registryFunction.h"
163 #include "epicsThread.h"
164 #include "epicsExit.h"
165 #include "epicsStdio.h"
166+#include "epicsString.h"
167 #include "dbStaticLib.h"
168 #include "subRecord.h"
169 #include "dbAccess.h"
170 #include "asDbLib.h"
171 #include "iocInit.h"
172 #include "iocsh.h"
173+#include "osiFileName.h"
174 #include "epicsInstallDir.h"
175
176 extern "C" int softIoc_registerRecordDeviceDriver(struct dbBase *pdbbase);
177
178-#define DBD_FILE EPICS_BASE "/dbd/softIoc.dbd"
179-#define EXIT_FILE EPICS_BASE "/db/softIocExit.db"
180+#ifndef EPICS_BASE
181+// so IDEs knows EPICS_BASE is a string constant
182+# define EPICS_BASE "/"
183+# error -DEPICS_BASE required
184+#endif
185
186-const char *arg0;
187-const char *base_dbd = DBD_FILE;
188-const char *exit_db = EXIT_FILE;
189+#define DBD_BASE "dbd/softIoc.dbd"
190+#define EXIT_BASE "db/softIocExit.db"
191+#define DBD_FILE_REL "../../" DBD_BASE
192+#define EXIT_FILE_REL "../../" EXIT_BASE
193+#define DBD_FILE EPICS_BASE "/" DBD_BASE
194+#define EXIT_FILE EPICS_BASE "/" EXIT_BASE
195
196+namespace {
197
198 static void exitSubroutine(subRecord *precord) {
199 epicsExitLater((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE);
200 }
201
202-static void usage(int status) {
203- printf("Usage: %s [-D softIoc.dbd] [-h] [-S] [-a ascf]\n", arg0);
204- puts("\t[-m macro=value,macro2=value2] [-d file.db]");
205- puts("\t[-x prefix] [st.cmd]");
206- puts("Compiled-in path to softIoc.dbd is:");
207- printf("\t%s\n", base_dbd);
208- epicsExit(status);
209+void usage(const char *arg0, const std::string& base_dbd) {
210+ std::cout<<"Usage: "<<arg0<<
211+ " [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf]\n"
212+ "[-m macro=value,macro2=value2] [-d file.db]\n"
213+ "[-x prefix] [st.cmd]\n"
214+ "\n"
215+ " -D <dbd> If used, must come first. Specify the path to the softIoc.dbdfile."
216+ " The compile-time install location is saved in the binary as a default.\n"
217+ "\n"
218+ " -h Print this mesage and exit.\n"
219+ "\n"
220+ " -S Prevents an interactive shell being started.\n"
221+ "\n"
222+ " -s Previously caused a shell to be started. Now accepted and ignored.\n"
223+ "\n"
224+ " -a <acf> Access Security configuration file. Macro substitution is\n"
225+ " performed.\n"
226+ "\n"
227+ " -m <MAC>=<value>,... Set/replace macro definitions used by subsequent -d and\n"
228+ " -a.\n"
229+ "\n"
230+ " -d <db> Load records from file (dbLoadRecords). Macro substitution is\n"
231+ " performed.\n"
232+ "\n"
233+ " -x <prefix> Load softIocExit.db. Provides a record \"<prefix>:exit\".\n"
234+ " Put 0 to exit with success, or non-zero to exit with an error.\n"
235+ "\n"
236+ "Any number of -m and -d arguments can be interspersed; the macros are applied\n"
237+ "to the following .db files. Each later -m option causes earlier macros to be\n"
238+ "discarded.\n"
239+ "\n"
240+ "A st.cmd file is optional. If any databases were loaded the st.cmd file will\n"
241+ "be run *after* iocInit. To perform iocsh commands before iocInit, all database\n"
242+ "loading must be performed by the script itself, or by the user from the\n"
243+ "interactive IOC shell.\n"
244+ "\n"
245+ "Compiled-in path to softIoc.dbd is:\n"
246+ "\t"<<base_dbd.c_str()<<"\n";
247 }
248
249-
250-int main(int argc, char *argv[])
251+void errIf(int ret, const std::string& msg)
252 {
253- char *dbd_file = const_cast<char*>(base_dbd);
254- char *macros = NULL;
255- char xmacro[PVNAME_STRINGSZ + 4];
256- int startIocsh = 1; /* default = start shell */
257- int loadedDb = 0;
258-
259- arg0 = strrchr(*argv, '/');
260- if (!arg0) {
261- arg0 = *argv;
262- } else {
263- ++arg0; /* skip the '/' */
264- }
265-
266- --argc, ++argv;
267-
268- /* Do this here in case the dbd file not available */
269- if (argc>0 && **argv=='-' && (*argv)[1]=='h') {
270- usage(EXIT_SUCCESS);
271- }
272-
273- if (argc>1 && **argv=='-' && (*argv)[1]=='D') {
274- dbd_file = *++argv;
275- argc -= 2;
276- ++argv;
277- }
278-
279- if (dbLoadDatabase(dbd_file, NULL, NULL)) {
280- epicsExit(EXIT_FAILURE);
281- }
282-
283+ if(ret)
284+ throw std::runtime_error(msg);
285+}
286+
287+void lazy_dbd(const std::string& dbd_file) {
288+ static bool loaded;
289+ if(loaded) return;
290+ loaded = true;
291+
292+ errIf(dbLoadDatabase(dbd_file.c_str(), NULL, NULL),
293+ std::string("Failed to load DBD file: ")+dbd_file);
294+ std::cout<<"dbLoadDatabase(\""<<dbd_file<<"\")\n";
295+
296 softIoc_registerRecordDeviceDriver(pdbbase);
297+ std::cout<<"softIoc_registerRecordDeviceDriver(pdbbase)\n";
298 registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);
299+}
300
301- while (argc>1 && **argv == '-') {
302- switch ((*argv)[1]) {
303- case 'a':
304- if (macros) asSetSubstitutions(macros);
305- asSetFilename(*++argv);
306- --argc;
307- break;
308-
309- case 'd':
310- if (dbLoadRecords(*++argv, macros)) {
311- epicsExit(EXIT_FAILURE);
312- }
313- loadedDb = 1;
314- --argc;
315- break;
316-
317- case 'h':
318- usage(EXIT_SUCCESS);
319-
320- case 'm':
321- macros = *++argv;
322- --argc;
323- break;
324-
325- case 'S':
326- startIocsh = 0;
327- break;
328-
329- case 's':
330- break;
331-
332- case 'x':
333- epicsSnprintf(xmacro, sizeof xmacro, "IOC=%s", *++argv);
334- if (dbLoadRecords(exit_db, xmacro)) {
335- epicsExit(EXIT_FAILURE);
336- }
337- loadedDb = 1;
338- --argc;
339- break;
340-
341- default:
342- printf("%s: option '%s' not recognized\n", arg0, *argv);
343- usage(EXIT_FAILURE);
344- }
345- --argc;
346- ++argv;
347- }
348-
349- if (argc>0 && **argv=='-') {
350- switch((*argv)[1]) {
351- case 'a':
352- case 'd':
353- case 'm':
354- case 'x':
355- printf("%s: missing argument to option '%s'\n", arg0, *argv);
356- usage(EXIT_FAILURE);
357-
358- case 'h':
359- usage(EXIT_SUCCESS);
360-
361- case 'S':
362- startIocsh = 0;
363- break;
364-
365- case 's':
366- break;
367-
368- default:
369- printf("%s: option '%s' not recognized\n", arg0, *argv);
370- usage(EXIT_FAILURE);
371- }
372- --argc;
373- ++argv;
374- }
375-
376- if (loadedDb) {
377- iocInit();
378- epicsThreadSleep(0.2);
379- }
380-
381- /* run user's startup script */
382- if (argc>0) {
383- if (iocsh(*argv)) epicsExit(EXIT_FAILURE);
384- epicsThreadSleep(0.2);
385- loadedDb = 1; /* Give it the benefit of the doubt... */
386- }
387-
388- /* start an interactive shell if it was requested */
389- if (startIocsh) {
390- iocsh(NULL);
391- } else {
392- if (loadedDb) {
393- epicsThreadExitMain();
394- } else {
395- printf("%s: Nothing to do!\n", arg0);
396- usage(EXIT_FAILURE);
397- }
398+} // namespace
399+
400+int main(int argc, char *argv[])
401+{
402+ try {
403+ std::string dbd_file(DBD_FILE),
404+ exit_file(EXIT_FILE),
405+ macros, // scratch space for macros (may be given more than once)
406+ xmacro;
407+ bool interactive = true;
408+ bool loadedDb = false;
409+
410+ // attempt to compute relative paths
411+ {
412+ std::string prefix;
413+ char *cprefix = epicsGetExecDir();
414+ if(cprefix) {
415+ try {
416+ prefix = cprefix;
417+ free(cprefix);
418+ } catch(...) {
419+ free(cprefix);
420+ throw;
421+ }
422+ }
423+
424+ dbd_file = prefix + DBD_FILE_REL;
425+ exit_file = prefix + EXIT_FILE_REL;
426+ }
427+
428+ int opt;
429+
430+ while ((opt = getopt(argc, argv, "ha:d:m:Ssx:")) != -1) {
431+ switch (opt) {
432+ case 'h': /* Print usage */
433+ usage(argv[0], dbd_file);
434+ epicsExit(0);
435+ return 0;
436+ default:
437+ usage(argv[0], dbd_file);
438+ std::cerr<<"Unknown argument: -"<<char(opt)<<"\n";
439+ epicsExit(2);
440+ return 2;
441+ case 'a':
442+ lazy_dbd(dbd_file);
443+ if (!macros.empty()) {
444+ if(asSetSubstitutions(macros.c_str()))
445+ throw std::bad_alloc();
446+ std::cout<<"asSetSubstitutions(\""<<macros<<"\")\n";
447+ }
448+ if(asSetFilename(optarg))
449+ throw std::bad_alloc();
450+ std::cout<<"asSetFilename(\""<<optarg<<"\")\n";
451+ break;
452+ case 'd':
453+ lazy_dbd(dbd_file);
454+ errIf(dbLoadRecords(optarg, macros.c_str()),
455+ std::string("Failed to load: ")+optarg);
456+ std::cout<<"dbLoadRecords(\""<<optarg<<"\"";
457+ if(!macros.empty())
458+ std::cout<<", \""<<macros<<"\"";
459+ std::cout<<")\n";
460+ loadedDb = true;
461+ break;
462+ case 'm':
463+ macros = optarg;
464+ break;
465+ case 'S':
466+ interactive = false;
467+ break;
468+ case 's':
469+ break; // historical
470+ case 'x':
471+ lazy_dbd(dbd_file);
472+ xmacro = "IOC=";
473+ xmacro += optarg;
474+ errIf(dbLoadRecords(exit_file.c_str(), xmacro.c_str()),
475+ std::string("Failed to load: ")+exit_file);
476+ loadedDb = true;
477+ break;
478+ }
479+ }
480+
481+ lazy_dbd(dbd_file);
482+
483+ if(optind<argc) {
484+ // run script
485+ // ignore any extra positional args (historical)
486+
487+ std::cout<<"# Begin "<<argv[optind]<<"\n";
488+ errIf(iocsh(argv[optind]),
489+ std::string("Error in ")+argv[optind]);
490+ std::cout<<"# End "<<argv[optind]<<"\n";
491+
492+ epicsThreadSleep(0.2);
493+ loadedDb = true; /* Give it the benefit of the doubt... */
494+ }
495+
496+ if (loadedDb) {
497+ std::cout<<"iocInit()\n";
498+ iocInit();
499+ epicsThreadSleep(0.2);
500+ }
501+
502+ if(interactive) {
503+ std::cout.flush();
504+ std::cerr.flush();
505+ if(iocsh(NULL)) {
506+ epicsExit(1);
507+ return 1;
508+ }
509+
510+ } else {
511+ if (loadedDb) {
512+ epicsThreadExitMain();
513+
514+ } else {
515+ usage(argv[0], dbd_file);
516+ std::cerr<<"Nothing to do!\n";
517+ epicsExit(1);
518+ return 1;
519+ }
520+ }
521+
522+ epicsExit(0);
523+ return 0;
524+
525+ }catch(std::exception& e){
526+ std::cerr<<"Error: "<<e.what()<<"\n";
527+ epicsExit(2);
528+ return 2;
529 }
530- epicsExit(EXIT_SUCCESS);
531- /*Note that the following statement will never be executed*/
532- return 0;
533 }
534diff --git a/modules/libcom/src/misc/unixFileName.h b/modules/libcom/src/misc/unixFileName.h
535index 36e818c..9d7af25 100644
536--- a/modules/libcom/src/misc/unixFileName.h
537+++ b/modules/libcom/src/misc/unixFileName.h
538@@ -14,7 +14,29 @@
539 #ifndef unixFileNameH
540 #define unixFileNameH
541
542+#include <shareLib.h>
543+
544+#ifdef __cplusplus
545+extern "C" {
546+#endif
547+
548 #define OSI_PATH_LIST_SEPARATOR ":"
549 #define OSI_PATH_SEPARATOR "/"
550
551+/** Return the absolute path of the current executable.
552+ @returns NULL or the path. Caller must free()
553+ */
554+epicsShareFunc
555+char *epicsGetExecName(void);
556+
557+/** Return the absolute path of the directory containing the current executable.
558+ @returns NULL or the path. Caller must free()
559+ */
560+epicsShareFunc
561+char *epicsGetExecDir(void);
562+
563+#ifdef __cplusplus
564+}
565+#endif
566+
567 #endif /* unixFileNameH */
568diff --git a/modules/libcom/src/osi/Makefile b/modules/libcom/src/osi/Makefile
569index ecbf4c2..0352e9f 100644
570--- a/modules/libcom/src/osi/Makefile
571+++ b/modules/libcom/src/osi/Makefile
572@@ -123,6 +123,7 @@ Com_SRCS += osdMonotonic.c
573 Com_SRCS += osdProcess.c
574 Com_SRCS += osdNetIntf.c
575 Com_SRCS += osdMessageQueue.c
576+Com_SRCS += osdgetexec.c
577
578 Com_SRCS += devLibVME.c
579 Com_SRCS += devLibVMEOSD.c
580diff --git a/modules/libcom/src/osi/os/Darwin/osdgetexec.c b/modules/libcom/src/osi/os/Darwin/osdgetexec.c
581new file mode 100644
582index 0000000..4e4961c
583--- /dev/null
584+++ b/modules/libcom/src/osi/os/Darwin/osdgetexec.c
585@@ -0,0 +1,50 @@
586+
587+#include <string.h>
588+#include <stdlib.h>
589+
590+#include <mach-o/dyld.h>
591+
592+#define epicsExportSharedSymbols
593+#include <osiFileName.h>
594+
595+char *epicsGetExecName(void)
596+{
597+ uint32_t max = 64u;
598+ char *ret = NULL;
599+
600+ while(1) {
601+ char *temp = realloc(ret, max);
602+ if(!temp) {
603+ /* we treat alloc failure as terminal */
604+ free(ret);
605+ ret = NULL;
606+ break;
607+ }
608+ ret = temp;
609+
610+ /* cf. "man 3 dyld" */
611+ if(_NSGetExecutablePath(ret, &max)==0) {
612+ /* max left unchanged */
613+ ret[max-1] = '\0';
614+ break;
615+ }
616+ /* max has been updated with required size */
617+ }
618+
619+ /* TODO: _NSGetExecutablePath() doesn't follow symlinks */
620+
621+ return ret;
622+}
623+
624+char *epicsGetExecDir(void)
625+{
626+ char *ret = epicsGetExecName();
627+ if(ret) {
628+ char *sep = strrchr(ret, '/');
629+ if(sep) {
630+ /* nil the charactor after the / */
631+ sep[1] = '\0';
632+ }
633+ }
634+ return ret;
635+}
636diff --git a/modules/libcom/src/osi/os/Linux/osdgetexec.c b/modules/libcom/src/osi/os/Linux/osdgetexec.c
637new file mode 100644
638index 0000000..cba5d78
639--- /dev/null
640+++ b/modules/libcom/src/osi/os/Linux/osdgetexec.c
641@@ -0,0 +1,54 @@
642+
643+#include <string.h>
644+#include <stdlib.h>
645+#include <unistd.h>
646+#include <limits.h>
647+
648+#define epicsExportSharedSymbols
649+#include <osiFileName.h>
650+
651+char *epicsGetExecName(void)
652+{
653+ size_t max = PATH_MAX;
654+ char *ret = NULL;
655+ ssize_t n;
656+
657+ while(1) {
658+ char *temp = realloc(ret, max);
659+ if(!temp) {
660+ /* we treat alloc failure as terminal */
661+ free(ret);
662+ ret = NULL;
663+ break;
664+ }
665+ ret = temp;
666+
667+ n = readlink("/proc/self/exe", ret, max);
668+ if(n == -1) {
669+ free(ret);
670+ ret = NULL;
671+ break;
672+ } else if(n < max) {
673+ /* readlink() never adds a nil */
674+ ret[n] = '\0';
675+ break;
676+ }
677+
678+ max += 64;
679+ }
680+
681+ return ret;
682+}
683+
684+char *epicsGetExecDir(void)
685+{
686+ char *ret = epicsGetExecName();
687+ if(ret) {
688+ char *sep = strrchr(ret, '/');
689+ if(sep) {
690+ /* nil the charactor after the / */
691+ sep[1] = '\0';
692+ }
693+ }
694+ return ret;
695+}
696diff --git a/modules/libcom/src/osi/os/WIN32/osdgetexec.c b/modules/libcom/src/osi/os/WIN32/osdgetexec.c
697new file mode 100644
698index 0000000..a46ce50
699--- /dev/null
700+++ b/modules/libcom/src/osi/os/WIN32/osdgetexec.c
701@@ -0,0 +1,52 @@
702+
703+#include <string.h>
704+#include <stdlib.h>
705+#include <windows.h>
706+
707+#define epicsExportSharedSymbols
708+#include <osiFileName.h>
709+
710+char *epicsGetExecName(void)
711+{
712+ size_t max = 128;
713+ char *ret = NULL;
714+ DWORD n;
715+
716+ while(1) {
717+ char *temp = realloc(ret, max);
718+ if(!temp) {
719+ /* we treat alloc failure as terminal */
720+ free(ret);
721+ ret = NULL;
722+ break;
723+ }
724+ ret = temp;
725+
726+ n = GetModuleFileName(NULL, ret, max);
727+ if(n == 0) {
728+ free(ret);
729+ ret = NULL;
730+ break;
731+ } else if(n < max) {
732+ ret[n] = '\0';
733+ break;
734+ }
735+
736+ max += 64;
737+ }
738+
739+ return ret;
740+}
741+
742+char *epicsGetExecDir(void)
743+{
744+ char *ret = epicsGetExecName();
745+ if(ret) {
746+ char *sep = strrchr(ret, '\\');
747+ if(sep) {
748+ /* nil the charactor after the / */
749+ sep[1] = '\0';
750+ }
751+ }
752+ return ret;
753+}
754diff --git a/modules/libcom/src/osi/os/WIN32/osiFileName.h b/modules/libcom/src/osi/os/WIN32/osiFileName.h
755index 6ff0308..ced745f 100644
756--- a/modules/libcom/src/osi/os/WIN32/osiFileName.h
757+++ b/modules/libcom/src/osi/os/WIN32/osiFileName.h
758@@ -15,7 +15,29 @@
759 #ifndef osiFileNameH
760 #define osiFileNameH
761
762+#include <shareLib.h>
763+
764+#ifdef __cplusplus
765+extern "C" {
766+#endif
767+
768 #define OSI_PATH_LIST_SEPARATOR ";"
769 #define OSI_PATH_SEPARATOR "\\"
770
771+/** Return the absolute path of the current executable.
772+ @returns NULL or the path. Caller must free()
773+ */
774+epicsShareFunc
775+char *epicsGetExecName(void);
776+
777+/** Return the absolute path of the directory containing the current executable.
778+ @returns NULL or the path. Caller must free()
779+ */
780+epicsShareFunc
781+char *epicsGetExecDir(void);
782+
783+#ifdef __cplusplus
784+}
785+#endif
786+
787 #endif /* osiFileNameH */
788diff --git a/modules/libcom/src/osi/os/cygwin32/osiFileName.h b/modules/libcom/src/osi/os/cygwin32/osiFileName.h
789index 6d7fd6e..1e77990 100644
790--- a/modules/libcom/src/osi/os/cygwin32/osiFileName.h
791+++ b/modules/libcom/src/osi/os/cygwin32/osiFileName.h
792@@ -14,7 +14,29 @@
793 #ifndef osiFileNameH
794 #define osiFileNameH
795
796+#include <shareLib.h>
797+
798+#ifdef __cplusplus
799+extern "C" {
800+#endif
801+
802 #define OSI_PATH_LIST_SEPARATOR ";"
803 #define OSI_PATH_SEPARATOR "\\"
804
805+/** Return the absolute path of the current executable.
806+ @returns NULL or the path. Caller must free()
807+ */
808+epicsShareFunc
809+char *epicsGetExecName(void);
810+
811+/** Return the absolute path of the directory containing the current executable.
812+ @returns NULL or the path. Caller must free()
813+ */
814+epicsShareFunc
815+char *epicsGetExecDir(void);
816+
817+#ifdef __cplusplus
818+}
819+#endif
820+
821 #endif /* osiFileNameH */
822diff --git a/modules/libcom/src/osi/os/default/osdgetexec.c b/modules/libcom/src/osi/os/default/osdgetexec.c
823new file mode 100644
824index 0000000..0bec9ea
825--- /dev/null
826+++ b/modules/libcom/src/osi/os/default/osdgetexec.c
827@@ -0,0 +1,14 @@
828+#include <stdlib.h>
829+
830+#define epicsExportSharedSymbols
831+#include <osiFileName.h>
832+
833+char *epicsGetExecName(void)
834+{
835+ return NULL;
836+}
837+
838+char *epicsGetExecDir(void)
839+{
840+ return NULL;
841+}
842diff --git a/modules/libcom/src/osi/os/freebsd/osdgetexec.c b/modules/libcom/src/osi/os/freebsd/osdgetexec.c
843new file mode 100644
844index 0000000..39c0a16
845--- /dev/null
846+++ b/modules/libcom/src/osi/os/freebsd/osdgetexec.c
847@@ -0,0 +1,68 @@
848+
849+#include <string.h>
850+#include <stdlib.h>
851+#include <unistd.h>
852+#include <limits.h>
853+
854+#define epicsExportSharedSymbols
855+#include <osiFileName.h>
856+
857+char *epicsGetExecName(void)
858+{
859+ size_t max = PATH_MAX;
860+ char *ret = NULL;
861+ ssize_t n;
862+
863+ while(1) {
864+ char *temp = realloc(ret, max);
865+ if(!temp) {
866+ /* we treat alloc failure as terminal */
867+ free(ret);
868+ ret = NULL;
869+ break;
870+ }
871+ ret = temp;
872+
873+ n = readlink("/proc/curproc/file", ret, max);
874+ if(n == -1) {
875+ free(ret);
876+ ret = NULL;
877+ break;
878+ } else if(n < max) {
879+ /* readlink() never adds a nil */
880+ ret[n] = '\0';
881+ break;
882+ }
883+
884+ max += 64;
885+ }
886+
887+ if(!ret) {
888+ int mib[4];
889+ mib[0] = CTL_KERN;
890+ mib[1] = KERN_PROC;
891+ mib[2] = KERN_PROC_PATHNAME;
892+ mib[3] = -1;
893+
894+ ret = malloc(max);
895+ if(ret) {
896+ sysctl(mib, 4, ret, &cb, NULL, 0);
897+ /* TODO: error check */
898+ }
899+ }
900+
901+ return ret;
902+}
903+
904+char *epicsGetExecDir(void)
905+{
906+ char *ret = epicsGetExecName();
907+ if(ret) {
908+ char *sep = strrchr(ret, '/');
909+ if(sep) {
910+ /* nil the charactor after the / */
911+ sep[1] = '\0';
912+ }
913+ }
914+ return ret;
915+}
916diff --git a/modules/libcom/src/osi/os/solaris/osdgetexec.c b/modules/libcom/src/osi/os/solaris/osdgetexec.c
917new file mode 100644
918index 0000000..ff9739c
919--- /dev/null
920+++ b/modules/libcom/src/osi/os/solaris/osdgetexec.c
921@@ -0,0 +1,30 @@
922+
923+#include <string.h>
924+#include <stdlib.h>
925+
926+#define epicsExportSharedSymbols
927+#include <osiFileName.h>
928+
929+char *epicsGetExecName(void)
930+{
931+ const char *raw = getexecname();
932+ char *ret = NULL;
933+ /* manpage says getexecname() might return a relative path. we treat this as an error */
934+ if(raw[0]=='/') {
935+ ret = strdup(raw);
936+ }
937+ return ret;
938+}
939+
940+char *epicsGetExecDir(void)
941+{
942+ char *ret = epicsGetExecName();
943+ if(ret) {
944+ char *sep = strrchr(ret, '/');
945+ if(sep) {
946+ /* nil the charactor after the / */
947+ sep[1] = '\0';
948+ }
949+ }
950+ return ret;
951+}
952diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile
953index f2e495a..4a5e457 100755
954--- a/modules/libcom/test/Makefile
955+++ b/modules/libcom/test/Makefile
956@@ -232,6 +232,11 @@ osiSockTest_SRCS += osiSockTest.c
957 testHarness_SRCS += osiSockTest.c
958 TESTS += osiSockTest
959
960+TESTPROD_HOST += testexecname
961+testexecname_SRCS += testexecname.c
962+# no point in including in testHarness. Not implemented for RTEMS/vxWorks.
963+TESTS += testexecname
964+
965 ifeq ($(BUILD_CLASS),HOST)
966 ifneq ($(OS_CLASS),WIN32)
967 # This test can only be run on a build host, and is broken on Windows
968diff --git a/modules/libcom/test/testexecname.c b/modules/libcom/test/testexecname.c
969new file mode 100644
970index 0000000..87be847
971--- /dev/null
972+++ b/modules/libcom/test/testexecname.c
973@@ -0,0 +1,24 @@
974+
975+#include <string.h>
976+
977+#include <epicsUnitTest.h>
978+#include <testMain.h>
979+
980+#include <osiFileName.h>
981+
982+MAIN(testexecname)
983+{
984+ testPlan(1);
985+
986+ {
987+ char *buf = epicsGetExecName();
988+ if(!buf) {
989+ testSkip(1, "epicsGetExecName() not available for this target");
990+ } else {
991+ char *loc = strstr(buf, "testexecname");
992+ testOk(!!loc, "Find \"testexecname\" in \"%s\"", buf);
993+ }
994+ }
995+
996+ return testDone();
997+}
998diff --git a/src/tools/Makefile b/src/tools/Makefile
999index 0df1797..0d0c011 100644
1000--- a/src/tools/Makefile
1001+++ b/src/tools/Makefile
1002@@ -39,6 +39,8 @@ PERL_SCRIPTS += tap-to-junit-xml.pl
1003 PERL_SCRIPTS += useManifestTool.pl
1004 PERL_SCRIPTS += genVersionHeader.pl
1005
1006+PERL_SCRIPTS += makeRPath.py
1007+
1008 HTMLS = style.css
1009 HTMLS += EPICS/Getopts.html
1010 HTMLS += EPICS/Path.html
1011diff --git a/src/tools/makeRPath.py b/src/tools/makeRPath.py
1012new file mode 100644
1013index 0000000..000b8b4
1014--- /dev/null
1015+++ b/src/tools/makeRPath.py
1016@@ -0,0 +1,80 @@
1017+#!/usr/bin/env python
1018+
1019+from __future__ import print_function
1020+
1021+import sys
1022+import os
1023+from collections import OrderedDict # used as OrderedSet
1024+
1025+from argparse import ArgumentParser
1026+
1027+if os.environ.get('EPICS_DEBUG_RPATH','')=='YES':
1028+ sys.stderr.write('%s'%sys.argv)
1029+
1030+P = ArgumentParser(description='''Compute and output -rpath entries for each of the given paths.
1031+ Paths under --root will be computed as relative to --final .''',
1032+epilog='''
1033+eg. A library to be placed in /build/lib and linked against libraries in
1034+'/build/lib', '/build/module/lib', and '/other/lib' would pass:
1035+
1036+ "makeRPath.py -F /build/lib -R /build /build/lib /build/module/lib /other/lib"
1037+which prints "-Wl,-rpath,$ORIGIN/. -Wl,-rpath,$ORIGIN/../module/lib -Wl,-rpath,/other/lib"
1038+''')
1039+P.add_argument('-F','--final',default=os.getcwd(), help='Final install location for ELF file')
1040+P.add_argument('-R','--root',default='', help='Root(s) of relocatable tree. Separate with :')
1041+P.add_argument('-O', '--origin', default='$ORIGIN')
1042+P.add_argument('path', nargs='*')
1043+args = P.parse_args()
1044+
1045+# eg.
1046+# target to be installed as: /build/bin/blah
1047+#
1048+# post-install will copy as: /install/bin/blah
1049+#
1050+# Need to link against:
1051+# /install/lib/libA.so
1052+# /build/lib/libB.so
1053+# /other/lib/libC.so
1054+#
1055+# Want final result to be:
1056+# -rpath $ORIGIN/../lib -rpath /other/lib \
1057+# -rpath-link /build/lib -rpath-link /install/lib
1058+
1059+fdir = os.path.abspath(args.final)
1060+roots = [os.path.abspath(root) for root in args.root.split(':') if len(root)]
1061+
1062+# find the root which contains the final location
1063+froot = None
1064+for root in roots:
1065+ frel = os.path.relpath(fdir, root)
1066+ if not frel.startswith('..'):
1067+ # final dir is under this root
1068+ froot = root
1069+ break
1070+
1071+if froot is None:
1072+ sys.stderr.write("makeRPath: Final location %s\nNot under any of: %s\n"%(fdir, roots))
1073+ sys.exit(1)
1074+
1075+output = OrderedDict()
1076+for path in args.path:
1077+ path = os.path.abspath(path)
1078+
1079+ for root in roots:
1080+ rrel = os.path.relpath(path, root)
1081+ if not rrel.startswith('..'):
1082+ # path is under this root
1083+
1084+ # some older binutils don't seem to handle $ORIGIN correctly
1085+ # when locating dependencies of libraries. So also provide
1086+ # the absolute path for internal use by 'ld' only.
1087+ output['-Wl,-rpath-link,'+path] = True
1088+
1089+ # frel is final location relative to enclosing root
1090+ # rrel is target location relative to enclosing root
1091+ path = os.path.relpath(rrel, frel)
1092+ break
1093+
1094+ output['-Wl,-rpath,'+os.path.join(args.origin, path)] = True
1095+
1096+print(' '.join(output))

Subscribers

People subscribed via source and target branches