6784f2cc5d042998ca55eb2f8068dcbca87be656
[citadel.git] / appimage / ctdlvisor.c
1 // This is a supervisor program that handles start/stop/restart of
2 // the various Citadel System components, when we are running on
3 // an AppImage instance.
4 //
5 // Copyright (c) 2021 by the citadel.org team
6 //
7 // This program is open source software.  It runs great on the
8 // Linux operating system (and probably elsewhere).  You can use,
9 // copy, and run it under the terms of the GNU General Public
10 // License version 3.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16
17
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <stdio.h>
21 #include <sys/wait.h>
22 #include <errno.h>
23 #include <signal.h>
24 #include <string.h>
25 #include <limits.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28
29 pid_t citserver_pid;
30 pid_t webcit_pid;
31 pid_t webcits_pid;
32 int shutting_down = 0;
33
34 // Call this instead of exit() just for common diagnostics etc.
35 void ctdlvisor_exit(int code) {
36         printf("ctdlvisor: exit code %d\n", code);
37         exit(code);
38 }
39
40
41 // Interrupting this program with a signal will begin an orderly shutdown.
42 void signal_handler(int signal) {
43         fprintf(stderr, "ctdlvisor: caught signal %d\n", signal);
44
45         while(shutting_down) {
46                 fprintf(stderr, "ctdlvisor: already shutting down\n");
47                 sleep(1);
48         }
49
50         int status;
51         pid_t who_exited;
52         char *what_exited = NULL;
53
54         shutting_down = 1;
55         kill(citserver_pid, SIGTERM);
56         kill(webcit_pid, SIGTERM);
57         kill(webcits_pid, SIGTERM);
58         do {
59                 fprintf(stderr, "ctdlvisor: waiting for all child process to exit...\n");
60                 who_exited = waitpid(-1, &status, 0);
61                 if (who_exited == citserver_pid) {
62                         what_exited = "Citadel Server";
63                 }
64                 else if (who_exited == webcit_pid) {
65                         what_exited = "WebCit HTTP";
66                 }
67                 else if (who_exited == webcits_pid) {
68                         what_exited = "WebCit HTTPS";
69                 }
70                 else {
71                         what_exited = "unknown";
72                 }
73                 if (who_exited >= 0) {
74                         fprintf(stderr, "ctdlvisor: %d (%s) ended, status=%d\n", who_exited, what_exited, status);
75                 }
76         } while (who_exited >= 0);
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 with data directory %s\n", bin, getenv("CTDL_DIR"));
103                 detach_from_tty();
104                 execlp(bin, "citserver", "-x9", "-h", getenv("CTDL_DIR"), NULL);
105                 exit(errno);
106         }
107         else {
108                 fprintf(stderr, "ctdlvisor: citserver running on pid=%d\n", pid);
109                 return(pid);
110         }
111 }
112
113
114 pid_t start_webcit() {
115         char bin[1024];
116         sprintf(bin, "%s/usr/local/webcit/webcit", getenv("APPDIR"));
117         char wchome[1024];
118         sprintf(wchome, "-h%s/usr/local/webcit", getenv("APPDIR"));
119         pid_t pid = fork();
120         if (pid == 0) {
121                 fprintf(stderr, "ctdlvisor: executing %s\n", bin);
122                 detach_from_tty();
123                 execlp(bin, "webcit", "-x9", wchome, "-p", getenv("HTTP_PORT"), "uds", getenv("CTDL_DIR"), NULL);
124                 exit(errno);
125         }
126         else {
127                 fprintf(stderr, "ctdlvisor: webcit (HTTP) running on pid=%d\n", pid);
128                 return(pid);
129         }
130 }
131
132
133 pid_t start_webcits() {
134         char bin[1024];
135         sprintf(bin, "%s/usr/local/webcit/webcit", getenv("APPDIR"));
136         char wchome[1024];
137         sprintf(wchome, "-h%s/usr/local/webcit", getenv("APPDIR"));
138         pid_t pid = fork();
139         if (pid == 0) {
140                 fprintf(stderr, "ctdlvisor: executing %s\n", bin);
141                 detach_from_tty();
142                 execlp(bin, "webcit", "-x9", wchome, "-s", "-p", getenv("HTTPS_PORT"), "uds", getenv("CTDL_DIR"), NULL);
143                 exit(errno);
144         }
145         else {
146                 fprintf(stderr, "ctdlvisor: webcit (HTTPS) running on pid=%d\n", pid);
147                 return(pid);
148         }
149 }
150
151
152 void main_loop(void) {
153         int status;
154         pid_t who_exited;
155         int citserver_exit_code = 0;
156
157         do {
158                 who_exited = waitpid(-1, &status, 0);
159                 fprintf(stderr, "ctdlvisor: pid=%d exited, status=%d, exitcode=%d\n", who_exited, status, WEXITSTATUS(status));
160
161                 // A *deliberate* exit of citserver will cause ctdlvisor to shut the whole AppImage down.
162                 // If it crashes, however, we will start it back up.
163                 if (who_exited == citserver_pid) {
164                         citserver_exit_code = WEXITSTATUS(status);
165                         if ((WIFEXITED(status)) && (citserver_exit_code == 0)) {
166                                 fprintf(stderr, "ctdlvisor: citserver exited normally - ending AppImage session\n");
167                                 shutting_down = 1;
168                                 kill(webcit_pid, SIGTERM);
169                                 kill(webcits_pid, SIGTERM);
170                         }
171                         else if ((WIFEXITED(status)) && (citserver_exit_code >= 101) && (citserver_exit_code <= 109)) {
172                                 fprintf(stderr, "ctdlvisor: citserver exited intentionally - ending AppImage session\n");
173                                 shutting_down = 1;
174                                 kill(webcit_pid, SIGTERM);
175                                 kill(webcits_pid, SIGTERM);
176                         }
177                         else {
178                                 if (WIFSIGNALED(status)) {
179                                         fprintf(stderr, "ctdlvisor: citserver crashed on signal %d\n", WTERMSIG(status));
180                                 }
181                                 citserver_pid = start_citadel();
182                         }
183                 }
184
185                 // WebCit processes are restarted if they exit for any reason.
186                 if ((who_exited == webcit_pid) && (!shutting_down))     webcit_pid = start_webcit();
187                 if ((who_exited == webcits_pid) && (!shutting_down))    webcits_pid = start_webcits();
188
189                 // If we somehow end up in an endless loop, at least slow it down.
190                 sleep(1);
191
192         } while (who_exited >= 0);
193         ctdlvisor_exit(citserver_exit_code);
194 }
195
196
197 int main(int argc, char **argv) {
198         int a;
199         int migrate_mode = 0;
200
201         if (getenv("APPDIR") == NULL) {
202                 fprintf(stderr, "ctdlvisor: APPDIR is not set.  This program must be run from within an AppImage.\n");
203                 ctdlvisor_exit(1);
204         }
205
206         /* parse command-line arguments */
207         while ((a=getopt(argc, argv, "cm")) != EOF) switch(a) {
208
209                 // test this binary for compatibility and exit
210                 case 'c':
211                         fprintf(stderr, "%s: binary compatibility confirmed\n", argv[0]);
212                         exit(0);
213                         break;
214
215                 // run ctdlmigrate only
216                 case 'm':
217                         migrate_mode = 1;
218                         break;
219
220                 // any other parameter makes it crash and burn
221                 default:
222                         fprintf(stderr, "usage\n");
223                         exit(1);
224         }
225
226         fprintf(stderr, "ctdlvisor: Welcome to the Citadel System, brought to you using AppImage.\n");
227         fprintf(stderr, "ctdlvisor: LD_LIBRARY_PATH = %s\n", getenv("LD_LIBRARY_PATH"));
228         fprintf(stderr, "ctdlvisor:            PATH = %s\n", getenv("PATH"));
229         fprintf(stderr, "ctdlvisor:          APPDIR = %s\n", getenv("APPDIR"));
230         fprintf(stderr, "ctdlvisor:  data directory = %s\n", getenv("CTDL_DIR"));
231         fprintf(stderr, "ctdlvisor:       HTTP port = %s\n", getenv("HTTP_PORT"));
232         fprintf(stderr, "ctdlvisor:      HTTPS port = %s\n", getenv("HTTPS_PORT"));
233
234         if (access(getenv("CTDL_DIR"), R_OK|W_OK|X_OK)) {
235                 fprintf(stderr, "ctdlvisor: %s: %s\n", getenv("CTDL_DIR"), strerror(errno));
236                 ctdlvisor_exit(errno);
237         }
238
239         signal(SIGHUP, signal_handler);
240
241         // "migrate mode" means we just start the server and then run ctdlmigrate interactively.
242         if (migrate_mode) {
243                 citserver_pid = start_citadel();
244                 fprintf(stderr, "ctdlvisor: waiting a moment for citserver to initialize...\n");
245                 sleep(5);
246                 char bin[1024];
247                 sprintf(bin, "%s/usr/local/citadel/ctdlmigrate", getenv("APPDIR"));
248                 system(bin);
249                 kill(citserver_pid, SIGTERM);
250         }
251
252         // Otherwise, it's just a normal happy day in Citadel land.
253         else {
254                 signal(SIGTERM, signal_handler);
255                 signal(SIGINT, signal_handler);
256                 signal(SIGQUIT, signal_handler);
257         
258                 citserver_pid = start_citadel();
259                 webcit_pid = start_webcit();
260                 webcits_pid = start_webcits();
261         
262                 main_loop();
263         }
264
265         ctdlvisor_exit(0);
266 }