ctdb-event: Allow tool to enable/disable scripts without daemon
[amitay/samba.git] / ctdb / event / event_tool.c
1 /*
2    CTDB event daemon utility code
3
4    Copyright (C) Amitay Isaacs  2018
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "replace.h"
21 #include "system/filesys.h"
22 #include "system/time.h"
23
24 #include <popt.h>
25 #include <talloc.h>
26 #include <tevent.h>
27
28 #include "lib/util/debug.h"
29
30 #include "common/cmdline.h"
31 #include "common/logging.h"
32 #include "common/path.h"
33
34 #include "event/event_protocol_api.h"
35 #include "event/event.h"
36 #include "event/event_tool.h"
37
38 struct event_tool_context {
39         struct cmdline_context *cmdline;
40         struct tevent_context *ev;
41         struct ctdb_event_context *eclient;
42 };
43
44 static int compact_args(TALLOC_CTX *mem_ctx,
45                         const char **argv,
46                         int argc,
47                         int from,
48                         const char **result)
49 {
50         char *arg_str;
51         int i;
52
53         if (argc <= from) {
54                 *result = NULL;
55                 return 0;
56         }
57
58         arg_str = talloc_strdup(mem_ctx, argv[from]);
59         if (arg_str == NULL) {
60                 return ENOMEM;
61         }
62
63         for (i = from+1; i < argc; i++) {
64                 arg_str = talloc_asprintf_append(arg_str, " %s", argv[i]);
65                 if (arg_str == NULL) {
66                         return ENOMEM;
67                 }
68         }
69
70         *result = arg_str;
71         return 0;
72 }
73
74 static int event_command_run(TALLOC_CTX *mem_ctx,
75                              int argc,
76                              const char **argv,
77                              void *private_data)
78 {
79         struct event_tool_context *ctx = talloc_get_type_abort(
80                 private_data, struct event_tool_context);
81         struct tevent_req *req;
82         struct ctdb_event_request_run request_run;
83         const char *arg_str = NULL;
84         const char *t;
85         int timeout, ret = 0, result = 0;
86         bool ok;
87
88         if (argc < 3) {
89                 cmdline_usage(ctx->cmdline, "run");
90                 return 1;
91         }
92
93         ret = ctdb_event_init(ctx, ctx->ev, &ctx->eclient);
94         if (ret != 0) {
95                 D_ERR("Failed to initialize event client, ret=%d\n", ret);
96                 return ret;
97         }
98
99         timeout = atoi(argv[0]);
100         if (timeout < 0) {
101                 timeout = 0;
102         }
103
104         ret = compact_args(mem_ctx, argv, argc, 3, &arg_str);
105         if (ret != 0) {
106                 D_ERR("Memory allocation error\n");
107                 return 1;
108         }
109
110         request_run.component = argv[1];
111         request_run.event = argv[2];
112         request_run.args = arg_str;
113         request_run.timeout = timeout;
114         request_run.flags = 0;
115
116         t = getenv("CTDB_TEST_MODE");
117         if (t != NULL) {
118                 t = getenv("CTDB_EVENT_RUN_ALL");
119                 if (t != NULL) {
120                         request_run.flags = CTDB_EVENT_RUN_ALL;
121                 }
122         }
123
124         req = ctdb_event_run_send(mem_ctx,
125                                   ctx->ev,
126                                   ctx->eclient,
127                                   &request_run);
128         if (req == NULL) {
129                 D_ERR("Memory allocation error\n");
130                 return 1;
131         }
132
133         tevent_req_poll(req, ctx->ev);
134
135         ok = ctdb_event_run_recv(req, &ret, &result);
136         if (!ok) {
137                 D_ERR("Failed to run event %s in %s, ret=%d\n",
138                       argv[2],
139                       argv[1],
140                       ret);
141                 return 1;
142         }
143
144         D_NOTICE("Command run finished with result=%d\n", result);
145
146         if (result == ENOENT) {
147                 printf("Event dir for %s does not exist\n", argv[1]);
148         } else if (result == ETIME) {
149                 printf("Event %s in %s timed out\n", argv[2], argv[1]);
150         } else if (result == ECANCELED) {
151                 printf("Event %s in %s got cancelled\n", argv[2], argv[1]);
152         } else if (result == ENOEXEC) {
153                 printf("Event %s in %s failed\n", argv[2], argv[1]);
154         } else if (result != 0) {
155                 printf("Failed to run event %s in %s, result=%d\n",
156                        argv[2],
157                        argv[1],
158                        result);
159         }
160
161         ret = (result < 0) ? -result : result;
162         return ret;
163 }
164
165 static double timeval_delta(struct timeval *tv2, struct timeval *tv)
166 {
167         return (tv2->tv_sec - tv->tv_sec) +
168                (tv2->tv_usec - tv->tv_usec) * 1.0e-6;
169 }
170
171 static void print_status_one(struct ctdb_event_script *script)
172 {
173         if (script->result == -ETIME) {
174                 printf("%-20s %-10s %s",
175                        script->name,
176                        "TIMEDOUT",
177                        ctime(&script->begin.tv_sec));
178         } else if (script->result == -ENOEXEC) {
179                 printf("%-20s %-10s\n", script->name, "DISABLED");
180         } else if (script->result < 0) {
181                 printf("%-20s %-10s (%s)\n",
182                        script->name,
183                        "CANNOT RUN",
184                        strerror(-script->result));
185         } else if (script->result == 0) {
186                 printf("%-20s %-10s %.3lf %s",
187                        script->name,
188                        "OK",
189                        timeval_delta(&script->end, &script->begin),
190                        ctime(&script->begin.tv_sec));
191         } else {
192                 printf("%-20s %-10s %.3lf %s",
193                        script->name,
194                        "ERROR",
195                        timeval_delta(&script->end, &script->begin),
196                        ctime(&script->begin.tv_sec));
197         }
198
199         if (script->result != 0 && script->result != -ENOEXEC) {
200                 printf("  OUTPUT: %s\n",
201                        script->output == NULL ? "" : script->output);
202         }
203 }
204
205 static void print_status(const char *component,
206                          const char *event,
207                          int result,
208                          struct ctdb_event_reply_status *status)
209 {
210         int i;
211
212         if (result != 0) {
213                 if (result == ENOENT) {
214                         printf("Event dir for %s does not exist\n", component);
215                 } else if (result == EINVAL) {
216                         printf("Event %s has never run in %s\n",
217                                event,
218                                component);
219                 } else {
220                         printf("Unknown error (%d) for event %s in %s\n",
221                                result,
222                                event,
223                                component);
224                 }
225                 return;
226         }
227
228         for (i=0; i<status->script_list->num_scripts; i++) {
229                 print_status_one(&status->script_list->script[i]);
230         }
231 }
232
233 static int event_command_status(TALLOC_CTX *mem_ctx,
234                                 int argc,
235                                 const char **argv,
236                                 void *private_data)
237 {
238         struct event_tool_context *ctx = talloc_get_type_abort(
239                 private_data, struct event_tool_context);
240         struct tevent_req *req;
241         struct ctdb_event_request_status request_status;
242         struct ctdb_event_reply_status *reply_status;
243         int ret = 0, result = 0;
244         bool ok;
245
246         if (argc != 2) {
247                 cmdline_usage(ctx->cmdline, "run");
248                 return 1;
249         }
250
251         ret = ctdb_event_init(ctx, ctx->ev, &ctx->eclient);
252         if (ret != 0) {
253                 D_ERR("Failed to initialize event client, ret=%d\n", ret);
254                 return ret;
255         }
256
257         request_status.component = argv[0];
258         request_status.event = argv[1];
259
260         req = ctdb_event_status_send(mem_ctx,
261                                      ctx->ev,
262                                      ctx->eclient,
263                                      &request_status);
264         if (req == NULL) {
265                 D_ERR("Memory allocation error\n");
266                 return 1;
267         }
268
269         tevent_req_poll(req, ctx->ev);
270
271         ok = ctdb_event_status_recv(req,
272                                     &ret,
273                                     &result,
274                                     mem_ctx,
275                                     &reply_status);
276         if (!ok) {
277                 D_ERR("Failed to get status for event %s in %s, ret=%d\n",
278                       argv[1],
279                       argv[0],
280                       ret);
281                 return 1;
282         }
283
284         D_NOTICE("Command status finished with result=%d\n", result);
285
286         print_status(argv[0], argv[1], result, reply_status);
287
288         if (reply_status == NULL) {
289                 ret = result;
290         } else {
291                 ret = reply_status->summary;
292                 ret = (ret < 0) ? -ret : ret;
293         }
294         return ret;
295 }
296
297 static int event_command_script(TALLOC_CTX *mem_ctx,
298                                 struct event_tool_context *ctx,
299                                 const char *component,
300                                 const char *script,
301                                 enum ctdb_event_script_action action)
302 {
303         struct tevent_req *req;
304         struct ctdb_event_request_script request_script;
305         int ret = 0, result = 0;
306         bool ok;
307
308         ret = ctdb_event_init(ctx, ctx->ev, &ctx->eclient);
309         if (ret != 0) {
310                 D_ERR("Failed to initialize event client, ret=%d\n", ret);
311                 return ret;
312         }
313
314         request_script.component = component;
315         request_script.script = script;
316         request_script.action = action;
317
318         req = ctdb_event_script_send(mem_ctx,
319                                      ctx->ev,
320                                      ctx->eclient,
321                                      &request_script);
322         if (req == NULL) {
323                 D_ERR("Memory allocation error\n");
324                 return 1;
325         }
326
327         tevent_req_poll(req, ctx->ev);
328
329         ok = ctdb_event_script_recv(req, &ret, &result);
330         if (!ok) {
331                 D_ERR("Failed to %s script, ret=%d\n",
332                       (action == CTDB_EVENT_SCRIPT_DISABLE ?
333                        "disable" :
334                        "enable"),
335                       ret);
336                 return 1;
337         }
338
339         D_NOTICE("Command script finished with result=%d\n", result);
340
341         if (result == EINVAL) {
342                 printf("Script %s is invalid in %s\n", script, component);
343         } else if (result == ENOENT) {
344                 printf("Script %s does not exist in %s\n", script, component);
345         }
346
347         return result;
348 }
349
350 static int event_command_script_enable(TALLOC_CTX *mem_ctx,
351                                        int argc,
352                                        const char **argv,
353                                        void *private_data)
354 {
355         struct event_tool_context *ctx = talloc_get_type_abort(
356                 private_data, struct event_tool_context);
357         struct stat statbuf;
358         char *script, *etc_script, *data_script;
359         int ret;
360
361         if (argc != 2) {
362                 cmdline_usage(ctx->cmdline, "script enable");
363                 return 1;
364         }
365
366         script = talloc_asprintf(mem_ctx, "events/%s/%s.script", argv[0], argv[1]);
367         if (script == NULL) {
368                 return ENOMEM;
369         }
370
371         etc_script = path_etcdir_append(mem_ctx, script);
372         if (etc_script == NULL) {
373                 return ENOMEM;
374         }
375
376         data_script = path_datadir_append(mem_ctx, script);
377         if (data_script == NULL) {
378                 return ENOMEM;
379         }
380
381         ret = lstat(etc_script, &statbuf);
382         if (ret == 0) {
383                 if (S_ISLNK(statbuf.st_mode)) {
384                         /* Link already exists */
385                         return 0;
386                 } else if (S_ISREG(statbuf.st_mode)) {
387                         return event_command_script(mem_ctx,
388                                                     ctx,
389                                                     argv[0],
390                                                     argv[1],
391                                                     CTDB_EVENT_SCRIPT_ENABLE);
392                 }
393
394                 printf("Script %s is not a file or a link\n", etc_script);
395                 return EINVAL;
396         } else {
397                 if (errno == ENOENT) {
398                         ret = stat(data_script, &statbuf);
399                         if (ret != 0) {
400                                 printf("Script %s does not exist in %s\n",
401                                        argv[1], argv[0]);
402                                 return ENOENT;
403                         }
404
405                         ret = symlink(data_script, etc_script);
406                         if (ret != 0) {
407                                 printf("Failed to create symlink %s\n",
408                                       etc_script);
409                                 return EIO;
410                         }
411
412                         return 0;
413                 }
414
415                 printf("Script %s does not exist\n", etc_script);
416                 return EINVAL;
417         }
418 }
419
420 static int event_command_script_disable(TALLOC_CTX *mem_ctx,
421                                         int argc,
422                                         const char **argv,
423                                         void *private_data)
424 {
425         struct event_tool_context *ctx = talloc_get_type_abort(
426                 private_data, struct event_tool_context);
427         struct stat statbuf;
428         char *script, *etc_script;
429         int ret;
430
431
432         if (argc != 2) {
433                 cmdline_usage(ctx->cmdline, "script disable");
434                 return 1;
435         }
436
437         script = talloc_asprintf(mem_ctx, "events/%s/%s.script", argv[0], argv[1]);
438         if (script == NULL) {
439                 return ENOMEM;
440         }
441
442         etc_script = path_etcdir_append(mem_ctx, script);
443         if (etc_script == NULL) {
444                 return ENOMEM;
445         }
446
447         ret = lstat(etc_script, &statbuf);
448         if (ret == 0) {
449                 if (S_ISLNK(statbuf.st_mode)) {
450                         /* Link exists */
451                         ret = unlink(etc_script);
452                         if (ret != 0) {
453                                 printf("Failed to remove symlink %s\n",
454                                        etc_script);
455                                 return EIO;
456                         }
457
458                         return 0;
459                 } else if (S_ISREG(statbuf.st_mode)) {
460                         return event_command_script(mem_ctx,
461                                                     ctx,
462                                                     argv[0],
463                                                     argv[1],
464                                                     CTDB_EVENT_SCRIPT_DISABLE);
465                 }
466
467                 printf("Script %s is not a file or a link\n", etc_script);
468                 return EINVAL;
469         }
470
471         return 0;
472 }
473
474 struct cmdline_command event_commands[] = {
475         { "run", event_command_run,
476                 "Run an event", "<timeout> <component> <event> <args>" },
477         { "status", event_command_status,
478                 "Get status of an event", "<component> <event>" },
479         { "script enable", event_command_script_enable,
480                 "Enable an event script", "<component> <script>" },
481         { "script disable", event_command_script_disable,
482                 "Disable an event script", "<component> <script>" },
483         CMDLINE_TABLEEND
484 };
485
486 int event_tool_init(TALLOC_CTX *mem_ctx,
487                     const char *prog,
488                     struct poptOption *options,
489                     int argc,
490                     const char **argv,
491                     bool parse_options,
492                     struct event_tool_context **result)
493 {
494         struct event_tool_context *ctx;
495         int ret;
496
497         ctx = talloc_zero(mem_ctx, struct event_tool_context);
498         if (ctx == NULL) {
499                 D_ERR("Memory allocation error\n");
500                 return ENOMEM;
501         }
502
503         ret = cmdline_init(mem_ctx,
504                            prog,
505                            options,
506                            event_commands,
507                            &ctx->cmdline);
508         if (ret != 0) {
509                 D_ERR("Failed to initialize cmdline, ret=%d\n", ret);
510                 talloc_free(ctx);
511                 return ret;
512         }
513
514         ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options);
515         if (ret != 0) {
516                 cmdline_usage(ctx->cmdline, NULL);
517                 talloc_free(ctx);
518                 return ret;
519         }
520
521         *result = ctx;
522         return 0;
523 }
524
525 int event_tool_run(struct event_tool_context *ctx, int *result)
526 {
527         int ret;
528
529         ctx->ev = tevent_context_init(ctx);
530         if (ctx->ev == NULL) {
531                 D_ERR("Failed to initialize tevent\n");
532                 return ENOMEM;
533         }
534
535         ret = cmdline_run(ctx->cmdline, ctx, result);
536         return ret;
537 }
538
539 #ifdef CTDB_EVENT_TOOL
540
541 static struct {
542         const char *debug;
543 } event_data = {
544         .debug = "ERROR",
545 };
546
547 struct poptOption event_options[] = {
548         { "debug", 'd', POPT_ARG_STRING, &event_data.debug, 0,
549                 "debug level", "ERROR|WARNING|NOTICE|INFO|DEBUG" },
550         POPT_TABLEEND
551 };
552
553 int main(int argc, const char **argv)
554 {
555         TALLOC_CTX *mem_ctx;
556         struct event_tool_context *ctx;
557         int ret, result = 0;
558         bool ok;
559
560         mem_ctx = talloc_new(NULL);
561         if (mem_ctx == NULL) {
562                 fprintf(stderr, "Memory allocation error\n");
563                 exit(1);
564         }
565
566         ret = event_tool_init(mem_ctx,
567                               "ctdb-event",
568                               event_options,
569                               argc,
570                               argv,
571                               true,
572                               &ctx);
573         if (ret != 0) {
574                 talloc_free(mem_ctx);
575                 exit(1);
576         }
577
578         setup_logging("ctdb-event", DEBUG_STDERR);
579         ok = debug_level_parse(event_data.debug, &DEBUGLEVEL);
580         if (!ok) {
581                 DEBUGLEVEL = DEBUG_ERR;
582         }
583
584         ret = event_tool_run(ctx, &result);
585         if (ret != 0) {
586                 exit(1);
587         }
588
589         talloc_free(mem_ctx);
590         exit(result);
591 }
592
593 #endif /* CTDB_EVENT_TOOL */