2

My program uses an existing library with limited documentation, over which I have no control. For the purposes of this question it is used as follows:

#include <theirlib.hpp>

using their_lib::Node;

int main ( int argc, char** argv )
{
    Node my_node;

    // connect to the master communication server
    my_node.init();

    // ...
}

For reasons best known to itself, init() takes no parameters, instead trying to find the URI of the communication server (as well as other parameters) from an environment variable. I can see this with ltrace:

...
15:43:24 libtheirlib.so->getenv("THEIRLIB_MASTER_URI") = nil <0.000173>
...

Now, I can wrap my program with a script and set the environment variables beforehand, but what if I want to be able to change them at runtime? I can use setenv() but this still strikes me as polluting the environment, and what if I want to connect to multiple master servers at once?

What are my options (if any) to prevent theirlib from using environment variables to change its behaviour and force it to use a value I can supply in code?

litelite
  • 2,857
  • 4
  • 23
  • 33
Phydeaux
  • 2,795
  • 3
  • 17
  • 35
  • Maybe play around with `LD_PRELOAD`? – Arkadiusz Drabczyk Aug 23 '17 at 15:27
  • None, short of recompiling it or very dirty and platform-specific linker tricks... –  Aug 23 '17 at 15:28
  • 3
    As your program will run in a subshell anyway, there is no "polluting the environment" -- no-one else will see what you `setenv()`. And if the underlying library is not prepared to reconnect to a different URL, no changing of the environment or other tricks (short of modifying the lib itself) will make it do so... – DevSolar Aug 23 '17 at 15:28
  • 1
    Maybe you could do a wrap for getenv. Look here for some help: https://stackoverflow.com/questions/13961774/gnu-gcc-ld-wrapping-a-call-to-symbol-with-caller-and-callee-defined-in-the-sam – Fausto Carvalho Marques Silva Aug 23 '17 at 15:32
  • And here https://stackoverflow.com/questions/2924440/advice-on-mocking-system-calls/2925879#2925879 – Fausto Carvalho Marques Silva Aug 23 '17 at 15:33
  • @DevSolar good point about the subshell. But as I understand it, the library will reconnect, since it calls `getenv` each time `Node::init` is called. – Phydeaux Aug 23 '17 at 15:43
  • Then `setenv` seems to be the cleanest sollution all-around. Do not fix what isn't broken. – DevSolar Aug 23 '17 at 15:44
  • @DevSolar yes, I agree, thank you. It seems messy, but that is a natural consequence of the dependency on this library I suppose. – Phydeaux Aug 23 '17 at 15:47
  • Added the comment as an answer as it does seem to solve your problem. – DevSolar Aug 23 '17 at 15:50
  • 2
    Note that [getenv and setenv are unsafe in a threaded program](https://rachelbythebay.com/w/2017/01/30/env/). – ephemient Aug 23 '17 at 16:06

2 Answers2

4

Your program will run in a subshell, so there is no "polluting the environment" -- no other process will see what you setenv().

DevSolar
  • 67,862
  • 21
  • 134
  • 209
1

Dirty trick, but it's possible to interpose your own implementation of getenv.

#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>
static char *theirlib_master_uri;
static char *(*real_getenv)(const char *);
char *getenv(const char *name) {
    if (!strcmp(name, "THEIRLIB_MASTER_URI"))
        return theirlib_master_uri;
    if (!real_getenv)
        *((void **)&real_getenv) = dlsym(RTLD_NEXT, "getenv");
    return real_getenv(name);
}

This can be done either in a LD_PRELOAD shared object or in your own program.

ephemient
  • 198,619
  • 38
  • 280
  • 391