Making a process read a different file for the same filename

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.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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.

Method 2

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).

Method 3

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  ==>  %sn", 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);
    }
}


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments