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 1188
cli_write(int sock, const char *s)
90
{
91
        int i, l;
92
93 1188
        i = vstrlen(s);
94 1188
        l = write (sock, s, i);
95 1188
        if (i == l)
96 1188
                return;
97 0
        perror("Write error CLI socket");
98 0
        RL_EXIT(1);
99 1188
}
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 184
cli_sock(const char *T_arg, const char *S_arg)
109
{
110
        int fd;
111
        int sock;
112
        unsigned status;
113 184
        char *answer = NULL;
114
        char buf[CLI_AUTH_RESPONSE_LEN + 1];
115
        const char *err;
116
117 184
        sock = VTCP_open(T_arg, NULL, timeout, &err);
118 184
        if (sock < 0) {
119 4
                fprintf(stderr, "Connection failed (%s): %s\n", T_arg, err);
120 4
                return (-1);
121
        }
122
123 180
        (void)VCLI_ReadResult(sock, &status, &answer, timeout);
124 180
        if (status == CLIS_AUTH) {
125 176
                if (S_arg == NULL) {
126 4
                        fprintf(stderr, "Authentication required\n");
127 4
                        free(answer);
128 4
                        closefd(&sock);
129 4
                        return (-1);
130
                }
131 172
                fd = open(S_arg, O_RDONLY);
132 172
                if (fd < 0) {
133 8
                        fprintf(stderr, "Cannot open \"%s\": %s\n",
134 4
                            S_arg, strerror(errno));
135 4
                        closefd(&sock);
136 4
                        free(answer);
137 4
                        return (-1);
138
                }
139 168
                VCLI_AuthResponse(fd, answer, buf);
140 168
                closefd(&fd);
141 168
                free(answer);
142
143 168
                cli_write(sock, "auth ");
144 168
                cli_write(sock, buf);
145 168
                cli_write(sock, "\n");
146 168
                (void)VCLI_ReadResult(sock, &status, &answer, timeout);
147 168
        }
148 172
        if (status != CLIS_OK && status != CLIS_TRUNCATED) {
149 8
                fprintf(stderr, "Rejected %u\n%s\n", status, answer);
150 8
                closefd(&sock);
151 8
                free(answer);
152 8
                return (-1);
153
        }
154 164
        free(answer);
155
156 164
        cli_write(sock, "ping\n");
157 164
        (void)VCLI_ReadResult(sock, &status, &answer, timeout);
158 164
        if (status != CLIS_OK || strstr(answer, "PONG") == NULL) {
159 4
                fprintf(stderr, "No pong received from server\n");
160 4
                closefd(&sock);
161 4
                free(answer);
162 4
                return (-1);
163
        }
164 160
        free(answer);
165
166 160
        return (sock);
167 184
}
168
169
static unsigned
170 192
pass_answer(int fd, enum pass_mode_e mode)
171
{
172
        unsigned u, status;
173 192
        char *answer = NULL;
174
175 192
        u = VCLI_ReadResult(fd, &status, &answer, timeout);
176 192
        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 192
        if (p_arg && answer != NULL) {
187 16
                printf("%-3u %-8zu\n%s", status, vstrlen(answer), answer);
188 192
        } else if (p_arg) {
189 0
                printf("%-3u %-8u\n", status, 0U);
190 0
        } else {
191 176
                if (mode == pass_interactive)
192 32
                        printf("%u\n", status);
193 176
                if (answer != NULL)
194 176
                        printf("%s\n", answer);
195 176
                if (status == CLIS_TRUNCATED)
196 4
                        printf("[response was truncated]\n");
197
        }
198 192
        free(answer);
199 192
        (void)fflush(stdout);
200 192
        return (status);
201
}
202
203
static void v_noreturn_
204 148
do_args(int sock, int argc, char * const *argv)
205
{
206
        int i;
207
        unsigned status;
208
209 364
        for (i = 0; i < argc; i++) {
210
                /* XXX: We should really CLI-quote these */
211 216
                if (i > 0)
212 68
                        cli_write(sock, " ");
213 216
                cli_write(sock, argv[i]);
214 216
        }
215 148
        cli_write(sock, "\n");
216 148
        status = pass_answer(sock, pass_script);
217 148
        closefd(&sock);
218 148
        if (status == CLIS_OK || status == CLIS_TRUNCATED)
219 116
                exit(0);
220 32
        if (!p_arg)
221 32
                fprintf(stderr, "Command failed with error code %u\n", status);
222 32
        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 24
send_line(char *l)
230
{
231 24
        if (l) {
232 24
                cli_write(line_sock, l);
233 24
                cli_write(line_sock, "\n");
234 24
                if (*l)
235 24
                        add_history(l);
236 24
                rl_callback_handler_install("vinyl> ", send_line);
237 24
        } else {
238 0
                RL_EXIT(0);
239
        }
240 24
}
241
242
static char *
243 56
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 56
        char *answer = NULL;
250
        const char *err;
251
252 56
        if (!state) {
253 20
                cli_write(line_sock, "help -j\n");
254 20
                u = VCLI_ReadResult(line_sock, NULL, &answer, timeout);
255 20
                if (u) {
256 0
                        free(answer);
257 0
                        return (NULL);
258
                }
259 20
                jsn_cmds = vjsn_parse(answer, &err);
260 20
                free(answer);
261 20
                if (err != NULL)
262 0
                        return (NULL);
263 20
                AN(jsn_cmds);
264 20
                AN(jsn_cmds->value);
265 20
                assert (vjsn_is_array(jsn_cmds->value));
266 20
                jv = VTAILQ_FIRST(&jsn_cmds->value->children);
267 20
                assert (vjsn_is_number(jv));
268 20
                jv = VTAILQ_NEXT(jv, list);
269 20
                assert (vjsn_is_array(jv));
270 20
                jv = VTAILQ_NEXT(jv, list);
271 20
                assert (vjsn_is_number(jv));
272 20
                jv = VTAILQ_NEXT(jv, list);
273 20
        }
274 780
        while (jv != NULL) {
275 760
                assert (vjsn_is_object(jv));
276 760
                jv2 = VTAILQ_FIRST(&jv->children);
277 760
                AN(jv2);
278 760
                jv = VTAILQ_NEXT(jv, list);
279 760
                assert (vjsn_is_string(jv2));
280 760
                assert (!vstrcmp(jv2->name, "request"));
281 760
                if (!strncmp(text, jv2->value, vstrlen(text)))
282 36
                        return (strdup(jv2->value));
283
        }
284 20
        vjsn_delete(&jsn_cmds);
285 20
        return (NULL);
286 56
}
287
288
static char **
289 20
vinyladm_completion (const char *text, int start, int end)
290
{
291
        char **matches;
292 20
        (void)end;
293 20
        matches = (char **)NULL;
294 20
        if (start == 0)
295 20
                matches = rl_completion_matches(text, command_generator);
296 20
        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 8
interactive(int sock)
306
{
307
        struct pollfd fds[2];
308
        int i;
309
        unsigned status;
310 8
        line_sock = sock;
311 8
        rl_already_prompted = 1;
312 8
        rl_callback_handler_install("vinyl> ", send_line);
313 8
        rl_attempted_completion_function = vinyladm_completion;
314
315 8
        fds[0].fd = sock;
316 8
        fds[0].events = POLLIN;
317 8
        fds[1].fd = 0;
318 8
        fds[1].events = POLLIN;
319
320 8
        cli_write(sock, "banner\n");
321 212
        while (1) {
322 212
                i = poll(fds, 2, -1);
323 212
                if (i == -1 && errno == EINTR) {
324 0
                        continue;
325
                }
326 212
                assert(i > 0);
327 212
                if (fds[0].revents & POLLIN) {
328
                        /* Get rid of the prompt, kinda hackish */
329 32
                        printf("\r           \r");
330 32
                        status = pass_answer(fds[0].fd, pass_interactive);
331 32
                        rl_forced_update_display();
332 32
                        if (status == CLIS_CLOSE)
333 8
                                RL_EXIT(0);
334 24
                }
335 204
                if (fds[1].revents & POLLIN) {
336 180
                        rl_callback_read_char();
337 180
                }
338
        }
339
}
340
341
/*
342
 * No arguments given, simply pass bytes on stdin/stdout and CLI socket
343
 */
344
static void v_noreturn_
345 4
pass(int sock)
346
{
347
        struct pollfd fds[2];
348
        char buf[1024];
349
        int i;
350
        ssize_t n;
351 4
        int busy = 0;
352
353 4
        fds[0].fd = sock;
354 4
        fds[0].events = POLLIN;
355 4
        fds[1].fd = 0;
356 4
        fds[1].events = POLLIN;
357 28
        while (1) {
358 28
                i = poll(fds, 2, -1);
359 28
                if (i == -1 && errno == EINTR) {
360 0
                        continue;
361
                }
362 28
                assert(i > 0);
363 28
                if (fds[0].revents & POLLIN) {
364 12
                        (void)pass_answer(fds[0].fd, pass_script);
365 12
                        busy = 0;
366 12
                        if (fds[1].fd < 0)
367 0
                                RL_EXIT(0);
368 12
                }
369 28
                if (fds[1].revents & POLLIN || fds[1].revents & POLLHUP) {
370 16
                        n = read(fds[1].fd, buf, sizeof buf - 1);
371 16
                        if (n == 0) {
372 4
                                if (!busy)
373 4
                                        RL_EXIT(0);
374 0
                                fds[1].fd = -1;
375 12
                        } else if (n < 0) {
376 0
                                RL_EXIT(0);
377 0
                        } else {
378 12
                                busy = 1;
379 12
                                buf[n] = '\0';
380 12
                                cli_write(sock, buf);
381
                        }
382 12
                }
383
        }
384
}
385
386
387
static void v_noreturn_
388 24
usage(int status)
389
{
390 24
        fprintf(stderr,
391
            "Usage: vinyladm [-h] [-n workdir] [-p] [-S secretfile] "
392
            "[-T [address]:port] [-t timeout] [command [...]]\n");
393 24
        fprintf(stderr,
394
            "       vinyladm [-n name] -x workdir\n");
395 24
        fprintf(stderr, "\t-n is mutually exclusive with -S and -T\n");
396 24
        exit(status);
397
}
398
399
static int
400 164
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 164
        vsm = VSM_New();
409 164
        AN(vsm);
410 324
        if (VSM_Arg(vsm, 'n', n_arg) < 0 ||
411 164
            VSM_Arg(vsm, 't', t_arg) < 0 ||
412 160
            VSM_Attach(vsm, STDERR_FILENO) < 0) {
413 8
                fprintf(stderr, "%s\n", VSM_Error(vsm));
414 8
                VSM_Destroy(&vsm);
415 8
                return (-1);
416
        }
417
418 156
        T_start = T_arg = VSM_Dup(vsm, "Arg", "-T");
419 156
        S_arg = VSM_Dup(vsm, "Arg", "-S");
420 156
        VSM_Destroy(&vsm);
421
422 156
        if (T_arg == NULL) {
423 0
                fprintf(stderr, "No -T in shared memory\n");
424 0
                return (-1);
425
        }
426
427 156
        sock = -1;
428 156
        while (*T_arg) {
429 156
                p = strchr(T_arg, '\n');
430 156
                AN(p);
431 156
                *p = '\0';
432 156
                sock = cli_sock(T_arg, S_arg);
433 156
                if (sock >= 0)
434 156
                        break;
435 0
                T_arg = p + 1;
436
        }
437 156
        free(T_start);
438 156
        free(S_arg);
439 156
        return (sock);
440 164
}
441
442
static int
443 12
t_arg_timeout(const char *t_arg)
444
{
445 12
        char *p = NULL;
446
447 12
        AN(t_arg);
448 12
        timeout = strtod(t_arg, &p);
449 24
        if ((p != NULL && *p != '\0') ||
450 12
            !isfinite(timeout) || timeout < 0) {
451 0
                fprintf(stderr, "-t: Invalid argument: %s", t_arg);
452 0
                return (-1);
453
        }
454 12
        return (1);
455 12
}
456
457
#define OPTARG "hn:pS:T:t:x:"
458
459
int
460 228
main(int argc, char * const *argv)
461
{
462
        char *wd;
463 228
        const char *T_arg = NULL;
464 228
        const char *S_arg = NULL;
465 228
        const char *n_arg = NULL;
466 228
        const char *t_arg = NULL;
467
        int opt, sock;
468
469 228
        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 472
        while ((opt = getopt(argc, argv, "+" OPTARG)) != -1) {
482 280
                switch (opt) {
483
                case 'h':
484
                        /* Usage help */
485 4
                        usage(0);
486
                case 'n':
487 164
                        n_arg = optarg;
488 164
                        break;
489
                case 'p':
490 8
                        p_arg = 1;
491 8
                        break;
492
                case 'S':
493 24
                        S_arg = optarg;
494 24
                        break;
495
                case 'T':
496 28
                        T_arg = optarg;
497 28
                        break;
498
                case 't':
499 20
                        t_arg = optarg;
500 20
                        break;
501
                case 'x':
502 20
                        AN(optarg);
503 20
                        if (vstrcmp(optarg, "workdir")) {
504 4
                                fprintf(stderr, "Invalid -x argument\n");
505 4
                                usage(1);
506
                        }
507 16
                        if (argc != optind) {
508 4
                                fprintf(stderr, "-x workdir must be the last argument\n");
509 4
                                usage(1);
510
                        }
511 12
                        wd = VIN_n_Arg(n_arg);
512 12
                        AN(wd);
513 12
                        printf("%s\n", wd);
514 12
                        free(wd);
515 12
                        exit(0);
516
                default:
517 12
                        usage(1);
518
                }
519
        }
520
521 192
        argc -= optind;
522 192
        argv += optind;
523
524 192
        if (T_arg != NULL) {
525 28
                if (n_arg != NULL)
526 0
                        usage(1);
527 28
                sock = cli_sock(T_arg, S_arg);
528 28
        } else {
529 164
                if (S_arg != NULL)
530 0
                        usage(1);
531 164
                sock = n_arg_sock(n_arg, t_arg);
532
        }
533 192
        if (sock < 0)
534 32
                exit(2);
535
536 160
        if (t_arg != NULL && t_arg_timeout(t_arg) < 0)
537 0
                exit(2);
538
539 160
        if (argc > 0) {
540 148
                VSIG_Arm_int();
541 148
                VSIG_Arm_term();
542 148
                do_args(sock, argc, argv);
543
                NEEDLESS(exit(0));
544
        }
545
546 12
        if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && !p_arg)
547 8
                interactive(sock);
548
        else
549 4
                pass(sock);
550
        NEEDLESS(exit(0));
551
}