Skip to content

RFC: getting rid of multiple rtap_app binaries, enable alternatives when loading components #104

@ArcEye

Description

@ArcEye

Issue by mhaberler
Sat Feb 20 18:18:21 2016
Originally opened as machinekit/machinekit#874


[a bit longish but fundamental change - I'd appreciate a look over the shoulder while at it]

this lays out a proposed change to solve two issues at once:

  1. getting rid of the per-flavor rtapi_app_<flavor> binaries (helps simplifiying the build and distribution, including the cmake build)
  2. enable loading of components from different directories specified by a path preference (needed for the multicore branch)

(1) is an annoyance due to the way shared libraries are resolved by dlopen() in setuid binaries (rtapi_app needs to be setuid so it can talk to hardware); see man dlopen for the gory details.

The current solution @zultron came up with is:

The component shared libs (.so files) live under rtlib/<flavor>/<compname>.so, and to force loading from a particular rtlib/<flavor>/ the per-thread rtapi_app binary gets an rpath set during build - this means that once you start rtapi_app_<flavor>, this binary will load shared libs with dlopen only from its rpath (rtlib/<flavor>/).

While this works great, the downside is: we need multiple rtapi_app binaries - one per flavor - and they really only differ in the rpath attribute. Also, the scripts/realtime code needs to figure the flavor and start the right binary. Not much of an issue though.

(2) The harder issue is the introduction of a versioned HAL API in the multicore branch. To make a component multicore-safe, it must employ the new HAL pin API, and accessor functions; manipulating raw memory locations through pointers as done in the old API is impossible to make thread-safe in a sane manner. However, at the pin/signal level the API's are compatible. So a v1 pin and a v2 pin can be linked to a signal, and things will work as before.

@ArcEye has adapted comp to icomp to employ the v2 HAL API, and rewritten most of the components to use it, and I have converted some of the C comps to v2 (stepgen, pwmgen, encoder, sim_encoder).

This means we now have two sets of components: the existing set of legacy components, and all the rewritten v2 components. This bears the question how these are distinguished and loaded. As per above rpath scheme, all components need to reside in the same trusted directory. What @ArcEye and me have done so far is to give these comps different basenames; so for example there exists a rtlib/posix/xor.so and rtlib/posix/xorv2.so component.

This works, but means that ALL halcmd/Python HAL configs which should be converted to v2 need to be rewritten as the comp name changes. It also makes regression testing v2 against v1 comps harder as this is a somewhat ad-doc name change.

A better solution would be to provide a preference path of directories to load components from, like for example rtlib/posix/v2:rtlib/posix, and have the comp loading code in rtapi_app do the right thing depending on this path preference. This avoids wholesale rewrite of HAL configs as the preference path could be passed by some other means (halcmd primitive, API parameter, environment variable etc).

However, this is at odds with the security intentions of the rpath feature: directories of an rpath on a setuid binary are trusted. And the rpath resolution ONLY works if there is no slash in the first argument to dlopen().

To cite the manpage:

If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname. Otherwise, the dynamic linker searches for the library as follows (see ld.so(8) for further details): ... details of loading, including rpath feature..

So as soon as a slash is found in the dlopen() argument the rpath feature is essentially disabled. This is a problem if we want to distinguish shared libaries aka versioned components by pathnames like v2/comp.so etc.

what does work - and that could be the start towards a solution to both issues - is:

  • provide a way for rtapi_app to learn the path to the rtlib/<flavor> (eg from config values, like EMC2_RTLIB_BASE_DIR which points to rtlib/ - since rtapi_app knows the current flavor, it can construct the path to rtlib/<flavor> from EMC2_RTLIB_BASE_DIR
  • before loading a shared library, do a chdir(rtlib/<flavor>)
  • ALWAYS use a relative path in the dlopen argument, for instance dlopen(./xor.so) or dlopen(./v2/xor.so)
  • post loading, pop back to original working directory (so among others, an accidential core file is dropped in the right directory)

the gist of the idea is - the preference path is just a subdir under a trusted directory, and hence trusted as well.

Now if we also set the rpath to EMC2_RTLIB_BASE_DIR rather than to rtlib/<flavor>, then we also do not need separate binaries because there will be only one rtapi_app with an rpath which applies to all flavors.

From what I've tried this works. I would appreciate feedback on the idea before I do this in full.

[edit: the way dlopen() works, it seems this scheme avoids the rpath functionality as it does not apply altogether to dlopen() loading from relative paths. This suggests the rpath to EMC2_RTLIB_BASE_DIR can be dropped as long as EMC2_RTLIB_BASE_DIR is available some way to chdir to. ]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions