vinyl-cache/bin/vinyladm/vinyladm.c
0
/*-
1
 * Copyright (c) 2006 Verdens Gang AS
2
 * Copyright (c) 2006-2026 Varnish Software AS
3
 * All rights reserved.
4
 *
5
 * Author: Cecilie Fritzvold <cecilihf@linpro.no>
6
 *
7
 * SPDX-License-Identifier: BSD-2-Clause
8
 *
9
 * Redistribution and use in source and binary forms, with or without
10
 * modification, are permitted provided that the following conditions
11
 * are met:
12
 * 1. Redistributions of source code must retain the above copyright
13
 *    notice, this list of conditions and the following disclaimer.
14
 * 2. Redistributions in binary form must reproduce the above copyright
15
 *    notice, this list of conditions and the following disclaimer in the
16
 *    documentation and/or other materials provided with the distribution.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
22
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
 * SUCH DAMAGE.
29
 */
30
31
#include "config.h"
32
33
#include <sys/types.h>
34
#include <sys/socket.h>
35
36
#include <stdio.h>
37
38
#if defined(HAVE_EDIT_READLINE_READLINE_H)
39
#  include <edit/readline/readline.h>
40
#elif defined(HAVE_LIBEDIT)
41
#  include <editline/readline.h>
42
#elif defined (HAVE_READLINE_READLINE_H)
43
#  include <readline/readline.h>
44
#  ifdef HAVE_READLINE_HISTORY_H
45
#    include <readline/history.h>
46
#  else
47
#    error missing history.h - this should have got caught in configure
48
#  endif
49
#else
50
#  error missing readline.h - this should have got caught in configure
51
#endif
52
53
#include <math.h>
54
#include <fcntl.h>
55
#include <poll.h>
56
#include <stdint.h>
57
#include <stdlib.h>
58
#include <string.h>
59
#include <unistd.h>
60
61
#include "vdef.h"
62
63
#include "vqueue.h"
64
65
#include "vapi/vsig.h"
66
#include "vapi/vsm.h"
67
#include "vas.h"
68
#include "vcli.h"
69
#include "vin.h"
70
#include "vjsn.h"
71
#include "vtcp.h"
72
73
#define RL_EXIT(status) \
74
        do { \
75
                rl_callback_handler_remove(); \
76
                exit(status); \
77
        } while (0)
78
79
enum pass_mode_e {
80
        pass_script,
81
        pass_interactive,
82
};
83
84
static double timeout = 5;
85
static int p_arg = 0;
86
static int line_sock;
87
88
static void
89 6363
cli_write(int sock, const char *s)
90
{
91
        int i, l;
92
93 6363
        i = vstrlen(s);
94 6363
        l = write (sock, s, i);
95 6363
        if (i == l)
96 6363
                return;
97 0
        perror("Write error CLI socket");
98 0
        RL_EXIT(1);
99 6363
}
100
101
/*
102
 * This function establishes a connection to the specified ip and port and
103
 * sends a command to vinyld. If vinyld returns an OK status, the result
104
 * is printed and 0 returned. Else, an error message is printed and 1 is
105
 * returned
106
 */
