fateshare: a plugin for managing child processes
[vpp.git] / src / plugins / fateshare / fateshare.c
1 /*
2  * fateshare.c - skeleton vpp engine plug-in
3  *
4  * Copyright (c) 2022 Cisco and/or its affiliates.
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at:
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #include <vnet/vnet.h>
19 #include <vnet/plugin/plugin.h>
20 #include <fateshare/fateshare.h>
21
22 #include <vlibapi/api.h>
23 #include <vlibmemory/api.h>
24 #include <vpp/app/version.h>
25 #include <stdbool.h>
26
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
30 #include <limits.h>
31
32 fateshare_main_t fateshare_main;
33
34 /* Action function shared between message handler and debug CLI */
35
36 static void
37 child_handler (int sig)
38 {
39   pid_t pid;
40   int status;
41   fateshare_main_t *kmp = &fateshare_main;
42
43   while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
44     {
45       if (pid == kmp->monitor_pid)
46         {
47           clib_warning ("Monitor child %d exited with status %d!", pid,
48                         status);
49           kmp->vlib_main->main_loop_exit_now = 1;
50         }
51       else
52         {
53           clib_warning ("child %d exited with status %d!", pid, status);
54         }
55     }
56 }
57
58 clib_error_t *
59 launch_monitor (fateshare_main_t *kmp)
60 {
61   clib_error_t *error = 0;
62   pid_t ppid_before_fork = getpid ();
63   pid_t cpid = fork ();
64   if (cpid == -1)
65     {
66       perror (0);
67       error = clib_error_return (0, "can not fork");
68       goto done;
69     }
70   clib_warning ("fateshare about to launch monitor %v.", kmp->monitor_cmd);
71   int logfd =
72     open ((char *) kmp->monitor_logfile, O_APPEND | O_RDWR | O_CREAT, 0777);
73   if (logfd < 0)
74     {
75       error = clib_error_return (0, "can not open log file");
76       goto done;
77     }
78   if (cpid)
79     {
80       /* parent */
81       kmp->monitor_pid = cpid;
82       close (logfd);
83       return 0;
84     }
85   else
86     {
87       dup2 (logfd, 1);
88       dup2 (logfd, 2);
89       int r = prctl (PR_SET_PDEATHSIG, SIGTERM);
90       if (r == -1)
91         {
92           perror (0);
93           exit (1);
94         }
95       pid_t current_ppid = getppid ();
96       if (current_ppid != ppid_before_fork)
97         {
98           fprintf (stderr, "parent pid changed while starting (%d => %d)\n",
99                    ppid_before_fork, current_ppid);
100           if (current_ppid == 1)
101             {
102               fprintf (stderr, "exiting.\n");
103               exit (1);
104             }
105         }
106
107       int r1 = setpgid (getpid (), 0);
108       if (r1 != 0)
109         {
110           perror ("setpgid error");
111           exit (1);
112         }
113
114       u8 *scmd = format (0, "%v\0", kmp->monitor_cmd);
115       u8 *logfile_base = format (0, "%v\0", kmp->monitor_logfile);
116       int fd = logfd - 1;
117       while (fd > 2)
118         {
119           close (fd);
120           fd--;
121         }
122
123       fd = open ("/dev/null", O_RDONLY);
124       if (fd < 0)
125         {
126           exit (1);
127         }
128       dup2 (fd, 0);
129
130       char *ppid_str = (char *) format (0, "%lld\0", current_ppid);
131
132       char **argv = 0;
133       vec_validate (argv, vec_len (kmp->commands) + 3 - 1);
134       argv[0] = (void *) scmd;
135       argv[1] = ppid_str;
136       argv[2] = (char *) logfile_base;
137       int i;
138       vec_foreach_index (i, kmp->commands)
139         {
140           argv[3 + i] = (char *) kmp->commands[i];
141         }
142
143       int res = execv (argv[0], argv);
144       clib_warning ("ERROR during execve: %d", res);
145       perror ("execve");
146
147       exit (0);
148     }
149 done:
150
151   return error;
152 }
153
154 static clib_error_t *
155 fateshare_config (vlib_main_t *vm, unformat_input_t *input)
156 {
157   fateshare_main_t *fmp = &fateshare_main;
158   u8 *command = 0;
159   u8 **new_command = 0;
160   clib_error_t *error = 0;
161
162   /* unix config may make vpp fork, we want to run after that. */
163   if ((error = vlib_call_config_function (vm, unix_config)))
164     return error;
165
166   /* Defaults */
167   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
168     {
169       if (unformat (input, "monitor %s", &fmp->monitor_cmd))
170         {
171           clib_warning ("setting monitor to %v", fmp->monitor_cmd);
172         }
173       else if (unformat (input, "logfile %s", &fmp->monitor_logfile))
174         {
175           clib_warning ("setting logfile to %v", fmp->monitor_logfile);
176         }
177       else if (unformat (input, "command %s", &command))
178         {
179           vec_add2 (fmp->commands, new_command, 1);
180           *new_command = command;
181         }
182       else
183         return clib_error_return (0, "unknown input `%U'",
184                                   format_unformat_error, input);
185     }
186
187   vec_add2 (fmp->commands, new_command, 1);
188   *new_command = 0;
189
190   /* Establish handler. */
191   struct sigaction sa;
192   sigemptyset (&sa.sa_mask);
193   sa.sa_flags = 0;
194   sa.sa_handler = child_handler;
195
196   sigaction (SIGCHLD, &sa, NULL);
197
198   if (fmp->monitor_cmd == 0)
199     {
200       char *p, path[PATH_MAX];
201       int rv;
202
203       /* find executable path */
204       if ((rv = readlink ("/proc/self/exe", path, PATH_MAX - 1)) == -1)
205         return clib_error_return (
206           0, "could not stat /proc/self/exe - set monitor manually");
207
208       /* readlink doesn't provide null termination */
209       path[rv] = 0;
210
211       /* strip filename */
212       if ((p = strrchr (path, '/')) == 0)
213         return clib_error_return (
214           0, "could not determine vpp directory - set monitor manually");
215       *p = 0;
216
217       fmp->monitor_cmd = format (0, "%s/vpp_fateshare_monitor\0", path);
218     }
219   if (fmp->monitor_logfile == 0)
220     {
221       fmp->monitor_logfile =
222         format (0, "/tmp/vpp-fateshare-monitor-log.txt\0");
223     }
224   error = launch_monitor (fmp);
225
226   return error;
227 }
228
229 clib_error_t *
230 fateshare_init (vlib_main_t *vm)
231 {
232   fateshare_main_t *kmp = &fateshare_main;
233   clib_error_t *error = 0;
234
235   kmp->vlib_main = vm;
236
237   return error;
238 }
239
240 static clib_error_t *
241 fateshare_send_hup_fn (vlib_main_t *vm, unformat_input_t *input,
242                        vlib_cli_command_t *cmd)
243 {
244   clib_error_t *error = 0;
245   fateshare_main_t *kmp = &fateshare_main;
246
247   if (kmp->monitor_pid)
248     {
249       int rc = kill (kmp->monitor_pid, SIGHUP);
250       if (rc)
251         {
252           error = clib_error_return (
253             0, "can not send signal to monitor process: %s", strerror (errno));
254         }
255     }
256   else
257     {
258       error = clib_error_return (0, "can not find monitor process");
259     }
260
261   return error;
262 }
263
264 VLIB_EARLY_CONFIG_FUNCTION (fateshare_config, "fateshare");
265
266 VLIB_INIT_FUNCTION (fateshare_init);
267
268 VLIB_CLI_COMMAND (fateshare_restart_process_command, static) = {
269   .path = "fateshare restart-processes",
270   .short_help = "restart dependent processes",
271   .function = fateshare_send_hup_fn,
272 };
273
274 VLIB_PLUGIN_REGISTER () = {
275   .version = VPP_BUILD_VER,
276   .description = "Run child processes which will share fate with VPP, restart "
277                  "them if they quit",
278   .default_disabled = 1,
279 };
280
281 /*
282  * fd.io coding-style-patch-verification: ON
283  *
284  * Local Variables:
285  * eval: (c-set-style "gnu")
286  * End:
287  */