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