107
static int
108 987
cli_sock(const char *T_arg, const char *S_arg)
109
{
110
        int fd;
111
        int sock;
112
        unsigned status;
113 987
        char *answer = NULL;
114
        char buf[CLI_AUTH_RESPONSE_LEN + 1];
115
        const char *err;
116
117 987
        sock = VTCP_open(T_arg, NULL, timeout, &err);
118 987
        if (sock < 0) {
119 21
                fprintf(stderr, "Connection failed (%s): %s\n", T_arg, err);
120 21
                return (-1);
121
        }
122
123 966
        (void)VCLI_ReadResult(sock, &status, &answer, timeout);
124 966
        if (status == CLIS_AUTH) {
125 945
                if (S_arg == NULL) {
126 21
                        fprintf(stderr, "Authentication required\n");
127 21
                        free(answer);
128 21
                        closefd(&sock);
129 21
                        return (-1);
130
                }
131 924
                fd = open(S_arg, O_RDONLY);
132 924
                if (fd < 0) {
133 42
                        fprintf(stderr, "Cannot open \"%s\": %s\n",
134 21
                            S_arg, strerror(errno));
135 21
                        closefd(&sock);
136 21
                        free(answer);
137 21
                        return (-1);
138
                }
139 903
                VCLI_AuthResponse(fd, answer, buf);
140 903
                closefd(&fd);
141 903
                free(answer);
142
143 903
                cli_write(sock, "auth ");
144 903
                cli_write(sock, buf);
145 903
                cli_write(sock, "\n");
146 903
                (void)VCLI_ReadResult(sock, &status, &answer, timeout);
147 903
        }
148 924
        if (status != CLIS_OK && status != CLIS_TRUNCATED) {
149 42
                fprintf(stderr, "Rejected %u\n%s\n", status, answer);
150 42
                closefd(&sock);
151 42
                free(answer);
152 42
                return (-1);
153
        }
154 882
        free(answer);
155
156 882
        cli_write(sock, "ping\n");
157 882
        (void)VCLI_ReadResult(sock, &status, &answer, timeout);
158 882
        if (status != CLIS_OK || strstr(answer, "PONG") == NULL) {
159 21
                fprintf(stderr, "No pong received from server\n");
160 21
                closefd(&sock);
161 21
                free(answer);
162 21
                return (-1);
163
        }
164 861
        free(answer);
165
166 861
        return (sock);
167 987
}
168
169
static unsigned
170 1029
pass_answer(int fd, enum pass_mode_e mode)
171
{
172
        unsigned u, status;
173 1029
        char *answer = NULL;
174
175 1029
        u = VCLI_ReadResult(fd, &status, &answer, timeout);
176 1029
        if (u) {
177 0
                if (status == CLIS_COMMS) {
178 0
                        fprintf(stderr, "%s\n", answer);
179 0
                        RL_EXIT(2);
180 0
                }
181 0
                if (answer)
182 0
                        fprintf(stderr, "%s\n", answer);
183 0
                RL_EXIT(1);
184 0
        }
185
186 1029
        if (p_arg && answer != NULL) {
187 84
                printf("%-3u %-8zu\n%s", status, vstrlen(answer), answer);
188 1029
        } else if (p_arg) {
189 0
                printf("%-3u %-8u\n", status, 0U);
190 0
        } else {
191 945
                if (mode == pass_interactive)
192 168
                        printf("%u\n", status);
193 945
                if (answer != NULL)
194 945
                        printf("%s\n", answer);
195 945
                if (status == CLIS_TRUNCATED)
196 21
                        printf("[response was truncated]\n");
197
        }
198 1029
        free(answer);
199 1029
        (void)fflush(stdout);
200 1029
        return (status);
201
}
202
203
static void v_noreturn_
204 798
do_args(int sock, int argc, char * const *argv)
205
{
206
        int i;
207
        unsigned status;
208
209 1953
        for (i = 0; i < argc; i++) {
210
                /* XXX: We should really CLI-quote these */
211 1155
                if (i > 0)
212 357
                        cli_write(sock, " ");
213 1155
                cli_write(sock, argv[i]);
214 1155
        }
215 798
        cli_write(sock, "\n");
216 798
        status = pass_answer(sock, pass_script);
217 798
        closefd(&sock);
218 798
        if (status == CLIS_OK || status == CLIS_TRUNCATED)
219 651
                exit(0);
220 147
        if (!p_arg)
221 147
                fprintf(stderr, "Command failed with error code %u\n", status);
222 147
        exit(1);
223
}
224
225
/* Callback for readline, doesn't take a private pointer, so we need
226
 * to have a global variable.
227
 */
228
static void v_matchproto_()
229 126
send_line(char *l)
230
{
231 126
        if (l) {
232 126
                cli_write(line_sock, l);
233 126
                cli_write(line_sock, "\n");
234 126
                if (*l)
235 126
                        add_history(l);
236 126
                rl_callback_handler_install("vinyl> ", send_line);
237 126
        } else {
238 0
                RL_EXIT(0);
239
        }
240 126
}
241
242
static char *
243 294
command_generator (const char *text, int state)
244
{
245
        static struct vjsn *jsn_cmds;
246
        static const struct vjsn_val *jv;
247
        struct vjsn_val *jv2;
248
        unsigned u;
249 294
        char *answer = NULL;
250
        const char *err;
251
252 294
        if (!state) {
253 105
                cli_write(line_sock, "help -j\n");
254 105
                u = VCLI_ReadResult(line_sock, NULL, &answer, timeout);
255 105
                if (u) {
256 0
                        free(answer);
257 0
                        return (NULL);
258
                }
259 105
                jsn_cmds = vjsn_parse(answer, &err);
260 105
                free(answer);
261 105
                if (err != NULL)
262 0
                        return (NULL);
263 105
                AN(jsn_cmds);
264 105
                AN(jsn_cmds->value);
265 105
                assert (vjsn_is_array(jsn_cmds->value));
266 105
                jv = VTAILQ_FIRST(&jsn_cmds->value->children);
267 105
                assert (vjsn_is_number(jv));
268 105
                jv = VTAILQ_NEXT(jv, list);
269 105
                assert (vjsn_is_array(jv));
270 105
                jv = VTAILQ_NEXT(jv, list);
271 105
                assert (vjsn_is_number(jv));
272 105
                jv = VTAILQ_NEXT(jv, list);
273 105
        }
274 3990
        while (jv != NULL) {
275 3885
                assert (vjsn_is_object(jv));
276 3885
                jv2 = VTAILQ_FIRST(&jv->children);
277 3885
                AN(jv2);
278 3885
                jv = VTAILQ_NEXT(jv, list);
279 3885
                assert (vjsn_is_string(jv2));
280 3885
                assert (!vstrcmp(jv2->name, "request"));
281 3885
                if (!vstrncmp(text, jv2->value, vstrlen(text)))
282 189
                        return (strdup(jv2->value));
283
        }
284 105
        vjsn_delete(&jsn_cmds);
285 105
        return (NULL);
286 294
}
287
288
static char **
289 105
vinyladm_completion (const char *text, int start, int end)
290
{
291
        char **matches;
292 105
        (void)end;
293 105
        matches = (char **)NULL;
294 105
        if (start == 0)
295 105
                matches = rl_completion_matches(text, command_generator);
296 105
        return (matches);
297
}
298
299
300
/*
301
 * No arguments given, simply pass bytes on stdin/stdout and CLI socket
302
 * Send a "banner" to vinyld, to provoke a welcome message.
303
 */
304
static void v_noreturn_
305 42
interactive(int sock)
306
{
307
        struct pollfd fds[2];
308
        int i;
309
        unsigned status;
310 42
        line_sock = sock;
311 42
        rl_already_prompted = 1;
312 42
        rl_callback_handler_install("vinyl> ", send_line);
313 42
        rl_attempted_completion_function = vinyladm_completion;
314
315 42
        fds[0].fd = sock;
316 42
        fds[0].events = POLLIN;
317 42
        fds[1].fd = 0;
318 42
        fds[1].events = POLLIN;
319
320 42
        cli_write(sock, "banner\n");
321 1113
        while (1) {
322 1113
                i = poll(fds, 2, -1);
323 1113
                if (i == -1 && errno == EINTR) {
324 0
                        continue;
325
                }
326 1113
                assert(i > 0);
327 1113
                if (fds[0].revents & POLLIN) {
328
                        /* Get rid of the prompt, kinda hackish */
329 168
                        printf("\r           \r");
330 168
                        status = pass_answer(fds[0].fd, pass_interactive);
331 168
                        rl_forced_update_display();
332 168
                        if (status == CLIS_CLOSE)
333 42
                                RL_EXIT(0);
334 126
                }
335 1071
                if (fds[1].revents & POLLIN) {
336 945
                        rl_callback_read_char();
337 945
                }
338
        }
339
}
340
341
/*
342
 * No arguments given, simply pass bytes on stdin/stdout and CLI socket
343
 */
