Subversion Repositories Mokou

Rev

Rev 13 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/* $Id: service.c 14 2024-09-07 13:07:50Z nishi $ */

#include "mk_service.h"

#include <fcntl.h>
#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

#include "mk_log.h"
#include "mk_util.h"

struct mk_service** services = NULL;

#ifdef __linux__
const char* sys_signame[] = {
        "",
        "HUP",
        "INT",
        "QUIT",
        "ILL",
        "TRAP",
        "ABRT",
        "BUS",
        "FPE",
        "KILL",
        "USR1",
        "SEGV",
        "USR2",
        "PIPE",
        "ALRM",
        "TERM",
        "STKFLT",
        "CHLD",
        "CONT",
        "STOP",
        "TSTP",
        "TTIN",
        "TTOU",
        "URG",
        "XCPU",
        "XFSZ",
        "VTALRM",
        "PROF",
        "WINCH",
        "POLL",
        "PWR",
        "SYS",
        "RTMIN"
};
#endif

void mk_service_scan(void){
        if(services != NULL){
                int i;
                for(i = 0; services[i] != NULL; i++){
                        if(services[i]->name != NULL) free(services[i]->name);
                        if(services[i]->stop != NULL) free(services[i]->stop);
                        if(services[i]->description != NULL) free(services[i]->description);
                        if(services[i]->exec != NULL) free(services[i]->exec);
                        if(services[i]->pidfile != NULL) free(services[i]->pidfile);
                        free(services[i]);
                }
                free(services);
                mk_log("Cleaning up the list");
        }
        services = malloc(sizeof(*services));
        services[0] = NULL;

        mk_log("Scanning the service directory.");

        DIR* dir = opendir(PREFIX "/etc/mokou");
        if(dir != NULL){
                struct dirent* d;
                while((d = readdir(dir)) != NULL){
                        if(mk_endswith(d->d_name, ".conf")){
                                char* path = mk_strcat(PREFIX "/etc/mokou/", d->d_name);
                                char* str = mk_strcat("Reading ", path);
                                mk_log(str);
                                free(str);

                                FILE* f = fopen(path, "r");
                                if(f != NULL){
                                        struct stat s;
                                        stat(path, &s);
                                        char* buffer = malloc(s.st_size + 1);
                                        buffer[s.st_size] = 0;
                                        fread(buffer, s.st_size, 1, f);
                                        int i;
                                        int incr = 0;

                                        char* desc = NULL;
                                        char* exec = NULL;
                                        char* stop = NULL;
                                        char* pidfile = NULL;
                                        uid_t uid = 0;
                                        gid_t gid = 0;
                                        bool bad = false;
                                        
                                        for(i = 0;; i++){
                                                if(buffer[i] == '\n' || buffer[i] == 0){
                                                        char oldc = buffer[i];
                                                        buffer[i] = 0;

                                                        char* line = buffer + incr;

                                                        if(strlen(line) > 0 && line[0] != '#'){
                                                                int j;

                                                                for(j = 0; line[j] != 0; j++){
                                                                        if(line[j] == '='){
                                                                                line[j] = 0;
                                                                                
                                                                                char* key = line;
                                                                                char* value = line + j + 1;
                                                                                if(strcmp(key, "description") == 0){
                                                                                        if(desc != NULL) free(desc);
                                                                                        desc = mk_strdup(value);
                                                                                }else if(strcmp(key, "exec") == 0){
                                                                                        if(exec != NULL) free(exec);
                                                                                        exec = mk_strdup(value);
                                                                                }else if(strcmp(key, "pidfile") == 0){
                                                                                        if(pidfile != NULL) free(pidfile);
                                                                                        pidfile = mk_strdup(value);
                                                                                }else if(strcmp(key, "stop") == 0){
                                                                                        if(stop != NULL) free(stop);
                                                                                        stop = mk_strdup(value);
                                                                                }else if(strcmp(key, "user") == 0){
                                                                                        struct passwd* p = getpwnam(value);
                                                                                        if(p != NULL){
                                                                                                uid = p->pw_uid;
                                                                                                gid = p->pw_gid;
                                                                                        }else{
                                                                                                mk_log("Could not find the specified user");
                                                                                                bad = true;
                                                                                        }
                                                                                }
        
                                                                                break;
                                                                        }
                                                                }
                                                        }

                                                        incr = i + 1;
                                                        if(oldc == 0) break;
                                                }
                                        }
                                        fclose(f);

                                        if(exec == NULL){
                                                char* log = mk_strcat(desc == NULL ? path : desc, ": Missing exec");
                                                mk_log(log);
                                                free(log);
                                                bad = true;
                                        }
                                        if(pidfile == NULL){
                                                char* log = mk_strcat(desc == NULL ? path : desc, ": Missing pidfile");
                                                mk_log(log);
                                                free(log);
                                                bad = true;
                                        }

                                        if(!bad){
                                                char* log = mk_strcat3("Adding ", desc == NULL ? path : desc, " to the list");
                                                mk_log(log);
                                                free(log);

                                                int i;
                                                struct mk_service* serv = malloc(sizeof(*serv));
                                                serv->name = mk_strdup(d->d_name);

                                                for(i = strlen(d->d_name) - 1; i >= 0; i--){
                                                        if(serv->name[i] == '.'){
                                                                serv->name[i] = 0;
                                                                break;
                                                        }
                                                }

                                                serv->description = desc != NULL ? mk_strdup(desc) : NULL;
                                                serv->stop = stop != NULL ? mk_strdup(stop) : NULL;
                                                serv->exec = mk_strdup(exec);
                                                serv->pidfile = mk_strdup(pidfile);
                                                serv->uid = uid;
                                                serv->gid = gid;
                                                serv->stopped = false;

                                                struct mk_service** oldsrvs = services;
                                                for(i = 0; oldsrvs[i] != NULL; i++);
                                                services = malloc(sizeof(*services) * (i + 2));
                                                for(i = 0; oldsrvs[i] != NULL; i++){
                                                        services[i] = oldsrvs[i];
                                                }
                                                services[i] = serv;
                                                services[i + 1] = NULL;
                                                free(oldsrvs);
                                        }

                                        if(desc != NULL) free(desc);
                                        if(exec != NULL) free(exec);
                                        if(pidfile != NULL) free(pidfile);
                                }

                                free(path);
                        }
                }
                closedir(dir);
        }else{
                mk_log("Cannot open the directory.");
        }
}

