12

I have an application that reads a file. Let's call it processname and the file ~/.configuration. When processname runs it always reads ~/.configuration and can't be configured differently. There are also other applications that rely on "~/.configuration", before and after, but not while processname is running.

Wrapping processname in a script that replaces the contents of ~/.configuration is an option, but I recently had a power outage (while the contents were swapped out), where I lost the previous contents of said file, so this is not desirable.

Is there a way (perhaps using something distantly related to LD_DEBUG=files processname?) for fooling a process into reading different contents when it tries to read a specific file? Searching and replacing the filename in the executable is a bit too invasive, but should work as well.

I know it's possible to write a kernel module that takes over the open() call (https://news.ycombinator.com/item?id=2972958), but is there a simpler or cleaner way?

EDIT: When searching for ~/.configuration in the processname executable I discovered that it tried to read another filename right before reading ~/.configuration. Problem solved.

1

3 Answers 3

11

In recent versions of Linux, you can unshare the mount namespace. That is, you can start processes that view the virtual file system differently (with file systems mounted differently).

That can also be done with chroot, but unshare is more adapted to your case.

Like chroot, you need superuser priviledged to unshare the mount namespace.

So, say you have ~/.configuration and ~/.configuration-for-that-cmd files.

You can start a process for which ~/.configuration is actually a bind-mount of ~/.configuration-for-that-cmd in there, and execute that-cmd in there.

like:

sudo unshare -m sh -c "
   mount --bind '$HOME/.configuration-for-that-cmd' \
                '$HOME/.configuration' &&
     exec that-cmd"

that-cmd and all its descendant processes will see a different ~/.configuration.

that-cmd above will run as root, use sudo -u another-user that-cmd if it need to run as a another-user.

4
  • I think your solution is probably the better of the two given so far (and given what the OP is after, redirecting based on time or the results of a detection process seems iffy to me), but I think they're wanting a single file to show up differently. So they'd probably have to mount it elsewhere and use a symlink, counting on the differing mount points to act as the actual point of redirection.
    – Bratchley
    Commented Jun 28, 2013 at 15:38
  • 1
    @JoelDavis, you can bind-mount any file, not only directory ones. Commented Jun 28, 2013 at 16:28
  • TIL. Are there security controls with that, though? I tried it using a subdirectory where I was in (binding from /etc/fstab) and it returned "Not a directory" but I did pretty much the same thing under /test and it worked without issue.
    – Bratchley
    Commented Jun 28, 2013 at 16:36
  • Actually, nm I can see the difference, I did it to a directory first time and a file the next. I was assuming that it would just redirect/modify the VFS as appropriate. Anywho, thanks for the new toy.
    – Bratchley
    Commented Jun 28, 2013 at 16:48
3

Soft links.

Create two config files, and point to one of them with a soft link most of the time, but change the soft link to point to the other one when the special app is running.

(I know this is a horrible hack, but it's slightly more reliable than changing file contents).

Or, manipulate $HOME.

In the script which starts the annoying process, set $HOME to be something under the regular $HOME directory, and your app should then use the config file located there (tested, and works for basic shell commands, ~ expands to $HOME).

Depending on what else the process does, changing $HOME may have unintended consequences (i.e. output files might end up in the wrong place).

0
2

You can do this using the LD_PRELOAD trick. Here's an implementation which maps paths beginning with a specific prefix to another location. The code is also on github.

For example, you could fake the existence of a file in /etc/ without being root. This was necessary for the owncloud client which refuses to work when the file /etc/ownCloud/sync-exclude.list does not exist.

It works by overriding the open() and open64() functions to map one directory to another, for example all open() calls to /etc/ownCloud/... could be redirected to /home/user1/.etc/ownCloud/....

Just adjust the path_map, then compile and run your program with the lib preloaded:

gcc -std=c99 -Wall -shared -fPIC path-mapping.c -o path-mapping.so -ldl

LD_PRELOAD=/path/to/my/path-mapping.so someprogram

Source code of path-mapping.c:

#define _GNU_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <dlfcn.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdarg.h>
#include <malloc.h>

// List of path pairs. Paths beginning with the first item will be
// translated by replacing the matching part with the second item.
static const char *path_map[][2] = {
    { "/etc/ownCloud/", "/home/user1/.etc/ownCloud/" },
};

__thread char *buffer = NULL;
__thread int buffer_size = -1;

typedef FILE* (*orig_fopen_func_type)(const char *path, const char *mode);
typedef int (*orig_open_func_type)(const char *pathname, int flags, ...);

static int starts_with(const char *str, const char *prefix) {
    return (strncmp(prefix, str, strlen(prefix)) == 0);
}

static char *get_buffer(int min_size) {
    int step = 63;
    if (min_size < 1) {
        min_size = 1;
    }
    if (min_size > buffer_size) {
        if (buffer != NULL) {
            free(buffer);
            buffer = NULL;
            buffer_size = -1;
        }
        buffer = malloc(min_size + step);
        if (buffer != NULL) {
            buffer_size = min_size + step;
        }
    }
    return buffer;
}

static const char *fix_path(const char *path)
{
    int count = (sizeof path_map) / (sizeof *path_map); // Array length
    for (int i = 0; i < count; i++) {
        const char *prefix = path_map[i][0];
        const char *replace = path_map[i][1];
        if (starts_with(path, prefix)) {
            const char *rest = path + strlen(prefix);
            char *new_path = get_buffer(strlen(path) + strlen(replace) - strlen(prefix));
            strcpy(new_path, replace);
            strcat(new_path, rest);
            printf("Mapped Path: %s  ==>  %s\n", path, new_path);
            return new_path;
        }
    }
    return path;
}


int open(const char *pathname, int flags, ...)
{
    const char *new_path = fix_path(pathname);

    orig_open_func_type orig_func;
    orig_func = (orig_open_func_type)dlsym(RTLD_NEXT, "open");

    // If O_CREAT is used to create a file, the file access mode must be given.
    if (flags & O_CREAT) {
        va_list args;
        va_start(args, flags);
        int mode = va_arg(args, int);
        va_end(args);
        return orig_func(new_path, flags, mode);
    } else {
        return orig_func(new_path, flags);
    }
}

int open64(const char *pathname, int flags, ...)
{
    const char *new_path = fix_path(pathname);

    orig_open_func_type orig_func;
    orig_func = (orig_open_func_type)dlsym(RTLD_NEXT, "open64");

    // If O_CREAT is used to create a file, the file access mode must be given.
    if (flags & O_CREAT) {
        va_list args;
        va_start(args, flags);
        int mode = va_arg(args, int);
        va_end(args);
        return orig_func(new_path, flags, mode);
    } else {
        return orig_func(new_path, flags);
    }
}
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .