1
0
mirror of https://github.com/haiwen/seafile-server.git synced 2025-04-27 19:15:07 +00:00
seafile-server/controller/seafile-controller.c
feiniks d50359c908
Add and del ccnet compilation dependency in seafile (#347)
* Add and del ccnet compilation dependency in seafile

* Del import ccnet

* Del extra ccnet compilation dependencies

* Del support WIN32
2020-05-09 16:31:47 +08:00

939 lines
23 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <glib.h>
#include "utils.h"
#include "log.h"
#include "seafile-controller.h"
#define CHECK_PROCESS_INTERVAL 10 /* every 10 seconds */
#if defined(__sun)
#define PROC_SELF_PATH "/proc/self/path/a.out"
#else
#define PROC_SELF_PATH "/proc/self/exe"
#endif
SeafileController *ctl;
static char *controller_pidfile = NULL;
char *bin_dir = NULL;
char *installpath = NULL;
char *topdir = NULL;
char *seafile_ld_library_path = NULL;
static const char *short_opts = "hvftc:d:l:g:G:P:F:";
static const struct option long_opts[] = {
{ "help", no_argument, NULL, 'h', },
{ "version", no_argument, NULL, 'v', },
{ "foreground", no_argument, NULL, 'f', },
{ "test", no_argument, NULL, 't', },
{ "config-dir", required_argument, NULL, 'c', },
{ "seafile-dir", required_argument, NULL, 'd', },
{ "central-config-dir", required_argument, NULL, 'F' },
{ "logdir", required_argument, NULL, 'l', },
{ "ccnet-debug-level", required_argument, NULL, 'g' },
{ "seafile-debug-level", required_argument, NULL, 'G' },
{ "pidfile", required_argument, NULL, 'P' },
};
static void controller_exit (int code) __attribute__((noreturn));
static int read_seafdav_config();
static void
controller_exit (int code)
{
if (code != 0) {
seaf_warning ("seaf-controller exited with code %d\n", code);
}
exit(code);
}
//
// Utility functions Start
//
/* returns the pid of the newly created process */
static int
spawn_process (char *argv[])
{
char **ptr = argv;
GString *buf = g_string_new(argv[0]);
while (*(++ptr)) {
g_string_append_printf (buf, " %s", *ptr);
}
seaf_message ("spawn_process: %s\n", buf->str);
g_string_free (buf, TRUE);
pid_t pid = fork();
if (pid == 0) {
/* child process */
execvp (argv[0], argv);
seaf_warning ("failed to execvp %s\n", argv[0]);
exit(-1);
} else {
/* controller */
if (pid == -1)
seaf_warning ("error when fork %s: %s\n", argv[0], strerror(errno));
else
seaf_message ("spawned %s, pid %d\n", argv[0], pid);
return (int)pid;
}
}
#define PID_ERROR_ENOENT 0
#define PID_ERROR_OTHER -1
/**
* @return
* - pid if successfully opened and read the file
* - PID_ERROR_ENOENT if file not exists,
* - PID_ERROR_OTHER if other errors
*/
static int
read_pid_from_pidfile (const char *pidfile)
{
FILE *pf = g_fopen (pidfile, "r");
if (!pf) {
if (errno == ENOENT) {
return PID_ERROR_ENOENT;
} else {
return PID_ERROR_OTHER;
}
}
int pid = PID_ERROR_OTHER;
if (fscanf (pf, "%d", &pid) < 0) {
seaf_warning ("bad pidfile format: %s\n", pidfile);
fclose(pf);
return PID_ERROR_OTHER;
}
fclose(pf);
return pid;
}
static void
kill_by_force (int which)
{
if (which < 0 || which >= N_PID)
return;
char *pidfile = ctl->pidfile[which];
int pid = read_pid_from_pidfile(pidfile);
if (pid > 0) {
// if SIGKILL send success, then remove related pid file
if (kill ((pid_t)pid, SIGKILL) == 0) {
g_unlink (pidfile);
}
}
}
//
// Utility functions End
//
static int
start_ccnet_server ()
{
if (!ctl->config_dir)
return -1;
seaf_message ("starting ccnet-server ...\n");
static char *logfile = NULL;
if (logfile == NULL) {
logfile = g_build_filename (ctl->logdir, "ccnet.log", NULL);
}
char *argv[] = {
"ccnet-server",
"-F", ctl->central_config_dir,
"-c", ctl->config_dir,
"-f", logfile,
"-d",
"-P", ctl->pidfile[PID_CCNET],
NULL};
int pid = spawn_process (argv);
if (pid <= 0) {
seaf_warning ("Failed to spawn ccnet-server\n");
return -1;
}
return 0;
}
static int
start_seaf_server ()
{
if (!ctl->config_dir || !ctl->seafile_dir)
return -1;
seaf_message ("starting seaf-server ...\n");
static char *logfile = NULL;
if (logfile == NULL) {
logfile = g_build_filename (ctl->logdir, "seafile.log", NULL);
}
char *argv[] = {
"seaf-server",
"-F", ctl->central_config_dir,
"-c", ctl->config_dir,
"-d", ctl->seafile_dir,
"-l", logfile,
"-P", ctl->pidfile[PID_SERVER],
"-p", ctl->rpc_pipe_path,
NULL};
int pid = spawn_process (argv);
if (pid <= 0) {
seaf_warning ("Failed to spawn seaf-server\n");
return -1;
}
return 0;
}
static const char *
get_python_executable() {
static const char *python = NULL;
if (python != NULL) {
return python;
}
static const char *try_list[] = {
"python3"
};
int i;
for (i = 0; i < G_N_ELEMENTS(try_list); i++) {
char *binary = g_find_program_in_path (try_list[i]);
if (binary != NULL) {
python = binary;
break;
}
}
if (python == NULL) {
python = g_getenv ("PYTHON");
if (python == NULL) {
python = "python";
}
}
return python;
}
static void
init_seafile_path ()
{
GError *error = NULL;
char *binary = g_file_read_link (PROC_SELF_PATH, &error);
char *tmp = NULL;
if (error != NULL) {
seaf_warning ("failed to readlink: %s\n", error->message);
return;
}
bin_dir = g_path_get_dirname (binary);
tmp = g_path_get_dirname (bin_dir);
installpath = g_path_get_dirname (tmp);
topdir = g_path_get_dirname (installpath);
g_free (binary);
g_free (tmp);
}
static void
setup_python_path()
{
static GList *path_list = NULL;
if (path_list != NULL) {
/* Only setup once */
return;
}
path_list = g_list_prepend (path_list,
g_build_filename (installpath, "seahub", NULL));
path_list = g_list_prepend (path_list,
g_build_filename (installpath, "seahub/thirdpart", NULL));
path_list = g_list_prepend (path_list,
g_build_filename (installpath, "seahub/seahub-extra", NULL));
path_list = g_list_prepend (path_list,
g_build_filename (installpath, "seahub/seahub-extra/thirdparts", NULL));
path_list = g_list_prepend (path_list,
g_build_filename (installpath, "seafile/lib/python3.6/site-packages", NULL));
path_list = g_list_prepend (path_list,
g_build_filename (installpath, "seafile/lib64/python3.6/site-packages", NULL));
path_list = g_list_reverse (path_list);
GList *ptr;
GString *new_pypath = g_string_new (g_getenv("PYTHONPATH"));
for (ptr = path_list; ptr != NULL; ptr = ptr->next) {
const char *path = (char *)ptr->data;
g_string_append_c (new_pypath, ':');
g_string_append (new_pypath, path);
}
g_setenv ("PYTHONPATH", g_string_free (new_pypath, FALSE), TRUE);
/* seaf_message ("PYTHONPATH is:\n\n%s\n", g_getenv ("PYTHONPATH")); */
}
static void
setup_env ()
{
g_setenv ("CCNET_CONF_DIR", ctl->config_dir, TRUE);
g_setenv ("SEAFILE_CONF_DIR", ctl->seafile_dir, TRUE);
g_setenv ("SEAFILE_CENTRAL_CONF_DIR", ctl->central_config_dir, TRUE);
g_setenv ("SEAFILE_RPC_PIPE_PATH", ctl->rpc_pipe_path, TRUE);
char *seahub_dir = g_build_filename (installpath, "seahub", NULL);
char *seafdav_conf = g_build_filename (ctl->central_config_dir, "seafdav.conf", NULL);
g_setenv ("SEAHUB_DIR", seahub_dir, TRUE);
g_setenv ("SEAFDAV_CONF", seafdav_conf, TRUE);
setup_python_path();
}
static int
start_seafevents() {
if (!ctl->has_seafevents)
return 0;
static char *seafevents_config_file = NULL;
static char *seafevents_log_file = NULL;
if (seafevents_config_file == NULL)
seafevents_config_file = g_build_filename (topdir,
"conf/seafevents.conf",
NULL);
if (seafevents_log_file == NULL)
seafevents_log_file = g_build_filename (ctl->logdir,
"seafevents.log",
NULL);
char *argv[] = {
(char *)get_python_executable(),
"-m", "seafevents.main",
"--config-file", seafevents_config_file,
"--logfile", seafevents_log_file,
"-P", ctl->pidfile[PID_SEAFEVENTS],
NULL
};
int pid = spawn_process (argv);
if (pid <= 0) {
seaf_warning ("Failed to spawn seafevents.\n");
return -1;
}
return 0;
}
static int
start_seafdav() {
static char *seafdav_log_file = NULL;
if (seafdav_log_file == NULL)
seafdav_log_file = g_build_filename (ctl->logdir,
"seafdav.log",
NULL);
SeafDavConfig conf = ctl->seafdav_config;
char port[16];
snprintf (port, sizeof(port), "%d", conf.port);
char *argv[] = {
(char *)get_python_executable(),
"-m", "wsgidav.server.server_cli",
"--server", "gunicorn",
"--root", "/",
"--log-file", seafdav_log_file,
"--pid", ctl->pidfile[PID_SEAFDAV],
"--port", port,
"--host", conf.host,
NULL
};
int pid = spawn_process (argv);
if (pid <= 0) {
seaf_warning ("Failed to spawn seafdav\n");
return -1;
}
return 0;
}
static void
run_controller_loop ()
{
GMainLoop *mainloop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (mainloop);
}
static gboolean
need_restart (int which)
{
if (which < 0 || which >= N_PID)
return FALSE;
int pid = read_pid_from_pidfile (ctl->pidfile[which]);
if (pid == PID_ERROR_ENOENT) {
seaf_warning ("pid file %s does not exist\n", ctl->pidfile[which]);
return TRUE;
} else if (pid == PID_ERROR_OTHER) {
seaf_warning ("failed to read pidfile %s: %s\n", ctl->pidfile[which], strerror(errno));
return FALSE;
} else {
char buf[256];
snprintf (buf, sizeof(buf), "/proc/%d", pid);
if (g_file_test (buf, G_FILE_TEST_IS_DIR)) {
return FALSE;
} else {
seaf_warning ("path /proc/%d doesn't exist, restart progress [%d]\n", pid, which);
return TRUE;
}
}
}
static gboolean
check_process (void *data)
{
if (need_restart(PID_SERVER)) {
seaf_message ("seaf-server need restart...\n");
start_seaf_server();
}
if (need_restart(PID_CCNET)) {
seaf_message ("ccnet-server need restart...\n");
start_ccnet_server();
}
if (ctl->seafdav_config.enabled) {
if (need_restart(PID_SEAFDAV)) {
seaf_message ("seafdav need restart...\n");
start_seafdav ();
}
}
if (ctl->has_seafevents && need_restart(PID_SEAFEVENTS)) {
seaf_message ("seafevents need restart...\n");
start_seafevents ();
}
return TRUE;
}
static void
start_process_monitor ()
{
ctl->check_process_timer = g_timeout_add (
CHECK_PROCESS_INTERVAL * 1000, check_process, NULL);
}
static int seaf_controller_start ();
/* This would also stop seaf-server & other components */
static void
stop_services ()
{
seaf_message ("shutting down all services ...\n");
kill_by_force(PID_CCNET);
kill_by_force(PID_SERVER);
kill_by_force(PID_SEAFDAV);
if (ctl->has_seafevents)
kill_by_force(PID_SEAFEVENTS);
}
static void
init_pidfile_path (SeafileController *ctl)
{
char *pid_dir = g_build_filename (topdir, "pids", NULL);
if (!g_file_test(pid_dir, G_FILE_TEST_EXISTS)) {
if (g_mkdir(pid_dir, 0777) < 0) {
seaf_warning("failed to create pid dir %s: %s", pid_dir, strerror(errno));
controller_exit(1);
}
}
ctl->pidfile[PID_CCNET] = g_build_filename (pid_dir, "ccnet.pid", NULL);
ctl->pidfile[PID_SERVER] = g_build_filename (pid_dir, "seaf-server.pid", NULL);
ctl->pidfile[PID_SEAFDAV] = g_build_filename (pid_dir, "seafdav.pid", NULL);
ctl->pidfile[PID_SEAFEVENTS] = g_build_filename (pid_dir, "seafevents.pid", NULL);
}
static int
seaf_controller_init (SeafileController *ctl,
char *central_config_dir,
char *config_dir,
char *seafile_dir,
char *logdir)
{
init_seafile_path ();
if (!g_file_test (config_dir, G_FILE_TEST_IS_DIR)) {
seaf_warning ("invalid config_dir: %s\n", config_dir);
return -1;
}
if (!g_file_test (seafile_dir, G_FILE_TEST_IS_DIR)) {
seaf_warning ("invalid seafile_dir: %s\n", seafile_dir);
return -1;
}
if (logdir == NULL) {
char *topdir = g_path_get_dirname(config_dir);
logdir = g_build_filename (topdir, "logs", NULL);
if (checkdir_with_mkdir(logdir) < 0) {
fprintf (stderr, "failed to create log folder \"%s\": %s\n",
logdir, strerror(errno));
return -1;
}
g_free (topdir);
}
ctl->central_config_dir = central_config_dir;
ctl->config_dir = config_dir;
ctl->seafile_dir = seafile_dir;
ctl->rpc_pipe_path = g_build_filename (installpath, "runtime", NULL);
ctl->logdir = logdir;
if (read_seafdav_config() < 0) {
return -1;
}
char *seafevents_config_file = g_build_filename (topdir,
"conf/seafevents.conf",
NULL);
if (!g_file_test (seafevents_config_file, G_FILE_TEST_EXISTS)) {
seaf_message ("No seafevents.\n");
ctl->has_seafevents = FALSE;
} else {
ctl->has_seafevents = TRUE;
}
g_free (seafevents_config_file);
init_pidfile_path (ctl);
setup_env ();
return 0;
}
static int
seaf_controller_start ()
{
if (start_ccnet_server () < 0) {
seaf_warning ("Failed to start ccnet server\n");
return -1;
}
if (start_seaf_server() < 0) {
seaf_warning ("Failed to start seaf server\n");
return -1;
}
start_process_monitor ();
return 0;
}
static int
write_controller_pidfile ()
{
if (!controller_pidfile)
return -1;
pid_t pid = getpid();
FILE *pidfile = g_fopen(controller_pidfile, "w");
if (!pidfile) {
seaf_warning ("Failed to fopen() pidfile %s: %s\n",
controller_pidfile, strerror(errno));
return -1;
}
char buf[32];
snprintf (buf, sizeof(buf), "%d\n", pid);
if (fputs(buf, pidfile) < 0) {
seaf_warning ("Failed to write pidfile %s: %s\n",
controller_pidfile, strerror(errno));
fclose (pidfile);
return -1;
}
fflush (pidfile);
fclose (pidfile);
return 0;
}
static void
remove_controller_pidfile ()
{
if (controller_pidfile) {
g_unlink (controller_pidfile);
}
}
static void
sigint_handler (int signo)
{
stop_services ();
remove_controller_pidfile();
signal (signo, SIG_DFL);
raise (signo);
}
static void
sigchld_handler (int signo)
{
waitpid (-1, NULL, WNOHANG);
}
static void
sigusr1_handler (int signo)
{
seafile_log_reopen();
}
static void
set_signal_handlers ()
{
signal (SIGINT, sigint_handler);
signal (SIGTERM, sigint_handler);
signal (SIGCHLD, sigchld_handler);
signal (SIGUSR1, sigusr1_handler);
signal (SIGPIPE, SIG_IGN);
}
static void
usage ()
{
fprintf (stderr, "Usage: seafile-controller OPTIONS\n"
"OPTIONS:\n"
" -b, --bin-dir insert a directory in front of the PATH env\n"
" -c, --config-dir ccnet config dir\n"
" -d, --seafile-dir seafile dir\n"
);
}
/* seafile-controller -t is used to test whether config file is valid */
static void
test_config (const char *central_config_dir,
const char *ccnet_dir,
const char *seafile_dir)
{
char buf[1024];
GError *error = NULL;
int retcode = 0;
char *child_stdout = NULL;
char *child_stderr = NULL;
snprintf(buf,
sizeof(buf),
"ccnet-server -F \"%s\" -c \"%s\" -t",
central_config_dir,
ccnet_dir);
g_spawn_command_line_sync (buf,
&child_stdout, /* stdout */
&child_stderr, /* stderror */
&retcode,
&error);
if (error != NULL) {
fprintf (stderr,
"failed to run \"ccnet-server -t\": %s\n",
error->message);
exit (1);
}
if (child_stdout) {
fputs (child_stdout, stdout);
}
if (child_stderr) {
fputs (child_stderr, stdout);
}
if (retcode != 0) {
fprintf (stderr,
"failed to run \"ccnet-server -t\"\n");
exit (1);
}
exit(0);
}
static int
read_seafdav_config()
{
int ret = 0;
char *seafdav_conf = NULL;
GKeyFile *key_file = NULL;
GError *error = NULL;
seafdav_conf = g_build_filename(ctl->central_config_dir, "seafdav.conf", NULL);
if (!g_file_test(seafdav_conf, G_FILE_TEST_EXISTS)) {
goto out;
}
key_file = g_key_file_new ();
if (!g_key_file_load_from_file (key_file, seafdav_conf,
G_KEY_FILE_KEEP_COMMENTS, NULL)) {
seaf_warning("Failed to load seafdav.conf\n");
ret = -1;
goto out;
}
/* enabled */
ctl->seafdav_config.enabled = g_key_file_get_boolean(key_file, "WEBDAV", "enabled", &error);
if (error != NULL) {
if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
seaf_message ("Error when reading WEBDAV.enabled, use default value 'false'\n");
}
ctl->seafdav_config.enabled = FALSE;
g_clear_error (&error);
goto out;
}
if (!ctl->seafdav_config.enabled) {
goto out;
}
/* host */
char *host = seaf_key_file_get_string (key_file, "WEBDAV", "host", &error);
if (error != NULL) {
g_clear_error(&error);
ctl->seafdav_config.host = g_strdup("0.0.0.0");
} else {
ctl->seafdav_config.host = host;
}
/* port */
ctl->seafdav_config.port = g_key_file_get_integer(key_file, "WEBDAV", "port", &error);
if (error != NULL) {
if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
seaf_message ("Error when reading WEBDAV.port, use deafult value 8080\n");
}
ctl->seafdav_config.port = 8080;
g_clear_error (&error);
}
if (ctl->seafdav_config.port <= 0 || ctl->seafdav_config.port > 65535) {
seaf_warning("Failed to load seafdav config: invalid port %d\n", ctl->seafdav_config.port);
ret = -1;
goto out;
}
out:
if (key_file) {
g_key_file_free (key_file);
}
g_free (seafdav_conf);
return ret;
}
static int
init_syslog_config ()
{
char *seafile_conf = g_build_filename (ctl->central_config_dir, "seafile.conf", NULL);
GKeyFile *key_file = g_key_file_new ();
int ret = 0;
if (!g_key_file_load_from_file (key_file, seafile_conf,
G_KEY_FILE_KEEP_COMMENTS, NULL)) {
seaf_warning("Failed to load seafile.conf.\n");
ret = -1;
goto out;
}
set_syslog_config (key_file);
out:
g_key_file_free (key_file);
g_free (seafile_conf);
return ret;
}
int main (int argc, char **argv)
{
if (argc <= 1) {
usage ();
exit (1);
}
char *config_dir = DEFAULT_CONFIG_DIR;
char *central_config_dir = NULL;
char *seafile_dir = NULL;
char *logdir = NULL;
char *ccnet_debug_level_str = "info";
char *seafile_debug_level_str = "debug";
int daemon_mode = 1;
gboolean test_conf = FALSE;
int c;
while ((c = getopt_long (argc, argv, short_opts,
long_opts, NULL)) != EOF)
{
switch (c) {
case 'h':
usage ();
exit(1);
break;
case 'v':
fprintf (stderr, "seafile-controller version 1.0\n");
break;
case 't':
test_conf = TRUE;
break;
case 'c':
config_dir = optarg;
break;
case 'F':
central_config_dir = g_strdup(optarg);
break;
case 'd':
seafile_dir = g_strdup(optarg);
break;
case 'f':
daemon_mode = 0;
break;
case 'L':
logdir = g_strdup(optarg);
break;
case 'g':
ccnet_debug_level_str = optarg;
break;
case 'G':
seafile_debug_level_str = optarg;
break;
case 'P':
controller_pidfile = optarg;
break;
default:
usage ();
exit (1);
}
}
#if !GLIB_CHECK_VERSION(2, 35, 0)
g_type_init();
#endif
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init (NULL);
#endif
if (!seafile_dir) {
fprintf (stderr, "<seafile_dir> must be specified with --seafile-dir\n");
exit(1);
}
if (!central_config_dir) {
fprintf (stderr, "<central_config_dir> must be specified with --central-config-dir\n");
exit(1);
}
central_config_dir = ccnet_expand_path (central_config_dir);
config_dir = ccnet_expand_path (config_dir);
seafile_dir = ccnet_expand_path (seafile_dir);
if (test_conf) {
test_config (central_config_dir, config_dir, seafile_dir);
}
ctl = g_new0 (SeafileController, 1);
if (seaf_controller_init (ctl, central_config_dir, config_dir, seafile_dir, logdir) < 0) {
controller_exit(1);
}
char *logfile = g_build_filename (ctl->logdir, "controller.log", NULL);
if (seafile_log_init (logfile, ccnet_debug_level_str,
seafile_debug_level_str) < 0) {
seaf_warning ("Failed to init log.\n");
controller_exit (1);
}
if (init_syslog_config () < 0) {
controller_exit (1);
}
set_signal_handlers ();
if (seaf_controller_start () < 0)
controller_exit (1);
#ifndef WIN32
if (daemon_mode) {
#ifndef __APPLE__
daemon (1, 0);
#else /* __APPLE */
/* daemon is deprecated under APPLE
* use fork() instead
* */
switch (fork ()) {
case -1:
seaf_warning ("Failed to daemonize");
exit (-1);
break;
case 0:
/* all good*/
break;
default:
/* kill origin process */
exit (0);
}
#endif /* __APPLE */
}
#endif /* !WIN32 */
if (controller_pidfile == NULL) {
controller_pidfile = g_strdup(g_getenv ("SEAFILE_PIDFILE"));
}
if (controller_pidfile != NULL) {
if (write_controller_pidfile () < 0) {
seaf_warning ("Failed to write pidfile %s\n", controller_pidfile);
return -1;
}
}
run_controller_loop ();
return 0;
}