const char* mk_errors[] = {
        "Success",
        "No such service",
        "Service is alive",
        "Failed to start",
        "Service is dead",
        "Bad signal",
        "Could not stop the service",
        "Could not run the stop command"
};

int mk_stop_service(const char* name){
        int i;
        for(i = 0; services[i] != NULL; i++){
                if(strcmp(services[i]->name, name) == 0){
                        struct mk_service* srv = services[i];
                        char* log = mk_strcat("Stopping ", name);
                        mk_log(log);
                        free(log);

                        srv->stopped = true; /* No more resurrecting */

                        bool alive = false;

                        FILE* f = fopen(srv->pidfile, "r");
                        unsigned long long pid;
                        if(f != NULL){
                                fscanf(f, "%llu", &pid);
                                fclose(f);
                                alive = kill(pid, 0) == 0;
                        }

                        if(!alive){
                                mk_log("Process seems to be dead, not stopping");
                                return 4;
                        }

                        if(srv->stop == NULL || srv->stop[0] == '#'){
                                int sig = -1;
                                if(srv->stop == NULL){
                                        sig = SIGINT;
                                }
                                if(sig == -1){
                                        int i;
                                        for(i = 1; i < NSIG; i++){
                                                if(strcmp(sys_signame[i], srv->stop + 1) == 0){
                                                        sig = i;
                                                        break;
                                                }
                                        }
                                }
                                if(sig == -1){
                                        mk_log("Bad signal");
                                        return 5;
                                }else{
                                        log = mk_strcat("Sending SIG", sys_signame[sig]);
                                        mk_log(log);
                                        free(log);
                                        kill(pid, sig);
                                }
                        }else{
                                char** pargv = malloc(sizeof(*pargv));
                                pargv[0] = NULL;
        
                                int i;
                                int incr = 0;
                                for(i = 0;; i++){
                                        if(srv->stop[i] == 0 || srv->stop[i] == ' '){
                                                char* str = malloc(i - incr + 1);
                                                memcpy(str, srv->stop + incr, i - incr);
                                                str[i - incr] = 0;
        
                                                char** oldargv = pargv;
                                                int j;
                                                for(j = 0; oldargv[j] != NULL; j++);
                                                pargv = malloc(sizeof(*pargv) * (j + 2));
                                                for(j = 0; oldargv[j] != NULL; j++) pargv[j] = oldargv[j];
                                                pargv[j] = str;
                                                pargv[j + 1]  = NULL;
                                                free(oldargv);
        
                                                incr = i + 1;
                                                if(srv->exec[i] == 0) break;
                                        }
                                }

                                bool fail = false;
                                pid_t pid = fork();
                                if(pid == 0){
                                        int n = open("/dev/null", O_RDWR);
                                        dup2(n, 1);
                                        dup2(n, 2);
                                        setgid(srv->gid);
                                        setegid(srv->gid);
                                        setuid(srv->uid);
                                        seteuid(srv->uid);
                                        execvp(pargv[0], pargv);
                                        _exit(-1);
                                }else{
                                        int status;
                                        waitpid(pid, &status, 0);
                                        if(WEXITSTATUS(status) != 0) fail = true;
                                }

                                for(i = 0; pargv[i] != NULL; i++) free(pargv[i]);
                                free(pargv);

                                if(fail){
                                        mk_log("Failed to run stop command");
                                        return 7;
                                }
                        }
                        
                        usleep(100);

                        bool dead = false;
                        for(i = 0; i < 3; i++){
                                if(kill(pid, 0) == -1){
                                        mk_log("Process died");
                                        dead = true;
                                        break;
                                }else{
                                        mk_log("Process is still alive");
                                }
                                if(i != 2) sleep(1);
                        }
                        if(!dead){
                                mk_log("Could not kill the process");
                                return 6;
                        }

                        return 0;
                }
        }
        return 1;
}

