1
0
Fork 0
knot-resolver/utils/client/kresc.c
Daniel Baumann fbc604e215
Adding upstream version 5.7.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:56:17 +02:00

458 lines
10 KiB
C

/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <arpa/inet.h>
#include <contrib/ccan/asprintf/asprintf.h>
#include <editline/readline.h>
#include <errno.h>
#include <histedit.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#define HISTORY_FILE "kresc_history"
#define PROGRAM_NAME "kresc"
FILE *g_tty = NULL; //!< connection to the daemon
static char *run_cmd(const char *cmd, size_t * out_len);
const char *prompt(EditLine * e)
{
return PROGRAM_NAME "> ";
}
bool starts_with(const char *a, const char *b)
{
if (strncmp(a, b, strlen(b)) == 0)
return 1;
return 0;
}
//! Returns Lua name of type of value, NULL on error. Puts length of type in name_len;
char *get_type_name(const char *value)
{
if (value == NULL) {
return NULL;
}
for (int i = 0; value[i]; i++) {
if (value[i] == ')') {
//Return NULL to prevent unexpected function call
return NULL;
}
}
char *cmd = afmt("type(%s)", value);
if (!cmd) {
perror("While tab-completing.");
return NULL;
}
size_t name_len;
char *type = run_cmd(cmd, &name_len);
if (!type) {
return NULL;
}
free(cmd);
if (starts_with(type, "[")) {
//Return "nil" on non-valid name.
free(type);
return strdup("nil");
} else {
type[strlen(type) - 1] = '\0';
return type;
}
}
static void complete_function(EditLine * el)
{
//Add left parenthesis to function name.
el_insertstr(el, "(");
}
static void complete_members(EditLine * el, const char *str,
const char *str_type, int str_len, char *dot)
{
char *table = strdup(str);
if (!table) {
perror("While tab-completing");
return;
}
//Get only the table name (without partial member name).
if (dot) {
*(table + (dot - str)) = '\0';
}
//Insert a dot after the table name.
if (!strncmp(str_type, "table", 5)) {
el_insertstr(el, ".");
str_len++;
}
//Check if the substring before dot is a valid table name.
const char *t_type = get_type_name(table);
if (t_type && !strncmp("table", t_type, 5)) {
//Get string of members of the table.
char *cmd =
afmt
("do local s=\"\"; for i in pairs(%s) do s=s..i..\"\\n\" end return(s) end",
table);
if (!cmd) {
perror("While tab-completing.");
goto complete_members_exit;
}
size_t members_len;
char *members = run_cmd(cmd, &members_len);
free(cmd);
if (!members) {
perror("While communication with daemon");
goto complete_members_exit;
}
//Split members by newline.
char *members_tok = strdup(members);
free(members);
if (!members_tok) {
goto complete_members_exit;
}
char *token = strtok(members_tok, "\n");
int matches = 0;
char *lastmatch = NULL;
if (!dot || dot - str + 1 == strlen(str)) {
//Prints all members.
while (token) {
char *member = afmt("%s.%s", table, token);
const char *member_type = get_type_name(member);
if (member && member_type) {
printf("\n%s (%s)", member, member_type);
free(member);
free((void *)member_type);
} else if (member) {
printf("\n%s", member);
free(member);
}
token = strtok(NULL, "\n");
matches++;
}
} else {
//Print members matching the current line.
while (token) {
if (starts_with(token, dot + 1)) {
const char *member_type =
get_type_name(afmt
("%s.%s", table,
token));
if (member_type) {
printf("\n%s.%s (%s)", table,
token, member_type);
free((void *)member_type);
} else {
printf("\n%s.%s", table, token);
}
lastmatch = token;
matches++;
}
token = strtok(NULL, "\n");
}
//Complete matching member.
if (matches == 1) {
el_deletestr(el, str_len);
el_insertstr(el, table);
el_insertstr(el, ".");
el_insertstr(el, lastmatch);
}
}
if (matches > 1) {
printf("\n");
}
free(members_tok);
}
complete_members_exit:
free(table);
if(t_type) {
free((void*)t_type);
}
}
static void complete_globals(EditLine * el, const char *str, int str_len)
{
//Parse Lua globals.
size_t globals_len;
char *globals = run_cmd("_G.__orig_name_list", &globals_len);
if (!globals) {
perror("While tab-completing");
return;
}
//Show possible globals.
char *globals_tok = strdup(globals);
free(globals);
if (!globals_tok) {
return;
}
char *token = strtok(globals_tok, "\n");
int matches = 0;
char *lastmatch = NULL;
while (token) {
if (str && starts_with(token, str)) {
char *name = get_type_name(token);
printf("\n%s (%s)", token, name);
free(name);
lastmatch = token;
matches++;
}
token = strtok(NULL, "\n");
}
if (matches > 1) {
printf("\n");
}
//Complete matching global.
if (matches == 1) {
el_deletestr(el, str_len);
el_insertstr(el, lastmatch);
}
free(globals_tok);
}
static unsigned char complete(EditLine * el, int ch)
{
int argc, pos;
const char **argv;
const LineInfo *li = el_line(el);
Tokenizer *tok = tok_init(NULL);
//Tokenize current line.
int ret = tok_line(tok, li, &argc, &argv, NULL, &pos);
if (ret != 0) {
perror("While tab-completing.");
goto complete_exit;
}
//Show help.
if (argc == 0) {
size_t help_len;
char *help = run_cmd("help()", &help_len);
if (help) {
printf("\n%s", help);
free(help);
} else {
perror("While communication with daemon");
}
goto complete_exit;
}
if (argc > 1) {
goto complete_exit;
}
//Get name of type of current line.
const char *type = get_type_name(argv[0]);
if (!type) {
goto complete_exit;
}
//Get position of last dot in current line (useful for parsing table).
char *dot = strrchr(argv[0], '.');
if (strncmp(type, "table", 5) != 0 && !dot) {
//Line is not a name of some table and there is no dot in it.
complete_globals(el, argv[0], pos);
} else if ((dot && strncmp(type, "nil", 3) == 0)
|| strncmp(type, "table", 5) == 0) {
//Current line (or part of it) is a name of some table.
complete_members(el, argv[0], type, pos, dot);
} else if (strncmp(type, "function", 8) == 0) {
//Current line is a function.
complete_function(el);
}
if (type) {
free((void *)type);
}
complete_exit:
tok_reset(tok);
tok_end(tok);
return CC_REDISPLAY;
}
//! Initialize connection to the daemon; return 0 on success.
static int init_tty(const char *path)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return 1;
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
size_t plen = strlen(path);
if (plen + 1 > sizeof(addr.sun_path)) {
fprintf(stderr, "Path too long\n");
close(fd);
return 1;
}
memcpy(addr.sun_path, path, plen + 1);
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr))) {
perror("While connecting to daemon");
close(fd);
return 1;
}
g_tty = fdopen(fd, "r+");
if (!g_tty) {
perror("While opening TTY");
close(fd);
return 1;
}
// Switch to binary mode and consume the text "> ".
if (fprintf(g_tty, "__binary\n") < 0 || !fread(&addr, 2, 1, g_tty)
|| fflush(g_tty)) {
perror("While initializing TTY");
fclose(g_tty);
g_tty = NULL;
return 1;
}
return 0;
}
//! Run a command on the daemon; return the answer or NULL on failure, puts answer length to out_len.
static char *run_cmd(const char *cmd, size_t * out_len)
{
if (!g_tty || !cmd)
return NULL;
if (fprintf(g_tty, "%s", cmd) < 0 || fflush(g_tty))
return NULL;
uint32_t len;
if (!fread(&len, sizeof(len), 1, g_tty))
return NULL;
len = ntohl(len);
if (!len)
return NULL;
char *msg = malloc(1 + (size_t) len);
if (!msg)
return NULL;
if (len && !fread(msg, len, 1, g_tty)) {
free(msg);
return NULL;
}
msg[len] = '\0';
*out_len = len;
return msg;
}
static int interact(void)
{
EditLine *el;
History *hist;
int count;
const char *line;
HistEvent ev;
el = el_init(PROGRAM_NAME, stdin, stdout, stderr);
el_set(el, EL_PROMPT, prompt);
el_set(el, EL_EDITOR, "emacs");
el_set(el, EL_ADDFN, PROGRAM_NAME "-complete",
"Perform " PROGRAM_NAME " completion.", complete);
el_set(el, EL_BIND, "^I", PROGRAM_NAME "-complete", NULL);
hist = history_init();
if (hist == 0) {
perror("While initializing command history");
return 1;
}
history(hist, &ev, H_SETSIZE, 800);
el_set(el, EL_HIST, history, hist);
char *hist_file = NULL;
char *data_home = getenv("XDG_DATA_HOME");
//Check whether $XDG_DATA_HOME is set.
if (!data_home || *data_home == '\0') {
const char *home = getenv("HOME"); //This should be set on any POSIX compliant OS, even for nobody
//Create necessary folders.
char *dirs[3] =
{ afmt("%s/.local", home), afmt("%s/.local/share", home),
afmt("%s/.local/share/knot-resolver/", home)
};
bool ok = true;
for (int i = 0; i < 3; i++) {
if (mkdir(dirs[i], 0755) && errno != EEXIST) {
ok = false;
break;
}
}
if (ok) {
hist_file =
afmt("%s/.local/share/knot-resolver/" HISTORY_FILE, home);
}
} else {
if (!mkdir(afmt("%s/knot-resolver/", data_home), 0755)
|| errno == EEXIST) {
hist_file = afmt("%s/knot-resolver/" HISTORY_FILE, data_home);
}
}
//Load history file
if (hist_file) {
history(hist, &ev, H_LOAD, hist_file);
} else {
perror("While opening history file");
}
while (1) {
line = el_gets(el, &count);
if (count > 0) {
history(hist, &ev, H_ENTER, line);
size_t msg_len;
char *msg = run_cmd(line, &msg_len);
if (!msg) {
perror("While communication with daemon");
history_end(hist);
el_end(el);
free(msg);
return 1;
}
printf("%s", msg);
if (msg_len > 0 && msg[msg_len - 1] != '\n') {
printf("\n");
}
if (hist_file) {
history(hist, &ev, H_SAVE, hist_file);
}
free(msg);
}
}
history_end(hist);
free(hist_file);
el_end(el);
if (feof(stdin))
return 0;
perror("While reading input");
return 1;
}
int main(int argc, char **argv)
{
fprintf(stderr, "Warning! %s is highly experimental, use at own risk.\n", argv[0]);
fprintf(stderr, "Please tell authors what features you expect from client utility.\n");
if (argc != 2) {
fprintf(stderr, "Usage: %s tty/xxxxx\n", argv[0]);
return 1;
}
int res = init_tty(argv[1]);
if (!res)
res = interact();
if (g_tty)
fclose(g_tty);
return res;
}