summaryrefslogtreecommitdiff
path: root/main.c
blob: 7cb565ba832455c9db511450e623abac0fd471bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/* See LICENSE file for copyright and license details. */
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include "mount.c"

#ifndef MNTRUN_VERSION
#define MNTRUN_VERSION "(unknown)"
#endif

#define USAGE "usage: mntrun [-dhv] [-b source dest] [[-o|-m] upperdir lowerdir workdir mountpoint] command\n"

static inline int checkflag(char *arg, char f) {
    return arg && (arg[0] == '-' && arg[1] == f && arg[2] == '\0');
}

int namespace_main(void *data) {
    uid_t uid = getuid();
    gid_t gid = getgid();

    int verbose = 0;
    char **argv = data;

    if(checkflag(argv[0], 'h')) {
        fputs(USAGE, stderr);
        return EXIT_SUCCESS;
    }

    if(checkflag(argv[0], 'v')) {
        puts("mntrun " MNTRUN_VERSION);
        return EXIT_SUCCESS;
    }

    if(checkflag(argv[0], 'd')) {
        verbose = 1;
        argv ++;
    }

    /* Jump back here over-and-over until all mount directives are parsed. */
    do_mounts:
    if(checkflag(argv[0], 'b')) {
        if(!argv[1] || !argv[2]) goto exit_usage;

        if(mount_bind(argv[1], argv[2]) == -1) {
            if(errno) fprintf(stderr, "Failed to bind '%s' to '%s': %s\n", argv[1], argv[2], strerror(errno));
            return EXIT_FAILURE;
        }
        argv += 3;
        goto do_mounts;
    }

    int merge = 0;
    if(checkflag(argv[0], 'o') || (merge = checkflag(argv[0], 'm'))) {
        char *type_str = merge ? "merge" : "overlay";

        if(!argv[1] || !argv[2] || !argv[3] || !argv[4]) goto exit_usage;
        if(verbose) printf("[%s]\n\tlower=%s\n\tupper=%s\n\twork=%s\n\tmount=%s\n", type_str, argv[1], argv[2], argv[3], argv[4]);

        char *lowers = argv[1];
        if(merge) {
            lowers = malloc(strlen(argv[4]) + 1 + strlen(argv[1]));
            sprintf(lowers, "%s:%s", argv[4], argv[1]);
        }

        if(mount_overlay(uid, gid, lowers, argv[2], argv[3], argv[4]) == -1) {
            if(errno) fprintf(stderr, "Failed to mount %s on '%s': %s\n", type_str, argv[4], strerror(errno));
            return EXIT_FAILURE;
        }
        argv += 5;
        if(merge) {
            free(lowers);
            merge = 0;
        }
        goto do_mounts;
    }
    /* Check if a command was specified */
    if(!argv[0]) goto exit_usage;

    setuid(uid);

    /* These next lines take the remaining values in `argv` and join them with spaces in `command`. */
    int charc;
    for(int i = 0; argv[i]; i ++) charc += strlen(argv[i]);
    char *command = malloc(charc + 1);
    command[0] = '\0';
    for(int i = 0; argv[i]; i ++) {
        strcat(command, " ");
        strcat(command, argv[i]);
    }
    
    int status = system(command);
    if(status == -1) {
        perror("system");
        return EXIT_FAILURE;
    }
    return status;

exit_usage:
    fputs(USAGE, stderr);
    return EXIT_FAILURE;
}

int main(int argc, char **argv) {
    #define STACK_SIZE 1024 * 1024
    char *stack = malloc(STACK_SIZE);
    pid_t pid = clone(namespace_main, stack + STACK_SIZE, CLONE_NEWNS | SIGCHLD, argv + 1);
    if(pid == -1) {
        perror("Failed to create a new mount namespace");
        return EXIT_FAILURE;
    }
    
    int wstatus;
    if (waitpid(pid, &wstatus, 0) == -1) {
        /* The only error which may occurs is EINTR, but I'm not 100% sure. */
        perror("waitpid");
        return EXIT_FAILURE;
    }
    return WEXITSTATUS(wstatus);
}