Moved most of the command line logic to the shell script
[citadel.git] / appimage / ctdlvisor.c
1 //
2 // This is a supervisor program that handles start/stop/restart of
3 // the various Citadel System components, when we are running on
4 // an AppImage instance.
5 //
6 // Copyright (c) 2021 by the citadel.org team
7 //
8 // This program is open source software.  It runs great on the
9 // Linux operating system (and probably elsewhere).  You can use,
10 // copy, and run it under the terms of the GNU General Public
11 // License version 3.  Richard Stallman is an asshole communist.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17
18
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <sys/wait.h>
23 #include <errno.h>
24 #include <signal.h>
25 #include <string.h>
26 #include <limits.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29
30 pid_t citserver_pid;
31 pid_t webcit_pid;
32 pid_t webcits_pid;
33 int shutting_down = 0;
34
35 // Call this instead of exit() just for common diagnostics etc.
36 void ctdlvisor_exit(int code) {
37         printf("ctdlvisor: exit code %d\n", code);
38         exit(code);
39 }
40
41
42 // Interrupting this program with a signal will begin an orderly shutdown.
43 void signal_handler(int signal) {
44         fprintf(stderr, "ctdlvisor: caught signal %d\n", signal);
45
46         while(shutting_down) {
47                 fprintf(stderr, "ctdlvisor: already shutting down\n");
48                 sleep(1);
49         }
50
51         int status;
52         pid_t who_exited;
53         char *what_exited = NULL;
54
55         shutting_down = 1;
56         kill(citserver_pid, SIGTERM);
57         kill(webcit_pid, SIGTERM);
58         kill(webcits_pid, SIGTERM);
59         do {
60                 fprintf(stderr, "ctdlvisor: waiting for all child process to exit...\n");
61                 who_exited = waitpid(-1, &status, 0);
62                 if (who_exited == citserver_pid) {
63                         what_exited = "Citadel Server";
64                 }
65                 else if (who_exited == webcit_pid) {
66                         what_exited = "WebCit HTTP";
67                 }
68                 else if (who_exited == webcits_pid) {
69                         what_exited = "WebCit HTTPS";
70                 }
71                 else {
72                         what_exited = "unknown";
73                 }
74                 fprintf(stderr, "ctdlvisor: pid=%d (%s) exited, status=%d, exitcode=%d\n", who_exited, what_exited, status, WEXITSTATUS(status));
75         } while (who_exited >= 0);
76
77         ctdlvisor_exit(0);
78 }
79
80
81 void detach_from_tty(void) {
82         signal(SIGHUP, SIG_IGN);
83         signal(SIGINT, SIG_IGN);
84         signal(SIGQUIT, SIG_IGN);
85
86         setsid();       // become our own process group leader
87         umask(0);
88         if (    (freopen("/dev/null", "r", stdin) != stdin) ||
89                 (freopen("/dev/null", "w", stdout) != stdout) ||
90                 (freopen("/dev/null", "w", stderr) != stderr)
91         ) {
92                 fprintf(stderr, "sysdep: unable to reopen stdio: %s\n", strerror(errno));
93         }
94 }
95
96
97 pid_t start_citadel() {
98         char bin[1024];
99         sprintf(bin, "%s/usr/local/citadel/citserver", getenv("APPDIR"));
100         pid_t pid = fork();
101         if (pid == 0) {
102                 fprintf(stderr, "ctdlvisor: executing %s\n", bin);
103                 detach_from_tty();
104                 execlp(bin, "citserver", "-x9", "-h", getenv("CTDL_DIR"), NULL);
105                 exit(errno);
106         }
107         else {
108                 return(pid);
109         }
110 }
111
112
113 pid_t start_webcit() {
114         char bin[1024];
115         sprintf(bin, "%s/usr/local/webcit/webcit", getenv("APPDIR"));
116         char wchome[1024];
117         sprintf(wchome, "-h%s/usr/local/webcit", getenv("APPDIR"));
118         pid_t pid = fork();
119         if (pid == 0) {
120                 fprintf(stderr, "ctdlvisor: executing %s\n", bin);
121                 detach_from_tty();
122                 execlp(bin, "webcit", "-x9", wchome, "-p", getenv("HTTP_PORT"), "uds", getenv("CTDL_DIR"), NULL);
123                 exit(errno);
124         }
125         else {
126                 return(pid);
127         }
128 }
129
130
131 pid_t start_webcits() {
132         char bin[1024];
133         sprintf(bin, "%s/usr/local/webcit/webcit", getenv("APPDIR"));
134         char wchome[1024];
135         sprintf(wchome, "-h%s/usr/local/webcit", getenv("APPDIR"));
136         pid_t pid = fork();
137         if (pid == 0) {
138                 fprintf(stderr, "ctdlvisor: executing %s\n", bin);
139                 detach_from_tty();
140                 execlp(bin, "webcit", "-x9", wchome, "-s", "-p", getenv("HTTPS_PORT"), "uds", getenv("CTDL_DIR"), NULL);
141                 exit(errno);
142         }
143         else {
144                 return(pid);
145         }
146 }
147
148
149 void test_binary_compatibility(void) {
150         char cmd[1024];
151         int ret;
152         fprintf(stderr, "ctdlvisor: testing compatibility...\n");
153         sprintf(cmd, "%s/usr/local/citadel/citserver -c", getenv("APPDIR"));
154         ret = system(cmd);
155         if (ret) {
156                 fprintf(stderr, "ctdlvisor: this appimage cannot run on your system.\n"
157                                 "           The reason may be indicated by any error messages appearing above.\n");
158         }
159         else {
160                 fprintf(stderr, "ctdlvisor: this appimage appears to be compatible with your system.\n");
161         }
162         exit(ret);
163 }
164
165
166 void main_loop(void) {
167         int status;
168         pid_t who_exited;
169         int citserver_exit_code = 0;
170
171         do {
172                 fprintf(stderr, "ctdlvisor: waiting for any child process to exit...\n");
173                 who_exited = waitpid(-1, &status, 0);
174                 fprintf(stderr, "ctdlvisor: pid=%d exited, status=%d, exitcode=%d\n", who_exited, status, WEXITSTATUS(status));
175
176                 // A *deliberate* exit of citserver will cause ctdlvisor to shut the whole AppImage down.
177                 // If it crashes, however, we will start it back up.
178                 if (who_exited == citserver_pid) {
179                         citserver_exit_code = WEXITSTATUS(status);
180                         if (citserver_exit_code == 0) {
181                                 fprintf(stderr, "ctdlvisor: citserver exited normally - ending AppImage session\n");
182                                 shutting_down = 1;
183                                 kill(webcit_pid, SIGTERM);
184                                 kill(webcits_pid, SIGTERM);
185                         }
186                         else if ((citserver_exit_code >= 101) && (citserver_exit_code <= 109)) {
187                                 fprintf(stderr, "ctdlvisor: citserver exited intentionally - ending AppImage session\n");
188                                 shutting_down = 1;
189                                 kill(webcit_pid, SIGTERM);
190                                 kill(webcits_pid, SIGTERM);
191                         }
192                         else {
193                                 citserver_pid = start_citadel();
194                         }
195                 }
196
197                 // WebCit processes are restarted if they exit for any reason.
198                 if ((who_exited == webcit_pid) && (!shutting_down))     webcit_pid = start_webcit();
199                 if ((who_exited == webcits_pid) && (!shutting_down))    webcits_pid = start_webcits();
200
201                 // If we somehow end up in an endless loop, at least slow it down.
202                 sleep(1);
203
204         } while (who_exited >= 0);
205         ctdlvisor_exit(citserver_exit_code);
206 }
207
208
209 int main(int argc, char **argv) {
210
211         if (getenv("APPDIR") == NULL) {
212                 fprintf(stderr, "ctdlvisor: APPDIR is not set.  This program must be run from within an AppImage.\n");
213                 ctdlvisor_exit(1);
214         }
215
216         fprintf(stderr, "ctdlvisor: Welcome to the Citadel System, brought to you using AppImage.\n");
217         fprintf(stderr, "ctdlvisor: LD_LIBRARY_PATH = %s\n", getenv("LD_LIBRARY_PATH"));
218         fprintf(stderr, "ctdlvisor:            PATH = %s\n", getenv("PATH"));
219         fprintf(stderr, "ctdlvisor:          APPDIR = %s\n", getenv("APPDIR"));
220         fprintf(stderr, "ctdlvisor:  data directory = %s\n", getenv("CTDL_DIR"));
221         fprintf(stderr, "ctdlvisor:       HTTP port = %s\n", getenv("HTTP_PORT"));
222         fprintf(stderr, "ctdlvisor:      HTTPS port = %s\n", getenv("HTTPS_PORT"));
223
224         if (access(getenv("CTDL_DIR"), R_OK|W_OK|X_OK)) {
225                 fprintf(stderr, "ctdlvisor: %s: %s\n", getenv("CTDL_DIR"), strerror(errno));
226                 ctdlvisor_exit(errno);
227         }
228
229         signal(SIGTERM, signal_handler);
230         signal(SIGHUP, signal_handler);
231         signal(SIGINT, signal_handler);
232         signal(SIGQUIT, signal_handler);
233
234         citserver_pid = start_citadel();
235         webcit_pid = start_webcit();
236         webcits_pid = start_webcits();
237
238         main_loop();
239         ctdlvisor_exit(0);
240 }