Sélectionner une révision Git
-
Warren PONS a rédigéWarren PONS a rédigé
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;
}