diff --git a/README.md b/README.md index d4b09ec..7672dd0 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,13 @@ they can be used to install a basic HEP software stack. This project provides some notes and example C/C++ code for packaging software that uses/requires specific CPU instruction sets such as SSE, AVX. +## [Relocatable Software](RelocatableSoftware) +This project provides some notes and example code for developing +packages that are _relocatable_. That is, the collection of installed +files can be moved from their installed location to anywhere else on +the system and still function without any system or user configuration +changes. + # Contributing Questions and comments on any of the projects are welcome, simply [raise an issue](https://github.com/HSF/packaging/issues). If you want to actively contribute to the working group, please contact us through our [HSF Page](http://hepsoftwarefoundation.org/activities/packaging.html). diff --git a/RelocatableSoftware/DevTools/README.md b/RelocatableSoftware/DevTools/README.md new file mode 100644 index 0000000..dbf172f --- /dev/null +++ b/RelocatableSoftware/DevTools/README.md @@ -0,0 +1,65 @@ +Relocatability and Development Tools +==================================== +This looks at relocatability of/in files that a package may install for +use by other packages that use, and thus develop against, it. It therefore +focuses on the HEP case of C/C++/Fortran libraries that use, and are used by, +other libraries. + +CMake +===== +To support use of a Project by a CMake based client project, scripts for +use with CMake's [`find_package`](http://www.cmake.org/cmake/help/v3.2/command/find_package.html) command in "config" +mode should be provided. A `FindPACKAGENAME.cmake` should *not* be implemented, including the use of CMake commands +like `find_path`, `find_library` as these are intended to locate packages not supplying any CMake support files. CMake +"ProjectConfig.cmake" files are installed alongside the project and can self-locate the project's headers/libraries/executables +without having to find anything. + +If the Project itself is built with CMake, "ProjectConfig.cmake" files are very easy to create +via the [`CMakePackageConfigHelpers`](http://www.cmake.org/cmake/help/v3.2/module/CMakePackageConfigHelpers.html) module +and the [`install`](http://www.cmake.org/cmake/help/v3.2/command/install.html) command's `EXPORT` signature. +These make use of CMake's [imported targets](http://www.cmake.org/cmake/help/v3.2/command/add_library.html?#imported-libraries) and the ability for CMake scripts to self-locate themselves (e.g. [`CMAKE_CURRENT_LIST_FILE`](https://cmake.org/cmake/help/v3.2/variable/CMAKE_CURRENT_LIST_FILE.html)to allow the resultant "ProjectConfig.cmake" file(s) +to be completely relocatable. + +Creating and managing these files can become more complicated with Projects that depend on others. +Generally, this can be handled with + +- Consistent use of imported targets to avoid hard-coding paths to dependent libraries/headers +- Minimizing public link dependencies, as these must be refound, even if the client does not use the dependency directly +- "ProjectConfig.cmake" files should call `find_package` for any compile or link time dependencies. This + refinds any dependencies and hence creates the required imported targets. How the dependencies are located + by `find_package` should be left to the configuration management system, which can point CMake to + the right locations using the standard CMake command line/environment variables such as [`CMAKE_PREFIX_PATH`](http://www.cmake.org/cmake/help/v3.2/variable/CMAKE_PREFIX_PATH.html) + - This also works for build wrapper systems (e.g. spack's env setup, Homebrew's sh/superenv or Nix environments for example). + +However, this is not necessarily a complete solution. + +Pkg-Config +========== +Scripts for the [pkg-config](http://www.freedesktop.org/wiki/Software/pkg-config/) +tool can also be made relocatable by using the builtin `pcfiledir` variable. +This expands to the directory holding the `.pc` file, and so for an +example project Foo this could be written as + +``` +prefix=${pcfiledir}/../.. +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: Foo +Libs: -L${libdir} -lfoo +Cflags: -I${includedir} +``` + +Here the relative path from `pcfiledir` to the prefix and the relative +`lib` and `include` paths have been hand written for clarity, but can easily +be created from expansion variables set by the buildsystem of Foo. + +Pkg-config can also handled dependencies, and the `PKG_CONFIG_PATH` (and possibly `PKG_CONFIG_LIBDIR`) +environment variable should be used to correctly resolves paths to these. +As with CMake, this should be handled by the configuration management or +build wrapper. + +Other tools +=========== +**TODO**: Autotools (though probably via `pkg-config`), SCons, others, Python packaging. + diff --git a/RelocatableSoftware/README.md b/RelocatableSoftware/README.md new file mode 100644 index 0000000..934911f --- /dev/null +++ b/RelocatableSoftware/README.md @@ -0,0 +1,347 @@ +Relocatable Software: Issues, Tools and Techniques +================================================== +A **Relocatable** software package is one in which a package +(collection of programs, libraries, resource files) can be moved lock stock from its +initial installation location to anywhere else on the filesystem and still +run *without* user intervention. Implementing this ability in software +provides several benefits, including + +- Minimal setup for end users, as they do not need to set package-specific + environment variables or edit configuration files +- Easier binary packaging and deployment, as binaries do not require + installation at the same location either at install or use time (e.g. + NFS/AFS/CVMFS mount point) + +This project describes some of the issues that arise in making relocatable +software packages from the source code to binary packaging level, and +discusses tools and techniques to help the developer and end user. Several +example projects in C++ and Python are provided as illustrations of the +techniques. Whilst primarily concerned with the "front line" programming languages +used in HEP (C, C++ and Python), it is open to comments and examples +from other languages in use or under consideration. + +What is Relocatability? +======================= +Say we have installed a package `SLPackage` that comprises a program, library, plugins, and +resource files: + +``` +/home/ + +- user/ + +- Projects/ + +- SLPackage/ + +- bin/ + | +- slp_program >------------------- >--- + +- include/ | | + | +- slp.h | | + +- lib/ | | + | +- libslp.so <----------- links to | +          | +- plugins/ | | +          | | +- a.so <-| | +          | | +- b.so <-| loads | +          | +- cmake/                           | + | | +- SLPackage/ | + | | +- SLPackageConfig.cmake | + | +- pkgconfig/ | + | +- SLPackage.pc | + +- share/ | + +- SLPackage/ | + +- resource.txt <-------------- reads +``` + +If `SLPackage` is relocatable, then we can move it across the filesystem, e.g.: + +``` +$ mv /home/user/Projects/SLPackage /home/user/Another/Workspace +... + +/home/ + +- user/ + +- Another/ + +- Workspace/ + +- SLPackage/ + +- bin/ + | +- slp_program + +- include/ + | +- slp.h + +- lib/ + | +- libslp.so + | +- plugins/ +           | | +- a.so +           | | +- b.so + | +- cmake/ + | | +- SLPackage/ + | | +- SLPackageConfig.cmake + | +- pkgconfig/ + | +- SLPackage.pc + +- share/ + +- SLPackage/ + +- resource.txt +``` + +and the user would be able to run `slp_program` or link to `libslp` without making *any* changes +to either the files comprising `SLPackage` or the runtime environment (`PATH` +might be edited for convenience, but `slp_program` would still be runnable via a fully +qualified path). _Note that the relocation keeps the files comprising +`SLPackage` in the same locations relative to each other_. +[OS X Application and Framework Bundles/Packages](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/Introduction/Introduction.html#//apple_ref/doc/uid/10000123i) +are the classic example of relocatable programs and libraries respectively, and +the term 'Portable Binary' is often used on Linux. + +Though basic, this example illustrates three of the core issues of relocatability and +the corresponding technical aspects: + +- **How does `slp_program` locate its `resource.txt` file, or `libslp` its `plugin`s at runtime?** + - [_Binaries able to self-locate themselves on the filesystem at runtime_](SLPackage) +- **How does `slp_program` locate its dynamic library `libslp.so` dependency at runtime?** + - _Link/Run time lookup of dynamic libraries_ +- **How do `SLPackage`'s CMake, pkg-config, and other support files find `SLPackage`'s library and headers + when used by a client?** + - [_Script self-location on the filesystem at runtime_](DevTools) + +Whilst the example only illustrates moving a package across a local +filesystem, it is equally valid for moves across network filesystems with +different mount points or even between different systems. Of course the package is then only usable if the +OS/toolchain mounting the filesystem is the same, or binary compatible +with, the OS/toolchain the package was built for. Though not a direct +issue for relocatability, programming and compiling for binary +compatibility is helpful for simplifying binary packaging and deployment. +The issues here include: + +- Software development + - Mostly a training/policy issue for individual projects (but perhaps HSF can help) + - Clear and well-managed API/ABI versioning, especially for compiled languages + - Ensures software can be used by as wide a range of upstream clients as possible + - Nevertheless, can be tricky for [languages like C++](https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C%2B%2B) + - There [are tools to help check compatibility](https://fedoraproject.org/wiki/How_to_check_for_ABI_changes_in_a_package) at least for ELF, but needs a more thorough survey. + - Program for compatibility with multiple versions of any dependencies + - Dependencies should provide a versioning header [as per HSF (draft) guidelines](https://github.com/HEP-SF/documents/blob/master/HSF-TN/draft-2016-PROJ/draft-HSF-TN-2016-PROJ.md) + - Hide dependencies as implementation details as far as possible. + - Consider versioned symbols and/or inlined namespaces? +- Building binaries + - Policy issue for packager(s). + - Target minimal system API/ABI, e.g. `-mmacosx-min-version` on OS X or build for suitable + minimum `glibc` on Linux. + - On Linux, consider "standalone" toolkit of glibc, binutils, gcc (c.f. Nix, Portage, Linuxbrew package managers) + +(Re)Locating the Interpreter for Programs +========================================= +Programs implemented using intepreted languages such as Python are usually written as scripts using (on Unix platforms) +a ["shebang"](https://en.wikipedia.org/wiki/Shebang_(Unix)) on the first line to define the interpreter program to pass the remainder of the script to. For example, a Python "hello world" program might be written as + +```Python +#!/usr/bin/python + +print("hello world") +``` + +This hard codes the system interpreter into the program and whilst this program is relocatable (assuming a valid system +Python install), it cannot be used with any other interpreter. Typical HEP software stacks install, and require use of, +their own interpreters, whose paths may also end up hard coded into scripts: + +```Python +#!/custom/stack/root/python/2.7/bin/python + +print("hello world") +``` + +The resulting stack is then not relocatable as the interpreter path will not exist after relocation. + +Rather than hard coding system or custom interpreter paths, script authors should prefer the use of the +[`env`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html) program as the shebang, e.g. + +```Python +#!/usr/bin/env python + +print("hello world") +``` + +Use of `env` makes the program relocatable, but defers location of the interpreter to the `PATH` environment variable, +and consequently the configuration management system for the software stack. Whilst package authors should prefer +usage of the `env` pattern, software stack managers can also consider rewriting the shebang line during install +and on any relocation to the absolute path of the required interpreter. As it is plain text, simple regular expression +replacement can be used, but the chosen packaging system must support this, and care must be taken +if the resultant stack is to be deployed over network file systems (and hence unknown mount points). + +Binary programs (ELF, Mach-O) also have interpreters. Whilst it is not normally +required to change these, tools such as `patchelf` provide the required +functionality. Developers do not need to worry about this in their packages/builds +as any rewrite of the interpreter should be handled by the package manager. + + +(Re)Locating Dynamic Libraries +============================== +A non-trivial package will usually be partioned into a main +program/libraries linking to 1-N internal, plus 0-M external, libraries. +For [static libraries/linking](https://en.wikipedia.org/wiki/Static_library), the libraries only need locating at build/link time. +When [dynamic/shared libraries](https://en.wikipedia.org/wiki/Dynamic_linker) +are used, client programs/libraries must locate the needed libraries at +both build/link and *run* times, and it is this run time location +that is discussed here. + +**TODO**: How the dynamic linker/loader works on different platforms. Topics include: + +- Dynamic loader paths, including `LD_LIBRARY_PATH`, `RPATH` and `RUNPATH` (inc. `@rpath` +and others on OS X, `$ORIGIN` on Linux), plus Windows DLL search paths. +- Relative RPATHs, both on [OS X](http://www.kitware.com/blog/home/post/510) and [Linux](http://linux.die.net/man/8/ld.so) +- Lookup paths when implementing "Plugin" architectures (i.e. loading dynamic libraries into an already running program) + +Dynamic programs and libraries can be queried by system tools to display what they link to and how these paths are resolved. +To query what a dynamic executable links to, the commands + +```console +... Linux ... +$ ldd + +... OS X ... +$ otool -L +``` + +may be used. Additionally, detailed runtime information on how the dynamic linker/loader resolves links when loading/running the program can be obtained by setting platform specific environment variables, e.g. + +```console +... Linux ... +$ LD_DEBUG=all + +... OS X ... +$ export DYLD_PRINT_LIBRARIES=1 +$ export DYLD_PRINT_RPATHS=1 +$ +``` + +These can be useful for tracing runtime issues. See the `ld.so/ld-linux.so` (Linux) or `dyld` (OS X) `man` pages for additional details. + +**TODO**: Remember to document the odd difference in behaviour of `$ORIGIN` between link and run times. Basically, it appears that binutils `ld` *does not* expand it at link time, which can result in error messages about needing `-rpath-link`. This *appears* to be a [missing feature or bug in binutils](https://sourceware.org/bugzilla/show_bug.cgi?id=16936) + +**TODO**: Behaviour of tools of as CMake and Autotools, which encode +the rpath into the locally built binaries by default. This enables them +to be run directly for testing and guarantees that they will find their +dependencies. At install time, rpaths are usually stripped, unless +configured otherwise. + + +(Re)Locating Language Modules +============================= +**TODO** How to handle module lookup, e.g. `PYTHONPATH` for Python (other languages?). Things that package authors can do. +Things that the packaging system should do (inc. any packaging system provided by the language, e.g. `pip`, `virtualenv`). +Things best left to configuration management. + + +Relocatability with External Dependencies +========================================= +So far we have only considered a package with no external dependencies +other than always required OS/language standard libraries. What happens if +our `SLPackage` binaries link with those from another package, +e.g. `slp_program` or `libslp` links to a "`libbar`" from package `Bar`. + +1. We can move `SLPackage` if the binaries have `RPATH` with an absolute path to `libbar`. +2. We cannot move `Bar` without updating `Foo`'s RPATH or using/updating dynamic + loader environment paths +3. We can move both `SLPackage` and `Bar` provided relative RPATHs + are used and both stay in the same locations relative to each other. + +This only covers the case for typical binary dependencies. If dependencies exist between +package's resource files (e.g. data, configuration), similar solutions can apply +if access uses a path-like lookup mechanism. + + +Patching Upstream Software +========================== +The preceding sections cover cases where "we" are developing the +software, or have identified relocatability issues and are in a +position to patch these. Typical HEP software +stacks will use a large number of packages not directly maintained by +the experiment/community using them, and not all of them may meet the +criteria for full relocatability. How to handle these? + +In the first instance, a feature request should always be put in with the +upstream maintainers. Other techniques for patching are discussed +below. + +Self-Location +------------- +If a program uses environment variables to set paths for resource +location, then one way to non-intrusively patch this is to create +a self-locating Bash/Python script wrapping the actual executable. +The script can contain the derived relative path(s) needed and set +these at runtime before executing the actual program, forwarding any +additional arguments. + +For libraries using environment variables, it may be possible to wrap +these with a small facade library. This would do nothing more that +self-locate, set the needed environment variables and expose the rest +of the library symbols. However, this has implications for usability +and runtime manipulation of the environment by clients. + +When absolute paths are hardcoded into binaries, then only intrusive +patching is likely to work. For simple cases, application of the +techniques discussed earlier may be able to provide a fully relocatable +solution. At worst, hard coded paths could be replaced with environment +variable lookup and wrapper scripts. In more complex cases, it may be possible to patch the +binary directly at install time (**TODO**: tools for this? One known +limitation is that new path must be shorter than old one. Easy to pad +with null bytes, adding more space changes offsets etc) to +rewrite hardcoded paths. Note that this still results in hard coded +paths, so can only really be handled by a package manager system and would +not work for deploying software over network file systems where final +mount points are not guaranteed to be identical. + +Interpreter Paths +----------------- +Shebangs are plain text, so are straightforward to patch directly using regular expression +find/replace directly, or via tooling at build or install time. + + +Library RPATHs +-------------- +1. RPATHs can be changed at install time by the packaging system/tools (`patchelf`, `otool`, `install_name_tool` etc) +2. Runtime/chroot based tools like [PRoot](https://github.com/proot-me/PRoot/blob/master/doc/proot/manual.txt) may +also be useful. + + +Relocation and Packaging +======================== +**TODO** Topics involving relocatability when it comes to the packaging level. Define "packaging" as the things we need +to do/write to allow `spack|brew|whatever install mypackage` to work, whether building `mypackage` from source locally +or downloading and unpacking a binary. The "package manager" needs to include tooling to manage relocation, and this +may include things like changing RPATHs in binaries, to shebangs in interpreted programs. Other topics like deployment +to CVMFS etc. + +Take simple example of two packages and a "typical" versioned tree plus "views"? e.g. + +``` ++- whateverroot/ + +- packages/ + | +- Foo/ + | | +- 1.0/ + | | | +- bin/ + | | | +- lib/ + | | | +- ... + | | +- 2.0/ + | | +- ... + | +- Bar/ + | +- 1.0-usingFoo1.0/ + | | +- bin/ + | | +- lib/ + | +- 1.0-usingFoo2.0/ + | +- bin/ + | +- lib/ + +- views/ + +- release-1.0/ + | +- bin/ + | +- lib/ + +- release-2.0/ + +- bin/ + +- lib/ +``` + +Assume Foo/Bar are relocatable according to earlier topics, how should package manager deal with pointing Bar to its +needed Foo, and how do views work in this case? + + +Conclusions +=========== +**TODO** + + + diff --git a/RelocatableSoftware/SLPackage/CMakeLists.txt b/RelocatableSoftware/SLPackage/CMakeLists.txt new file mode 100644 index 0000000..ae17777 --- /dev/null +++ b/RelocatableSoftware/SLPackage/CMakeLists.txt @@ -0,0 +1,40 @@ +# - Build/Test Examples for Self-Locating Binaries +cmake_minimum_required(VERSION 3.3) +project(SLPackage VERSION 0.1.0) + +# - Use standard install locations +# These are, by default, relative to CMAKE_PREFIX_PATH so permit relocatability +include(GNUInstallDirs) + +# - Use install directories to configure output paths of build products +# We do this so we can reproduce the install layout in the build directory +# and hence relative paths between products(*) +# (*) resource files are slightly different +set(SLPackage_BUILDPRODUCT_DIRECTORY "${PROJECT_BINARY_DIR}/BuildProducts") +if(NOT CMAKE_CONFIGURATION_TYPES) + # Single mode tools (make etc) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${SLPackage_BUILDPRODUCT_DIRECTORY}/${CMAKE_INSTALL_LIBDIR}") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${SLPackage_BUILDPRODUCT_DIRECTORY}/${CMAKE_INSTALL_LIBDIR}") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${SLPackage_BUILDPRODUCT_DIRECTORY}/${CMAKE_INSTALL_BINDIR}") +else() + # Multimode tools (Xcode) + foreach(_mode ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${_mode} _mode_uc) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${_mode_uc} "${SLPackage_BUILDPRODUCT_DIRECTORY}/${_mode}/${CMAKE_INSTALL_LIBDIR}") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${_mode_uc} "${SLPackage_BUILDPRODUCT_DIRECTORY}/${_mode}/${CMAKE_INSTALL_LIBDIR}") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${_mode_uc} "${SLPackage_BUILDPRODUCT_DIRECTORY}/${_mode}/${CMAKE_INSTALL_BINDIR}") + endforeach() +endif() + +# - Force C++11 without checking available features +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# - Build subdirs +add_subdirectory(programs) + +# - copy/configure resources to build dir +file(COPY resources + DESTINATION ${SLPackage_BUILDPRODUCT_DIRECTORY}/${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME} + ) diff --git a/RelocatableSoftware/SLPackage/README.md b/RelocatableSoftware/SLPackage/README.md new file mode 100644 index 0000000..75dc9c9 --- /dev/null +++ b/RelocatableSoftware/SLPackage/README.md @@ -0,0 +1,266 @@ +SLPackage +=========== +Example program/library code to demonstrate basic relocation concepts. +The following software is required to build the base set of programs: + +- [CMake](https://www.cmake.org) version 3.3 or newer +- C++11 compatible compiler (GNU, Clang, Intel) +- macOS or Linux OS (Windows not yet supported) + +The following software is optional: + +- [Qt5](https://www.qt.io) for the [qt](programs/slp_program-qt.cpp) example application +- [Poco](http://pocoproject.org) for the [poco](slp_program-poco.cpp) example application + +To build the software, simply create a build directory and run `cmake` inside it, +pointing it to the top level source directory (i.e. the directory holding this +README): + +```console +$ ls +CMakeLists.txt README.md programs resources scripting +$ mkdir build +$ cd build +$ cmake .. +$ cmake --build . +``` + +If CMake has issues finding any dependencies, ensure their root install +prefixes are listed in `CMAKE_PREFIX_PATH` and pass this list to +`cmake` on the command line or set it in your environment. + +Build Outputs +============= +All build products are output under the `BuildProducts` subdirectory of +the build directory. This contains a hierarchy intended to match the +install layout, but note that there is no `install` target at present. + +If you configure the project for an IDE like Xcode, +the `BuildProducts` directory will contain an extra level of directories +to support each build type (`Release`, `Debug` etc) + + +Programs/Applications +===================== +The [programs](programs) directory contains executables in C++, Python, and +Ruby that demonstrate program self-location in these languages. PRs are +welcome to demonstrate this functionality in other languages. + +C++: The `slp-binreloc` program +------------------------------- + +This basic C/C++ example demonstrates the use of the [`binreloc`](programs/binreloc) +library for simple application self-location. It also comes with a basic resource +file "resource.txt" to show how these can be located. Simply running the application +will print its location and the contents of the resource file: + +``` console +$ ./BuildProducts/bin/slp-binreloc +[application in]: /AbsPathToWhereYouRanCMake/./BuildProducts/bin +[resource]: 'hello from builtin SLPackage resource file! +' +$ +``` + +Relocatability can be tested by copying the `BuildProducts` directory to any other location +you like on the local machine. Rerun `slp-binreloc` and it should print its new location and +the resource contents. You can prove that it's not using build time paths by removing +the original build directory. + + +C++: The `slp-poco` program +--------------------------- + +Demonstrates the self-location interfaces supplied by the [Poco](http://pocoproject.org) libraries. + + +C++: The `slp-qt` program +------------------------- + +Demonstrates the self-location interfaces supplied by the [Qt5](https://www.qt.io) libraries. + + +Python: The `slp-python` program +-------------------------------- + +Demonstrates basic self-location in Python. + +Ruby: The `slp-ruby` program +---------------------------- + +Demonstrates basic self-location in Ruby. + + +Dealing with Packaging Issues +============================= + +When implementing new or patching existing HEP packages, the above techniques +should ensure relocatablity. Packaging involves integrating many pieces of software, +many of which may not be fully relocatable. This section aims to provide demonstrations +of the typical cases and fixes where possible (and "this is broken" where not...). +Examples are: + +1. A program/library links to others - use of `patchelf` and `install_name_tool`, RPATHS, + RUNPATHS, and linker tricks. +2. A program/library hard codes a path into binary. For example + + ``` c + /* bad.cc */ + #define RESOURCEPATH /usr/share/badpackage + ``` + + Likely to be the most awkward case, but conda/spack may have the tools to repoint these + paths. +3. A package has text based files that hard code paths, e.g. + + ``` sh + #!/some/build/time/path/to/python + ``` + + Simple search-and-replace should work here. + + +STUFF FROM TOP-LEVEL README IMPORTED BELOW +========================================== +Self-Location of Compiled and Interpreted Executables +===================================================== +How can a program or library introspect itself when running to find out where on the +filesystem it was loaded from? If we know this location, then default +resource files and search paths can easily be derived from known relative +locations. For example, say the `Foo` application is written in C++ +and uses a `resource.txt` file: + +``` ++- + +- bin/ + | +- slp_program + +- share/ + +- slp_program/ + +- resource.txt +``` + +A typical solution to locating `resource.txt` at runtime would be to use an +environment variable and query this in the program, e.g. in C/C++: + +```C++ +const char* resourcePath = getenv("SLPACKAGE_RESOURCE_PATH"); +``` + +Whilst this does enable relocatability, it relies on the user setting this +variable correctly, knowing to do so, and changing it if the package is +moved. It is easier if the binary can introspect itself at runtime +to determine where on the filesystem its physical file is stored - this +is _self-location_. Using this information, the path to the `resource.txt` +file can be determined by joining the binary file path with the +_relative path_ from it to the resource file. Here, the relative path is known +at compile/install time so is hard-coded into the binary, but because +it is relative, the "bundle" of binary and resource may be freely +moved together without invalidating the relative path between the files. + +There are various language dependent techniques to implement +self-location in applications and libraries. Self-locating *resources* +(e.g. `A.txt` "loads" `../extra/B.txt` in a hierarchical system) is +outside the scope of this document as it is highly implementation +dependent. The sections below describe +the minimal (as far as is known) code needed to obtain the location +of the currently executing program or library for a handful of languages, +and additions are welcome. Note that languages may have additional +builtins or simple extensions to handle either self-location or the +specific use case of locating resource files (e.g., see the notes +on the Go language below). + +Python +------ +The full path to the current file is easily obtained using the `os` +module: + +```Python +import os +selfLocation = os.path.realpath(__file__) +``` + +This is equally valid for programs, modules and packages. + +Ruby +---- +Ruby's construct is very similar to Python, using the `File` builtin + +```Ruby +selfLocation = File.expand_path(__FILE__) +``` + +This should be equally valid for programs and gems/packages. + +Go +-- +Information from [Sebastien Binet](https://github.com/sbinet) + +> As you put a 'go' section in your brain dump, I feel compelled to pipe in :) +> +> go programs are statically linked (at least the pure-go ones) so the +> issue of locating DSOs is moot. +> +> for other resources, the canonical way is to compile the assets inside +> the binary: +> +> https://github.com/jteeuwen/go-bindata +> +> or locate them from $GOPATH (the $PYTHONPATH for Go): +> +> https://github.com/hwaf/gas + +Bash +---- +An executable script can be located using the `readlink` command on +the `$0` command line argument, though this does not resolve hardlinks. +Whilst GNU `readlink` can fully traverse softlinks using the `-f` +argument, this is not portable. + +```Bash +# GNU readlink only +selfLocation=$(readlink -f $0) +``` + +For `readlink` implementations not supporting the `-f` argument, +workarounds are needed. Depending on the platform, these may vary from +using Python (!, though not unreasonable on OS X platforms) to pure +Bash/Sh implementations. The latter basically involve iterating over any +sequence of symlinks. A discussion on this with example implementations is [covered on StackOverflow](http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac) + + +C/C++ +----- +Though C/C++ applications *may* get passed a string *representing* the program name as the zeroth element of the `argv` argument to `main`: + +```C++ +int main(int argc, char* argv[]) { + std::cout << argv[0] << "\n"; +} +``` + +this is not *guaranteed* to be the actual filesystem location of the program +(see, for example +[this discussion](http://stackoverflow.com/questions/2050961/is-argv0-name-of-executable-an-accepted-standard-or-just-a-common-conventi) +). +In particular, the actual invocation of a program may be through a soft or hard link whose +filesystem location is completely separate from that of the executable. +Though links can be followed to some extent, hardlinks in particular cannot +easily be resolved. Rather, most self-location relies on querying the +*process* itself. + +Some basic techniques, but also APIs, are demonstrated in the [`SLPackage`](SLPackage`) example project, including + +- [binreloc](https://github.com/drbenmorgan/Resourceful) at low level for C/C++ +- C++ Application objects in frameworks such as: + - [Qt](http://doc.qt.io/qt-5/qcoreapplication.html#applicationDirPath) + - [Poco](http://pocoproject.org/docs/Poco.Util.Application.html) + +There are some paths to resource files that, depending on exact use case, may require +hard-coding or use of standard environment variables. On UNIX these are typically the +directories used for/by system programs, or for temporary usage. + +- `/etc` +- `/var` +- `/tmp` or `TMPDIR` + + diff --git a/RelocatableSoftware/SLPackage/programs/CMakeLists.txt b/RelocatableSoftware/SLPackage/programs/CMakeLists.txt new file mode 100644 index 0000000..ffeb529 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/CMakeLists.txt @@ -0,0 +1,61 @@ +# - Build application/library using binreloc for self-location + +# - Configure binreloc +# Just mangles symbols which is not strictly neccessary for programs. +# However, when we use it in libraries, we *might* get symbol clashes if +# we link together several libraries, each with their own binreloc code. +#string(RANDOM LENGTH 16 MANGLE_BINRELOC) +#set(MANGLE_BINRELOC "hsf_reloc_binreloc${MANGLE_BINRELOC}") +#configure_file(binreloc/hsf_binreloc.h.in hsf_binreloc.h @ONLY) +include(binreloc/GenerateBinreloc.cmake) + +# - Configure "front end" to binreloc, adding known *relative path* +# from exe dir to resource dir +file(RELATIVE_PATH SLP_BINDIR_TO_RESOURCEDIR + "${CMAKE_INSTALL_FULL_BINDIR}" + "${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME}/resources" + ) +configure_file(SLPApplicationPaths.cpp.in SLPApplicationPaths.cpp @ONLY) + +add_executable(slp-binreloc + SLPApplicationPaths.h + ${CMAKE_CURRENT_BINARY_DIR}/SLPApplicationPaths.cpp + slp-binreloc.cpp + ) +target_include_directories(slp-binreloc PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ) + +# Generate and link binreloc code for this exe. +# It's always a private link because binreloc is only +# ever an implementation detail of clients. +generate_binreloc(SLPackage) +target_link_libraries(slp-binreloc PRIVATE SLPackage_binreloc) + +#----------------------------------------------------------------------- +# - Optional Poco demo for its builtin self location +# - Only need Util lib directly for Application base class +find_package(Poco QUIET COMPONENTS Util) + +if(NOT Poco_FOUND) + message(STATUS "Poco not found, disabling build of slp-poco") +else() + add_executable(slp-poco slp-poco.cpp) + target_link_libraries(slp-poco Poco::Util) +endif() + +#----------------------------------------------------------------------- +# - Optional Qt5 demo for its builtin self-location +# - Only need core lib +find_package(Qt5Core QUIET) + +if(NOT Qt5Core_FOUND) + message(STATUS "Qt5Core not found, disabling build of slp-qt") +else() + add_executable(slp-qt slp-qt.cpp) + target_link_libraries(slp-qt Qt5::Core) +endif() + + + diff --git a/RelocatableSoftware/SLPackage/programs/README.md b/RelocatableSoftware/SLPackage/programs/README.md new file mode 100644 index 0000000..5bb8dd0 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/README.md @@ -0,0 +1,45 @@ +`hsfreloc` Programs +=================== +`hsfreloc` +---------- +Simple program showing self-location using `binreloc` C code. This +is generaly portable across UNIX platforms, but not yet native Windows. + +On Win32, it looks like the [`GetModuleFileName`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms683197%28v=vs.85%29.aspx) function can be +used to determine the full path to the file containing a given module. +If it's called with a `NULL` module handle, it will retrieve the path +to the executable of the current process. That can be used for programs. +For DLLs, it's mentioned that [DllMain](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx) can be used as this gets +passed a handle to the DLL module. That handle can be passed to +`GetModuleFileName`. **Note the warning** on the [MSDN Reference](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx) +about the limitations of what can be done in this entry point function! +Whilst a few years old now, MSDN's +[Best Practice for Creating Dlls](http://msdn.microsoft.com/en-us/windows/hardware/gg487379.aspx) discusses these limitations. +One recommendation seen is to store the module handle as static +data of the DLL, setting it in DllMain, and provide a public API that will +call GetModuleFileName using this handle. + +`hsfreloc-poco` +--------------- +Uses Poco's builtin `Application`class for self-location. This requires +Poco's [`Util`](http://pocoproject.org/docs/Poco.Util.html) library. +Should be portable to all of Poco's supported platforms. + +The program does not go any further than self-location. The same +ideas/techniques as shown in `hsfreloc` can be used to derive +additional locations. + +`hsfreloc-qt` +------------ +Uses Qt5's [`QAaplication`](http://doc.qt.io/qt-5/qapplication.html) class +for program self-location. This requires Qt5's `QtCore` library. +Should be portable to all of Qt5's supported platforms. + +The program does not go any further than self-location. The same +ideas/techniques as shown in `hsfreloc` can be used to derive +additional locations. Note that Qt also provides its own resource and +plugin system, so these can be used instead (and probably should +be preferred it Qt is in use anyway). + + + diff --git a/RelocatableSoftware/SLPackage/programs/SLPApplicationPaths.cpp.in b/RelocatableSoftware/SLPackage/programs/SLPApplicationPaths.cpp.in new file mode 100644 index 0000000..51a0925 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/SLPApplicationPaths.cpp.in @@ -0,0 +1,40 @@ +#include "SLPApplicationPaths.h" + +#include "SLPackage_binreloc.h" +#include + +namespace { +//! Initialize binreloc if needed +int initBinreloc() { + int errorCode {0}; + static bool isInit {false}; + if (!isInit) { + BrInitError err; + errorCode = br_init(&err); + } + return errorCode; +} +} + +namespace SLP { +const std::string& getApplicationDir() { + static std::string appDir; + if (appDir.empty()) { + initBinreloc(); + char* myExeDir = br_find_exe_dir(""); + appDir = myExeDir; + free(myExeDir); + } + return appDir; +} + +const std::string& getResourceDir() { + static std::string binToResPath("@SLP_BINDIR_TO_RESOURCEDIR@"); + static std::string resourceDir; + if (resourceDir.empty()) { + resourceDir = getApplicationDir() + "/" + binToResPath; + } + return resourceDir; +} + +} // namespace HSFReloc diff --git a/RelocatableSoftware/SLPackage/programs/SLPApplicationPaths.h b/RelocatableSoftware/SLPackage/programs/SLPApplicationPaths.h new file mode 100644 index 0000000..3a4fab8 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/SLPApplicationPaths.h @@ -0,0 +1,19 @@ +/** \file SLPApplicationPaths.h + * \brief C++ interface to binreloc and resource paths +*/ + +#ifndef SLPAPPLICATIONPATHS_HH +#define SLPAPPLICATIONPATHS_HH + +#include + +namespace SLP { +//! Return path to running executable +const std::string& getApplicationDir(); + +//! Return path to resource directory for this executable +const std::string& getResourceDir(); +} + +#endif // HSFRELOC_HH + diff --git a/RelocatableSoftware/SLPackage/programs/binreloc/GenerateBinreloc.cmake b/RelocatableSoftware/SLPackage/programs/binreloc/GenerateBinreloc.cmake new file mode 100644 index 0000000..6f5583b --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/binreloc/GenerateBinreloc.cmake @@ -0,0 +1,38 @@ +#.rst: +# +get_filename_component(_GENERATE_BINRELOC_MODULE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) + +function(generate_binreloc _name) + # _name must be a valid C identifier + if(NOT (_name MATCHES "[_a-zA-Z][_a-zA-Z0-9]*")) + message(FATAL_ERROR "generate_binreloc: prefix for generated target/code must be a valid C-identifier") + endif() + set(BR_NAME "${_name}_binreloc") + + # Add interface target, not object as + # - might need to know PIC or not... + # .. but can prevent object being installed... + # Do it first as this will ensure an error if the target already exists + # without possibly overwriting files + add_library(${BR_NAME} INTERFACE) + + # Need to uniquely mangle symbols + string(RANDOM LENGTH 16 MANGLE_BINRELOC) + set(MANGLE_BINRELOC "${BR_NAME}${MANGLE_BINRELOC}") + + # Configure .h and .c files + set(BR_HEADER_TEMPLATE "${_GENERATE_BINRELOC_MODULE_DIR}/_binreloc.h.in") + set(BR_SOURCE_TEMPLATE "${_GENERATE_BINRELOC_MODULE_DIR}/_binreloc.c.in") + set(BR_GEN_HEADER "${CMAKE_CURRENT_BINARY_DIR}/${BR_NAME}.h") + set(BR_GEN_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${BR_NAME}.c") + configure_file("${BR_HEADER_TEMPLATE}" "${BR_GEN_HEADER}" @ONLY) + configure_file("${BR_SOURCE_TEMPLATE}" "${BR_GEN_SOURCE}" @ONLY) + + # Add sources and usage requirements to target/sources + target_sources(${BR_NAME} INTERFACE "${BR_GEN_HEADER}" "${BR_GEN_SOURCE}") + target_include_directories(${BR_NAME} INTERFACE "${CMAKE_CURRENT_BINARY_DIR}") + set_source_files_properties("${BR_GEN_SOURCE}" + PROPERTIES COMPILE_DEFINITIONS ENABLE_BINRELOC + ) +endfunction() + diff --git a/RelocatableSoftware/SLPackage/programs/binreloc/_binreloc.c.in b/RelocatableSoftware/SLPackage/programs/binreloc/_binreloc.c.in new file mode 100644 index 0000000..fa3e34d --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/binreloc/_binreloc.c.in @@ -0,0 +1,498 @@ +/* BinReloc - a library for creating relocatable executables + * Written by: Hongli Lai + * Modifications by: Ben Morgan + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of thr Boost Software License, + * + * http://www.boost.org/LICENSE_1_0.txt + * +*/ +#include "@BR_NAME@.h" + +#ifdef ENABLE_BINRELOC + #include + #include + #include +#endif /* ENABLE_BINRELOC */ +#include +#include +#include +#include +#if defined(__APPLE__) && defined(__MACH__) +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @internal + * Find the canonical filename of the executable. Returns the filename + * (which must be freed) or NULL on error. If the parameter 'error' is + * not NULL, the error code will be stored there, if an error occured. + */ +static char * +_br_find_exe (BrInitError *error) +{ +#ifndef ENABLE_BINRELOC + if (error) + *error = BR_INIT_ERROR_DISABLED; + return NULL; +#elif defined(sun) || defined(__sun) + char *path; + path = getexecname(); + return strdup(path); +#elif defined(__APPLE__) && defined(__MACH__) + char path[MAXPATHLEN+1]; + uint32_t path_len = MAXPATHLEN; + // SPI first appeared in Mac OS X 10.2 + _NSGetExecutablePath(path, &path_len); + return strdup(path); +#else + char *path, *path2, *line, *result; + size_t buf_size; + ssize_t size; + struct stat stat_buf; + FILE *f; + + /* Read from /proc/self/exe (symlink) */ + if (sizeof (path) > SSIZE_MAX) + buf_size = SSIZE_MAX - 1; + else + buf_size = PATH_MAX - 1; + path = (char *) malloc (buf_size); + if (path == NULL) { + /* Cannot allocate memory. */ + if (error) + *error = BR_INIT_ERROR_NOMEM; + return NULL; + } + path2 = (char *) malloc (buf_size); + if (path2 == NULL) { + /* Cannot allocate memory. */ + if (error) + *error = BR_INIT_ERROR_NOMEM; + free (path); + return NULL; + } + +#ifdef __FreeBSD__ + strncpy (path2, "/proc/self/file", buf_size - 1); +#else + strncpy (path2, "/proc/self/exe", buf_size - 1); +#endif + + while (1) { + int i; + + size = readlink (path2, path, buf_size - 1); + if (size == -1) { + /* Error. */ + free (path2); + break; + } + + /* readlink() success. */ + path[size] = '\0'; + + /* Check whether the symlink's target is also a symlink. + * We want to get the final target. */ + i = stat (path, &stat_buf); + if (i == -1) { + /* Error. */ + free (path2); + break; + } + + /* stat() success. */ + if (!S_ISLNK (stat_buf.st_mode)) { + /* path is not a symlink. Done. */ + free (path2); + return path; + } + + /* path is a symlink. Continue loop and resolve this. */ + strncpy (path, path2, buf_size - 1); + } + +#if defined(__FreeBSD__) +{ + char *name, *start, *end; + char *buffer = NULL, *temp; + struct stat finfo; + + name = (char*) getprogname(); + start = end = getenv("PATH"); + + while (*end) { + end = strchr (start, ':'); + if (!end) end = strchr (start, '\0'); + + /* Resize `buffer' for path component, '/', name and a '\0' */ + temp = realloc (buffer, end - start + 1 + strlen (name) + 1); + if (temp) { + buffer = temp; + + strncpy (buffer, start, end - start); + *(buffer + (end - start)) = '/'; + strcpy (buffer + (end - start) + 1, name); + + if ((stat(buffer, &finfo)==0) && (!S_ISDIR (finfo.st_mode))) { + path = strdup(buffer); + free (buffer); + return path; + } + } /* else... ignore the failure; `buffer' is still valid anyway. */ + + start = end + 1; + } + /* Path search failed */ + free (buffer); + + if (error) + *error = BR_INIT_ERROR_DISABLED; + return NULL; +} +#endif + + /* readlink() or stat() failed; this can happen when the program is + * running in Valgrind 2.2. Read from /proc/self/maps as fallback. */ + + buf_size = PATH_MAX + 128; + line = (char *) realloc (path, buf_size); + if (line == NULL) { + /* Cannot allocate memory. */ + free (path); + if (error) + *error = BR_INIT_ERROR_NOMEM; + return NULL; + } + + f = fopen ("/proc/self/maps", "r"); + if (f == NULL) { + free (line); + if (error) + *error = BR_INIT_ERROR_OPEN_MAPS; + return NULL; + } + + /* The first entry should be the executable name. */ + result = fgets (line, (int) buf_size, f); + if (result == NULL) { + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_READ_MAPS; + return NULL; + } + + /* Get rid of newline character. */ + buf_size = strlen (line); + if (buf_size <= 0) { + /* Huh? An empty string? */ + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_INVALID_MAPS; + return NULL; + } + if (line[buf_size - 1] == 10) + line[buf_size - 1] = 0; + + /* Extract the filename; it is always an absolute path. */ + path = strchr (line, '/'); + + /* Sanity check. */ + if (strstr (line, " r-xp ") == NULL || path == NULL) { + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_INVALID_MAPS; + return NULL; + } + + path = strdup (path); + free (line); + fclose (f); + return path; +#endif /* ENABLE_BINRELOC */ +} + + +/** @internal + * Find the canonical filename of the executable which owns symbol. + * Returns a filename which must be freed, or NULL on error. + */ +static char * +_br_find_exe_for_symbol (const void *symbol, BrInitError *error) +{ +#ifndef ENABLE_BINRELOC + if (error) + *error = BR_INIT_ERROR_DISABLED; + return (char *) NULL; +#else + #define SIZE PATH_MAX + 100 + FILE *f; + size_t address_string_len; + char *address_string, line[SIZE], *found; + + if (symbol == NULL) + return (char *) NULL; + + f = fopen ("/proc/self/maps", "r"); + if (f == NULL) + return (char *) NULL; + + address_string_len = 4; + address_string = (char *) malloc (address_string_len); + /* Handle OOM (Tracker issue #35) */ + if (!address_string) + { + if (error) + *error = BR_INIT_ERROR_NOMEM; + return (char *) NULL; + } + found = (char *) NULL; + + while (!feof (f)) { + char *start_addr, *end_addr, *end_addr_end, *file; + void *start_addr_p, *end_addr_p; + size_t len; + + if (fgets (line, SIZE, f) == NULL) + break; + + /* Sanity check. */ + if (strstr (line, " r-xp ") == NULL || strchr (line, '/') == NULL) + continue; + + /* Parse line. */ + start_addr = line; + end_addr = strchr (line, '-'); + file = strchr (line, '/'); + + /* More sanity check. */ + if (!(file > end_addr && end_addr != NULL && end_addr[0] == '-')) + continue; + + end_addr[0] = '\0'; + end_addr++; + end_addr_end = strchr (end_addr, ' '); + if (end_addr_end == NULL) + continue; + + end_addr_end[0] = '\0'; + len = strlen (file); + if (len == 0) + continue; + if (file[len - 1] == '\n') + file[len - 1] = '\0'; + + /* Get rid of "(deleted)" from the filename. */ + len = strlen (file); + if (len > 10 && strcmp (file + len - 10, " (deleted)") == 0) + file[len - 10] = '\0'; + + /* I don't know whether this can happen but better safe than sorry. */ + len = strlen (start_addr); + if (len != strlen (end_addr)) + continue; + + + /* Transform the addresses into a string in the form of 0xdeadbeef, + * then transform that into a pointer. */ + if (address_string_len < len + 3) { + address_string_len = len + 3; + address_string = (char *) realloc (address_string, address_string_len); + /* Handle OOM (Tracker issue #35) */ + if (!address_string) + { + if (error) + *error = BR_INIT_ERROR_NOMEM; + return (char *) NULL; + } + } + + memcpy (address_string, "0x", 2); + memcpy (address_string + 2, start_addr, len); + address_string[2 + len] = '\0'; + sscanf (address_string, "%p", &start_addr_p); + + memcpy (address_string, "0x", 2); + memcpy (address_string + 2, end_addr, len); + address_string[2 + len] = '\0'; + sscanf (address_string, "%p", &end_addr_p); + + + if (symbol >= start_addr_p && symbol < end_addr_p) { + found = file; + break; + } + } + + free (address_string); + fclose (f); + + if (found == NULL) + return (char *) NULL; + else + return strdup (found); +#endif /* ENABLE_BINRELOC */ +} + + +#ifndef BINRELOC_RUNNING_DOXYGEN + #undef NULL + #define NULL ((char *) 0) /* typecasted as char* for C++ type safeness */ +#endif + +static char *exe = (char *) NULL; + + +/** Initialize the BinReloc library (for applications). + * + * This function must be called before using any other BinReloc functions. + * It attempts to locate the application's canonical filename. + * + * @note If you want to use BinReloc for a library, then you should call + * br_init_lib() instead. + * @note Initialization failure is not fatal. BinReloc functions will just + * fallback to the supplied default path. + * + * @param error If BinReloc failed to initialize, then the error code will + * be stored in this variable. Set to NULL if you want to + * ignore this. See #BrInitError for a list of error codes. + * + * @returns 1 on success, 0 if BinReloc failed to initialize. + */ +int +br_init (BrInitError *error) +{ + exe = _br_find_exe (error); + return exe != NULL; +} + + +/** Initialize the BinReloc library (for libraries). + * + * This function must be called before using any other BinReloc functions. + * It attempts to locate the calling library's canonical filename. + * + * @note The BinReloc source code MUST be included in your library, or this + * function won't work correctly. + * @note Initialization failure is not fatal. BinReloc functions will just + * fallback to the supplied default path. + * + * @param error If BinReloc failed to initialize, then the error code will + * be stored in this variable. Set to NULL if you want to + * ignore this. See #BrInitError for a list of error codes. + * + * @returns 1 on success, 0 if a filename cannot be found. + */ +int +br_init_lib (BrInitError *error) +{ + exe = _br_find_exe_for_symbol ((const void *) "", error); + return exe != NULL; +} + + +/** Find the canonical filename of the current application. + * + * @param default_exe A default filename which will be used as fallback. + * @returns A string containing the application's canonical filename, + * which must be freed when no longer necessary. If BinReloc is + * not initialized, or if br_init() failed, then a copy of + * default_exe will be returned. If default_exe is NULL, then + * NULL will be returned. + */ +char * +br_find_exe (const char *default_exe) +{ + if (exe == (char *) NULL) { + /* BinReloc is not initialized. */ + if (default_exe != (const char *) NULL) + return strdup (default_exe); + else + return (char *) NULL; + } + return strdup (exe); +} + + +/** Locate the directory in which the current application is installed. + * + * The prefix is generated by the following pseudo-code evaluation: + * \code + * dirname(exename) + * \endcode + * + * @param default_dir A default directory which will used as fallback. + * @return A string containing the directory, which must be freed when no + * longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_dir + * will be returned. If default_dir is NULL, then NULL will be + * returned. + */ +char * +br_find_exe_dir (const char *default_dir) +{ + if (exe == NULL) { + /* BinReloc not initialized. */ + if (default_dir != NULL) + return strdup (default_dir); + else + return NULL; + } + + return br_dirname (exe); +} + + + +/*********************** + * Utility functions + ***********************/ + +/** Extracts the directory component of a path. + * + * Similar to g_dirname() or the dirname commandline application. + * + * Example: + * \code + * br_dirname ("/usr/local/foobar"); --> Returns: "/usr/local" + * \endcode + * + * @param path A path. + * @returns A directory name. This string should be freed when no longer needed. + */ +char * +br_dirname (const char *path) +{ + char *end, *result; + + if (path == (const char *) NULL) + return (char *) NULL; + + end = strrchr (path, '/'); + if (end == (const char *) NULL) + return strdup ("."); + + while (end > path && *end == '/') + end--; + result = strndup (path, end - path + 1); + if (result[0] == 0) { + free (result); + return strdup ("/"); + } else + return result; +} + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + diff --git a/RelocatableSoftware/SLPackage/programs/binreloc/_binreloc.h.in b/RelocatableSoftware/SLPackage/programs/binreloc/_binreloc.h.in new file mode 100644 index 0000000..03a02f0 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/binreloc/_binreloc.h.in @@ -0,0 +1,65 @@ +/** \file hsf_binreloc.h + * \brief Query program/library location on filesystem + * + * BinReloc - a library for creating relocatable executables + * Written by: Hongli Lai + * Modifications by: Ben Morgan + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of thr Boost Software License, + * + * http://www.boost.org/LICENSE_1_0.txt + * +*/ +#ifndef __BINRELOC_H__ +#define __BINRELOC_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** These error codes can be returned by br_init(), br_init_lib() */ +typedef enum { + /** Cannot allocate memory. */ + BR_INIT_ERROR_NOMEM, + /** Unable to open /proc/self/maps; see errno for details. */ + BR_INIT_ERROR_OPEN_MAPS, + /** Unable to read from /proc/self/maps; see errno for details. */ + BR_INIT_ERROR_READ_MAPS, + /** The file format of /proc/self/maps is invalid; kernel bug? */ + BR_INIT_ERROR_INVALID_MAPS, + /** BinReloc is disabled (the ENABLE_BINRELOC macro is not defined). */ + BR_INIT_ERROR_DISABLED +} BrInitError; + + +#ifndef BINRELOC_RUNNING_DOXYGEN +/* Mangle symbol names to avoid symbol + * collisions with other ELF objects. + */ +#define br_init @MANGLE_BINRELOC@_br_init +#define br_init_lib @MANGLE_BINRELOC@_br_init_lib +#define br_find_exe @MANGLE_BINRELOC@_br_find_exe +#define br_find_exe_dir @MANGLE_BINRELOC@_br_find_exe_dir +#define br_dirname @MANGLE_BINRELOC@_br_dirname +#endif + +/* Initialization */ +int br_init (BrInitError *error); +int br_init_lib (BrInitError *error); + +/* Paths to binary this code was compiled into + * and directory containing it +*/ +char *br_find_exe (const char *default_exe); +char *br_find_exe_dir (const char *default_dir); + +/* Utility functions */ +char *br_dirname (const char *path); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BINRELOC_H__ */ diff --git a/RelocatableSoftware/SLPackage/programs/binreloc/original/binreloc.c b/RelocatableSoftware/SLPackage/programs/binreloc/original/binreloc.c new file mode 100644 index 0000000..aea6e66 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/binreloc/original/binreloc.c @@ -0,0 +1,845 @@ +/* BinReloc - a library for creating relocatable executables + * Written by: Hongli Lai + * Modifications by: Ben Morgan + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of thr Boost Software License, + * + * http://www.boost.org/LICENSE_1_0.txt + * +*/ +#ifndef __BINRELOC_C__ +#define __BINRELOC_C__ + +#ifdef ENABLE_BINRELOC + #include + #include + #include +#endif /* ENABLE_BINRELOC */ +#include +#include +#include +#include +#if defined(__APPLE__) && defined(__MACH__) +#include +#include +#endif +#include "binreloc.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** @internal + * Find the canonical filename of the executable. Returns the filename + * (which must be freed) or NULL on error. If the parameter 'error' is + * not NULL, the error code will be stored there, if an error occured. + */ +static char * +_br_find_exe (BrInitError *error) +{ +#ifndef ENABLE_BINRELOC + if (error) + *error = BR_INIT_ERROR_DISABLED; + return NULL; +#elif defined(sun) || defined(__sun) + char *path; + path = getexecname(); + return strdup(path); +#elif defined(__APPLE__) && defined(__MACH__) + char path[MAXPATHLEN+1]; + uint32_t path_len = MAXPATHLEN; + // SPI first appeared in Mac OS X 10.2 + _NSGetExecutablePath(path, &path_len); + return strdup(path); +#else + char *path, *path2, *line, *result; + size_t buf_size; + ssize_t size; + struct stat stat_buf; + FILE *f; + + /* Read from /proc/self/exe (symlink) */ + if (sizeof (path) > SSIZE_MAX) + buf_size = SSIZE_MAX - 1; + else + buf_size = PATH_MAX - 1; + path = (char *) malloc (buf_size); + if (path == NULL) { + /* Cannot allocate memory. */ + if (error) + *error = BR_INIT_ERROR_NOMEM; + return NULL; + } + path2 = (char *) malloc (buf_size); + if (path2 == NULL) { + /* Cannot allocate memory. */ + if (error) + *error = BR_INIT_ERROR_NOMEM; + free (path); + return NULL; + } + +#ifdef __FreeBSD__ + strncpy (path2, "/proc/self/file", buf_size - 1); +#else + strncpy (path2, "/proc/self/exe", buf_size - 1); +#endif + + while (1) { + int i; + + size = readlink (path2, path, buf_size - 1); + if (size == -1) { + /* Error. */ + free (path2); + break; + } + + /* readlink() success. */ + path[size] = '\0'; + + /* Check whether the symlink's target is also a symlink. + * We want to get the final target. */ + i = stat (path, &stat_buf); + if (i == -1) { + /* Error. */ + free (path2); + break; + } + + /* stat() success. */ + if (!S_ISLNK (stat_buf.st_mode)) { + /* path is not a symlink. Done. */ + free (path2); + return path; + } + + /* path is a symlink. Continue loop and resolve this. */ + strncpy (path, path2, buf_size - 1); + } + +#if defined(__FreeBSD__) +{ + char *name, *start, *end; + char *buffer = NULL, *temp; + struct stat finfo; + + name = (char*) getprogname(); + start = end = getenv("PATH"); + + while (*end) { + end = strchr (start, ':'); + if (!end) end = strchr (start, '\0'); + + /* Resize `buffer' for path component, '/', name and a '\0' */ + temp = realloc (buffer, end - start + 1 + strlen (name) + 1); + if (temp) { + buffer = temp; + + strncpy (buffer, start, end - start); + *(buffer + (end - start)) = '/'; + strcpy (buffer + (end - start) + 1, name); + + if ((stat(buffer, &finfo)==0) && (!S_ISDIR (finfo.st_mode))) { + path = strdup(buffer); + free (buffer); + return path; + } + } /* else... ignore the failure; `buffer' is still valid anyway. */ + + start = end + 1; + } + /* Path search failed */ + free (buffer); + + if (error) + *error = BR_INIT_ERROR_DISABLED; + return NULL; +} +#endif + + /* readlink() or stat() failed; this can happen when the program is + * running in Valgrind 2.2. Read from /proc/self/maps as fallback. */ + + buf_size = PATH_MAX + 128; + line = (char *) realloc (path, buf_size); + if (line == NULL) { + /* Cannot allocate memory. */ + free (path); + if (error) + *error = BR_INIT_ERROR_NOMEM; + return NULL; + } + + f = fopen ("/proc/self/maps", "r"); + if (f == NULL) { + free (line); + if (error) + *error = BR_INIT_ERROR_OPEN_MAPS; + return NULL; + } + + /* The first entry should be the executable name. */ + result = fgets (line, (int) buf_size, f); + if (result == NULL) { + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_READ_MAPS; + return NULL; + } + + /* Get rid of newline character. */ + buf_size = strlen (line); + if (buf_size <= 0) { + /* Huh? An empty string? */ + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_INVALID_MAPS; + return NULL; + } + if (line[buf_size - 1] == 10) + line[buf_size - 1] = 0; + + /* Extract the filename; it is always an absolute path. */ + path = strchr (line, '/'); + + /* Sanity check. */ + if (strstr (line, " r-xp ") == NULL || path == NULL) { + fclose (f); + free (line); + if (error) + *error = BR_INIT_ERROR_INVALID_MAPS; + return NULL; + } + + path = strdup (path); + free (line); + fclose (f); + return path; +#endif /* ENABLE_BINRELOC */ +} + + +/** @internal + * Find the canonical filename of the executable which owns symbol. + * Returns a filename which must be freed, or NULL on error. + */ +static char * +_br_find_exe_for_symbol (const void *symbol, BrInitError *error) +{ +#ifndef ENABLE_BINRELOC + if (error) + *error = BR_INIT_ERROR_DISABLED; + return (char *) NULL; +#else + #define SIZE PATH_MAX + 100 + FILE *f; + size_t address_string_len; + char *address_string, line[SIZE], *found; + + if (symbol == NULL) + return (char *) NULL; + + f = fopen ("/proc/self/maps", "r"); + if (f == NULL) + return (char *) NULL; + + address_string_len = 4; + address_string = (char *) malloc (address_string_len); + /* Handle OOM (Tracker issue #35) */ + if (!address_string) + { + if (error) + *error = BR_INIT_ERROR_NOMEM; + return (char *) NULL; + } + found = (char *) NULL; + + while (!feof (f)) { + char *start_addr, *end_addr, *end_addr_end, *file; + void *start_addr_p, *end_addr_p; + size_t len; + + if (fgets (line, SIZE, f) == NULL) + break; + + /* Sanity check. */ + if (strstr (line, " r-xp ") == NULL || strchr (line, '/') == NULL) + continue; + + /* Parse line. */ + start_addr = line; + end_addr = strchr (line, '-'); + file = strchr (line, '/'); + + /* More sanity check. */ + if (!(file > end_addr && end_addr != NULL && end_addr[0] == '-')) + continue; + + end_addr[0] = '\0'; + end_addr++; + end_addr_end = strchr (end_addr, ' '); + if (end_addr_end == NULL) + continue; + + end_addr_end[0] = '\0'; + len = strlen (file); + if (len == 0) + continue; + if (file[len - 1] == '\n') + file[len - 1] = '\0'; + + /* Get rid of "(deleted)" from the filename. */ + len = strlen (file); + if (len > 10 && strcmp (file + len - 10, " (deleted)") == 0) + file[len - 10] = '\0'; + + /* I don't know whether this can happen but better safe than sorry. */ + len = strlen (start_addr); + if (len != strlen (end_addr)) + continue; + + + /* Transform the addresses into a string in the form of 0xdeadbeef, + * then transform that into a pointer. */ + if (address_string_len < len + 3) { + address_string_len = len + 3; + address_string = (char *) realloc (address_string, address_string_len); + /* Handle OOM (Tracker issue #35) */ + if (!address_string) + { + if (error) + *error = BR_INIT_ERROR_NOMEM; + return (char *) NULL; + } + } + + memcpy (address_string, "0x", 2); + memcpy (address_string + 2, start_addr, len); + address_string[2 + len] = '\0'; + sscanf (address_string, "%p", &start_addr_p); + + memcpy (address_string, "0x", 2); + memcpy (address_string + 2, end_addr, len); + address_string[2 + len] = '\0'; + sscanf (address_string, "%p", &end_addr_p); + + + if (symbol >= start_addr_p && symbol < end_addr_p) { + found = file; + break; + } + } + + free (address_string); + fclose (f); + + if (found == NULL) + return (char *) NULL; + else + return strdup (found); +#endif /* ENABLE_BINRELOC */ +} + + +#ifndef BINRELOC_RUNNING_DOXYGEN + #undef NULL + #define NULL ((char *) 0) /* typecasted as char* for C++ type safeness */ +#endif + +static char *exe = (char *) NULL; + + +/** Initialize the BinReloc library (for applications). + * + * This function must be called before using any other BinReloc functions. + * It attempts to locate the application's canonical filename. + * + * @note If you want to use BinReloc for a library, then you should call + * br_init_lib() instead. + * @note Initialization failure is not fatal. BinReloc functions will just + * fallback to the supplied default path. + * + * @param error If BinReloc failed to initialize, then the error code will + * be stored in this variable. Set to NULL if you want to + * ignore this. See #BrInitError for a list of error codes. + * + * @returns 1 on success, 0 if BinReloc failed to initialize. + */ +int +br_init (BrInitError *error) +{ + exe = _br_find_exe (error); + return exe != NULL; +} + + +/** Initialize the BinReloc library (for libraries). + * + * This function must be called before using any other BinReloc functions. + * It attempts to locate the calling library's canonical filename. + * + * @note The BinReloc source code MUST be included in your library, or this + * function won't work correctly. + * @note Initialization failure is not fatal. BinReloc functions will just + * fallback to the supplied default path. + * + * @param error If BinReloc failed to initialize, then the error code will + * be stored in this variable. Set to NULL if you want to + * ignore this. See #BrInitError for a list of error codes. + * + * @returns 1 on success, 0 if a filename cannot be found. + */ +int +br_init_lib (BrInitError *error) +{ + exe = _br_find_exe_for_symbol ((const void *) "", error); + return exe != NULL; +} + + +/** Find the canonical filename of the current application. + * + * @param default_exe A default filename which will be used as fallback. + * @returns A string containing the application's canonical filename, + * which must be freed when no longer necessary. If BinReloc is + * not initialized, or if br_init() failed, then a copy of + * default_exe will be returned. If default_exe is NULL, then + * NULL will be returned. + */ +char * +br_find_exe (const char *default_exe) +{ + if (exe == (char *) NULL) { + /* BinReloc is not initialized. */ + if (default_exe != (const char *) NULL) + return strdup (default_exe); + else + return (char *) NULL; + } + return strdup (exe); +} + + +/** Locate the directory in which the current application is installed. + * + * The prefix is generated by the following pseudo-code evaluation: + * \code + * dirname(exename) + * \endcode + * + * @param default_dir A default directory which will used as fallback. + * @return A string containing the directory, which must be freed when no + * longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_dir + * will be returned. If default_dir is NULL, then NULL will be + * returned. + */ +char * +br_find_exe_dir (const char *default_dir) +{ + if (exe == NULL) { + /* BinReloc not initialized. */ + if (default_dir != NULL) + return strdup (default_dir); + else + return NULL; + } + + return br_dirname (exe); +} + + +/** Locate the prefix in which the current application is installed. + * + * The prefix is generated by the following pseudo-code evaluation: + * \code + * dirname(dirname(exename)) + * \endcode + * + * @param default_prefix A default prefix which will used as fallback. + * @return A string containing the prefix, which must be freed when no + * longer necessary. If BinReloc is not initialized, or if + * the initialization function failed, then a copy of default_prefix + * will be returned. If default_prefix is NULL, then NULL will be returned. + */ +char * +br_find_prefix (const char *default_prefix) +{ + char *dir1, *dir2; + + if (exe == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_prefix != (const char *) NULL) + return strdup (default_prefix); + else + return (char *) NULL; + } + + dir1 = br_dirname (exe); + dir2 = br_dirname (dir1); + free (dir1); + return dir2; +} + + +/** Locate the application's binary folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/bin" + * \endcode + * + * @param default_bin_dir A default path which will used as fallback. + * @return A string containing the bin folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if + * the initialization function failed, then a copy of default_bin_dir will + * be returned. If default_bin_dir is NULL, then NULL will be returned. + */ +char * +br_find_bin_dir (const char *default_bin_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_bin_dir != (const char *) NULL) + return strdup (default_bin_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "bin"); + free (prefix); + return dir; +} + + +/** Locate the application's superuser binary folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/sbin" + * \endcode + * + * @param default_sbin_dir A default path which will used as fallback. + * @return A string containing the sbin folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_sbin_dir will + * be returned. If default_bin_dir is NULL, then NULL will be returned. + */ +char * +br_find_sbin_dir (const char *default_sbin_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_sbin_dir != (const char *) NULL) + return strdup (default_sbin_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "sbin"); + free (prefix); + return dir; +} + + +/** Locate the application's data folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/share" + * \endcode + * + * @param default_data_dir A default path which will used as fallback. + * @return A string containing the data folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_data_dir + * will be returned. If default_data_dir is NULL, then NULL will be + * returned. + */ +char * +br_find_data_dir (const char *default_data_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_data_dir != (const char *) NULL) + return strdup (default_data_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "share"); + free (prefix); + return dir; +} + + +/** Locate the application's localization folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/share/locale" + * \endcode + * + * @param default_locale_dir A default path which will used as fallback. + * @return A string containing the localization folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the + * initialization function failed, then a copy of default_locale_dir will be returned. + * If default_locale_dir is NULL, then NULL will be returned. + */ +char * +br_find_locale_dir (const char *default_locale_dir) +{ + char *data_dir, *dir; + + data_dir = br_find_data_dir ((const char *) NULL); + if (data_dir == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_locale_dir != (const char *) NULL) + return strdup (default_locale_dir); + else + return (char *) NULL; + } + + dir = br_build_path (data_dir, "locale"); + free (data_dir); + return dir; +} + + +/** Locate the application's library folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/lib" + * \endcode + * + * @param default_lib_dir A default path which will used as fallback. + * @return A string containing the library folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the initialization + * function failed, then a copy of default_lib_dir will be returned. + * If default_lib_dir is NULL, then NULL will be returned. + */ +char * +br_find_lib_dir (const char *default_lib_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_lib_dir != (const char *) NULL) + return strdup (default_lib_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "lib"); + free (prefix); + return dir; +} + + +/** Locate the application's libexec folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/libexec" + * \endcode + * + * @param default_libexec_dir A default path which will used as fallback. + * @return A string containing the libexec folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the initialization + * function failed, then a copy of default_libexec_dir will be returned. + * If default_libexec_dir is NULL, then NULL will be returned. + */ +char * +br_find_libexec_dir (const char *default_libexec_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_libexec_dir != (const char *) NULL) + return strdup (default_libexec_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "libexec"); + free (prefix); + return dir; +} + + +/** Locate the application's configuration files folder. + * + * The path is generated by the following pseudo-code evaluation: + * \code + * prefix + "/etc" + * \endcode + * + * @param default_etc_dir A default path which will used as fallback. + * @return A string containing the etc folder's path, which must be freed when + * no longer necessary. If BinReloc is not initialized, or if the initialization + * function failed, then a copy of default_etc_dir will be returned. + * If default_etc_dir is NULL, then NULL will be returned. + */ +char * +br_find_etc_dir (const char *default_etc_dir) +{ + char *prefix, *dir; + + prefix = br_find_prefix ((const char *) NULL); + if (prefix == (char *) NULL) { + /* BinReloc not initialized. */ + if (default_etc_dir != (const char *) NULL) + return strdup (default_etc_dir); + else + return (char *) NULL; + } + + dir = br_build_path (prefix, "etc"); + free (prefix); + return dir; +} + + +/*********************** + * Utility functions + ***********************/ + +/** Concatenate str1 and str2 to a newly allocated string. + * + * @param str1 A string. + * @param str2 Another string. + * @returns A newly-allocated string. This string should be freed when no longer needed. + */ +char * +br_strcat (const char *str1, const char *str2) +{ + char *result; + size_t len1, len2; + + if (str1 == NULL) + str1 = ""; + if (str2 == NULL) + str2 = ""; + + len1 = strlen (str1); + len2 = strlen (str2); + + result = (char *) malloc (len1 + len2 + 1); + /* Handle OOM (Tracker issue #35) */ + if (result) + { + memcpy (result, str1, len1); + memcpy (result + len1, str2, len2); + result[len1 + len2] = '\0'; + } + return result; +} + + +char * +br_build_path (const char *dir, const char *file) +{ + char *dir2, *result; + size_t len; + int must_free = 0; + + len = strlen (dir); + if (len > 0 && dir[len - 1] != '/') { + dir2 = br_strcat (dir, "/"); + must_free = 1; + } else + dir2 = (char *) dir; + + result = br_strcat (dir2, file); + if (must_free) + free (dir2); + return result; +} + + +/* Emulates glibc's strndup() */ +static char * +br_strndup (const char *str, size_t size) +{ + char *result = (char *) NULL; + size_t len; + + if (str == (const char *) NULL) + return (char *) NULL; + + len = strlen (str); + if (len == 0) + return strdup (""); + if (size > len) + size = len; + + result = (char *) malloc (len + 1); + /* Handle OOM (Tracker issue #35) */ + if (result) + { + memcpy (result, str, size); + result[size] = '\0'; + } + return result; +} + + +/** Extracts the directory component of a path. + * + * Similar to g_dirname() or the dirname commandline application. + * + * Example: + * \code + * br_dirname ("/usr/local/foobar"); --> Returns: "/usr/local" + * \endcode + * + * @param path A path. + * @returns A directory name. This string should be freed when no longer needed. + */ +char * +br_dirname (const char *path) +{ + char *end, *result; + + if (path == (const char *) NULL) + return (char *) NULL; + + end = strrchr (path, '/'); + if (end == (const char *) NULL) + return strdup ("."); + + while (end > path && *end == '/') + end--; + result = br_strndup (path, end - path + 1); + if (result[0] == 0) { + free (result); + return strdup ("/"); + } else + return result; +} + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BINRELOC_C__ */ diff --git a/RelocatableSoftware/SLPackage/programs/binreloc/original/binreloc.h.in b/RelocatableSoftware/SLPackage/programs/binreloc/original/binreloc.h.in new file mode 100644 index 0000000..22e2ad9 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/binreloc/original/binreloc.h.in @@ -0,0 +1,82 @@ +/** \file binreloc.h + * \brief Query program/library location on filesystem + * + * BinReloc - a library for creating relocatable executables + * Written by: Hongli Lai + * Modifications by: Ben Morgan + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of thr Boost Software License, + * + * http://www.boost.org/LICENSE_1_0.txt + * +*/ +#ifndef __BINRELOC_H__ +#define __BINRELOC_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** These error codes can be returned by br_init(), br_init_lib(), gbr_init() or gbr_init_lib(). */ +typedef enum { + /** Cannot allocate memory. */ + BR_INIT_ERROR_NOMEM, + /** Unable to open /proc/self/maps; see errno for details. */ + BR_INIT_ERROR_OPEN_MAPS, + /** Unable to read from /proc/self/maps; see errno for details. */ + BR_INIT_ERROR_READ_MAPS, + /** The file format of /proc/self/maps is invalid; kernel bug? */ + BR_INIT_ERROR_INVALID_MAPS, + /** BinReloc is disabled (the ENABLE_BINRELOC macro is not defined). */ + BR_INIT_ERROR_DISABLED +} BrInitError; + + +#ifndef BINRELOC_RUNNING_DOXYGEN + /* Mangle symbol names to avoid symbol + * collisions with other ELF objects. + */ + #define br_init @MANGLE_BINRELOC@_br_init + #define br_init_lib @MANGLE_BINRELOC@_br_init_lib + #define br_find_exe @MANGLE_BINRELOC@_br_find_exe + #define br_find_exe_dir @MANGLE_BINRELOC@_br_find_exe_dir + #define br_find_prefix @MANGLE_BINRELOC@_br_find_prefix + #define br_find_bin_dir @MANGLE_BINRELOC@_br_find_bin_dir + #define br_find_sbin_dir @MANGLE_BINRELOC@_br_find_sbin_dir + #define br_find_data_dir @MANGLE_BINRELOC@_br_find_data_dir + #define br_find_locale_dir @MANGLE_BINRELOC@_br_find_locale_dir + #define br_find_lib_dir @MANGLE_BINRELOC@_br_find_lib_dir + #define br_find_libexec_dir @MANGLE_BINRELOC@_br_find_libexec_dir + #define br_find_etc_dir @MANGLE_BINRELOC@_br_find_etc_dir + #define br_strcat @MANGLE_BINRELOC@_br_strcat + #define br_build_path @MANGLE_BINRELOC@_br_build_path + #define br_dirname @MANGLE_BINRELOC@_br_dirname +#endif + +int br_init (BrInitError *error); +int br_init_lib (BrInitError *error); + +char *br_find_exe (const char *default_exe); +char *br_find_exe_dir (const char *default_dir); +char *br_find_prefix (const char *default_prefix); +char *br_find_bin_dir (const char *default_bin_dir); +char *br_find_sbin_dir (const char *default_sbin_dir); +char *br_find_data_dir (const char *default_data_dir); +char *br_find_locale_dir (const char *default_locale_dir); +char *br_find_lib_dir (const char *default_lib_dir); +char *br_find_libexec_dir (const char *default_libexec_dir); +char *br_find_etc_dir (const char *default_etc_dir); + +/* Utility functions */ +char *br_strcat (const char *str1, const char *str2); +char *br_build_path (const char *dir, const char *file); +char *br_dirname (const char *path); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BINRELOC_H__ */ diff --git a/RelocatableSoftware/SLPackage/programs/python/slp-python b/RelocatableSoftware/SLPackage/programs/python/slp-python new file mode 100755 index 0000000..f3935ac --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/python/slp-python @@ -0,0 +1,6 @@ +#!/usr/bin/env python +import os +selfLocation = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == '__main__': + print(selfLocation) diff --git a/RelocatableSoftware/SLPackage/programs/ruby/slp-ruby b/RelocatableSoftware/SLPackage/programs/ruby/slp-ruby new file mode 100755 index 0000000..284b1f2 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/ruby/slp-ruby @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +selfLocation = File.expand_path(__FILE__) +puts selfLocation diff --git a/RelocatableSoftware/SLPackage/programs/slp-binreloc.cpp b/RelocatableSoftware/SLPackage/programs/slp-binreloc.cpp new file mode 100644 index 0000000..baf7ab8 --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/slp-binreloc.cpp @@ -0,0 +1,27 @@ +/** + \file slpackage.cpp + \brief Relocatable binreloc application +*/ + +#include +#include +#include + +#include "SLPApplicationPaths.h" + +int main(int argc, char *argv[]) { + auto appDir = SLP::getApplicationDir(); + std::cout << "[application in]: " << appDir << "\n"; + + auto resPath = SLP::getResourceDir() + "/" + "resource.txt"; + + std::ifstream resStream {resPath}; + if (resStream.good()) { + std::string resContent {std::istreambuf_iterator(resStream), + std::istreambuf_iterator()}; + std::cout << "[resource]: '" << resContent << "'\n"; + } else { + std::cerr << "[error]: could not open resource \"" << resPath << "\"\n"; + } + return 0; +} diff --git a/RelocatableSoftware/SLPackage/programs/slp-poco.cpp b/RelocatableSoftware/SLPackage/programs/slp-poco.cpp new file mode 100644 index 0000000..9c3939f --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/slp-poco.cpp @@ -0,0 +1,23 @@ +/** + \file hsf-reloc-poco.cpp + \brief Relocatable Poco core application + + Poco applications can be implemented as classes derived from the Application + base class. A macro is provided to construct/use the class in the actual + main() function. +*/ + +#include + +//! Concrete application class +class HSFReloc : public Poco::Util::Application { + //! Implementation of main method + int main(const std::vector&) { + auto hsfrelocDir = this->config().getString("application.dir"); + this->logger().information(hsfrelocDir); + return 0; + } +}; + +POCO_APP_MAIN(HSFReloc) + diff --git a/RelocatableSoftware/SLPackage/programs/slp-qt.cpp b/RelocatableSoftware/SLPackage/programs/slp-qt.cpp new file mode 100644 index 0000000..3b40e0b --- /dev/null +++ b/RelocatableSoftware/SLPackage/programs/slp-qt.cpp @@ -0,0 +1,16 @@ +/** + \file hsf-reloc-qt.cpp + \brief Relocatable Qt5 core application +*/ + +#include + +#include + +int main(int argc, char *argv[]) { + QCoreApplication app(argc, argv); + auto appDir = QCoreApplication::applicationDirPath(); + std::cout << appDir.toStdString() << "\n"; + + return 0; +} diff --git a/RelocatableSoftware/SLPackage/resources/resource.txt b/RelocatableSoftware/SLPackage/resources/resource.txt new file mode 100644 index 0000000..52b34de --- /dev/null +++ b/RelocatableSoftware/SLPackage/resources/resource.txt @@ -0,0 +1 @@ +hello from builtin SLPackage resource file!