int mk_start_service(const char* name){
        int i;
        for(i = 0; services[i] != NULL; i++){
                if(strcmp(services[i]->name, name) == 0){
                        struct mk_service* srv = services[i];
                        char* log = mk_strcat("Starting ", name);
                        mk_log(log);
                        free(log);

                        bool alive = false;

                        FILE* f = fopen(srv->pidfile, "r");
                        if(f != NULL){
                                unsigned long long pid;
                                fscanf(f, "%llu", &pid);
                                fclose(f);
                                alive = kill(pid, 0) == 0;
                        }
                        if(alive){
                                mk_log("Process seems to be alive, not starting");
                                return 2;
                        }

                        char** pargv = malloc(sizeof(*pargv));
                        pargv[0] = NULL;

                        int i;
                        int incr = 0;
                        for(i = 0;; i++){
                                if(srv->exec[i] == 0 || srv->exec[i] == ' '){
                                        char* str = malloc(i - incr + 1);
                                        memcpy(str, srv->exec + incr, i - incr);
                                        str[i - incr] = 0;

                                        char** oldargv = pargv;
                                        int j;
                                        for(j = 0; oldargv[j] != NULL; j++);
                                        pargv = malloc(sizeof(*pargv) * (j + 2));
                                        for(j = 0; oldargv[j] != NULL; j++) pargv[j] = oldargv[j];
                                        pargv[j] = str;
                                        pargv[j + 1]  = NULL;
                                        free(oldargv);

                                        incr = i + 1;
                                        if(srv->exec[i] == 0) break;
                                }
                        }

                        bool fail = false;

                        pid_t pid = fork();
                        if(pid == 0){
                                int n = open("/dev/null", O_RDWR);
                                dup2(n, 1);
                                dup2(n, 2);
                                setgid(srv->gid);
                                setegid(srv->gid);
                                setuid(srv->uid);
                                seteuid(srv->uid);
                                execvp(pargv[0], pargv);
                                _exit(-1);
                        }else{
                                int status;
                                waitpid(pid, &status, 0);
                                if(WEXITSTATUS(status) != 0) fail = true;
                        }
                        for(i = 0; pargv[i] != NULL; i++) free(pargv[i]);
                        free(pargv);
                        if(fail){
                                log = mk_strcat("Failed to start ", name);
                                mk_log(log);
                                free(log);
                                srv->stopped = false;
                                return 3;
                        }else{
                                log = mk_strcat("Started ", name);
                                mk_log(log);
                                free(log);
                                srv->stopped = false;
                        }

                        return 0;
                }
        }
        return 1;
}

void mk_start_services(void){
        int i;
        for(i = 0; services[i] != NULL; i++){
                mk_start_service(services[i]->name);
        }
}

void mk_resurrect_services(void){
        int i;
        bool re = false;
        for(i = 0; services[i] != NULL; i++){
                if(!services[i]->stopped){
                        bool alive = false;

                        FILE* f = fopen(services[i]->pidfile, "r");
                        if(f != NULL){
                                unsigned long long pid;
                                fscanf(f, "%llu", &pid);
                                fclose(f);
                                alive = kill(pid, 0) == 0;
                        }
                        if(!alive){
                                if(!re){
                                        mk_log("Resurrection");
                                        re = true;
                                }
                                mk_start_service(services[i]->name);
                        }
                }
        }
}