aboutsummaryrefslogtreecommitdiffstats
path: root/init/subsys.c
blob: 704678fbd6ce549c6c194195767031c9ade9f623 (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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <unistd.h>

#include <config.h>
#include <log.h>
#include <util.h>

#define MAX_SUBSYSTEMS 32
#define MAX_RESPAWN 3

struct subsystem_info {
    char *fn_name;
    int (*fn)(void);
    int pid;
    void *stack;
    char mode;
    int respawn_count;
};

extern long stack_size;
extern int mainpid;
static struct subsystem_info *subsystems[MAX_SUBSYSTEMS + 1];
int subsystem_count = 0;

static int __subsystem_entry(struct subsystem_info *info)
{
    /* entry point from clone(). we setup the process name so we know
       what we are looking at from a glance in a ps view or htop or
       whatever. */
    char *name = malloc(16 * sizeof(char));
    snprintf(name, 16, NAME_SHORTHAND ": %s", info->fn_name);
    name[15] = '\0';
    prctl(PR_SET_NAME, name);
    free(name);

    /* clear signal handlers so SIGTERM is no longer caught */
    static sigset_t set;
    sigprocmask(SIG_SETMASK, &set, NULL);

    int ret = info->fn();

    return ret;
}

char *subsystem_get_name(int pid)
{
    for(int i = 0; i < MAX_SUBSYSTEMS; ++i) {
        struct subsystem_info *subsystem = subsystems[i];
        if(!subsystem || subsystem->pid != pid)
            continue;

        return subsystem->fn_name;
    }
    return 0;
}

int subsystem_change_mode(int pid, char mode)
{
    for(int i = 0; i < MAX_SUBSYSTEMS; ++i) {
        struct subsystem_info *subsystem = subsystems[i];
        if(!subsystem || subsystem->pid != pid)
            continue;

        subsystem->mode = mode;
        return 0;
    }

    return 1;
}

int subsystem_handle_term(int pid)
{
    for(int i = 0; i < MAX_SUBSYSTEMS; ++i) {
        struct subsystem_info *subsystem = subsystems[i];
        if(!subsystem || subsystem->pid != pid)
            continue;

        if(subsystem->mode == PANICMODE_RESPAWN
                && subsystem->respawn_count < MAX_RESPAWN) {
            ++(subsystem->respawn_count);

            int pid = clone((int (*)(void *))__subsystem_entry,
                    (void *)((long)(subsystem->stack) + stack_size),
                    CLONE_FILES | CLONE_VM | SIGCHLD, subsystem);
            subsystem->pid = pid;
            if(pid < 0) {
                print(LOG_CRIT "subsys: cannot re-start subsystem %s: "
                        "clone failed (errno %d)", subsystem->fn_name, errno);
                if(munmap(subsystem->stack, stack_size) < 0)
                    print(LOG_CRIT "subsys: failed to deallocate "
                            "stack for subsystem %s (%d) (errno %d)",
                            subsystem->fn_name, pid, errno);
                free(subsystem);
                return 0;
            }

            subsystem->mode = 'o';
            return 0;
        } else if(subsystem->mode == PANICMODE_RESPAWN) {
            panic("subsys: exceeded maximum respawn count for subsystem "
                    "%s (%d)", subsystem->fn_name, subsystem->pid);
        }

        if(munmap(subsystem->stack, stack_size) < 0)
            print(LOG_CRIT "subsys: failed to deallocate stack "
                    "for subsystem %s (%d) (errno %d)",
                    subsystem->fn_name, pid, errno);
        subsystems[i] = 0;
        --subsystem_count;
        free(subsystem);

        return 0;
    }

    return 1;
}

int __impl_start_subsystem(char *fn_name, int (*fn)(void))
{
    if(getpid() != mainpid) {
        print(LOG_CRIT "subsys: cannot perform subsystem inception "
                "(attempted from %d)", getpid());
        return 1;
    }
    if(subsystem_count >= MAX_SUBSYSTEMS) {
        print(LOG_CRIT "subsys: cannot start subsystem %s: "
                "reached maximum number of subsystems", fn_name);
        return 1;
    }

    /* because CLONE_VM is being set, our stack is not duplicated and
       therefore we need to map a stack */
    void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE,
            MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_STACK | MAP_PRIVATE, -1, 0);
    if((long)stack <= 0) {
        print(LOG_CRIT "subsys: cannot start subsystem %s: "
                "failed to allocate stack (errno %d)", fn_name, errno);
        return 1;
    }

    /* the libc gods have graced us with the ability to pass one (1) arg
       to the function. struct required. the absence of a free is not a
       memory leak because we free it above. */
    struct subsystem_info *info = malloc(sizeof(struct subsystem_info));
    info->fn_name = fn_name;
    info->fn = fn;
    info->stack = stack;
    info->mode = 'o';
    info->respawn_count = 0;

    int pid = clone((int (*)(void *))__subsystem_entry,
            (void *)((long)stack + stack_size),
            CLONE_FILES | CLONE_VM | SIGCHLD, info);
    info->pid = pid;
    if(pid < 0) {
        print(LOG_CRIT "subsys: cannot start subsystem %s: "
                "clone failed (errno %d)", fn_name, errno);
        munmap(stack, stack_size);
        free(info);
        return 1;
    }

    for(int i = 0; i < MAX_SUBSYSTEMS; ++i) {
        if(!subsystems[i]) {
            subsystems[i] = info;
            ++subsystem_count;
            break;
        }
    }

    return 0;
}