1 /*
2 Copyright 2020 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <signal.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30
31 char *SPAWN_PROCESS = NULL;
32 char *SPAWN_PROCESS_ON_SIGNAL = NULL;
33 int REFUSE_TO_DIE = 0;
34 char **NEXT_PROCESS_ARGV = NULL;
35 int NEXT_PROCESS_ARGC = 0;
36
37 char *PIDFILE = NULL;
38
spawn_process(const char * program)39 void spawn_process(const char *program)
40 {
41 pid_t pid = fork();
42 if (pid < 0)
43 {
44 printf("Could not fork\n");
45 exit(1);
46 }
47 else if (pid == 0)
48 {
49 const char * args[NEXT_PROCESS_ARGC + 2]; // One for program and one for NULL.
50 args[0] = program;
51 for (int c = 1; c <= NEXT_PROCESS_ARGC; c++)
52 {
53 args[c] = NEXT_PROCESS_ARGV[c-1];
54 }
55 args[NEXT_PROCESS_ARGC + 1] = NULL;
56 execv(program, (char * const *)args);
57
58 printf("Could not execute %s\n", program);
59 exit(1);
60 }
61 }
62
signal_handler(int signal)63 void signal_handler(int signal)
64 {
65 (void)signal;
66 // No-op. All the handling is in the main loop
67 }
68
process_signal(void)69 void process_signal(void)
70 {
71 if (SPAWN_PROCESS_ON_SIGNAL)
72 {
73 // Insert artificial delay so that a match for the agent right after
74 // attempting to kill the daemon will not work. Trying to make it as
75 // difficult as possible for the killing script! :-)
76 sleep(1);
77
78 spawn_process(SPAWN_PROCESS_ON_SIGNAL);
79 }
80
81 if (!REFUSE_TO_DIE)
82 {
83 if (PIDFILE)
84 {
85 unlink(PIDFILE);
86 }
87 exit(0);
88 }
89 }
90
main(int argc,char ** argv)91 int main(int argc, char **argv)
92 {
93 sigset_t mask;
94 sigemptyset(&mask);
95 struct sigaction sig;
96 memset(&sig, 0, sizeof(sig));
97 sig.sa_handler = &signal_handler;
98 sig.sa_mask = mask;
99 const int signals[] = { SIGTERM, SIGQUIT, SIGINT, SIGHUP, 0 };
100
101 for (int c = 0; signals[c]; c++)
102 {
103 if (sigaction(signals[c], &sig, NULL) != 0)
104 {
105 printf("Unable to set signal handlers\n");
106 return 1;
107 }
108 }
109
110 for (int c = 1; c < argc; c++)
111 {
112 if (strcmp(argv[c], "--spawn-process") == 0)
113 {
114 if (++c + 1 >= argc)
115 {
116 printf("%s requires two arguments\n", argv[c]);
117 return 1;
118 }
119 // The reason for splitting the argument into two parts is to avoid
120 // a false match on a process just because the it has an argument
121 // containing the string we are looking for.
122 SPAWN_PROCESS = malloc(strlen(argv[c]) + strlen(argv[c+1]) + 2);
123 sprintf(SPAWN_PROCESS, "%s/%s", argv[c], argv[c+1]);
124
125 c++;
126 }
127 else if (strcmp(argv[c], "--spawn-process-on-signal") == 0)
128 {
129 if (++c + 1 >= argc)
130 {
131 printf("%s requires two arguments\n", argv[c]);
132 return 1;
133 }
134 // See comment for SPAWN_PROCESS.
135 SPAWN_PROCESS_ON_SIGNAL = malloc(strlen(argv[c]) + strlen(argv[c+1]) + 2);
136 sprintf(SPAWN_PROCESS_ON_SIGNAL, "%s/%s", argv[c], argv[c+1]);
137
138 c++;
139 }
140 else if (strcmp(argv[c], "--refuse-to-die") == 0)
141 {
142 REFUSE_TO_DIE = 1;
143 }
144 else if (strcmp(argv[c], "--pass-to-next-process") == 0)
145 {
146 // Stops argument processing and passes all remaining arguments to
147 // the spawned process instead.
148 NEXT_PROCESS_ARGV = &argv[++c];
149 NEXT_PROCESS_ARGC = argc - c;
150 break;
151 }
152 else
153 {
154 printf("Unknown argument: %s\n", argv[c]);
155 return 1;
156 }
157 }
158
159 const char *piddir = getenv("CFTEST_PREFIX");
160 if (piddir)
161 {
162 const char *file = strrchr(argv[0], '/');
163 file++;
164 PIDFILE = malloc(strlen(piddir) + strlen(file) + 6);
165 sprintf(PIDFILE, "%s/%s.pid", piddir, file);
166 }
167
168 pid_t child = fork();
169 if (child < 0)
170 {
171 printf("Could not fork\n");
172 exit(1);
173 }
174 else if (child > 0)
175 {
176 // Daemonize.
177 if (PIDFILE)
178 {
179 FILE *fptr = fopen(PIDFILE, "w");
180 if (!fptr)
181 {
182 printf("Could not open file %s\n", PIDFILE);
183 exit(1);
184 }
185 fprintf(fptr, "%d\n", (int)child);
186 fclose(fptr);
187 }
188
189 return 0;
190 }
191
192 if (SPAWN_PROCESS)
193 {
194 spawn_process(SPAWN_PROCESS);
195 }
196
197 // 60 seconds of consecutive sleep will end the program, just so that we do
198 // in fact terminate if we are left over.
199 while (sleep(60) != 0)
200 {
201 // If we didn't sleep the whole time, then it must be a signal.
202 process_signal();
203 }
204
205 return 1;
206 }
207