Skip to content
Snippets Groups Projects
Commit 84f97e19 authored by Jan Včelák's avatar Jan Včelák :rocket:
Browse files

Merge 'knotc interactive mode'

See merge request !520
parents 2dabc9b0 0ad28e69
No related branches found
No related tags found
No related merge requests found
Showing with 1499 additions and 312 deletions
......@@ -429,6 +429,8 @@ src/utils/common/exec.c
src/utils/common/exec.h
src/utils/common/hex.c
src/utils/common/hex.h
src/utils/common/lookup.c
src/utils/common/lookup.h
src/utils/common/msg.c
src/utils/common/msg.h
src/utils/common/netio.c
......@@ -465,7 +467,11 @@ src/utils/knotc/commands.c
src/utils/knotc/commands.h
src/utils/knotc/estimator.c
src/utils/knotc/estimator.h
src/utils/knotc/interactive.c
src/utils/knotc/interactive.h
src/utils/knotc/main.c
src/utils/knotc/process.c
src/utils/knotc/process.h
src/utils/knotd/main.c
src/utils/knsupdate/knsupdate_exec.c
src/utils/knsupdate/knsupdate_exec.h
......@@ -530,6 +536,7 @@ tests/server.c
tests/sockaddr.c
tests/test_conf.h
tests/tsig_key.c
tests/utils/test_lookup.c
tests/wire.c
tests/wire_ctx.c
tests/worker_pool.c
......
......@@ -7,6 +7,7 @@ Knot DNS has several dependencies:
* liburcu >= 0.5.4
* gnutls >= 3.0
* jansson >= 2.3
* libedit
Embedded libraries:
* lmdb (system library is preferred)
......
......@@ -424,6 +424,35 @@ AC_ARG_ENABLE([rosedb],
AS_IF([test "$enable_rosedb" = yes], [AC_DEFINE([HAVE_ROSEDB], [1], [Define to 1 to enable static RR query module.])])
AM_CONDITIONAL([HAVE_ROSEDB], [test "$enable_rosedb" = yes])
# libedit
AS_IF([test "$enable_daemon" = "yes"],[
# daemon enabled
PKG_CHECK_MODULES([libedit], [libedit], [with_libedit=yes], [
with_libedit=no
AC_CHECK_HEADER([histedit.h], [
# workaround for OpenBSD
AS_CASE([$host_os],
[openbsd*], [libedit_deps=-lcurses],
[libedit_deps=]
)
AC_CHECK_LIB([edit], [el_init], [
with_libedit=yes
libedit_CFLAGS=
libedit_LIBS="-ledit $libedit_deps"
], [], [$libedit_deps]
)
])
])
AS_IF([test "$with_libedit" != "yes"], [
AC_MSG_ERROR([libedit not found])
])
],[
# daemon disabled
with_libedit=no
libedit_CFLAGS=
libedit_LIBS=
])
############################################
# Dependencies needed for Knot DNS utilities
############################################
......@@ -554,6 +583,7 @@ AC_MSG_RESULT([
LibURCU: ${liburcu_LIBS} ${liburcu_CFLAGS}
GnuTLS: ${gnutls_LIBS} ${gnutls_CFLAGS}
Jansson: ${jansson_LIBS} ${jansson_CFLAGS}
Libedit: ${libedit_LIBS} ${libedit_CFLAGS}
LMDB: ${enable_lmdb} ${lmdb_LIBS} ${lmdb_CFLAGS}
Sanitizer: ${sanitize_CFLAGS}
......
......@@ -34,6 +34,7 @@ Knot DNS requires few libraries to be compiled:
* GnuTLS, at least 3.0
* Jansson, at least 2.3
* Userspace RCU, at least 0.5.4
* libedit
* lmdb (included)
* libcap-ng, at least 0.6.4 (optional)
* libidn (optional)
......
......@@ -34,6 +34,8 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.sp
\fBknotc\fP [\fIparameters\fP] \fIaction\fP [\fIaction_args\fP]
.SH DESCRIPTION
.sp
If no \fIaction\fP is specified, the program is executed in interactive mode.
.SS Parameters
.INDENT 0.0
.TP
......@@ -146,13 +148,22 @@ Set the item data in the transaction.
\fBconf\-unset\fP [\fIitem\fP] [\fIdata\fP\&...]
Unset the item data in the transaction.
.UNINDENT
.SH NOTE
.SS Note
.sp
Empty \fIzone\fP parameter means all zones.
.sp
Type \fIitem\fP parameter in the form of \fIsection\fP[\fB[\fP\fIid\fP\fB]\fP][\fB\&.\fP\fIname\fP].
.sp
(*) indicates a local operation which requires a configuration.
.SS Interactive mode
.sp
The utility provides interactive mode with basic line editing functionality,
command completion, and command history.
.sp
Interactive mode behavior can be customized in \fI~/.editrc\fP\&. Refer to
\fIeditrc(5)\fP for details.
.sp
Command history is saved in \fI~/.knotc_history\fP\&.
.SH EXAMPLES
.SS Reload the whole server configuration
.INDENT 0.0
......@@ -225,7 +236,7 @@ $ knotc conf\-commit
.UNINDENT
.SH SEE ALSO
.sp
\fIknotd(8)\fP, \fIknot.conf(5)\fP\&.
\fIknotd(8)\fP, \fIknot.conf(5)\fP, \fIeditrc(5)\fP\&.
.SH AUTHOR
CZ.NIC Labs <http://www.knot-dns.cz>
.SH COPYRIGHT
......
......@@ -11,6 +11,8 @@ Synopsis
Description
-----------
If no *action* is specified, the program is executed in interactive mode.
Parameters
..........
......@@ -126,7 +128,7 @@ Actions
Unset the item data in the transaction.
Note
----
....
Empty *zone* parameter means all zones.
......@@ -134,6 +136,17 @@ Type *item* parameter in the form of *section*\ [**[**\ *id*\ **]**\ ][**.**\ *n
(*) indicates a local operation which requires a configuration.
Interactive mode
................
The utility provides interactive mode with basic line editing functionality,
command completion, and command history.
Interactive mode behavior can be customized in `~/.editrc`. Refer to
:manpage:`editrc(5)` for details.
Command history is saved in `~/.knotc_history`.
Examples
--------
......@@ -185,4 +198,4 @@ Add example.org zone with a zonefile location
See Also
--------
:manpage:`knotd(8)`, :manpage:`knot.conf(5)`.
:manpage:`knotd(8)`, :manpage:`knot.conf(5)`, :manpage:`editrc(5)`.
......@@ -213,6 +213,10 @@ knotc_SOURCES = \
utils/knotc/commands.h \
utils/knotc/estimator.c \
utils/knotc/estimator.h \
utils/knotc/interactive.c \
utils/knotc/interactive.h \
utils/knotc/process.c \
utils/knotc/process.h \
utils/knotc/main.c
knotd_SOURCES = \
......@@ -372,7 +376,7 @@ libknotd_la_LIBADD = libknot.la libknot-yparser.la zscanner/libzscanner.la $(lib
knotd_CPPFLAGS = $(AM_CPPFLAGS) $(liburcu_CFLAGS)
knotd_LDADD = libknotd.la $(liburcu_LIBS)
knotc_LDADD = libknotd.la
knotc_LDADD = libknotd.la libknotus.la $(libedit_LIBS)
knot1to2_LDADD = libcontrib.la
####################################
......@@ -439,6 +443,8 @@ libknotus_la_SOURCES = \
utils/common/exec.h \
utils/common/hex.c \
utils/common/hex.h \
utils/common/lookup.c \
utils/common/lookup.h \
utils/common/msg.c \
utils/common/msg.h \
utils/common/netio.c \
......
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "utils/common/lookup.h"
#include "contrib/mempattern.h"
#include "contrib/ucw/mempool.h"
#include "libknot/error.h"
int lookup_init(lookup_t *lookup)
{
if (lookup == NULL) {
return KNOT_EINVAL;
}
memset(lookup, 0, sizeof(*lookup));
mm_ctx_mempool(&lookup->mm, MM_DEFAULT_BLKSIZE);
lookup->trie = hattrie_create_n(TRIE_BUCKET_SIZE, &lookup->mm);
if (lookup->trie == NULL) {
mp_delete(lookup->mm.ctx);
return KNOT_ENOMEM;
}
return KNOT_EOK;
}
static void reset_output(lookup_t *lookup)
{
if (lookup == NULL) {
return;
}
mm_free(&lookup->mm, lookup->found.key);
lookup->found.key = NULL;
lookup->found.data = NULL;
lookup->iter.count = 0;
mm_free(&lookup->mm, lookup->iter.first_key);
lookup->iter.first_key = NULL;
hattrie_iter_free(lookup->iter.it);
lookup->iter.it = NULL;
}
void lookup_deinit(lookup_t *lookup)
{
if (lookup == NULL) {
return;
}
reset_output(lookup);
hattrie_free(lookup->trie);
mp_delete(lookup->mm.ctx);
}
int lookup_insert(lookup_t *lookup, const char *str, void *data)
{
if (lookup == NULL || str == NULL) {
return KNOT_EINVAL;
}
size_t str_len = strlen(str);
if (str_len == 0) {
return KNOT_EINVAL;
}
value_t *val = hattrie_get(lookup->trie, str, str_len);
if (val == NULL) {
return KNOT_ENOMEM;
}
*val = data;
return KNOT_EOK;
}
void lookup_index(lookup_t *lookup)
{
if (lookup == NULL) {
return;
}
hattrie_build_index(lookup->trie);
}
static int set_key(lookup_t *lookup, char **dst, const char *key, size_t key_len)
{
if (*dst != NULL) {
mm_free(&lookup->mm, *dst);
}
*dst = mm_alloc(&lookup->mm, key_len + 1);
if (*dst == NULL) {
return KNOT_ENOMEM;
}
memcpy(*dst, key, key_len);
(*dst)[key_len] = '\0';
return KNOT_EOK;
}
int lookup_search(lookup_t *lookup, const char *str, size_t str_len)
{
if (lookup == NULL) {
return KNOT_EINVAL;
}
// Change NULL string to the empty one.
if (str == NULL) {
str = "";
}
reset_output(lookup);
size_t new_len = 0;
hattrie_iter_t *it = hattrie_iter_begin(lookup->trie, true);
for (; !hattrie_iter_finished(it); hattrie_iter_next(it)) {
size_t len;
const char *key = hattrie_iter_key(it, &len);
// Compare with a shorter key.
if (len < str_len) {
int ret = memcmp(str, key, len);
if (ret >= 0) {
continue;
} else {
break;
}
}
// Compare with an equal length or longer key.
int ret = memcmp(str, key, str_len);
if (ret == 0) {
lookup->iter.count++;
// First candidate.
if (lookup->iter.count == 1) {
ret = set_key(lookup, &lookup->found.key, key, len);
if (ret != KNOT_EOK) {
break;
}
lookup->found.data = *hattrie_iter_val(it);
new_len = len;
// Another candidate.
} else if (new_len > str_len) {
if (new_len > len) {
new_len = len;
}
while (memcmp(lookup->found.key, key, new_len) != 0) {
new_len--;
}
}
// Stop if greater than the key, and also than all the following keys.
} else if (ret < 0) {
break;
}
}
hattrie_iter_free(it);
switch (lookup->iter.count) {
case 0:
return KNOT_ENOENT;
case 1:
return KNOT_EOK;
default:
// Store full name of the first candidate.
if (set_key(lookup, &lookup->iter.first_key, lookup->found.key,
strlen(lookup->found.key)) != KNOT_EOK) {
return KNOT_ENOMEM;
}
lookup->found.key[new_len] = '\0';
lookup->found.data = NULL;
return KNOT_EFEWDATA;
}
}
void lookup_list(lookup_t *lookup)
{
if (lookup == NULL || lookup->iter.first_key == NULL) {
return;
}
if (lookup->iter.it != NULL) {
if (hattrie_iter_finished(lookup->iter.it)) {
hattrie_iter_free(lookup->iter.it);
lookup->iter.it = NULL;
return;
}
hattrie_iter_next(lookup->iter.it);
size_t len;
const char *key = hattrie_iter_key(lookup->iter.it, &len);
int ret = set_key(lookup, &lookup->found.key, key, len);
if (ret == KNOT_EOK) {
lookup->found.data = *hattrie_iter_val(lookup->iter.it);
}
return;
}
lookup->iter.it = hattrie_iter_begin(lookup->trie, true);
while (!hattrie_iter_finished(lookup->iter.it)) {
size_t len;
const char *key = hattrie_iter_key(lookup->iter.it, &len);
if (strcmp(key, lookup->iter.first_key) == 0) {
int ret = set_key(lookup, &lookup->found.key, key, len);
if (ret == KNOT_EOK) {
lookup->found.data = *hattrie_iter_val(lookup->iter.it);
}
break;
}
hattrie_iter_next(lookup->iter.it);
}
}
static void print_options(lookup_t *lookup, EditLine *el)
{
// Get terminal lines.
unsigned lines = 0;
if (el_get(el, EL_GETTC, "li", &lines) != 0 || lines < 3) {
return;
}
for (size_t i = 1; i <= lookup->iter.count; i++) {
lookup_list(lookup);
printf("\n%s", lookup->found.key);
if (i > 1 && i % (lines - 1) == 0 && i < lookup->iter.count) {
printf("\n Display next from %zu possibilities? (y or n)",
lookup->iter.count);
char next;
el_getc(el, &next);
if (next != 'y') {
break;
}
}
}
printf("\n");
fflush(stdout);
}
void lookup_complete(lookup_t *lookup, const char *str, size_t str_len,
EditLine *el, bool add_space)
{
if (lookup == NULL || el == NULL) {
return;
}
// Try to complete the command name.
int ret = lookup_search(lookup, str, str_len);
switch (ret) {
case KNOT_EOK:
el_deletestr(el, str_len);
el_insertstr(el, lookup->found.key);
if (add_space) {
el_insertstr(el, " ");
}
break;
case KNOT_EFEWDATA:
if (strlen(lookup->found.key) > str_len) {
el_deletestr(el, str_len);
el_insertstr(el, lookup->found.key);
} else {
print_options(lookup, el);
}
break;
default:
break;
}
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*!
* \file
*
* \brief Lookup container for textual strings.
*
* \addtogroup knot_utils
* @{
*/
#pragma once
#include <histedit.h>
#include "libknot/mm_ctx.h"
#include "contrib/hat-trie/hat-trie.h"
/*! Lookup context. */
typedef struct {
/*! Memory pool context. */
knot_mm_t mm;
/*! Main hat-trie storage. */
hattrie_t *trie;
/*! Current (iteration) data context. */
struct {
/*! Stored key. */
char *key;
/*! Corresponding key data. */
void *data;
} found;
/*! Iteration context. */
struct {
/*! Total number of possibilies. */
size_t count;
/*! The first possibility. */
char *first_key;
/*! Hat-trie iterator. */
hattrie_iter_t *it;
} iter;
} lookup_t;
/*!
* Initializes the lookup context.
*
* \param[in] lookup Lookup context.
*
* \return Error code, KNOT_EOK if successful.
*/
int lookup_init(lookup_t *lookup);
/*!
* Deinitializes the lookup context.
*
* \param[in] lookup Lookup context.
*/
void lookup_deinit(lookup_t *lookup);
/*!
* Inserts given key and data into the lookup.
*
* \param[in] lookup Lookup context.
* \param[in] str Textual key.
* \param[in] data Key textual data.
*
* \return Error code, KNOT_EOK if successful.
*/
int lookup_insert(lookup_t *lookup, const char *str, void *data);
/*!
* Indexes the lookup container.
*
* \param[in] lookup Lookup context.
*/
void lookup_index(lookup_t *lookup);
/*!
* Searches the lookup container for the given key.
*
* \note If one candidate, lookup.found contains the key/data,
* if more candidates, lookup.found contains the common key prefix and
* lookup.iter.first_key is the first candidate key.
*
* \param[in] lookup Lookup context.
* \param[in] str Textual key.
* \param[in] str_len Textual key length.
*
* \return Error code, KNOT_EOK if 1 candidate, KNOT_ENOENT if no candidate,
* and KNOT_EFEWDATA if more candidates are possible.
*/
int lookup_search(lookup_t *lookup, const char *str, size_t str_len);
/*!
* Moves the lookup iterator to the next key candidate.
*
* \note lookup.found is updated.
*
* \param[in] lookup Lookup context.
*/
void lookup_list(lookup_t *lookup);
/*!
* Completes the string based on the lookup content or prints all candidates.
*
* \param[in] lookup Lookup context.
* \param[in] str Textual key.
* \param[in] str_len Textual key length.
* \param[in] el Editline context.
* \param[in] add_space Add one space after completed string flag.
*/
void lookup_complete(lookup_t *lookup, const char *str, size_t str_len,
EditLine *el, bool add_space);
/*! @} */
......@@ -30,6 +30,8 @@
#include "utils/knotc/commands.h"
#include "utils/knotc/estimator.h"
#define CMD_EXIT "exit"
#define CMD_STATUS "status"
#define CMD_STOP "stop"
#define CMD_RELOAD "reload"
......@@ -97,10 +99,10 @@ static int check_conf_args(cmd_args_t *args)
return KNOT_EINVAL;
}
static int get_conf_key(char *key, knot_ctl_data_t *data)
static int get_conf_key(const char *key, knot_ctl_data_t *data)
{
// Get key0.
char *key0 = key;
const char *key0 = key;
// Check for id.
char *id = strchr(key, '[');
......@@ -410,7 +412,8 @@ static int zone_memstats(const knot_dname_t *dname, void *data)
.node_table = hattrie_create_n(TRIE_BUCKET_SIZE, &mem_ctx),
};
char *zone_name = knot_dname_to_str_alloc(dname);
char buff[KNOT_DNAME_TXT_MAXLEN + 1];
char *zone_name = knot_dname_to_str(buff, dname, sizeof(buff));
char *zone_file = conf_zonefile(conf(), dname);
zs_scanner_t *zs = malloc(sizeof(zs_scanner_t));
......@@ -419,7 +422,6 @@ static int zone_memstats(const knot_dname_t *dname, void *data)
log_zone_error(dname, "%s", knot_strerror(KNOT_ENOMEM));
hattrie_free(est.node_table);
free(zone_file);
free(zone_name);
free(zs);
return KNOT_ENOMEM;
}
......@@ -434,13 +436,11 @@ static int zone_memstats(const knot_dname_t *dname, void *data)
hattrie_apply_rev(est.node_table, estimator_free_trie_node, NULL);
hattrie_free(est.node_table);
free(zone_file);
free(zone_name);
zs_deinit(zs);
free(zs);
return KNOT_EPARSEFAIL;
}
free(zone_file);
free(zone_name);
zs_deinit(zs);
free(zs);
......@@ -663,48 +663,40 @@ static int cmd_conf_ctl(cmd_args_t *args)
}
const cmd_desc_t cmd_table[] = {
{ CMD_EXIT, NULL, CTL_NONE },
{ CMD_STATUS, cmd_ctl, CTL_STATUS },
{ CMD_STOP, cmd_ctl, CTL_STOP },
{ CMD_RELOAD, cmd_ctl, CTL_RELOAD },
{ CMD_ZONE_CHECK, cmd_zone_check, CTL_NONE, CMD_CONF_FREAD },
{ CMD_ZONE_MEMSTATS, cmd_zone_memstats, CTL_NONE, CMD_CONF_FREAD },
{ CMD_ZONE_STATUS, cmd_zone_ctl, CTL_ZONE_STATUS },
{ CMD_ZONE_RELOAD, cmd_zone_ctl, CTL_ZONE_RELOAD },
{ CMD_ZONE_REFRESH, cmd_zone_ctl, CTL_ZONE_REFRESH },
{ CMD_ZONE_RETRANSFER, cmd_zone_ctl, CTL_ZONE_RETRANSFER },
{ CMD_ZONE_FLUSH, cmd_zone_ctl, CTL_ZONE_FLUSH },
{ CMD_ZONE_SIGN, cmd_zone_ctl, CTL_ZONE_SIGN },
{ CMD_CONF_INIT, cmd_conf_init, CTL_NONE, CMD_CONF_FWRITE },
{ CMD_CONF_CHECK, cmd_conf_check, CTL_NONE, CMD_CONF_FREAD },
{ CMD_CONF_IMPORT, cmd_conf_import, CTL_NONE, CMD_CONF_FWRITE },
{ CMD_CONF_EXPORT, cmd_conf_export, CTL_NONE, CMD_CONF_FREAD },
{ CMD_CONF_LIST, cmd_conf_ctl, CTL_CONF_LIST, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_READ, cmd_conf_ctl, CTL_CONF_READ, CMD_CONF_FOPT_ITEM },
{ CMD_ZONE_CHECK, cmd_zone_check, CTL_NONE, CMD_CONF_FOPT_ZONE | CMD_CONF_FREAD },
{ CMD_ZONE_MEMSTATS, cmd_zone_memstats, CTL_NONE, CMD_CONF_FOPT_ZONE | CMD_CONF_FREAD },
{ CMD_ZONE_STATUS, cmd_zone_ctl, CTL_ZONE_STATUS, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_RELOAD, cmd_zone_ctl, CTL_ZONE_RELOAD, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_REFRESH, cmd_zone_ctl, CTL_ZONE_REFRESH, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_RETRANSFER, cmd_zone_ctl, CTL_ZONE_RETRANSFER, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_FLUSH, cmd_zone_ctl, CTL_ZONE_FLUSH, CMD_CONF_FOPT_ZONE },
{ CMD_ZONE_SIGN, cmd_zone_ctl, CTL_ZONE_SIGN, CMD_CONF_FOPT_ZONE },
{ CMD_CONF_INIT, cmd_conf_init, CTL_NONE, CMD_CONF_FWRITE },
{ CMD_CONF_CHECK, cmd_conf_check, CTL_NONE, CMD_CONF_FREAD },
{ CMD_CONF_IMPORT, cmd_conf_import, CTL_NONE, CMD_CONF_FWRITE },
{ CMD_CONF_EXPORT, cmd_conf_export, CTL_NONE, CMD_CONF_FREAD },
{ CMD_CONF_LIST, cmd_conf_ctl, CTL_CONF_LIST, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_READ, cmd_conf_ctl, CTL_CONF_READ, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_BEGIN, cmd_conf_ctl, CTL_CONF_BEGIN },
{ CMD_CONF_COMMIT, cmd_conf_ctl, CTL_CONF_COMMIT },
{ CMD_CONF_ABORT, cmd_conf_ctl, CTL_CONF_ABORT },
{ CMD_CONF_DIFF, cmd_conf_ctl, CTL_CONF_DIFF, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_GET, cmd_conf_ctl, CTL_CONF_GET, CMD_CONF_FOPT_ITEM },
{ CMD_CONF_SET, cmd_conf_ctl, CTL_CONF_SET, CMD_CONF_FREQ_ITEM | CMD_CONF_FOPT_DATA },
{ CMD_CONF_UNSET, cmd_conf_ctl, CTL_CONF_UNSET, CMD_CONF_FOPT_ITEM | CMD_CONF_FOPT_DATA },
{ NULL }
};
const cmd_desc_old_t cmd_table_old[] = {
{ "checkzone", CMD_ZONE_CHECK },
{ "memstats", CMD_ZONE_MEMSTATS },
{ "zonestatus", CMD_ZONE_STATUS },
{ "refresh", CMD_ZONE_REFRESH },
{ "flush", CMD_ZONE_FLUSH },
{ "signzone", CMD_ZONE_SIGN },
{ "checkconf", CMD_CONF_CHECK },
{ "conf-desc", CMD_CONF_LIST },
{ CMD_CONF_DIFF, cmd_conf_ctl, CTL_CONF_DIFF, CMD_CONF_FOPT_ITEM | CMD_CONF_FREQ_TXN },
{ CMD_CONF_GET, cmd_conf_ctl, CTL_CONF_GET, CMD_CONF_FOPT_ITEM | CMD_CONF_FREQ_TXN },
{ CMD_CONF_SET, cmd_conf_ctl, CTL_CONF_SET, CMD_CONF_FREQ_ITEM | CMD_CONF_FOPT_DATA | CMD_CONF_FREQ_TXN },
{ CMD_CONF_UNSET, cmd_conf_ctl, CTL_CONF_UNSET, CMD_CONF_FOPT_ITEM | CMD_CONF_FOPT_DATA | CMD_CONF_FREQ_TXN },
{ NULL }
};
const cmd_help_t cmd_help_table[] = {
static const cmd_help_t cmd_help_table[] = {
{ CMD_EXIT, "", "Exit interactive mode." },
{ "", "", "" },
{ CMD_STATUS, "", "Check if the server is running." },
{ CMD_STOP, "", "Stop the server if running." },
{ CMD_RELOAD, "", "Reload the server configuration and modified zones." },
......@@ -733,3 +725,18 @@ const cmd_help_t cmd_help_table[] = {
{ CMD_CONF_UNSET, "[<item>] [<data>...]", "Unset the item data in the transaction." },
{ NULL }
};
void print_commands(void)
{
printf("\nActions:\n");
for (const cmd_help_t *cmd = cmd_help_table; cmd->name != NULL; cmd++) {
printf(" %-15s %-20s %s\n", cmd->name, cmd->params, cmd->desc);
}
printf("\n"
"Note:\n"
" Empty <zone> parameter means all zones.\n"
" Type <item> parameter in the form of <section>[<identifier>].<name>.\n"
" (*) indicates a local operation which requires a configuration.\n");
}
......@@ -41,6 +41,8 @@ typedef enum {
CMD_CONF_FOPT_ITEM = 1 << 2, /*!< Optional item argument. */
CMD_CONF_FREQ_ITEM = 1 << 3, /*!< Required item argument. */
CMD_CONF_FOPT_DATA = 1 << 4, /*!< Optional item data argument. */
CMD_CONF_FOPT_ZONE = 1 << 5, /*!< Optional zone name argument. */
CMD_CONF_FREQ_TXN = 1 << 6, /*!< Required opened confdb transaction. */
} cmd_conf_flag_t;
struct cmd_desc;
......@@ -51,7 +53,7 @@ typedef struct {
const cmd_desc_t *desc;
knot_ctl_t *ctl;
int argc;
char **argv;
const char **argv;
cmd_flag_t flags;
} cmd_args_t;
......@@ -63,12 +65,6 @@ struct cmd_desc {
cmd_conf_flag_t flags;
};
/*! \brief Old command name translation. */
typedef struct {
const char *old_name;
const char *new_name;
} cmd_desc_old_t;
/*! \brief Command description. */
typedef struct {
const char *name;
......@@ -79,10 +75,7 @@ typedef struct {
/*! \brief Table of commands. */
extern const cmd_desc_t cmd_table[];
/*! \brief Table of command translations. */
extern const cmd_desc_old_t cmd_table_old[];
/*! \brief Table of command descriptions. */
extern const cmd_help_t cmd_help_table[];
/*! \brief Prints commands help. */
void print_commands(void);
/*! @} */
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <histedit.h>
#include "knot/common/log.h"
#include "utils/common/lookup.h"
#include "utils/knotc/interactive.h"
#include "utils/knotc/commands.h"
#include "contrib/string.h"
#define PROGRAM_NAME "knotc"
#define HISTORY_FILE ".knotc_history"
extern params_t params;
static void cmds_lookup(EditLine *el, const char *str, size_t str_len)
{
lookup_t lookup;
int ret = lookup_init(&lookup);
if (ret != KNOT_EOK) {
return;
}
// Fill the lookup with command names.
for (const cmd_desc_t *desc = cmd_table; desc->name != NULL; desc++) {
ret = lookup_insert(&lookup, desc->name, NULL);
if (ret != KNOT_EOK) {
goto cmds_lookup_finish;
}
}
lookup_index(&lookup);
lookup_complete(&lookup, str, str_len, el, true);
cmds_lookup_finish:
lookup_deinit(&lookup);
}
static void local_zones_lookup(EditLine *el, const char *str, size_t str_len)
{
lookup_t lookup;
int ret = lookup_init(&lookup);
if (ret != KNOT_EOK) {
return;
}
char buff[KNOT_DNAME_TXT_MAXLEN + 1];
// Fill the lookup with local zone names.
for (conf_iter_t iter = conf_iter(conf(), C_ZONE);
iter.code == KNOT_EOK; conf_iter_next(conf(), &iter)) {
conf_val_t val = conf_iter_id(conf(), &iter);
char *name = knot_dname_to_str(buff, conf_dname(&val), sizeof(buff));
ret = lookup_insert(&lookup, name, NULL);
if (ret != KNOT_EOK) {
goto local_zones_lookup_finish;
}
}
lookup_index(&lookup);
lookup_complete(&lookup, str, str_len, el, true);
local_zones_lookup_finish:
lookup_deinit(&lookup);
}
static char *get_id_name(const char *section)
{
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) {
desc++;
}
assert(desc->name != NULL);
knot_ctl_data_t query = {
[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
[KNOT_CTL_IDX_SECTION] = section
};
knot_ctl_t *ctl = NULL;
knot_ctl_type_t type;
knot_ctl_data_t reply;
// Try to get the first group item (possible id).
if (set_ctl(&ctl, desc, &params) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK ||
type != KNOT_CTL_TYPE_DATA || reply[KNOT_CTL_IDX_ERROR] != NULL) {
unset_ctl(ctl);
return NULL;
}
char *id_name = strdup(reply[KNOT_CTL_IDX_ITEM]);
unset_ctl(ctl);
return id_name;
}
static void id_lookup(EditLine *el, const char *str, size_t str_len,
const cmd_desc_t *cmd_desc, const char *section, bool add_space)
{
// Decide which confdb transaction to ask.
unsigned ctl_code = (cmd_desc->flags & CMD_CONF_FREQ_TXN) ?
CTL_CONF_GET : CTL_CONF_READ;
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL && desc->cmd != ctl_code) {
desc++;
}
assert(desc->name != NULL);
char *id_name = get_id_name(section);
if (id_name == NULL) {
return;
}
knot_ctl_data_t query = {
[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
[KNOT_CTL_IDX_SECTION] = section,
[KNOT_CTL_IDX_ITEM] = id_name
};
lookup_t lookup;
knot_ctl_t *ctl = NULL;
if (set_ctl(&ctl, desc, &params) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
lookup_init(&lookup) != KNOT_EOK) {
unset_ctl(ctl);
free(id_name);
return;
}
free(id_name);
while (true) {
knot_ctl_type_t type;
knot_ctl_data_t reply;
// Receive one section id.
if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) {
goto id_lookup_finish;
}
// Stop if finished transfer.
if (type != KNOT_CTL_TYPE_DATA) {
break;
}
// Insert the id into the lookup.
if (reply[KNOT_CTL_IDX_ERROR] != NULL ||
lookup_insert(&lookup, reply[KNOT_CTL_IDX_DATA], NULL) != KNOT_EOK) {
goto id_lookup_finish;
}
}
lookup_index(&lookup);
lookup_complete(&lookup, str, str_len, el, add_space);
id_lookup_finish:
lookup_deinit(&lookup);
unset_ctl(ctl);
}
static void list_lookup(EditLine *el, const char *section, const char *item)
{
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) {
desc++;
}
assert(desc->name != NULL);
knot_ctl_data_t query = {
[KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd),
[KNOT_CTL_IDX_SECTION] = section
};
lookup_t lookup;
knot_ctl_t *ctl = NULL;
if (set_ctl(&ctl, desc, &params) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK ||
knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK ||
lookup_init(&lookup) != KNOT_EOK) {
unset_ctl(ctl);
return;
}
while (true) {
knot_ctl_type_t type;
knot_ctl_data_t reply;
// Receive one section/item name.
if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) {
goto list_lookup_finish;
}
// Stop if finished transfer.
if (type != KNOT_CTL_TYPE_DATA) {
break;
}
const char *str = (section == NULL) ? reply[KNOT_CTL_IDX_SECTION] :
reply[KNOT_CTL_IDX_ITEM];
// Insert the name into the lookup.
if (reply[KNOT_CTL_IDX_ERROR] != NULL ||
lookup_insert(&lookup, str, NULL) != KNOT_EOK) {
goto list_lookup_finish;
}
}
lookup_index(&lookup);
lookup_complete(&lookup, item, strlen(item), el, section != NULL);
list_lookup_finish:
lookup_deinit(&lookup);
unset_ctl(ctl);
}
static void item_lookup(EditLine *el, const char *str, const cmd_desc_t *cmd_desc)
{
// List all sections.
if (str == NULL) {
list_lookup(el, NULL, "");
return;
}
// Check for id specification.
char *id = (strchr(str, '['));
if (id != NULL) {
char *section = strndup(str, id - str);
// Check for completed id specification.
char *id_stop = (strchr(id, ']'));
if (id_stop != NULL) {
// Complete the item name.
if (*(id_stop + 1) == '.') {
list_lookup(el, section, id_stop + 2);
}
} else {
// Complete the section id.
id_lookup(el, id + 1, strlen(id + 1), cmd_desc, section, false);
}
free(section);
} else {
// Check for item specification.
char *dot = (strchr(str, '.'));
if (dot != NULL) {
// Complete the item name.
char *section = strndup(str, dot - str);
list_lookup(el, section, dot + 1);
free(section);
} else {
// Complete the section name.
list_lookup(el, NULL, str);
}
}
}
static unsigned char complete(EditLine *el, int ch)
{
int argc, token, pos;
const char **argv;
const LineInfo *li = el_line(el);
Tokenizer *tok = tok_init(NULL);
// Parse the line.
int ret = tok_line(tok, li, &argc, &argv, &token, &pos);
if (ret != 0) {
goto complete_exit;
}
// Show possible commands.
if (argc == 0) {
print_commands();
goto complete_exit;
}
// Complete the command name.
if (token == 0) {
cmds_lookup(el, argv[0], pos);
goto complete_exit;
}
// Find the command descriptor.
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL && strcmp(desc->name, argv[0]) != 0) {
desc++;
}
if (desc->name == NULL) {
goto complete_exit;
}
if (token > 1 || desc->flags == CMD_CONF_FNONE ||
desc->flags == CMD_CONF_FREAD ||
desc->flags == CMD_CONF_FWRITE) {
goto complete_exit;
}
ret = set_config(desc, &params);
if (ret != KNOT_EOK) {
goto complete_exit;
}
// Complete the zone name.
if (desc->flags & CMD_CONF_FOPT_ZONE) {
if (desc->flags & CMD_CONF_FREAD) {
local_zones_lookup(el, argv[1], pos);
} else {
id_lookup(el, argv[1], pos, desc, "zone", true);
}
goto complete_exit;
}
// Complete the section/id/item name.
if (desc->flags & (CMD_CONF_FOPT_ITEM | CMD_CONF_FREQ_ITEM)) {
item_lookup(el, argv[1], desc);
goto complete_exit;
}
complete_exit:
conf_update(NULL);
tok_reset(tok);
tok_end(tok);
return CC_REDISPLAY;
}
static char *prompt(EditLine *el)
{
return PROGRAM_NAME"> ";
}
int interactive_loop(params_t *params)
{
char *hist_file = NULL;
const char *home = getenv("HOME");
if (home != NULL) {
hist_file = sprintf_alloc("%s/%s", home, HISTORY_FILE);
}
if (hist_file == NULL) {
log_notice("failed to get home directory");
}
EditLine *el = el_init(PROGRAM_NAME, stdin, stdout, stderr);
if (el == NULL) {
log_error("interactive mode not available");
free(hist_file);
return KNOT_ERROR;
}
History *hist = history_init();
if (hist == NULL) {
log_error("interactive mode not available");
el_end(el);
free(hist_file);
return KNOT_ERROR;
}
HistEvent hev = { 0 };
history(hist, &hev, H_SETSIZE, 100);
el_set(el, EL_HIST, history, hist);
history(hist, &hev, H_LOAD, hist_file);
el_set(el, EL_TERMINAL, NULL);
el_set(el, EL_EDITOR, "emacs");
el_set(el, EL_PROMPT, prompt);
el_set(el, EL_SIGNAL, 1);
el_source(el, NULL);
el_set(el, EL_ADDFN, PROGRAM_NAME"-complete",
"Perform "PROGRAM_NAME" completion.", complete);
el_set(el, EL_BIND, "^I", PROGRAM_NAME"-complete", NULL);
int count;
const char *line;
while ((line = el_gets(el, &count)) != NULL && count > 0) {
history(hist, &hev, H_ENTER, line);
Tokenizer *tok = tok_init(NULL);
// Tokenize the current line.
int argc;
const char **argv;
const LineInfo *li = el_line(el);
int ret = tok_line(tok, li, &argc, &argv, NULL, NULL);
if (ret != 0) {
continue;
}
// Process the command.
ret = process_cmd(argc, argv, params);
tok_reset(tok);
tok_end(tok);
// Check for the exit command.
if (ret == KNOT_CTL_ESTOP) {
break;
}
}
history(hist, &hev, H_SAVE, hist_file);
history_end(hist);
free(hist_file);
el_end(el);
return KNOT_EOK;
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*!
* \file
*
* \brief Knot control interactive mode.
*
* \addtogroup knot_utils
* @{
*/
#pragma once
#include "utils/knotc/process.h"
/*!
* Executes an interactive processing loop.
*
* \param[in] params Utility parameters.
*/
int interactive_loop(params_t *params);
/*! @} */
......@@ -16,17 +16,16 @@
#include <getopt.h>
#include <stdio.h>
#include <sys/stat.h>
#include "knot/common/log.h"
#include "knot/conf/conf.h"
#include "libknot/libknot.h"
#include "utils/knotc/commands.h"
#include "utils/common/params.h"
#include "utils/common/strtonum.h"
#include "utils/knotc/commands.h"
#include "utils/knotc/interactive.h"
#include "utils/knotc/process.h"
#define PROGRAM_NAME "knotc"
#define PROGRAM_NAME "knotc"
#define SPACE " "
#define DEFAULT_CTL_TIMEOUT 5
static void print_help(void)
......@@ -34,214 +33,31 @@ static void print_help(void)
printf("Usage: %s [parameters] <action> [action_args]\n"
"\n"
"Parameters:\n"
" -c, --config <file> Use a textual configuration file.\n"
" (default %s)\n"
" -C, --confdb <dir> Use a binary configuration database directory.\n"
" (default %s)\n"
" -s, --socket <path> Use a control UNIX socket path.\n"
" (default %s)\n"
" -t, --timeout <sec> Use a control socket timeout in seconds.\n"
" (default %u seconds)\n"
" -f, --force Forced operation. Overrides some checks.\n"
" -v, --verbose Enable debug output.\n"
" -h, --help Print the program help.\n"
" -V, --version Print the program version.\n"
"\n"
"Actions:\n",
" -c, --config <file>"SPACE"Use a textual configuration file.\n"
" "SPACE" (default %s)\n"
" -C, --confdb <dir> "SPACE"Use a binary configuration database directory.\n"
" "SPACE" (default %s)\n"
" -s, --socket <path>"SPACE"Use a control UNIX socket path.\n"
" "SPACE" (default %s)\n"
" -t, --timeout <sec>"SPACE"Use a control socket timeout in seconds.\n"
" "SPACE" (default %u seconds)\n"
" -f, --force "SPACE"Forced operation. Overrides some checks.\n"
" -v, --verbose "SPACE"Enable debug output.\n"
" -h, --help "SPACE"Print the program help.\n"
" -V, --version "SPACE"Print the program version.\n",
PROGRAM_NAME, CONF_DEFAULT_FILE, CONF_DEFAULT_DBDIR,
RUN_DIR "/knot.sock", DEFAULT_CTL_TIMEOUT);
for (const cmd_help_t *cmd = cmd_help_table; cmd->name != NULL; cmd++) {
printf(" %-15s %-20s %s\n", cmd->name, cmd->params, cmd->desc);
}
printf("\n"
"Note:\n"
" Empty <zone> parameter means all zones.\n"
" Type <item> parameter in the form of <section>[<identifier>].<name>.\n"
" (*) indicates a local operation which requires a configuration.\n");
print_commands();
}
static const cmd_desc_t* get_cmd_desc(const char *command)
{
/* Translate old command name. */
for (const cmd_desc_old_t *desc = cmd_table_old; desc->old_name != NULL; desc++) {
if (strcmp(desc->old_name, command) == 0) {
log_notice("obsolete command '%s', using '%s' instead",
desc->old_name, desc->new_name);
command = desc->new_name;
break;
}
}
/* Find requested command. */
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL) {
if (strcmp(desc->name, command) == 0) {
break;
}
desc++;
}
if (desc->name == NULL) {
log_error("invalid command '%s'", command);
return NULL;
}
return desc;
}
static int set_config(const cmd_desc_t *desc, const char *confdb,
const char *config, char *socket)
{
if (config != NULL && confdb != NULL) {
log_error("ambiguous configuration source");
return KNOT_EINVAL;
}
/* Mask relevant flags. */
cmd_conf_flag_t flags = desc->flags & (CMD_CONF_FREAD | CMD_CONF_FWRITE);
/* Choose the optimal config source. */
struct stat st;
bool import = false;
if (flags == CMD_CONF_FNONE && socket != NULL) {
import = false;
confdb = NULL;
} else if (confdb != NULL) {
import = false;
} else if (flags == CMD_CONF_FWRITE) {
import = false;
confdb = CONF_DEFAULT_DBDIR;
} else if (config != NULL){
import = true;
} else if (stat(CONF_DEFAULT_DBDIR, &st) == 0) {
import = false;
confdb = CONF_DEFAULT_DBDIR;
} else if (stat(CONF_DEFAULT_FILE, &st) == 0) {
import = true;
config = CONF_DEFAULT_FILE;
} else if (flags != CMD_CONF_FNONE) {
log_error("no configuration source available");
return KNOT_EINVAL;
}
const char *src = import ? config : confdb;
log_debug("%s '%s'", import ? "config" : "confdb",
(src != NULL) ? src : "empty");
/* Prepare config flags. */
conf_flag_t conf_flags = CONF_FNOHOSTNAME;
if (confdb != NULL && !(flags & CMD_CONF_FWRITE)) {
conf_flags |= CONF_FREADONLY;
}
/* Open confdb. */
conf_t *new_conf = NULL;
int ret = conf_new(&new_conf, conf_scheme, confdb, conf_flags);
if (ret != KNOT_EOK) {
log_error("failed to open configuration database '%s' (%s)",
(confdb != NULL) ? confdb : "", knot_strerror(ret));
return ret;
}
/* Import the config file. */
if (import) {
ret = conf_import(new_conf, config, true);
if (ret != KNOT_EOK) {
log_error("failed to load configuration file '%s' (%s)",
config, knot_strerror(ret));
conf_free(new_conf);
return ret;
}
}
/* Update to the new config. */
conf_update(new_conf);
return KNOT_EOK;
}
static int set_args_ctl(knot_ctl_t **ctl, const cmd_desc_t *desc,
const char *socket, int timeout)
{
if (desc == NULL) {
*ctl = NULL;
return KNOT_EINVAL;
}
/* Mask relevant flags. */
cmd_conf_flag_t flags = desc->flags & (CMD_CONF_FREAD | CMD_CONF_FWRITE);
/* Check if control socket is required. */
if (flags != CMD_CONF_FNONE) {
*ctl = NULL;
return KNOT_EOK;
}
/* Get control socket path. */
char *path = NULL;
if (socket != NULL) {
path = strdup(socket);
} else {
conf_val_t listen_val = conf_get(conf(), C_CTL, C_LISTEN);
conf_val_t rundir_val = conf_get(conf(), C_SRV, C_RUNDIR);
char *rundir = conf_abs_path(&rundir_val, NULL);
path = conf_abs_path(&listen_val, rundir);
free(rundir);
}
if (path == NULL) {
log_error("empty control socket path");
return KNOT_EINVAL;
}
log_debug("socket '%s'", path);
*ctl = knot_ctl_alloc();
if (*ctl == NULL) {
free(path);
return KNOT_ENOMEM;
}
knot_ctl_set_timeout(*ctl, timeout);
int ret = knot_ctl_connect(*ctl, path);
if (ret != KNOT_EOK) {
knot_ctl_free(*ctl);
log_error("failed to connect to socket '%s' (%s)", path,
knot_strerror(ret));
free(path);
return ret;
}
free(path);
return KNOT_EOK;
}
static void unset_args_ctl(knot_ctl_t *ctl)
{
if (ctl == NULL) {
return;
}
int ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL);
if (ret != KNOT_EOK && ret != KNOT_ECONN) {
log_error("failed to finish control (%s)", knot_strerror(ret));
}
knot_ctl_close(ctl);
knot_ctl_free(ctl);
}
params_t params = {
.flags = CMD_FNONE,
.timeout = DEFAULT_CTL_TIMEOUT * 1000
};
int main(int argc, char **argv)
{
cmd_flag_t flags = CMD_FNONE;
const char *config = NULL;
const char *confdb = NULL;
char *socket = NULL;
int timeout = DEFAULT_CTL_TIMEOUT * 1000;
bool verbose = false;
/* Long options. */
struct option opts[] = {
{ "config", required_argument, NULL, 'c' },
......@@ -260,27 +76,27 @@ int main(int argc, char **argv)
while ((opt = getopt_long(argc, argv, "c:C:s:t:fvhV", opts, &li)) != -1) {
switch (opt) {
case 'c':
config = optarg;
params.config = optarg;
break;
case 'C':
confdb = optarg;
params.confdb = optarg;
break;
case 's':
socket = optarg;
params.socket = optarg;
break;
case 't':
if (knot_str2int(optarg, &timeout) != KNOT_EOK) {
if (knot_str2uint16t(optarg, &params.timeout) != KNOT_EOK) {
print_help();
return EXIT_FAILURE;
}
// Convert to milliseconds.
timeout = (timeout > 0) ? timeout * 1000 : 0;
/* Convert to milliseconds. */
params.timeout *= 1000;
break;
case 'f':
flags |= CMD_FFORCE;
params.flags |= CMD_FFORCE;
break;
case 'v':
verbose = true;
params.verbose = true;
break;
case 'h':
print_help();
......@@ -294,59 +110,24 @@ int main(int argc, char **argv)
}
}
/* Check if there's at least one remaining non-option. */
if (argc - optind < 1) {
print_help();
return EXIT_FAILURE;
}
/* Set up simplified logging just to stdout/stderr. */
log_init();
log_levels_set(LOGT_STDOUT, LOG_ANY, LOG_MASK(LOG_INFO) | LOG_MASK(LOG_NOTICE));
log_levels_set(LOGT_STDERR, LOG_ANY, LOG_UPTO(LOG_WARNING));
log_levels_set(LOGT_SYSLOG, LOG_ANY, 0);
log_flag_set(LOG_FNO_TIMESTAMP | LOG_FNO_INFO);
if (verbose) {
if (params.verbose) {
log_levels_add(LOGT_STDOUT, LOG_ANY, LOG_MASK(LOG_DEBUG));
}
/* Check the command name. */
const cmd_desc_t *desc = get_cmd_desc(argv[optind]);
if (desc == NULL) {
log_close();
return EXIT_FAILURE;
}
/* Set up the configuration. */
int ret = set_config(desc, confdb, config, socket);
if (ret != KNOT_EOK) {
log_close();
return EXIT_FAILURE;
}
/* Prepare command parameters. */
cmd_args_t args = {
.desc = desc,
.argc = argc - optind - 1,
.argv = argv + optind + 1,
.flags = flags
};
/* Set control interface if necessary. */
ret = set_args_ctl(&args.ctl, desc, socket, timeout);
if (ret != KNOT_EOK) {
conf_free(conf());
log_close();
return EXIT_FAILURE;
int ret;
if (argc - optind < 1) {
ret = interactive_loop(&params);
} else {
ret = process_cmd(argc - optind, (const char **)argv + optind, &params);
}
/* Execute the command. */
ret = desc->fcn(&args);
/* Cleanup */
unset_args_ctl(args.ctl);
conf_free(conf());
log_close();
return ret == KNOT_EOK ? EXIT_SUCCESS : EXIT_FAILURE;
return (ret == KNOT_EOK) ? EXIT_SUCCESS : EXIT_FAILURE;
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/stat.h>
#include "knot/conf/conf.h"
#include "knot/common/log.h"
#include "utils/knotc/commands.h"
#include "utils/knotc/process.h"
static const cmd_desc_t *get_cmd_desc(const char *command)
{
/* Find requested command. */
const cmd_desc_t *desc = cmd_table;
while (desc->name != NULL) {
if (strcmp(desc->name, command) == 0) {
break;
}
desc++;
}
if (desc->name == NULL) {
log_error("invalid command '%s'", command);
return NULL;
}
return desc;
}
int set_config(const cmd_desc_t *desc, params_t *params)
{
if (params->config != NULL && params->confdb != NULL) {
log_error("ambiguous configuration source");
return KNOT_EINVAL;
}
/* Mask relevant flags. */
cmd_conf_flag_t flags = desc->flags & (CMD_CONF_FREAD | CMD_CONF_FWRITE);
/* Choose the optimal config source. */
struct stat st;
bool import = false;
if (flags == CMD_CONF_FNONE && params->socket != NULL) {
import = false;
params->confdb = NULL;
} else if (params->confdb != NULL) {
import = false;
} else if (flags == CMD_CONF_FWRITE) {
import = false;
params->confdb = CONF_DEFAULT_DBDIR;
} else if (params->config != NULL){
import = true;
} else if (stat(CONF_DEFAULT_DBDIR, &st) == 0) {
import = false;
params->confdb = CONF_DEFAULT_DBDIR;
} else if (stat(CONF_DEFAULT_FILE, &st) == 0) {
import = true;
params->config = CONF_DEFAULT_FILE;
} else if (flags != CMD_CONF_FNONE) {
log_error("no configuration source available");
return KNOT_EINVAL;
}
const char *src = import ? params->config : params->confdb;
log_debug("%s '%s'", import ? "config" : "confdb",
(src != NULL) ? src : "empty");
/* Prepare config flags. */
conf_flag_t conf_flags = CONF_FNOHOSTNAME;
if (params->confdb != NULL && !(flags & CMD_CONF_FWRITE)) {
conf_flags |= CONF_FREADONLY;
}
/* Open confdb. */
conf_t *new_conf = NULL;
int ret = conf_new(&new_conf, conf_scheme, params->confdb, conf_flags);
if (ret != KNOT_EOK) {
log_error("failed to open configuration database '%s' (%s)",
(params->confdb != NULL) ? params->confdb : "",
knot_strerror(ret));
return ret;
}
/* Import the config file. */
if (import) {
ret = conf_import(new_conf, params->config, true);
if (ret != KNOT_EOK) {
log_error("failed to load configuration file '%s' (%s)",
params->config, knot_strerror(ret));
conf_free(new_conf);
return ret;
}
}
/* Update to the new config. */
conf_update(new_conf);
return KNOT_EOK;
}
int set_ctl(knot_ctl_t **ctl, const cmd_desc_t *desc, params_t *params)
{
if (desc == NULL) {
*ctl = NULL;
return KNOT_EINVAL;
}
/* Mask relevant flags. */
cmd_conf_flag_t flags = desc->flags & (CMD_CONF_FREAD | CMD_CONF_FWRITE);
/* Check if control socket is required. */
if (flags != CMD_CONF_FNONE) {
*ctl = NULL;
return KNOT_EOK;
}
/* Get control socket path. */
char *path = NULL;
if (params->socket != NULL) {
path = strdup(params->socket);
} else {
conf_val_t listen_val = conf_get(conf(), C_CTL, C_LISTEN);
conf_val_t rundir_val = conf_get(conf(), C_SRV, C_RUNDIR);
char *rundir = conf_abs_path(&rundir_val, NULL);
path = conf_abs_path(&listen_val, rundir);
free(rundir);
}
if (path == NULL) {
log_error("empty control socket path");
return KNOT_EINVAL;
}
log_debug("socket '%s'", path);
*ctl = knot_ctl_alloc();
if (*ctl == NULL) {
free(path);
return KNOT_ENOMEM;
}
knot_ctl_set_timeout(*ctl, params->timeout);
int ret = knot_ctl_connect(*ctl, path);
if (ret != KNOT_EOK) {
knot_ctl_free(*ctl);
*ctl = NULL;
log_error("failed to connect to socket '%s' (%s)", path,
knot_strerror(ret));
free(path);
return ret;
}
free(path);
return KNOT_EOK;
}
void unset_ctl(knot_ctl_t *ctl)
{
if (ctl == NULL) {
return;
}
int ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL);
if (ret != KNOT_EOK && ret != KNOT_ECONN) {
log_error("failed to finish control (%s)", knot_strerror(ret));
}
knot_ctl_close(ctl);
knot_ctl_free(ctl);
}
int process_cmd(int argc, const char **argv, params_t *params)
{
if (argc == 0) {
return KNOT_ENOTSUP;
}
/* Check the command name. */
const cmd_desc_t *desc = get_cmd_desc(argv[0]);
if (desc == NULL) {
return KNOT_ENOENT;
}
/* Check for exit. */
if (desc->fcn == NULL) {
return KNOT_CTL_ESTOP;
}
/* Set up the configuration. */
int ret = set_config(desc, params);
if (ret != KNOT_EOK) {
return ret;
}
/* Prepare command parameters. */
cmd_args_t args = {
.desc = desc,
.argc = argc - 1,
.argv = argv + 1,
.flags = params->flags
};
/* Set control interface if necessary. */
ret = set_ctl(&args.ctl, desc, params);
if (ret != KNOT_EOK) {
conf_update(NULL);
return ret;
}
/* Execute the command. */
ret = desc->fcn(&args);
/* Cleanup */
unset_ctl(args.ctl);
conf_update(NULL);
return KNOT_EOK;
}
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*!
* \file
*
* \brief Knot control processing.
*
* \addtogroup knot_utils
* @{
*/
#pragma once
#include "utils/knotc/commands.h"
/*! Utility command line parameters. */
typedef struct {
cmd_flag_t flags;
const char *config;
const char *confdb;
const char *socket;
bool verbose;
uint16_t timeout;
} params_t;
/*!
* Prepares a proper configuration according to the specified command.
*
* \param[in] params Utility parameters.
*
* \return Error code, KNOT_EOK if successful.
*/
int set_config(const cmd_desc_t *desc, params_t *params);
/*!
* Estabilishes a control interface if necessary.
*
* \param[in] ctl Control context.
* \param[in] desc Utility command descriptor.
* \param[in] params Utility parameters.
*
* \return Error code, KNOT_EOK if successful.
*/
int set_ctl(knot_ctl_t **ctl, const cmd_desc_t *desc, params_t *params);
/*!
* Cleans up the control context.
*
* \param[in] ctl Control context.
*/
void unset_ctl(knot_ctl_t *ctl);
/*!
* Processes the given utility command.
*
* \param[in] argc Number of command arguments.
* \param[in] argv Command arguments.
* \param[in] params Utility parameters.
*
* \return Error code, KNOT_EOK if successful.
*/
int process_cmd(int argc, const char **argv, params_t *params);
/*! @} */
......@@ -59,3 +59,4 @@ zone_timers
zone_update
zonedb
ztree
utils/test_lookup
......@@ -64,7 +64,13 @@ check_PROGRAMS = \
ztree
check_PROGRAMS += \
modules/online_sign
modules/online_sign \
utils/test_lookup
utils_test_lookup_LDADD = \
$(top_builddir)/libtap/libtap.la \
$(top_builddir)/src/libknotus.la \
$(libedit_LIBS)
check-compile: $(check_PROGRAMS)
......
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <tap/basic.h>
#include "libknot/error.h"
#include "utils/common/lookup.h"
static void check_search_ok(lookup_t *l, const char *in, const char *out)
{
diag("Search for '%s'", in);
int ret = lookup_search(l, in, strlen(in));
ok(ret == KNOT_EOK, "Check found");
ok(strcmp(out, l->found.key) == 0, "Compare key");
ok(strcmp(out, l->found.data) == 0, "Compare data");
ok(l->iter.first_key == NULL, "Compare no first key");
ok(l->iter.count == 1, "Compare 1 count");
}
static void check_search_multi(lookup_t *l, const char *in, const char *out,
const char *first, size_t count)
{
diag("Search for '%s'", in);
int ret = lookup_search(l, in, strlen(in));
ok(ret == KNOT_EFEWDATA, "Check found multi");
ok(strcmp(out, l->found.key) == 0, "Compare key");
ok(l->found.data == NULL, "Compare no data");
ok(strcmp(first, l->iter.first_key) == 0, "Compare first key");
ok(l->iter.count == count, "Compare count");
}
static void check_search_none(lookup_t *l, const char *in)
{
diag("Search for '%s'", in);
int ret = lookup_search(l, in, strlen(in));
ok(ret == KNOT_ENOENT, "Check not found");
ok(l->found.key == NULL, "Check no key");
ok(l->found.data == NULL, "Check no data");
}
static void init(lookup_t *l, const char **table)
{
int ret = lookup_init(l);
ok(ret == KNOT_EOK, "Init");
while (*table != NULL) {
ret = lookup_insert(l, *table, (void *)*table);
ok(ret == KNOT_EOK, "Insert '%s'", *table);
table++;
}
lookup_index(l);
}
static void test_search_basic(void)
{
const char* table[] = {
"aa",
"bb",
NULL
};
lookup_t l;
init(&l, table);
check_search_ok(&l, "a", "aa");
check_search_ok(&l, "aa", "aa");
check_search_ok(&l, "b", "bb");
check_search_ok(&l, "bb", "bb");
check_search_none(&l, "0");
check_search_none(&l, "000");
check_search_none(&l, "00000000000000000000000000000000000000000000");
check_search_none(&l, "a0");
check_search_none(&l, "ab");
check_search_none(&l, "aaa");
check_search_none(&l, "bbb");
check_search_none(&l, "cc");
check_search_none(&l, "ccc");
check_search_none(&l, "cccccccccccccccccccccccccccccccccccccccccccc");
check_search_multi(&l, "", "", "aa", 2);
lookup_deinit(&l);
}
static void test_search_iter(void)
{
const char* table[] = {
"0",
"ab",
"abc",
"abcd",
"abc-1",
"abc-99",
"z",
NULL
};
lookup_t l;
init(&l, table);
check_search_multi(&l, "", "", "0", 7);
check_search_multi(&l, "a", "ab", "ab", 5);
check_search_multi(&l, "ab", "ab", "ab", 5);
check_search_multi(&l, "abc", "abc", "abc", 4);
check_search_multi(&l, "abc-", "abc-", "abc-1", 2);
lookup_deinit(&l);
}
int main(int argc, char *argv[])
{
plan_lazy();
diag("Search tests basic");
test_search_basic();
diag("Search tests multi-result");
test_search_iter();
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment