1

I am trying to run the same program in two linux namespace.

The program needs to read and write the file /tmp/server.log.

So I want to make sure that program A read/write server.log, but actually it reads and writes /tmp/server-A.log. And for program B read/write server.log, it is actually reading and writing /tmp/server-B.log.

I try to use mount but not succeeded ... Can anyone help me? Or is there another way for me to provide file isolation so that the two programs will not actually read/write the same file?

#define _GNU_SOURCE
#include<sched.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>

static int child_func(void* arg) {
  system("mount --bind /tmp ./a");
  FILE* file;
  file = fopen("/tmp/server.log","rw");
  // write some log ...
  return 0;
}

static int child2_func(void* arg) {
  system("mount --bind /tmp ./b");
  file = fopen("/tmp/server.log","rw");
  // write some log.... 
  return 0;
}


int main(int argc, char** argv) {
  // Allocate stack for child task.
  const int STACK_SIZE = 1 * 1024 * 1024;
  char* stack = malloc(STACK_SIZE);
  char* stack2 = malloc(STACK_SIZE);
  if (!stack || !stack2) {
    perror("malloc");
    exit(1);
  }
  pid_t pid,pid2;


  if ((pid = clone(child_func, stack + STACK_SIZE, CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, NULL)) == -1) {
    perror("clone");
    exit(1);
  }

  if ((pid2 = clone(child2_func, stack2 + STACK_SIZE, CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, NULL)) == -1) {
    perror("clone");
    exit(1);
  }


  waitpid(pid,NULL,0);
  waitpid(pid2,NULL,0);


  return 0;
}

Update: I solve the problem based on the solutions answered below! Their solutions really help me!

mrmitzh
  • 35
  • 4
  • 1
    So many problems here, but I'll start with the big one: why do you want to use Linux namespaces to solve such a simple problem, instead of just passing the filename to write as a parameter? – Joseph Sible-Reinstate Monica Apr 18 '20 at 01:44
  • I'm a bit confused. If you want a child process to write to `/tmp/server-A.log` and another to write to `/tmp/server-B.log`, why not simply set a variable, say `char suffix = 'A';` fork the first process, then set `suffix = 'B';` and fork the second, and then within each process create the filename using `suffix`? There a number of ways to do this. If you want more, then `#define SUFFIX "ABCDEFGHIJKLMNOPQRSTUVWXYZ"` and then keep a counter and loop forking `SUFFIX[n++]`, or something similar. – David C. Rankin Apr 18 '20 at 01:44
  • I believe there is a mount system call as well, no need to invoke a shell with system(): http://man7.org/linux/man-pages/man2/mount.2.html – Jeff Linahan Apr 18 '20 at 01:46
  • child_func is actually calling python script, and I am not able to modify the python script to specify the path to write, and I just use a simple function to show the case here @JosephSible-ReinstateMonica – mrmitzh Apr 18 '20 at 01:55
  • @David C. Rankin I am not able to do that, I just write this silly function to show what I need to do – mrmitzh Apr 18 '20 at 01:57
  • @JeffLinahan I will try using mount function, thank you – mrmitzh Apr 18 '20 at 01:57

2 Answers2

1

You want something like this:

#define _GNU_SOURCE

#include <sched.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <unistd.h>

int doChild(const char *source) {
    if(unshare(CLONE_NEWNS)) {
        perror("unshare");
        return 1;
    }
    if(mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL)) {
        perror("mount");
        return 1;
    }
    if(mount(source, "/tmp/server.log", NULL, MS_BIND, NULL)) {
        perror("mount");
        return 1;
    }
    execlp("myunmodifiablepythonscript", "myunmodifiablepythonscript", (char*)NULL);
    perror("execlp");
    return 1;
}

int main(void) {
    pid_t pidA, pidB;
    pidA = fork();
    if(pidA < 0) {
        perror("fork");
        return 1;
    } else if(pidA == 0) {
        return doChild("/tmp/server-A.log");
    }
    pidB = fork();
    if(pidB < 0) {
        perror("fork");
        /* n.b.: pidA will still be running as an orphan. */
        return 1;
    } else if(pidB == 0) {
        return doChild("/tmp/server-B.log");
    }
    waitpid(pidA, NULL, 0);
    /* n.b.: if pidB finishes first, it will be a zombie until pidA finishes. */
    waitpid(pidB, NULL, 0);
    return 0;
}

A few notes:

  • Using clone correctly (which you weren't) is a pain. It's much easier to just use fork and then unshare after.
  • systemd stupidly makes mounts shared by default, which basically makes mount namespaces do nothing (i.e., changes will propagate back to other namespaces, thus defeating the purpose of a private namespace). mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) undoes that to make them actually work.
  • I'm not sure what you were trying to bind mount, but it was the wrong thing. The right thing is mounting the individual log to the shared name.
  • Thank you so much!!! That's what I need, I use your idea and successfully implement what I want. After I add unshare function call and use mount correctly, I get what I want! – mrmitzh Apr 18 '20 at 05:28
  • I am new to StackOverflow, Thank you for reminding me! – mrmitzh Apr 18 '20 at 06:26
1
  1. You need to remount root to private. Check this link.

  2. When you do --bind, you need do it in reverse way.

static int child_func(void* arg) {
  mount("/", "/", NULL, MS_PRIVATE, NULL);
  mount("./a", "/tmp", NULL, MS_BIND, NULL);

  FILE* file;
  file = fopen("/tmp/server.log","w");

  return 0;
}
static int child_func(void* arg) {
  mount("/", "/", NULL, MS_PRIVATE, NULL);
  mount("./b", "/tmp", NULL, MS_BIND, NULL);

  FILE* file;
  file = fopen("/tmp/server.log","w");

  return 0;
}
randomeval
  • 599
  • 4
  • 13
  • Yes, you are right, I should remount root to private. But I think we should also call unshare(CLONE_NEWNS) before mounting. I first try your idea but it is still not working, after I try @Joseph Sible-Reinstate Monica 's idea combined with you, I get what I want. Thank you!!! – mrmitzh Apr 18 '20 at 05:30
  • If you are using `clone` with `CLONE_NEWNS`, it should be fine. I test it in Centos7.5. – randomeval Apr 18 '20 at 06:17
  • Maybe because I am using Ubuntu, anyway thank you again! – mrmitzh Apr 18 '20 at 06:27
  • 1
    I test it again and find that using clone with CLONE_NEWS don't need to call unshare again in the function. Maybe the reason behind is that we need to add MS_REC when mount "/" with MS_PRIVATE – mrmitzh Apr 18 '20 at 14:33