Commit 9fbdbe1d authored by Daniel Salzman's avatar Daniel Salzman

conf: improve parsing error detection + add block syntax check support

parent b75c97cb
......@@ -354,7 +354,7 @@ Default: off
.sp
Configuration of the server remote control.
.sp
Caution: The control protocol is not encrypted and is susceptible to replay
\fICaution:\fP The control protocol is not encrypted and is susceptible to replay
attacks in a short timeframe until message digest expires. For that reason,
it is recommended to use default UNIX socket.
.INDENT 0.0
......@@ -381,7 +381,7 @@ Default: \fI\%rundir\fP/knot.sock
An ordered list of \fI\%references\fP to ACL rules allowing the remote
control.
.sp
Caution: This option has no effect with UNIX socket.
\fICaution:\fP This option has no effect with UNIX socket.
.sp
Default: empty
.SH REMOTE SECTION
......@@ -603,6 +603,9 @@ If enabled, the server creates zone differences from changes you made to the
zone file upon server reload. This option is only relevant if the server
is a master server for the zone.
.sp
\fICaution:\fP This option has no effect with enabled
\fI\%dnssec\-signing\fP\&.
.sp
Default: off
.SS max\-journal\-size
.sp
......@@ -613,6 +616,8 @@ Default: unlimited
.sp
If enabled, automatic DNSSEC signing for the zone is turned on.
.sp
\fICaution:\fP Cannot be enabled on a slave zone.
.sp
Default: off
.SS kasp\-db
.sp
......@@ -783,7 +788,7 @@ Default: empty
.sp
A record owner prefix.
.sp
Caution: \fIprefix\fP doesn’t allow dots, address parts in the synthetic names are
\fICaution:\fP \fIprefix\fP doesn’t allow dots, address parts in the synthetic names are
separated with a dash.
.sp
Default: empty
......
......@@ -422,7 +422,7 @@ Control section
Configuration of the server remote control.
Caution: The control protocol is not encrypted and is susceptible to replay
*Caution:* The control protocol is not encrypted and is susceptible to replay
attacks in a short timeframe until message digest expires. For that reason,
it is recommended to use default UNIX socket.
......@@ -451,7 +451,7 @@ acl
An ordered list of :ref:`references<acl_id>` to ACL rules allowing the remote
control.
Caution: This option has no effect with UNIX socket.
*Caution:* This option has no effect with UNIX socket.
Default: empty
......@@ -706,6 +706,9 @@ If enabled, the server creates zone differences from changes you made to the
zone file upon server reload. This option is only relevant if the server
is a master server for the zone.
*Caution:* This option has no effect with enabled
:ref:`dnssec-signing<zone_dnssec-signing>`.
Default: off
.. _zone_max_journal_size:
......@@ -724,6 +727,8 @@ dnssec-signing
If enabled, automatic DNSSEC signing for the zone is turned on.
*Caution:* Cannot be enabled on a slave zone.
Default: off
.. _zone_kasp_db:
......@@ -923,7 +928,7 @@ prefix
A record owner prefix.
Caution: *prefix* doesn’t allow dots, address parts in the synthetic names are
*Caution:* *prefix* doesn’t allow dots, address parts in the synthetic names are
separated with a dash.
Default: empty
......
......@@ -30,6 +30,12 @@
#include "libknot/internal/namedb/namedb_lmdb.h"
#include "libknot/internal/sockaddr.h"
#include "libknot/yparser/ypformat.h"
#include "libknot/yparser/yptrafo.h"
/*! Configuration specific logging. */
#define CONF_LOG(severity, msg, ...) do { \
log_msg(severity, "config, " msg, ##__VA_ARGS__); \
} while (0)
#define MAX_INCLUDE_DEPTH 5
......@@ -44,7 +50,7 @@ static void rm_dir(const char *path)
{
DIR *dir = opendir(path);
if (dir == NULL) {
log_warning("failed to remove directory '%s'", path);
CONF_LOG(LOG_WARNING, "failed to remove directory '%s'", path);
return;
}
......@@ -54,7 +60,7 @@ static void rm_dir(const char *path)
struct dirent *entry = malloc(len);
if (entry == NULL) {
log_warning("failed to remove directory '%s'", path);
CONF_LOG(LOG_WARNING, "failed to remove directory '%s'", path);
closedir(dir);
return;
}
......@@ -83,7 +89,7 @@ static void rm_dir(const char *path)
// Secondly, delete the directory if it is empty.
if (ret != 0 || remove(path) != 0) {
log_warning("failed to remove whole directory '%s'", path);
CONF_LOG(LOG_WARNING, "failed to remove whole directory '%s'", path);
}
}
......@@ -122,7 +128,7 @@ int conf_new(
char tpl[] = "/tmp/knot-confdb.XXXXXX";
lmdb_opts.path = mkdtemp(tpl);
if (lmdb_opts.path == NULL) {
log_error("failed to create temporary directory");
CONF_LOG(LOG_ERR, "failed to create temporary directory");
ret = KNOT_ENOMEM;
goto new_error;
}
......@@ -370,42 +376,139 @@ void conf_deactivate_modules(
query_plan_free(query_plan);
}
static int parser_process(
static int exec_callbacks(
const yp_item_t *item,
conf_check_t *args)
{
for (size_t i = 0; i < YP_MAX_MISC_COUNT; i++) {
conf_check_f *fcn = (conf_check_f *)item->misc[i];
if (fcn == NULL) {
break;
}
int ret;
if ((ret = fcn(args)) != KNOT_EOK) {
return ret;
}
}
return KNOT_EOK;
}
static int previous_block_calls(
conf_t *conf,
namedb_txn_t *txn,
yp_parser_t *parser,
yp_check_ctx_t *ctx,
size_t *incl_depth)
conf_previous_t *prev,
const char **err_str)
{
int ret = yp_scheme_check_parser(ctx, parser);
if (ret != KNOT_EOK) {
return ret;
if (prev->id_len > 0) {
assert(prev->key0 != NULL);
conf_check_t args = {
.conf = conf,
.txn = txn,
.previous = prev,
.err_str = err_str
};
// Execute previous block callbacks.
int ret = exec_callbacks(prev->key0, &args);
if (ret != KNOT_EOK) {
return ret;
}
}
ret = conf_db_set(conf, txn, ctx);
if (ret != KNOT_EOK) {
return ret;
}
return KNOT_EOK;
}
const yp_item_t *item = (ctx->event == YP_EKEY0) ? ctx->key0 : ctx->key1;
conf_call_f *sem_check = (conf_call_f *)item->misc[0];
conf_call_f *callback = (conf_call_f *)item->misc[1];
conf_args_t args = {
conf, txn, parser->file.name, incl_depth, ctx->key0, ctx->key1,
ctx->id, ctx->id_len, ctx->data, ctx->data_len
static int item_calls(
conf_t *conf,
namedb_txn_t *txn,
yp_parser_t *parser,
yp_check_ctx_t *ctx,
conf_previous_t *prev,
size_t *incl_depth,
const char **err_str)
{
conf_check_t args = {
.conf = conf,
.txn = txn,
.parser = parser,
.check = ctx,
.include_depth = incl_depth,
.previous = prev,
.err_str = err_str
};
// Call semantic check if any.
if (sem_check != NULL && (ret = sem_check(&args)) != KNOT_EOK) {
return ret;
}
const yp_item_t *item;
// Prepare previous context.
switch (ctx->event) {
case YP_EKEY0:
// Reset previous context id.
prev->id_len = 0;
// Set previous context key0 if group item.
if (ctx->key0->type == YP_TGRP) {
prev->key0 = ctx->key0;
return KNOT_EOK;
}
item = ctx->key0;
break;
case YP_EID:
memcpy(prev->id, ctx->id, ctx->id_len);
prev->id_len = ctx->id_len;
prev->file = parser->file.name;
prev->line = parser->line_count;
item = ctx->key1;
break;
default:
assert(ctx->event == YP_EKEY1);
item = ctx->key1;
break;
}
// Execute item callbacks.
return exec_callbacks(item, &args);
}
// Call callback function if any.
if (callback != NULL && (ret = callback(&args)) != KNOT_EOK) {
return ret;
#define CONF_LOG_LINE(input, is_file, line, msg, ...) do { \
CONF_LOG(LOG_ERR, "%s%s%sline %zu, " msg, \
(is_file ? "file '" : ""), (is_file ? input : ""), \
(is_file ? "', " : ""), line, ##__VA_ARGS__); \
} while (0)
static void log_current_err(
const char *input,
bool is_file,
yp_parser_t *parser,
int ret,
const char *err_str)
{
CONF_LOG_LINE(input, is_file, parser->line_count,
"item '%.*s', value '%.*s' (%s)",
(int)parser->key_len, parser->key,
(int)parser->data_len, parser->data,
(err_str != NULL ? err_str : knot_strerror(ret)));
}
static void log_prev_err(
const char *input,
bool is_file,
struct conf_previous *prev,
int ret,
const char *err_str)
{
char buff[512];
size_t len = sizeof(buff);
// Get textual previous identifier.
if (yp_item_to_txt(prev->key0->var.g.id, prev->id, prev->id_len,
buff, &len, YP_SNOQUOTE) != KNOT_EOK) {
buff[0] = '\0';
}
return KNOT_EOK;
CONF_LOG_LINE(input, is_file, prev->line, "%s '%s' (%s)",
prev->key0->name + 1, buff,
(err_str != NULL ? err_str : knot_strerror(ret)));
}
int conf_parse(
......@@ -413,15 +516,17 @@ int conf_parse(
namedb_txn_t *txn,
const char *input,
bool is_file,
size_t *incl_depth)
size_t *incl_depth,
struct conf_previous *prev)
{
if (conf == NULL || txn == NULL || input == NULL ||
incl_depth == NULL) {
incl_depth == NULL || prev == NULL) {
return KNOT_EINVAL;
}
// Check for include loop.
if ((*incl_depth)++ > MAX_INCLUDE_DEPTH) {
CONF_LOG(LOG_ERR, "include loop detected");
return KNOT_EPARSEFAIL;
}
......@@ -432,43 +537,71 @@ int conf_parse(
yp_init(parser);
int ret;
// Set parser source.
if (is_file) {
ret = yp_set_input_file(parser, input);
} else {
ret = yp_set_input_string(parser, input, strlen(input));
}
if (ret != KNOT_EOK) {
goto init_error;
CONF_LOG(LOG_ERR, "failed to load file '%s' (%s)",
input, knot_strerror(ret));
goto parse_error;
}
// Initialize parser check context.
yp_check_ctx_t *ctx = yp_scheme_check_init(conf->scheme);
if (ctx == NULL) {
ret = KNOT_ENOMEM;
goto init_error;
goto parse_error;
}
int check_ret = KNOT_EOK;
const char *err_str = NULL;
// Parse the configuration.
while ((ret = yp_parse(parser)) == KNOT_EOK) {
ret = parser_process(conf, txn, parser, ctx, incl_depth);
if (ret != KNOT_EOK) {
check_ret = yp_scheme_check_parser(ctx, parser);
if (check_ret != KNOT_EOK) {
log_current_err(input, is_file, parser, check_ret, NULL);
break;
}
check_ret = conf_db_set(conf, txn, ctx);
if (check_ret != KNOT_EOK) {
log_current_err(input, is_file, parser, check_ret, NULL);
break;
}
if (ctx->event != YP_EKEY1) {
check_ret = previous_block_calls(conf, txn, prev, &err_str);
if (check_ret != KNOT_EOK) {
log_prev_err(input, is_file, prev, check_ret, err_str);
break;
}
}
check_ret = item_calls(conf, txn, parser, ctx, prev, incl_depth,
&err_str);
if (check_ret != KNOT_EOK) {
log_current_err(input, is_file, parser, check_ret, err_str);
break;
}
}
yp_scheme_check_deinit(ctx);
if (ret != KNOT_EOF) {
log_error("invalid configuration%s%s%s, line %zu (%s)",
(is_file ? " file '" : ""),
(is_file ? input : ""),
(is_file ? "'" : ""),
parser->line_count, knot_strerror(ret));
goto init_error;
if (ret == KNOT_EOF) {
// Call the last block callbacks.
ret = previous_block_calls(conf, txn, prev, &err_str);
if (ret != KNOT_EOK) {
log_prev_err(input, is_file, prev, ret, err_str);
}
} else if (ret != KNOT_EOK) {
log_current_err(input, is_file, parser, ret, NULL);
} else {
ret = check_ret;
}
yp_scheme_check_deinit(ctx);
parse_error:
(*incl_depth)--;
ret = KNOT_EOK;
init_error:
yp_deinit(parser);
free(parser);
......@@ -505,9 +638,10 @@ int conf_import(
}
size_t depth = 0;
conf_previous_t prev = { NULL };
// Parse and import given file.
ret = conf_parse(conf, &txn, input, is_file, &depth);
ret = conf_parse(conf, &txn, input, is_file, &depth, &prev);
if (ret != KNOT_EOK) {
conf->api->txn_abort(&txn);
return ret;
......
......@@ -60,6 +60,8 @@ typedef struct {
struct query_plan *query_plan;
} conf_t;
struct conf_previous;
/*!
* Returns the active configuration.
*/
......@@ -157,13 +159,14 @@ void conf_deactivate_modules(
/*!
* Parses textual configuration from the string or from the file.
*
* This function is used for includes processing!
* This function is not for direct using, just for includes processing!
*
* \param[in] conf Configuration.
* \param[in] txn Transaction.
* \param[in] input Configuration string or filename.
* \param[in] is_file Specifies if the input is string or input filename.
* \param[in] incl_depth The current include depth counter.
* \param[in] prev Previous context.
*
* \return Error code, KNOT_EOK if success.
*/
......@@ -172,7 +175,8 @@ int conf_parse(
namedb_txn_t *txn,
const char *input,
bool is_file,
size_t *incl_depth
size_t *incl_depth,
struct conf_previous *prev
);
/*!
......
......@@ -29,6 +29,15 @@
#include "libknot/internal/strlcat.h"
#include "libknot/yparser/yptrafo.h"
/*! Configuration specific logging. */
#define CONF_LOG(severity, msg, ...) do { \
log_msg(severity, "config, " msg, ##__VA_ARGS__); \
} while (0)
#define CONF_LOG_ZONE(severity, zone, msg, ...) do { \
log_msg_zone(severity, zone, "config, " msg, ##__VA_ARGS__); \
} while (0)
static conf_val_t raw_id_get(
conf_t *conf,
namedb_txn_t *txn,
......@@ -42,8 +51,8 @@ static conf_val_t raw_id_get(
val.code = conf_db_get(conf, txn, key0_name, key1_name, id, id_len, &val);
switch (val.code) {
default:
log_error("failed to read configuration '%s/%s' (%s)",
key0_name + 1, key1_name + 1, knot_strerror(val.code));
CONF_LOG(LOG_ERR, "failed to read '%s/%s' (%s)",
key0_name + 1, key1_name + 1, knot_strerror(val.code));
// FALLTHROUGH
case KNOT_EOK:
case KNOT_ENOENT:
......@@ -59,7 +68,7 @@ conf_val_t conf_get_txn(
{
// Check for empty key1.
if (key1_name == NULL) {
log_error("incomplete configuration specification");
CONF_LOG(LOG_ERR, "missing parameters");
conf_val_t val = { NULL };
val.code = KNOT_EINVAL;
return val;
......@@ -84,7 +93,7 @@ conf_val_t conf_id_get_txn(
}
conf_db_val(id);
} else {
log_error("incomplete configuration specification");
CONF_LOG(LOG_ERR, "missing parameters");
conf_val_t val = { NULL };
val.code = KNOT_EINVAL;
return val;
......@@ -101,7 +110,7 @@ conf_val_t conf_mod_get_txn(
{
// Check for empty input.
if (key1_name == NULL || mod_id == NULL) {
log_error("incomplete configuration specification");
CONF_LOG(LOG_ERR, "missing parameters");
conf_val_t val = { NULL };
val.code = KNOT_EINVAL;
return val;
......@@ -119,7 +128,7 @@ conf_val_t conf_zone_get_txn(
conf_val_t val = { NULL };
if (dname == NULL) {
log_error("incomplete configuration specification");
CONF_LOG(LOG_ERR, "missing parameters");
val.code = KNOT_EINVAL;
return val;
}
......@@ -132,8 +141,8 @@ conf_val_t conf_zone_get_txn(
case KNOT_EOK:
return val;
default:
log_zone_error(dname, "failed to read configuration '%s/%s' (%s)",
C_ZONE + 1, key1_name + 1, knot_strerror(val.code));
CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)",
C_ZONE + 1, key1_name + 1, knot_strerror(val.code));
// FALLTHROUGH
case KNOT_ENOENT:
break;
......@@ -149,8 +158,8 @@ conf_val_t conf_zone_get_txn(
val.data, val.len, &val);
break;
default:
log_zone_error(dname, "failed to read configuration '%s/%s' (%s)",
C_ZONE + 1, C_TPL + 1, knot_strerror(val.code));
CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)",
C_ZONE + 1, C_TPL + 1, knot_strerror(val.code));
// FALLTHROUGH
case KNOT_ENOENT:
// Use the default template.
......@@ -160,8 +169,8 @@ conf_val_t conf_zone_get_txn(
switch (val.code) {
default:
log_zone_error(dname, "failed to read configuration '%s/%s' (%s)",
C_TPL + 1, key1_name + 1, knot_strerror(val.code));
CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)",
C_TPL + 1, key1_name + 1, knot_strerror(val.code));
// FALLTHROUGH
case KNOT_EOK:
case KNOT_ENOENT:
......@@ -182,8 +191,8 @@ conf_val_t conf_default_get_txn(
CONF_DEFAULT_ID + 1, CONF_DEFAULT_ID[0], &val);
switch (val.code) {
default:
log_error("failed to read configuration '%s/%s' (%s)",
C_TPL + 1, key1_name + 1, knot_strerror(val.code));
CONF_LOG(LOG_ERR, "failed to read '%s/%s' (%s)",
C_TPL + 1, key1_name + 1, knot_strerror(val.code));
// FALLTHROUGH
case KNOT_EOK:
case KNOT_ENOENT:
......@@ -206,8 +215,8 @@ size_t conf_id_count_txn(
case KNOT_EOK:
break;
default:
log_error("failed to iterate through configuration '%s' (%s)",
key0_name + 1, knot_strerror(ret));
CONF_LOG(LOG_ERR, "failed to iterate through '%s' (%s)",
key0_name + 1, knot_strerror(ret));
// FALLTHROUGH
case KNOT_ENOENT:
return count;
......@@ -232,7 +241,7 @@ conf_iter_t conf_iter_txn(
iter.code = conf_db_iter_begin(conf, txn, key0_name, &iter);
switch (iter.code) {
default:
log_error("failed to iterate thgrough configuration '%s' (%s)",
CONF_LOG(LOG_ERR, "failed to iterate thgrough '%s' (%s)",
key0_name + 1, knot_strerror(iter.code));
// FALLTHROUGH
case KNOT_EOK:
......@@ -248,7 +257,7 @@ void conf_iter_next(
iter->code = conf_db_iter_next(conf, iter);
switch (iter->code) {
default:
log_error("failed to read next configuration item (%s)",
CONF_LOG(LOG_ERR, "failed to read next item (%s)",
knot_strerror(iter->code));
// FALLTHROUGH
case KNOT_EOK:
......@@ -267,7 +276,7 @@ conf_val_t conf_iter_id(
&val.blob_len);
switch (val.code) {
default:
log_error("failed to read configuration identifier (%s)",
CONF_LOG(LOG_ERR, "failed to read identifier (%s)",
knot_strerror(val.code));
// FALLTHROUGH
case KNOT_EOK:
......@@ -620,12 +629,12 @@ static char* get_filename(
}
break;
case '\0':
log_zone_warning(zone, "ignoring missing trailing "
"zonefile formatter");
CONF_LOG_ZONE(LOG_WARNING, zone, "ignoring missing "
"trailing zonefile formatter");
continue;
default:
log_zone_warning(zone, "ignoring zonefile formatter '%%%c'",
type);
CONF_LOG_ZONE(LOG_WARNING, zone, "ignoring zonefile "
"formatter '%%%c'", type);
continue;
}
......@@ -742,7 +751,8 @@ int conf_user_txn(
if (grp != NULL) {
*gid = grp->gr_gid;
} else {
log_error("invalid group name '%s'", sep_pos + 1);
CONF_LOG(LOG_ERR, "invalid group name '%s'",
sep_pos + 1);
free(user);
return KNOT_EINVAL;
}
......@@ -758,7 +768,7 @@ int conf_user_txn(
if (pwd != NULL) {
*uid = pwd->pw_uid;
} else {
log_error("invalid user name '%s'", user);
CONF_LOG(LOG_ERR, "invalid user name '%s'", user);
free(user);
return KNOT_EINVAL;
}
......@@ -792,7 +802,7 @@ conf_remote_t conf_remote_txn(
// Get remote address.
conf_val_t val = conf_id_get_txn(conf, txn, C_RMT, C_ADDR, id);
if (val.code != KNOT_EOK) {
log_error("invalid remote in configuration");
CONF_LOG(LOG_ERR, "invalid remote");
free(rundir);
return out;
}
......
......@@ -190,7 +190,7 @@ const yp_item_t conf_scheme[] = {
#endif
/***********/
{ C_TPL, YP_TGRP, YP_VGRP = { desc_template }, YP_FMULTI },
{ C_ZONE, YP_TGRP, YP_VGRP = { desc_zone }, YP_FMULTI },
{ C_INCL, YP_TSTR, YP_VNONE, YP_FNONE, { NULL, include_file } },
{ C_ZONE, YP_TGRP, YP_VGRP = { desc_zone }, YP_FMULTI, { check_zone } },
{ C_INCL, YP_TSTR, YP_VNONE, YP_FNONE, { include_file } },
{ NULL }
};
......@@ -180,29 +180,62 @@ int mod_id_to_txt(
}
int check_ref(
conf_args_t *args)
conf_check_t *args)
{
const yp_item_t *parent = args->key1->var.r.ref;
const char *err_str = "invalid reference";
const yp_item_t *parent = args->check->key1->var.r.ref;
// Try to find the id in the referenced category.
return conf_db_get(args->conf, args->txn, parent->name, NULL,
args->data, args->data_len, NULL);
int ret = conf_db_get(args->conf, args->txn, parent->name, NULL,
args->check->data, args->check->data_len, NULL);
if (ret != KNOT_EOK) {
*args->err_str = err_str;
}
return ret;
}
int check_modref(
conf_args_t *args)
conf_check_t *args)
{
const yp_name_t *mod_name = (const yp_name_t *)args->data;
const uint8_t *id = args->data + 1 + args->data[0];
size_t id_len = args->data_len - 1 - args->data[0];
const char *err_str = "invalid module reference";
const yp_name_t *mod_name = (const yp_name_t *)args->check->data;
const uint8_t *id = args->check->data + 1 + args->check->data[0];
size_t id_len = args->check->data_len - 1 - args->check->data[0];
// Try to find the module with id.
return conf_db_get(args->conf, args->txn, mod_name, NULL, id, id_len,
NULL);
int ret = conf_db_get(args->conf, args->txn, mod_name, NULL, id, id_len,
NULL);
if (ret != KNOT_EOK) {
*args->err_str = err_str;
}
return ret;
}
int check_zone(
conf_check_t *args)
{
const char *err_str = "slave zone with DNSSEC signing";
conf_val_t master = conf_zone_get_txn(args->conf, args->txn,
C_MASTER, args->previous->id);
conf_val_t dnssec = conf_zone_get_txn(args->conf, args->txn,
C_DNSSEC_SIGNING, args->previous->id);
// DNSSEC signing is not possible with slave zone.
if (conf_val_count(&master) > 0 && conf_bool(&dnssec)) {
*args->err_str = err_str;
return KNOT_EINVAL;
}
return KNOT_EOK;
}
int include_file(
conf_args_t *args)
conf_check_t *args)
{
size_t max_path = 4096;
char *path = malloc(max_path);
......@@ -212,12 +245,13 @@ int include_file(
// Prepare absolute include path.
int ret;
if (args->data[0] == '/') {
if (args->check->data[0] == '/') {
ret = snprintf(path, max_path, "%.*s",
(int)args->data_len, args->data);
(int)args->check->data_len, args->check->data);
} else {
char *full_current_name = realpath((args->file_name != NULL) ?
args->file_name : "./", NULL);
const char *file_name = args->parser->file.name != NULL ?
args->parser->file.name : "./";
char *full_current_name = realpath(file_name, NULL);
if (full_current_name == NULL) {
free(path);
return KNOT_ENOMEM;
......@@ -225,7 +259,7 @@ int include_file(
ret = snprintf(path, max_path, "%s/%.*s",
dirname(full_current_name),
(int)args->data_len, args->data);
(int)args->check->data_len, args->check->data);
free(full_current_name);
}
if (ret <= 0 || ret >= max_path)