344
static void v_noreturn_
345 21
pass(int sock)
346
{
347
        struct pollfd fds[2];
348
        char buf[1024];
349
        int i;
350
        ssize_t n;
351 21
        int busy = 0;
352
353 21
        fds[0].fd = sock;
354 21
        fds[0].events = POLLIN;
355 21
        fds[1].fd = 0;
356 21
        fds[1].events = POLLIN;
357 147
        while (1) {
358 147
                i = poll(fds, 2, -1);
359 147
                if (i == -1 && errno == EINTR) {
360 0
                        continue;
361
                }
362 147
                assert(i > 0);
363 147
                if (fds[0].revents & POLLIN) {
364 63
                        (void)pass_answer(fds[0].fd, pass_script);
365 63
                        busy = 0;
366 63
                        if (fds[1].fd < 0)
367 0
                                RL_EXIT(0);
368 63
                }
369 147
                if (fds[1].revents & POLLIN || fds[1].revents & POLLHUP) {
370 84
                        n = read(fds[1].fd, buf, sizeof buf - 1);
371 84
                        if (n == 0) {
372 21
                                if (!busy)
373 21
                                        RL_EXIT(0);
374 0
                                fds[1].fd = -1;
375 63
                        } else if (n < 0) {
376 0
                                RL_EXIT(0);
377 0
                        } else {
378 63
                                busy = 1;
379 63
                                buf[n] = '\0';
380 63
                                cli_write(sock, buf);
381
                        }
382 63
                }
383
        }
384
}
385
386
387
static void v_noreturn_
388 126
usage(int status)
389
{
390 126
        fprintf(stderr,
391
            "Usage: vinyladm [-h] [-n workdir] [-p] [-S secretfile] "
392
            "[-T [address]:port] [-t timeout] [command [...]]\n");
393 126
        fprintf(stderr,
394
            "       vinyladm [-n name] -x workdir\n");
395 126
        fprintf(stderr, "\t-n is mutually exclusive with -S and -T\n");
396 126
        exit(status);
397
}
398
399
static int
400 882
n_arg_sock(const char *n_arg, const char *t_arg)
401
{
402
        char *T_arg, *T_start;
403
        char *S_arg;
404
        struct vsm *vsm;
405
        char *p;
406
        int sock;
407
408 882
        vsm = VSM_New();
409 882
        AN(vsm);
410 1743
        if (VSM_Arg(vsm, 'n', n_arg) < 0 ||
411 882
            VSM_Arg(vsm, 't', t_arg) < 0 ||
412 861
            VSM_Attach(vsm, STDERR_FILENO) < 0) {
413 42
                fprintf(stderr, "%s\n", VSM_Error(vsm));
414 42
                VSM_Destroy(&vsm);
415 42
                return (-1);
416
        }
417
418 840
        T_start = T_arg = VSM_Dup(vsm, "Arg", "-T");
419 840
        S_arg = VSM_Dup(vsm, "Arg", "-S");
420 840
        VSM_Destroy(&vsm);
421
422 840
        if (T_arg == NULL) {
423 0
                fprintf(stderr, "No -T in shared memory\n");
424 0
                return (-1);
425
        }
426
427 840
        sock = -1;
428 840
        while (*T_arg) {
429 840
                p = strchr(T_arg, '\n');
430 840
                AN(p);
431 840
                *p = '\0';
432 840
                sock = cli_sock(T_arg, S_arg);
433 840
                if (sock >= 0)
434 840
                        break;
435 0
                T_arg = p + 1;
436
        }
437 840
        free(T_start);
438 840
        free(S_arg);
439 840
        return (sock);
440 882
}
441
442
static int
443 63
t_arg_timeout(const char *t_arg)
444
{
445 63
        char *p = NULL;
446
447 63
        AN(t_arg);
448 63
        timeout = strtod(t_arg, &p);
449 126
        if ((p != NULL && *p != '\0') ||
450 63
            !isfinite(timeout) || timeout < 0) {
451 0
                fprintf(stderr, "-t: Invalid argument: %s", t_arg);
452 0
                return (-1);
453
        }
454 63
        return (1);
455 63
}
456
457
#define OPTARG "hn:pS:T:t:x:"
458
459
int
460 1218
main(int argc, char * const *argv)
461
{
462
        char *wd;
463 1218
        const char *T_arg = NULL;
464 1218
        const char *S_arg = NULL;
465 1218
        const char *n_arg = NULL;
466 1218
        const char *t_arg = NULL;
467
        int opt, sock;
468
469 1218
        if (argc == 2 && !vstrcmp(argv[1], "--optstring")) {
470 0
                printf(OPTARG "\n");
471 0
                exit(0);
472
        }
473
        /*
474
         * By default linux::getopt(3) mangles the argv order, such that
475
         *      vinyladm -n bla param.set foo -bar
476
         * gets interpreted as
477
         *      vinyladm -n bla -bar param.set foo
478
         * The '+' stops that from happening
479
         * See #1496
480
         */
481 2520
        while ((opt = getopt(argc, argv, "+" OPTARG)) != -1) {
482 1491
                switch (opt) {
483
                case 'h':
484
                        /* Usage help */
485 21
                        usage(0);
486
                case 'n':
487 882
                        n_arg = optarg;
488 882
                        break;
489
                case 'p':
490 42
                        p_arg = 1;
491 42
                        break;
492
                case 'S':
493 126
                        S_arg = optarg;
494 126
                        break;
495
                case 'T':
496 147
                        T_arg = optarg;
497 147
                        break;
498
                case 't':
499 105
                        t_arg = optarg;
500 105
                        break;
501
                case 'x':
502 105
                        AN(optarg);
503 105
                        if (vstrcmp(optarg, "workdir")) {
504 21
                                fprintf(stderr, "Invalid -x argument\n");
505 21
                                usage(1);
506
                        }
507 84
                        if (argc != optind) {
508 21
                                fprintf(stderr, "-x workdir must be the last argument\n");
509 21
                                usage(1);
510
                        }
511 63
                        wd = VIN_n_Arg(n_arg);
512 63
                        AN(wd);
513 63
                        printf("%s\n", wd);
514 63
                        free(wd);
515 63
                        exit(0);
516
                default:
517 63
                        usage(1);
518
                }
519
        }
520
521 1029
        argc -= optind;
522 1029
        argv += optind;
523
524 1029
        if (T_arg != NULL) {
525 147
                if (n_arg != NULL)
526 0
                        usage(1);
527 147
                sock = cli_sock(T_arg, S_arg);
528 147
        } else {
529 882
                if (S_arg != NULL)
530 0
                        usage(1);
531 882
                sock = n_arg_sock(n_arg, t_arg);
532
        }
533 1029
        if (sock < 0)
534 168
                exit(2);
535
536 861
        if (t_arg != NULL && t_arg_timeout(t_arg) < 0)
537 0
                exit(2);
538
539 861
        if (argc > 0) {
540 798
                VSIG_Arm_int();
541 798
                VSIG_Arm_term();
542 798
                do_args(sock, argc, argv);
543
                NEEDLESS(exit(0));
544
        }
545
546 63
        if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && !p_arg)
547 42
                interactive(sock);
548
        else
549 21
                pass(sock);
550
        NEEDLESS(exit(0));
551
}