Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • 536fab591d04c38e7e1868f38a3ab49d3f3decd4
  • master par défaut protégée
2 résultats

index.css

Blame
  • server.c 14,44 Kio
    #define _POSIX_C_SOURCE 200808L
    
    #include <lektor/lktconfig.h>
    #include <lektor/common.h>
    #include <lektor/config.h>
    #include <lektor/net.h>
    #include <lektor/cmd.h>
    #include <lektor/reg.h>
    #include <lektor/database.h>
    #include <lektor/commands.h>
    #include <lektor/segv.h>
    
    #include <fcntl.h>
    #include <sys/types.h>
    #include <wait.h>
    #include <spawn.h>
    #include <libgen.h>
    #include <limits.h>
    #include <locale.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <strings.h>
    #include <pthread.h>
    #include <sys/stat.h>
    #include <termios.h>
    
    #if defined (LKT_STATIC_MODULE)
        REG_DECLARE(sdl2_reg)
        REG_DECLARE(repo_reg)
    #endif
    
    /* The environment */
    extern char **environ;
    
    /* Disable echo in the console, only for the program */
    struct termios ___tty_cur, ___tty_save;
    static int ___tty_must_be_restored = 0;
    
    __attribute__((constructor))
    static void ___tty_disable_echo(void)
    {
        GOTO_IF((tcgetattr(STDIN_FILENO, &___tty_cur) == -1),
                "Failed to get termios flags", error);
    
        ___tty_save = ___tty_cur;
        ___tty_cur.c_lflag &= ~ ECHO;
    
        GOTO_IF((tcsetattr(STDIN_FILENO, TCSAFLUSH, &___tty_cur) == -1),
                "Failed to turned of echo in termios flags", error);
        ___tty_must_be_restored = 1;
    
        return;
    error:
        /* Fails silently for the moment */
        return;
    }
    
    __attribute__((destructor))
    static void ___tty_enable_echo(void)
    {
        if (!___tty_must_be_restored) {
            LOG_DEBUG("INIT", "No need to restore the tty");
            return;
        }
        if (tcsetattr(STDIN_FILENO, TCSANOW, &___tty_save) == -1)
            LOG_ERROR("INIT", "Failed to reset termios flags");
        errno = 0;
        if (fflush(stdin) != 0)
            LOG_ERROR("INIT", "Failed to flush stdin: %s", strerror(errno));
    }
    
    /* Recursive mkdir, where the last word of the string is a file, not a folder. */
    static inline void
    __mkdir(const char *dir)
    {
        char tmp[PATH_MAX];
        char *p = NULL;
        safe_snprintf(tmp, sizeof(tmp), "%s", dir);
        size_t len = strlen(tmp);
        /* In our case, the final word is always a file, not a folder. */
        if (tmp[len - 1] == '/')
            tmp[len - 1] = 0;
        for (p = tmp + 1; *p; p++) {
            if (*p == '/') {
                *p = 0;
                mkdir(tmp, 00700);
                *p = '/';
            }
        }
        /* Don't do final mkdir here, because in our case the final word in the string
         * is a file, not a folder.
         * mkdir(tmp, S_IRWXU); */
    }
    
    /* Exec an other program wthout any argument */
    static pid_t ___klkt_pid = 0;
    
    __attribute__((destructor)) static inline void
    ___kill_klkt(void)
    {
        RETURN_UNLESS(___klkt_pid, "No klkt child process to wait for", NOTHING)
    
        if (kill(___klkt_pid, SIGTERM) != 0) {
            kill(___klkt_pid, SIGKILL);
            LOG_ERROR("INIT", "Failed to SIGTERM klkt with pid %ld, SIGKILL it", ___klkt_pid);
            return;
        }
    
        int status;
        do {
            if (waitpid(___klkt_pid, &status, WUNTRACED | WCONTINUED) == -1)
                LOG_ERROR("INIT", "Failed to wait for child klkt with pid %ld", ___klkt_pid);
    
            LOG_INFO("INIT", "Child klkt status is %s%d",
                     /* String desc */
                     WIFEXITED(status)   ? "exited with status " :
                     WIFSIGNALED(status) ? "killed by signal "   :
                     WIFSTOPPED(status)  ? "stopped by signal "  : "continued ",
                     /* Int that follows it */
                     WIFEXITED(status)   ? WEXITSTATUS(status) :
                     WIFSIGNALED(status) ? WTERMSIG(status)    :
                     WIFSTOPPED(status)  ? WSTOPSIG(status)    : 0);
    
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }
    
    static int
    launch_klkt(va_list UNUSED *___args)
    {
        /* Check env variable! */
        char *env_klkt_pid = getenv(LKT_ENV_KLKT_PID);
        if (NULL != env_klkt_pid) {
            LOG_DEBUG("INIT", "Found " LKT_ENV_KLKT_PID " := %s", env_klkt_pid);
            ___klkt_pid = strtol(env_klkt_pid, NULL, 0);
            return 0;
        }
        LOG_DEBUG("INIT", "No " LKT_ENV_KLKT_PID " env variable found, launch klkt ourself");
    
        /* Env var was not found, no existing lklt! Launch it ourself. */
        char exe_path[LKT_LINE_MAX];
        char try_name[LKT_LINE_MAX];
        const char *const appimage = getenv("APPIMAGE");
        char *args[]               = { "klkt", NULL };
        static const char *basename_appimage_arch    = "klkt.AppImage";
        static const char *basename_appimage_no_arch = "klkt-" LKT_ARCH ".AppImage";
    
        posix_spawn_file_actions_t action;
        posix_spawn_file_actions_init(&action);
        posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_RDONLY, 0);
        posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_RDONLY, 0);
    
        bool appimage_once_goto = false;
    
        /* No AppImage */
        if (appimage == NULL) {
        retry_without_appimage:
            appimage_once_goto = true;
            if (read_self_exe(exe_path, LKT_LINE_MAX)) {
                LOG_ERROR("INIT", "Failed to get the current executable path, not patching the PATH");
                goto error;
            }
        }
    
        /* AppImage */
        else {
            safe_strncpy(exe_path, appimage, LKT_LINE_MAX);
            LOG_DEBUG("INIT", "Running an AppImage, file is %s", appimage);
        }
    
        char *current_dir = dirname(exe_path);
    
        /* Normal try */
        errno = 0;
        safe_snprintf(try_name, LKT_LINE_MAX, "%s/klkt", current_dir);
        LOG_DEBUG("INIT", "Try to launch klkt with file %s", try_name);
        GOTO_UNLESS(posix_spawn(&___klkt_pid, try_name, &action, NULL, args, environ),
                    "Klkt launched!", end);
    
        /* AppImage try */
        safe_snprintf(try_name, LKT_LINE_MAX, "%s/klkt-" LKT_ARCH ".AppImage", current_dir);
        errno   = 0;
        args[0] = (char *) basename_appimage_arch;
        LOG_DEBUG("INIT", "Try to launch klkt with file %s", try_name);
        GOTO_UNLESS(posix_spawn(&___klkt_pid, try_name, &action, NULL, args, environ),
                    "Klkt launched!", end);
    
        safe_snprintf(try_name, LKT_LINE_MAX, "%s/klkt.AppImage", current_dir);
        errno   = 0;
        args[0] = (char *) basename_appimage_no_arch;
        LOG_DEBUG("INIT", "Try to launch klkt with file %s", try_name);
        GOTO_UNLESS(posix_spawn(&___klkt_pid, try_name, &action, NULL, args, environ),
                    "Klkt launched!", end);
    
        /* Oupsi */
    error:
        LOG_ERROR("INIT", "Failed to spawn klkt (%s): %s", try_name, strerror(errno));
        ___klkt_pid = 0; /* posix_spawnp may have modified it even in case of failure */
    
        /* If we are an AppImage, klkt might be inside the AppImage,
         * try to launch that executable */
        GOTO_UNLESS(appimage_once_goto, "Retry to launch klkt, without self executable path patching",
                    retry_without_appimage);
    
        return 1;
    
    end:
        LOG_INFO("INIT", "Klkt launched with pid %ld", ___klkt_pid, try_name);
        posix_spawn_file_actions_destroy(&action);
    
        /* Export the env var for klkt pid, reuse exe_path */
        safe_snprintf(exe_path, LKT_LINE_MAX, "%ld", ___klkt_pid);
        RETURN_IF(setenv(LKT_ENV_KLKT_PID, exe_path, 1),
                  "Failed to set env variable " LKT_ENV_KLKT_PID, 1);
        return 0;
    }
    
    static int ___is_appimage = 0;
    
    static inline void
    ___resolve_appimage(void)
    {
        /* Detect AppImage. Those env var names are defined for V2 AppImages, see
         * https://docs.appimage.org/packaging-guide/environment-variables.html */
        const char *env_APPIMAGE = getenv("APPIMAGE");
        const char *env_APPDIR   = getenv("APPDIR");
        const char *env_OWD      = getenv("OWD");
        const char *env_ARGV0    = getenv("ARGV0");
        if (env_APPDIR || env_ARGV0 || env_APPIMAGE || env_OWD) {
            LOG_DEBUG("INIT", "AppImage build detected, here are some usefull env vars");
            LOG_DEBUG("INIT", "AppImage env APPIMAGE = %s", env_APPIMAGE);
            LOG_DEBUG("INIT", "AppImage env APPDIR   = %s", env_APPDIR);
            LOG_DEBUG("INIT", "AppImage env OWD      = %s", env_OWD);
            LOG_DEBUG("INIT", "AppImage env ARGV0    = %s", env_ARGV0);
            ___is_appimage = 1;
        } else
            LOG_DEBUG("INIT", "No AppImage env variable found!");
    }
    
    static inline void
    ___resolve_path(void)
    {
        const char *env_PATH = getenv("PATH");
        RETURN_UNLESS(env_PATH, "Failed to get PATH in env", NOTHING);
        char new_path[PATH_MAX];
        char exe_path[LKT_LINE_MAX];
        GOTO_IF(read_self_exe(exe_path, LKT_LINE_MAX), "Failed to get exe path", error);
        char *self_dir = dirname(exe_path);
        LOG_DEBUG("INIT", "Try to patch PATH with %s", self_dir);
        safe_snprintf(new_path, PATH_MAX, "%s:%s", self_dir, env_PATH);
        GOTO_IF(setenv("PATH", new_path, 1), "Failed to set new PATH", error);
        env_PATH = getenv("PATH");
        LOG_DEBUG("INIT", "PATH is: %s", env_PATH);
        return;
    error:
        LOG_DEBUG("INIT", "Failed to patch PATH, it will remain as: %s", env_PATH);
    }
    
    int
    main(int argc, char *argv[])
    {
        install_segv_handler();
    
        REG_BEGIN(server_reg)
        REG_ADD(launch_klkt)
    #if defined (LKT_STATIC_MODULE)
        REG_REGISTER("repo", repo_reg)
        REG_REGISTER("sdl2", sdl2_reg)
    #endif
        REG_END()
    
        /* Enforce some locals */
        RETURN_UNLESS(setlocale(LC_ALL,     "en_US.UTF-8"), "Failed to set LC_ALL to UTF-8", 1);
        RETURN_UNLESS(setlocale(LC_CTYPE,   ""),            "Failed to set LC_CTYPE", 1);
        RETURN_UNLESS(setlocale(LC_NUMERIC, "C"),           "Failed to set LC_NUMERIC for mpv", 1);
    
        /* Place that in static part of code, to not smash the stack */
        static char exe[PATH_MAX];
        static char db_path[PATH_MAX];
        static char kara_dir[PATH_MAX];
    
        int autoclear, check_exclusive = 1, opt, dump_and_abort = 0;
        char *conf_file = safe_zero_malloc(PATH_MAX * sizeof(char));
        executable_name = "lektord";
    
        /* Check args */
        while ((opt = getopt(argc, argv, "DhFf:")) != -1) {
            switch (opt) {
            case 'F':
                check_exclusive = 0;
                break;
            case 'f':
                strncpy(conf_file, optarg, PATH_MAX);
                break;
            case 'D':
                dump_and_abort = 1;
                break;
            case 'h':
            default:
                print_help();
                exit(EXIT_SUCCESS);
            }
        }
    
        reg_export(server_reg);
        if (read_self_exe(exe, PATH_MAX))
            LOG_WARN("INIT", "Failed to read self executable path, restart may not work");
        executable_name = exe;
        LOG_WARN("INIT", "The argv[0] from main is: %s", exe);
    
        /* Resolves */
        ___resolve_appimage();
        ___resolve_path();
    
        /* Init the server */
        struct lkt_state srv;
        memset(&srv, 0, sizeof(struct lkt_state));
        srv.kara_prefix = kara_dir;
    
        if (lkt_queue_new(&srv.queue)) {
            LOG_ERROR("INIT", "Faield to create server queue");
            exit(EXIT_FAILURE);
        }
    
        /* Initialize the system. */
        if (!database_new(&srv.db)) {
            LOG_ERROR("INIT", "Failed to init memory db");
            exit(EXIT_FAILURE);
        }
    
        /* Read or create default config file. */
        int retry_config_once = 0;
    retry_config:
        if (conf_file[0] == '\0' && config_detect_file(conf_file, PATH_MAX)) {
            if (retry_config_once) {
                LOG_ERROR("INIT", "Failed to find a config file");
                exit(EXIT_FAILURE);
            } else {
                LOG_INFO("INIT", "Creating default config file");
                config_default_file(conf_file, PATH_MAX);
                __mkdir(conf_file); /* Create the folder for the file. */
                FILE *file_desc = fopen(conf_file, "w+");
                if (NULL == file_desc)
                    LOG_FATAL("Failed to open default config file '%s' to initialize it", conf_file);
                config_default(file_desc);
                fclose(file_desc);
                LOG_INFO("INIT", "Default configuration file has been writen to %s", conf_file);
                retry_config_once = 1;
                goto retry_config;
            }
        }
    
        if (config_new(srv.db, conf_file)) {
            LOG_ERROR("INIT", "Failed to read the config");
            exit(EXIT_FAILURE);
        }
    
        /* Dump and abort here, if we are dumping informations about the server */
        if (dump_and_abort) {
            fprintf(stderr, "Config file is: %s\n", conf_file);
            if (database_config_dump(srv.db, stderr))
                exit(EXIT_FAILURE);
            exit(EXIT_SUCCESS);
        }
    
        /* No longer need the config file */
        free(conf_file);
    
        /* Read the configuration. We already know that the config is valid */
        database_config_get(srv.db, "player",   "autoclear", &autoclear);
        database_config_get(srv.db, "database", "kara_dir",  kara_dir, PATH_MAX);
        database_config_get(srv.db, "server",   "host",      srv.host, HOST_NAME_MAX);
        database_config_get(srv.db, "server",   "port",      srv.port, 5);
    
        /* Quick check with an explicit error message, to remide the users to create the kara folder */
        if (access(kara_dir, R_OK | W_OK)) {
            LOG_ERROR("INIT", "No access in read / write for folder %s", kara_dir);
            exit(EXIT_FAILURE);
        }
    
        /* Finish to initialize. */
        database_config_get(srv.db, "database", "db_path", db_path, PATH_MAX);
        RETURN_UNLESS(database_open(srv.db, db_path, check_exclusive), "Can't open database", 1);
    
        if (kara_dir[strlen(kara_dir) - 1] != '/')
            strncat(kara_dir, "/", PATH_MAX - 1);
        srv.kara_prefix = kara_dir;
    
        database_config_queue_default(srv.db);
        if (!env_get(LKT_ENV_RESTART) && autoclear) {
            LOG_INFO("INIT", "Clear queue, autoclear is set in ini file");
            database_queue_clear(srv.db);
        }
        lkt_queue_make_available(&srv.queue, lkt_event_prop);
        lkt_queue_make_available(&srv.queue, lkt_event_update);
    
        {   /* Module initialization */
            char *module = safe_malloc(sizeof(char) * PATH_MAX);
            if (!module) {
                LOG_ERROR("INIT", "Out of memory");
                return 1;
            }
            database_config_get(srv.db, "player", "module", module, PATH_MAX);
            reg_import(module, &srv.window_mod.reg, &srv.window_mod.handle);
            database_config_get(srv.db, "repo", "module", module, PATH_MAX);
            reg_import(module, &srv.repo_mod.reg, &srv.repo_mod.handle);
            free(module);
    
            if (MOD_CALL(srv.repo_mod, "new", &srv.queue, srv.db))
                return 100;
            if (MOD_CALL(srv.window_mod, "new", &srv.queue, srv.db))
                return 101;
        }
    
        /* Get ENV */
        {
            char *env_current = env_get(LKT_ENV_CURRENT);
            if (env_current && env_current[0] && !STR_MATCH(env_current, "NULL")) {
                LOG_INFO("INIT", "Restart playback from %s", env_current);
                lkt_queue_send(&srv.queue, lkt_event_play_pos,
                               (void *) (size_t) strtol(env_current, NULL, 0));
            }
        }
    
        LOG_INFO("INIT", "Lektor was %s",
                 env_get(LKT_ENV_RESTART) ? "restarted" : "started");
    
        config_handle_hook(srv.db, "launched");
        lkt_listen(&srv);
        return EXIT_FAILURE;
    }