ctdb-tools: Always print script output in event status
[cs/samba-autobuild/.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             script->output != NULL) {
202                 printf("  OUTPUT: %s\n",
203                        script->output == NULL ? "" : script->output);
204         }
205 }
206
207 static void print_status(const char *component,
208                          const char *event,
209                          int result,
210                          struct ctdb_event_reply_status *status)
211 {
212         int i;
213
214         if (result != 0) {
215                 if (result == ENOENT) {
216                         printf("Event dir for %s does not exist\n", component);
217                 } else if (result == EINVAL) {
218                         printf("Event %s has never run in %s\n",
219                                event,
220                                component);
221                 } else {
222                         printf("Unknown error (%d) for event %s in %s\n",
223                                result,
224                                event,
225                                component);
226                 }
227                 return;
228         }
229
230         for (i=0; i<status->script_list->num_scripts; i++) {
231                 print_status_one(&status->script_list->script[i]);
232         }
233 }
234
235 static int event_command_status(TALLOC_CTX *mem_ctx,
236                                 int argc,
237                                 const char **argv,
238                                 void *private_data)
239 {
240         struct event_tool_context *ctx = talloc_get_type_abort(
241                 private_data, struct event_tool_context);
242         struct tevent_req *req;
243         struct ctdb_event_request_status request_status;
244         struct ctdb_event_reply_status *reply_status;
245         int ret = 0, result = 0;
246         bool ok;
247
248         if (argc != 2) {
249                 cmdline_usage(ctx->cmdline, "status");
250                 return 1;
251         }
252
253         ret = ctdb_event_init(ctx, ctx->ev, &ctx->eclient);
254         if (ret != 0) {
255                 D_ERR("Failed to initialize event client, ret=%d\n", ret);
256                 return ret;
257         }
258
259         request_status.component = argv[0];
260         request_status.event = argv[1];
261
262         req = ctdb_event_status_send(mem_ctx,
263                                      ctx->ev,
264                                      ctx->eclient,
265                                      &request_status);
266         if (req == NULL) {
267                 D_ERR("Memory allocation error\n");
268                 return 1;
269         }
270
271         tevent_req_poll(req, ctx->ev);
272
273         ok = ctdb_event_status_recv(req,
274                                     &ret,
275                                     &result,
276                                     mem_ctx,
277                                     &reply_status);
278         if (!ok) {
279                 D_ERR("Failed to get status for event %s in %s, ret=%d\n",
280                       argv[1],
281                       argv[0],
282                       ret);
283                 return 1;
284         }
285
286         D_NOTICE("Command status finished with result=%d\n", result);
287
288         print_status(argv[0], argv[1], result, reply_status);
289
290         if (reply_status == NULL) {
291                 ret = result;
292         } else {
293                 ret = reply_status->summary;
294                 ret = (ret < 0) ? -ret : ret;
295         }
296         return ret;
297 }
298
299 #define EVENT_SCRIPT_DISABLED      ' '
300 #define EVENT_SCRIPT_ENABLED       '*'
301
302 static int event_command_script_list(TALLOC_CTX *mem_ctx,
303                                      int argc,
304                                      const char **argv,
305                                      void *private_data)
306 {
307         struct event_tool_context *ctx = talloc_get_type_abort(
308                 private_data, struct event_tool_context);
309         char *subdir = NULL;
310         char *data_dir = NULL;
311         char *etc_dir = NULL;
312         char *t = NULL;
313         struct event_script_list *data_list = NULL;
314         struct event_script_list *etc_list = NULL;
315         unsigned int i, j, matched;
316         int ret = 0;
317
318         if (argc != 1) {
319                 cmdline_usage(ctx->cmdline, "script list");
320                 return 1;
321         }
322
323         subdir = talloc_asprintf(mem_ctx, "events/%s", argv[0]);
324         if (subdir == NULL) {
325                 return ENOMEM;
326         }
327
328         data_dir = path_datadir_append(mem_ctx, subdir);
329         if (data_dir == NULL) {
330                 return ENOMEM;
331         }
332
333         t = talloc_size(mem_ctx, PATH_MAX);
334         if (t == NULL) {
335                 return ENOMEM;
336         }
337
338         data_dir = realpath(data_dir, t);
339         if (data_dir == NULL) {
340                 if (errno != ENOENT) {
341                         return errno;
342                 }
343                 D_ERR("Command script list finished with result=%d\n", ENOENT);
344                 return ENOENT;
345         }
346
347         etc_dir = path_etcdir_append(mem_ctx, subdir);
348         if (etc_dir == NULL) {
349                 return ENOMEM;
350         }
351
352         /*
353          * Ignore error on ENOENT for cut down (e.g. fixed/embedded)
354          * installs that don't use symlinks but just populate etc_dir
355          * directly
356          */
357         ret = event_script_get_list(mem_ctx, data_dir, &data_list);
358         if (ret != 0 && ret != ENOENT) {
359                 D_ERR("Command script list finished with result=%d\n", ret);
360                 goto done;
361         }
362
363         ret = event_script_get_list(mem_ctx, etc_dir, &etc_list);
364         if (ret != 0) {
365                 D_ERR("Command script list finished with result=%d\n", ret);
366                 goto done;
367         }
368
369         D_NOTICE("Command script list finished with result=%d\n", ret);
370
371         if (data_list == NULL) {
372                 goto list_enabled_only;
373         }
374
375         /*
376          * First list scripts provided by CTDB.  Flag those that are
377          * enabled via a symlink and arrange for them to be excluded
378          * from the subsequent list of local scripts.
379          *
380          * Both lists are sorted, so walk the list of enabled scripts
381          * only once in this pass.
382          */
383         j = 0;
384         matched = 0;
385         for (i = 0; i < data_list->num_scripts; i++) {
386                 struct event_script *d = data_list->script[i];
387                 char flag = EVENT_SCRIPT_DISABLED;
388                 char buf[PATH_MAX];
389                 ssize_t len;
390
391                 /* Check to see if this script is enabled */
392                 while (j < etc_list->num_scripts) {
393                         struct event_script *e = etc_list->script[j];
394
395                         ret = strcmp(e->name, d->name);
396
397                         if (ret > 0) {
398                                 /*
399                                  * Enabled name is greater, so needs
400                                  * to be considered later: done
401                                  */
402                                 break;
403                         }
404
405                         if (ret < 0) {
406                                 /* Enabled name is less: next */
407                                 j++;
408                                 continue;
409                         }
410
411                         len = readlink(e->path, buf, sizeof(buf));
412                         if (len == -1 || (size_t)len >= sizeof(buf)) {
413                                 /*
414                                  * Not a link?  Disappeared?  Invalid
415                                  * link target?  Something else?
416                                  *
417                                  * Doesn't match provided script: next, done
418                                  */
419                                 j++;
420                                 break;
421                         }
422
423                         /* readlink() does not NUL-terminate */
424                         buf[len] = '\0';
425
426                         ret = strcmp(buf, d->path);
427                         if (ret != 0) {
428                                 /* Enabled link doesn't match: next, done */
429                                 j++;
430                                 break;
431                         }
432
433                         /*
434                          * Enabled script's symlink matches our
435                          * script: flag our script as enabled
436                          *
437                          * Also clear the enabled script so it can be
438                          * trivially skipped in the next pass
439                          */
440                         flag = EVENT_SCRIPT_ENABLED;
441                         TALLOC_FREE(etc_list->script[j]);
442                         j++;
443                         matched++;
444                         break;
445                 }
446
447                 printf("%c %s\n", flag, d->name);
448         }
449
450         /* Print blank line if both provided and local lists are being printed */
451         if (data_list->num_scripts > 0 && matched != etc_list->num_scripts) {
452                 printf("\n");
453         }
454
455 list_enabled_only:
456
457         /* Now print details of local scripts, after a blank line */
458         for (j = 0; j < etc_list->num_scripts; j++) {
459                 struct event_script *e = etc_list->script[j];
460                 char flag = EVENT_SCRIPT_DISABLED;
461
462                 if (e == NULL) {
463                         /* Matched in previous pass: next */
464                         continue;
465                 }
466
467                 /* Script is local: if executable then flag as enabled */
468                 if (e->enabled) {
469                         flag = EVENT_SCRIPT_ENABLED;
470                 }
471
472                 printf("%c %s\n", flag, e->name);
473         }
474
475         ret = 0;
476
477 done:
478         talloc_free(subdir);
479         talloc_free(data_dir);
480         talloc_free(etc_dir);
481         talloc_free(data_list);
482         talloc_free(etc_list);
483
484         return ret;
485 }
486
487 static int event_command_script(TALLOC_CTX *mem_ctx,
488                                 struct event_tool_context *ctx,
489                                 const char *component,
490                                 const char *script,
491                                 bool enable)
492 {
493         char *subdir, *etc_dir;
494         int result = 0;
495
496         subdir = talloc_asprintf(mem_ctx, "events/%s", component);
497         if (subdir == NULL) {
498                 return ENOMEM;
499         }
500
501         etc_dir = path_etcdir_append(mem_ctx, subdir);
502         if (etc_dir == NULL) {
503                 return ENOMEM;
504         }
505
506         if (enable) {
507                 result = event_script_chmod(etc_dir, script, true);
508         } else {
509                 result = event_script_chmod(etc_dir, script, false);
510         }
511
512         talloc_free(subdir);
513         talloc_free(etc_dir);
514
515         D_NOTICE("Command script finished with result=%d\n", result);
516
517         if (result == EINVAL) {
518                 printf("Script %s is invalid in %s\n", script, component);
519         } else if (result == ENOENT) {
520                 printf("Script %s does not exist in %s\n", script, component);
521         }
522
523         return result;
524 }
525
526 static int event_command_script_enable(TALLOC_CTX *mem_ctx,
527                                        int argc,
528                                        const char **argv,
529                                        void *private_data)
530 {
531         struct event_tool_context *ctx = talloc_get_type_abort(
532                 private_data, struct event_tool_context);
533         struct stat statbuf;
534         char *script, *etc_script;
535         int ret;
536
537         if (argc != 2) {
538                 cmdline_usage(ctx->cmdline, "script enable");
539                 return 1;
540         }
541
542         script = talloc_asprintf(mem_ctx, "events/%s/%s.script", argv[0], argv[1]);
543         if (script == NULL) {
544                 return ENOMEM;
545         }
546
547         etc_script = path_etcdir_append(mem_ctx, script);
548         if (etc_script == NULL) {
549                 return ENOMEM;
550         }
551
552         ret = lstat(etc_script, &statbuf);
553         if (ret == 0) {
554                 if (S_ISLNK(statbuf.st_mode)) {
555                         /* Link already exists */
556                         return 0;
557                 } else if (S_ISREG(statbuf.st_mode)) {
558                         return event_command_script(mem_ctx,
559                                                     ctx,
560                                                     argv[0],
561                                                     argv[1],
562                                                     true);
563                 }
564
565                 printf("Script %s is not a file or a link\n", etc_script);
566                 return EINVAL;
567         } else {
568                 if (errno == ENOENT) {
569                         char *t;
570                         char *data_script;
571
572                         data_script = path_datadir_append(mem_ctx, script);
573                         if (data_script == NULL) {
574                                 return ENOMEM;
575                         }
576
577                         t = talloc_size(mem_ctx, PATH_MAX);
578                         if (t == NULL) {
579                                 return ENOMEM;
580                         }
581
582                         data_script = realpath(data_script, t);
583                         if (data_script == NULL) {
584                                 if (errno != ENOENT) {
585                                         return errno;
586                                 }
587                                 printf("Script %s does not exist in %s\n",
588                                        argv[1],
589                                        argv[0]);
590                                 return ENOENT;
591                         }
592
593                         ret = stat(data_script, &statbuf);
594                         if (ret != 0) {
595                                 printf("Script %s does not exist in %s\n",
596                                        argv[1], argv[0]);
597                                 return ENOENT;
598                         }
599
600                         ret = symlink(data_script, etc_script);
601                         if (ret != 0) {
602                                 printf("Failed to create symlink %s\n",
603                                       etc_script);
604                                 return EIO;
605                         }
606
607                         return 0;
608                 }
609
610                 printf("Script %s does not exist\n", etc_script);
611                 return EINVAL;
612         }
613 }
614
615 static int event_command_script_disable(TALLOC_CTX *mem_ctx,
616                                         int argc,
617                                         const char **argv,
618                                         void *private_data)
619 {
620         struct event_tool_context *ctx = talloc_get_type_abort(
621                 private_data, struct event_tool_context);
622         struct stat statbuf;
623         char *script, *etc_script;
624         int ret;
625
626
627         if (argc != 2) {
628                 cmdline_usage(ctx->cmdline, "script disable");
629                 return 1;
630         }
631
632         script = talloc_asprintf(mem_ctx, "events/%s/%s.script", argv[0], argv[1]);
633         if (script == NULL) {
634                 return ENOMEM;
635         }
636
637         etc_script = path_etcdir_append(mem_ctx, script);
638         if (etc_script == NULL) {
639                 return ENOMEM;
640         }
641
642         ret = lstat(etc_script, &statbuf);
643         if (ret == 0) {
644                 if (S_ISLNK(statbuf.st_mode)) {
645                         /* Link exists */
646                         ret = unlink(etc_script);
647                         if (ret != 0) {
648                                 printf("Failed to remove symlink %s\n",
649                                        etc_script);
650                                 return EIO;
651                         }
652
653                         return 0;
654                 } else if (S_ISREG(statbuf.st_mode)) {
655                         return event_command_script(mem_ctx,
656                                                     ctx,
657                                                     argv[0],
658                                                     argv[1],
659                                                     false);
660                 }
661
662                 printf("Script %s is not a file or a link\n", etc_script);
663                 return EINVAL;
664         }
665
666         return 0;
667 }
668
669 struct cmdline_command event_commands[] = {
670         { "run", event_command_run,
671                 "Run an event", "<timeout> <component> <event> <args>" },
672         { "status", event_command_status,
673                 "Get status of an event", "<component> <event>" },
674         { "script list", event_command_script_list,
675                 "List event scripts", "<component>" },
676         { "script enable", event_command_script_enable,
677                 "Enable an event script", "<component> <script>" },
678         { "script disable", event_command_script_disable,
679                 "Disable an event script", "<component> <script>" },
680         CMDLINE_TABLEEND
681 };
682
683 int event_tool_init(TALLOC_CTX *mem_ctx,
684                     const char *prog,
685                     struct poptOption *options,
686                     int argc,
687                     const char **argv,
688                     bool parse_options,
689                     struct event_tool_context **result)
690 {
691         struct event_tool_context *ctx;
692         int ret;
693
694         ctx = talloc_zero(mem_ctx, struct event_tool_context);
695         if (ctx == NULL) {
696                 D_ERR("Memory allocation error\n");
697                 return ENOMEM;
698         }
699
700         ret = cmdline_init(mem_ctx,
701                            prog,
702                            options,
703                            NULL,
704                            event_commands,
705                            &ctx->cmdline);
706         if (ret != 0) {
707                 D_ERR("Failed to initialize cmdline, ret=%d\n", ret);
708                 talloc_free(ctx);
709                 return ret;
710         }
711
712         ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options);
713         if (ret != 0) {
714                 cmdline_usage(ctx->cmdline, NULL);
715                 talloc_free(ctx);
716                 return ret;
717         }
718
719         *result = ctx;
720         return 0;
721 }
722
723 int event_tool_run(struct event_tool_context *ctx, int *result)
724 {
725         int ret;
726
727         ctx->ev = tevent_context_init(ctx);
728         if (ctx->ev == NULL) {
729                 D_ERR("Failed to initialize tevent\n");
730                 return ENOMEM;
731         }
732
733         ret = cmdline_run(ctx->cmdline, ctx, result);
734         return ret;
735 }
736
737 #ifdef CTDB_EVENT_TOOL
738
739 static struct {
740         const char *debug;
741 } event_data = {
742         .debug = "ERROR",
743 };
744
745 struct poptOption event_options[] = {
746         { "debug", 'd', POPT_ARG_STRING, &event_data.debug, 0,
747                 "debug level", "ERROR|WARNING|NOTICE|INFO|DEBUG" },
748         POPT_TABLEEND
749 };
750
751 int main(int argc, const char **argv)
752 {
753         TALLOC_CTX *mem_ctx;
754         struct event_tool_context *ctx;
755         int ret, result = 0;
756         int level;
757         bool ok;
758
759         mem_ctx = talloc_new(NULL);
760         if (mem_ctx == NULL) {
761                 fprintf(stderr, "Memory allocation error\n");
762                 exit(1);
763         }
764
765         ret = event_tool_init(mem_ctx,
766                               "ctdb-event",
767                               event_options,
768                               argc,
769                               argv,
770                               true,
771                               &ctx);
772         if (ret != 0) {
773                 talloc_free(mem_ctx);
774                 exit(1);
775         }
776
777         setup_logging("ctdb-event", DEBUG_STDERR);
778         ok = debug_level_parse(event_data.debug, &level);
779         if (!ok) {
780                 level = DEBUG_ERR;
781         }
782         debuglevel_set(level);
783
784         ret = event_tool_run(ctx, &result);
785         if (ret != 0) {
786                 exit(1);
787         }
788
789         talloc_free(mem_ctx);
790         exit(result);
791 }
792
793 #endif /* CTDB_EVENT_TOOL */