ctdb-eventd: Fix CID 1438155
[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 #include "common/event_script.h"
34
35 #include "event/event_protocol_api.h"
36 #include "event/event.h"
37 #include "event/event_tool.h"
38
39 struct event_tool_context {
40         struct cmdline_context *cmdline;
41         struct tevent_context *ev;
42         struct ctdb_event_context *eclient;
43 };
44
45 static int compact_args(TALLOC_CTX *mem_ctx,
46                         const char **argv,
47                         int argc,
48                         int from,
49                         const char **result)
50 {
51         char *arg_str;
52         int i;
53
54         if (argc <= from) {
55                 *result = NULL;
56                 return 0;
57         }
58
59         arg_str = talloc_strdup(mem_ctx, argv[from]);
60         if (arg_str == NULL) {
61                 return ENOMEM;
62         }
63
64         for (i = from+1; i < argc; i++) {
65                 arg_str = talloc_asprintf_append(arg_str, " %s", argv[i]);
66                 if (arg_str == NULL) {
67                         return ENOMEM;
68                 }
69         }
70
71         *result = arg_str;
72         return 0;
73 }
74
75 static int event_command_run(TALLOC_CTX *mem_ctx,
76                              int argc,
77                              const char **argv,
78                              void *private_data)
79 {
80         struct event_tool_context *ctx = talloc_get_type_abort(
81                 private_data, struct event_tool_context);
82         struct tevent_req *req;
83         struct ctdb_event_request_run request_run;
84         const char *arg_str = NULL;
85         const char *t;
86         int timeout, ret = 0, result = 0;
87         bool ok;
88
89         if (argc < 3) {
90                 cmdline_usage(ctx->cmdline, "run");
91                 return 1;
92         }
93
94         ret = ctdb_event_init(ctx, ctx->ev, &ctx->eclient);
95         if (ret != 0) {
96                 D_ERR("Failed to initialize event client, ret=%d\n", ret);
97                 return ret;
98         }
99
100         timeout = atoi(argv[0]);
101         if (timeout < 0) {
102                 timeout = 0;
103         }
104
105         ret = compact_args(mem_ctx, argv, argc, 3, &arg_str);
106         if (ret != 0) {
107                 D_ERR("Memory allocation error\n");
108                 return 1;
109         }
110
111         request_run.component = argv[1];
112         request_run.event = argv[2];
113         request_run.args = arg_str;
114         request_run.timeout = timeout;
115         request_run.flags = 0;
116
117         t = getenv("CTDB_TEST_MODE");
118         if (t != NULL) {
119                 t = getenv("CTDB_EVENT_RUN_ALL");
120                 if (t != NULL) {
121                         request_run.flags = CTDB_EVENT_RUN_ALL;
122                 }
123         }
124
125         req = ctdb_event_run_send(mem_ctx,
126                                   ctx->ev,
127                                   ctx->eclient,
128                                   &request_run);
129         if (req == NULL) {
130                 D_ERR("Memory allocation error\n");
131                 return 1;
132         }
133
134         tevent_req_poll(req, ctx->ev);
135
136         ok = ctdb_event_run_recv(req, &ret, &result);
137         if (!ok) {
138                 D_ERR("Failed to run event %s in %s, ret=%d\n",
139                       argv[2],
140                       argv[1],
141                       ret);
142                 return 1;
143         }
144
145         D_NOTICE("Command run finished with result=%d\n", result);
146
147         if (result == ENOENT) {
148                 printf("Event dir for %s does not exist\n", argv[1]);
149         } else if (result == ETIMEDOUT) {
150                 printf("Event %s in %s timed out\n", argv[2], argv[1]);
151         } else if (result == ECANCELED) {
152                 printf("Event %s in %s got cancelled\n", argv[2], argv[1]);
153         } else if (result == ENOEXEC) {
154                 printf("Event %s in %s failed\n", argv[2], argv[1]);
155         } else if (result != 0) {
156                 printf("Failed to run event %s in %s, result=%d\n",
157                        argv[2],
158                        argv[1],
159                        result);
160         }
161
162         ret = (result < 0) ? -result : result;
163         return ret;
164 }
165
166 static double timeval_delta(struct timeval *tv2, struct timeval *tv)
167 {
168         return (tv2->tv_sec - tv->tv_sec) +
169                (tv2->tv_usec - tv->tv_usec) * 1.0e-6;
170 }
171
172 static void print_status_one(struct ctdb_event_script *script)
173 {
174         if (script->result == -ETIMEDOUT) {
175                 printf("%-20s %-10s %s",
176                        script->name,
177                        "TIMEDOUT",
178                        ctime(&script->begin.tv_sec));
179         } else if (script->result == -ENOEXEC) {
180                 printf("%-20s %-10s\n", script->name, "DISABLED");
181         } else if (script->result < 0) {
182                 printf("%-20s %-10s (%s)\n",
183                        script->name,
184                        "CANNOT RUN",
185                        strerror(-script->result));
186         } else if (script->result == 0) {
187                 printf("%-20s %-10s %.3lf %s",
188                        script->name,
189                        "OK",
190                        timeval_delta(&script->end, &script->begin),
191                        ctime(&script->begin.tv_sec));
192         } else {
193                 printf("%-20s %-10s %.3lf %s",
194                        script->name,
195                        "ERROR",
196                        timeval_delta(&script->end, &script->begin),
197                        ctime(&script->begin.tv_sec));
198         }
199
200         if (script->result != 0 && script->result != -ENOEXEC) {
201                 printf("  OUTPUT: %s\n",
202                        script->output == NULL ? "" : script->output);
203         }
204 }
205
206 static void print_status(const char *component,
207                          const char *event,
208                          int result,
209                          struct ctdb_event_reply_status *status)
210 {
211         int i;
212
213         if (result != 0) {
214                 if (result == ENOENT) {
215                         printf("Event dir for %s does not exist\n", component);
216                 } else if (result == EINVAL) {
217                         printf("Event %s has never run in %s\n",
218                                event,
219                                component);
220                 } else {
221                         printf("Unknown error (%d) for event %s in %s\n",
222                                result,
223                                event,
224                                component);
225                 }
226                 return;
227         }
228
229         for (i=0; i<status->script_list->num_scripts; i++) {
230                 print_status_one(&status->script_list->script[i]);
231         }
232 }
233
234 static int event_command_status(TALLOC_CTX *mem_ctx,
235                                 int argc,
236                                 const char **argv,
237                                 void *private_data)
238 {
239         struct event_tool_context *ctx = talloc_get_type_abort(
240                 private_data, struct event_tool_context);
241         struct tevent_req *req;
242         struct ctdb_event_request_status request_status;
243         struct ctdb_event_reply_status *reply_status;
244         int ret = 0, result = 0;
245         bool ok;
246
247         if (argc != 2) {
248                 cmdline_usage(ctx->cmdline, "status");
249                 return 1;
250         }
251
252         ret = ctdb_event_init(ctx, ctx->ev, &ctx->eclient);
253         if (ret != 0) {
254                 D_ERR("Failed to initialize event client, ret=%d\n", ret);
255                 return ret;
256         }
257
258         request_status.component = argv[0];
259         request_status.event = argv[1];
260
261         req = ctdb_event_status_send(mem_ctx,
262                                      ctx->ev,
263                                      ctx->eclient,
264                                      &request_status);
265         if (req == NULL) {
266                 D_ERR("Memory allocation error\n");
267                 return 1;
268         }
269
270         tevent_req_poll(req, ctx->ev);
271
272         ok = ctdb_event_status_recv(req,
273                                     &ret,
274                                     &result,
275                                     mem_ctx,
276                                     &reply_status);
277         if (!ok) {
278                 D_ERR("Failed to get status for event %s in %s, ret=%d\n",
279                       argv[1],
280                       argv[0],
281                       ret);
282                 return 1;
283         }
284
285         D_NOTICE("Command status finished with result=%d\n", result);
286
287         print_status(argv[0], argv[1], result, reply_status);
288
289         if (reply_status == NULL) {
290                 ret = result;
291         } else {
292                 ret = reply_status->summary;
293                 ret = (ret < 0) ? -ret : ret;
294         }
295         return ret;
296 }
297
298 #define EVENT_SCRIPT_DISABLED      ' '
299 #define EVENT_SCRIPT_ENABLED       '*'
300
301 static int event_command_script_list(TALLOC_CTX *mem_ctx,
302                                      int argc,
303                                      const char **argv,
304                                      void *private_data)
305 {
306         struct event_tool_context *ctx = talloc_get_type_abort(
307                 private_data, struct event_tool_context);
308         char *subdir = NULL;
309         char *data_dir = NULL;
310         char *etc_dir = NULL;
311         struct event_script_list *data_list = NULL;
312         struct event_script_list *etc_list = NULL;
313         unsigned int i, j, matched;
314         int ret = 0;
315
316         if (argc != 1) {
317                 cmdline_usage(ctx->cmdline, "script list");
318                 return 1;
319         }
320
321         subdir = talloc_asprintf(mem_ctx, "events/%s", argv[0]);
322         if (subdir == NULL) {
323                 return ENOMEM;
324         }
325
326         data_dir = path_datadir_append(mem_ctx, subdir);
327         if (data_dir == NULL) {
328                 return ENOMEM;
329         }
330
331         etc_dir = path_etcdir_append(mem_ctx, subdir);
332         if (etc_dir == NULL) {
333                 return ENOMEM;
334         }
335
336         /*
337          * Ignore error on ENOENT for cut down (e.g. fixed/embedded)
338          * installs that don't use symlinks but just populate etc_dir
339          * directly
340          */
341         ret = event_script_get_list(mem_ctx, data_dir, &data_list);
342         if (ret != 0 && ret != ENOENT) {
343                 D_ERR("Command script list finished with result=%d\n", ret);
344                 goto done;
345         }
346
347         ret = event_script_get_list(mem_ctx, etc_dir, &etc_list);
348         if (ret != 0) {
349                 D_ERR("Command script list finished with result=%d\n", ret);
350                 goto done;
351         }
352
353         D_NOTICE("Command script list finished with result=%d\n", ret);
354
355         if (data_list == NULL) {
356                 goto list_enabled_only;
357         }
358
359         /*
360          * First list scripts provided by CTDB.  Flag those that are
361          * enabled via a symlink and arrange for them to be excluded
362          * from the subsequent list of local scripts.
363          *
364          * Both lists are sorted, so walk the list of enabled scripts
365          * only once in this pass.
366          */
367         j = 0;
368         matched = 0;
369         for (i = 0; i < data_list->num_scripts; i++) {
370                 struct event_script *d = data_list->script[i];
371                 char flag = EVENT_SCRIPT_DISABLED;
372                 char buf[PATH_MAX];
373                 ssize_t len;
374
375                 /* Check to see if this script is enabled */
376                 while (j < etc_list->num_scripts) {
377                         struct event_script *e = etc_list->script[j];
378
379                         ret = strcmp(e->name, d->name);
380
381                         if (ret > 0) {
382                                 /*
383                                  * Enabled name is greater, so needs
384                                  * to be considered later: done
385                                  */
386                                 break;
387                         }
388
389                         if (ret < 0) {
390                                 /* Enabled name is less: next */
391                                 j++;
392                                 continue;
393                         }
394
395                         len = readlink(e->path, buf, sizeof(buf));
396                         if (len == -1 || len >= sizeof(buf)) {
397                                 /*
398                                  * Not a link?  Disappeared?  Invalid
399                                  * link target?  Something else?
400                                  *
401                                  * Doesn't match provided script: next, done
402                                  */
403                                 j++;
404                                 break;
405                         }
406
407                         /* readlink() does not NUL-terminate */
408                         buf[len] = '\0';
409
410                         ret = strcmp(buf, d->path);
411                         if (ret != 0) {
412                                 /* Enabled link doesn't match: next, done */
413                                 j++;
414                                 break;
415                         }
416
417                         /*
418                          * Enabled script's symlink matches our
419                          * script: flag our script as enabled
420                          *
421                          * Also clear the enabled script so it can be
422                          * trivially skipped in the next pass
423                          */
424                         flag = EVENT_SCRIPT_ENABLED;
425                         TALLOC_FREE(etc_list->script[j]);
426                         j++;
427                         matched++;
428                         break;
429                 }
430
431                 printf("%c %s\n", flag, d->name);
432         }
433
434         /* Print blank line if both provided and local lists are being printed */
435         if (data_list->num_scripts > 0 && matched != etc_list->num_scripts) {
436                 printf("\n");
437         }
438
439 list_enabled_only:
440
441         /* Now print details of local scripts, after a blank line */
442         for (j = 0; j < etc_list->num_scripts; j++) {
443                 struct event_script *e = etc_list->script[j];
444                 char flag = EVENT_SCRIPT_DISABLED;
445
446                 if (e == NULL) {
447                         /* Matched in previous pass: next */
448                         continue;
449                 }
450
451                 /* Script is local: if executable then flag as enabled */
452                 if (e->enabled) {
453                         flag = EVENT_SCRIPT_ENABLED;
454                 }
455
456                 printf("%c %s\n", flag, e->name);
457         }
458
459         ret = 0;
460
461 done:
462         talloc_free(subdir);
463         talloc_free(data_dir);
464         talloc_free(etc_dir);
465         talloc_free(data_list);
466         talloc_free(etc_list);
467
468         return ret;
469 }
470
471 static int event_command_script(TALLOC_CTX *mem_ctx,
472                                 struct event_tool_context *ctx,
473                                 const char *component,
474                                 const char *script,
475                                 bool enable)
476 {
477         char *subdir, *etc_dir;
478         int result = 0;
479
480         subdir = talloc_asprintf(mem_ctx, "events/%s", component);
481         if (subdir == NULL) {
482                 return ENOMEM;
483         }
484
485         etc_dir = path_etcdir_append(mem_ctx, subdir);
486         if (etc_dir == NULL) {
487                 return ENOMEM;
488         }
489
490         if (enable) {
491                 result = event_script_chmod(etc_dir, script, true);
492         } else {
493                 result = event_script_chmod(etc_dir, script, false);
494         }
495
496         talloc_free(subdir);
497         talloc_free(etc_dir);
498
499         D_NOTICE("Command script finished with result=%d\n", result);
500
501         if (result == EINVAL) {
502                 printf("Script %s is invalid in %s\n", script, component);
503         } else if (result == ENOENT) {
504                 printf("Script %s does not exist in %s\n", script, component);
505         }
506
507         return result;
508 }
509
510 static int event_command_script_enable(TALLOC_CTX *mem_ctx,
511                                        int argc,
512                                        const char **argv,
513                                        void *private_data)
514 {
515         struct event_tool_context *ctx = talloc_get_type_abort(
516                 private_data, struct event_tool_context);
517         struct stat statbuf;
518         char *script, *etc_script, *data_script;
519         int ret;
520
521         if (argc != 2) {
522                 cmdline_usage(ctx->cmdline, "script enable");
523                 return 1;
524         }
525
526         script = talloc_asprintf(mem_ctx, "events/%s/%s.script", argv[0], argv[1]);
527         if (script == NULL) {
528                 return ENOMEM;
529         }
530
531         etc_script = path_etcdir_append(mem_ctx, script);
532         if (etc_script == NULL) {
533                 return ENOMEM;
534         }
535
536         data_script = path_datadir_append(mem_ctx, script);
537         if (data_script == NULL) {
538                 return ENOMEM;
539         }
540
541         ret = lstat(etc_script, &statbuf);
542         if (ret == 0) {
543                 if (S_ISLNK(statbuf.st_mode)) {
544                         /* Link already exists */
545                         return 0;
546                 } else if (S_ISREG(statbuf.st_mode)) {
547                         return event_command_script(mem_ctx,
548                                                     ctx,
549                                                     argv[0],
550                                                     argv[1],
551                                                     true);
552                 }
553
554                 printf("Script %s is not a file or a link\n", etc_script);
555                 return EINVAL;
556         } else {
557                 if (errno == ENOENT) {
558                         ret = stat(data_script, &statbuf);
559                         if (ret != 0) {
560                                 printf("Script %s does not exist in %s\n",
561                                        argv[1], argv[0]);
562                                 return ENOENT;
563                         }
564
565                         ret = symlink(data_script, etc_script);
566                         if (ret != 0) {
567                                 printf("Failed to create symlink %s\n",
568                                       etc_script);
569                                 return EIO;
570                         }
571
572                         return 0;
573                 }
574
575                 printf("Script %s does not exist\n", etc_script);
576                 return EINVAL;
577         }
578 }
579
580 static int event_command_script_disable(TALLOC_CTX *mem_ctx,
581                                         int argc,
582                                         const char **argv,
583                                         void *private_data)
584 {
585         struct event_tool_context *ctx = talloc_get_type_abort(
586                 private_data, struct event_tool_context);
587         struct stat statbuf;
588         char *script, *etc_script;
589         int ret;
590
591
592         if (argc != 2) {
593                 cmdline_usage(ctx->cmdline, "script disable");
594                 return 1;
595         }
596
597         script = talloc_asprintf(mem_ctx, "events/%s/%s.script", argv[0], argv[1]);
598         if (script == NULL) {
599                 return ENOMEM;
600         }
601
602         etc_script = path_etcdir_append(mem_ctx, script);
603         if (etc_script == NULL) {
604                 return ENOMEM;
605         }
606
607         ret = lstat(etc_script, &statbuf);
608         if (ret == 0) {
609                 if (S_ISLNK(statbuf.st_mode)) {
610                         /* Link exists */
611                         ret = unlink(etc_script);
612                         if (ret != 0) {
613                                 printf("Failed to remove symlink %s\n",
614                                        etc_script);
615                                 return EIO;
616                         }
617
618                         return 0;
619                 } else if (S_ISREG(statbuf.st_mode)) {
620                         return event_command_script(mem_ctx,
621                                                     ctx,
622                                                     argv[0],
623                                                     argv[1],
624                                                     false);
625                 }
626
627                 printf("Script %s is not a file or a link\n", etc_script);
628                 return EINVAL;
629         }
630
631         return 0;
632 }
633
634 struct cmdline_command event_commands[] = {
635         { "run", event_command_run,
636                 "Run an event", "<timeout> <component> <event> <args>" },
637         { "status", event_command_status,
638                 "Get status of an event", "<component> <event>" },
639         { "script list", event_command_script_list,
640                 "List event scripts", "<component>" },
641         { "script enable", event_command_script_enable,
642                 "Enable an event script", "<component> <script>" },
643         { "script disable", event_command_script_disable,
644                 "Disable an event script", "<component> <script>" },
645         CMDLINE_TABLEEND
646 };
647
648 int event_tool_init(TALLOC_CTX *mem_ctx,
649                     const char *prog,
650                     struct poptOption *options,
651                     int argc,
652                     const char **argv,
653                     bool parse_options,
654                     struct event_tool_context **result)
655 {
656         struct event_tool_context *ctx;
657         int ret;
658
659         ctx = talloc_zero(mem_ctx, struct event_tool_context);
660         if (ctx == NULL) {
661                 D_ERR("Memory allocation error\n");
662                 return ENOMEM;
663         }
664
665         ret = cmdline_init(mem_ctx,
666                            prog,
667                            options,
668                            event_commands,
669                            &ctx->cmdline);
670         if (ret != 0) {
671                 D_ERR("Failed to initialize cmdline, ret=%d\n", ret);
672                 talloc_free(ctx);
673                 return ret;
674         }
675
676         ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options);
677         if (ret != 0) {
678                 cmdline_usage(ctx->cmdline, NULL);
679                 talloc_free(ctx);
680                 return ret;
681         }
682
683         *result = ctx;
684         return 0;
685 }
686
687 int event_tool_run(struct event_tool_context *ctx, int *result)
688 {
689         int ret;
690
691         ctx->ev = tevent_context_init(ctx);
692         if (ctx->ev == NULL) {
693                 D_ERR("Failed to initialize tevent\n");
694                 return ENOMEM;
695         }
696
697         ret = cmdline_run(ctx->cmdline, ctx, result);
698         return ret;
699 }
700
701 #ifdef CTDB_EVENT_TOOL
702
703 static struct {
704         const char *debug;
705 } event_data = {
706         .debug = "ERROR",
707 };
708
709 struct poptOption event_options[] = {
710         { "debug", 'd', POPT_ARG_STRING, &event_data.debug, 0,
711                 "debug level", "ERROR|WARNING|NOTICE|INFO|DEBUG" },
712         POPT_TABLEEND
713 };
714
715 int main(int argc, const char **argv)
716 {
717         TALLOC_CTX *mem_ctx;
718         struct event_tool_context *ctx;
719         int ret, result = 0;
720         bool ok;
721
722         mem_ctx = talloc_new(NULL);
723         if (mem_ctx == NULL) {
724                 fprintf(stderr, "Memory allocation error\n");
725                 exit(1);
726         }
727
728         ret = event_tool_init(mem_ctx,
729                               "ctdb-event",
730                               event_options,
731                               argc,
732                               argv,
733                               true,
734                               &ctx);
735         if (ret != 0) {
736                 talloc_free(mem_ctx);
737                 exit(1);
738         }
739
740         setup_logging("ctdb-event", DEBUG_STDERR);
741         ok = debug_level_parse(event_data.debug, &DEBUGLEVEL);
742         if (!ok) {
743                 DEBUGLEVEL = DEBUG_ERR;
744         }
745
746         ret = event_tool_run(ctx, &result);
747         if (ret != 0) {
748                 exit(1);
749         }
750
751         talloc_free(mem_ctx);
752         exit(result);
753 }
754
755 #endif /* CTDB_EVENT_TOOL */