diff options
Diffstat (limited to 'src/out.c')
-rw-r--r-- | src/out.c | 768 |
1 files changed, 768 insertions, 0 deletions
diff --git a/src/out.c b/src/out.c new file mode 100644 index 0000000..5567387 --- /dev/null +++ b/src/out.c @@ -0,0 +1,768 @@ +/** + * @file out.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief libyang output functions. + * + * Copyright (c) 2015 - 2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE /* asprintf, strdup */ + +#include "out.h" +#include "out_internal.h" + +#include <assert.h> +#include <errno.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" +#include "compat.h" +#include "log.h" +#include "printer_data.h" +#include "tree_data.h" +#include "tree_schema.h" + +/** + * @brief Align the desired size to 1 KB. + */ +#define REALLOC_CHUNK(NEW_SIZE) \ + NEW_SIZE + (1024 - (NEW_SIZE % 1024)) + +LIBYANG_API_DEF ly_bool +lyd_node_should_print(const struct lyd_node *node, uint32_t options) +{ + const struct lyd_node *elem; + + if (options & LYD_PRINT_WD_TRIM) { + /* do not print default nodes */ + if (node->flags & LYD_DEFAULT) { + /* implicit default node/NP container with only default nodes */ + return 0; + } else if (node->schema && (node->schema->nodetype & LYD_NODE_TERM)) { + if (lyd_is_default(node)) { + /* explicit default node */ + return 0; + } + } + } else if ((node->flags & LYD_DEFAULT) && (node->schema->nodetype == LYS_CONTAINER)) { + if (options & LYD_PRINT_KEEPEMPTYCONT) { + /* explicit request to print */ + return 1; + } + + /* avoid empty default containers */ + LYD_TREE_DFS_BEGIN(node, elem) { + if ((elem != node) && lyd_node_should_print(elem, options)) { + return 1; + } + assert(elem->flags & LYD_DEFAULT); + LYD_TREE_DFS_END(node, elem) + } + return 0; + } else if ((node->flags & LYD_DEFAULT) && !(options & LYD_PRINT_WD_MASK) && !(node->schema->flags & LYS_CONFIG_R)) { + /* LYD_PRINT_WD_EXPLICIT, find out if this is some input/output */ + if (!(node->schema->flags & (LYS_IS_INPUT | LYS_IS_OUTPUT | LYS_IS_NOTIF)) && (node->schema->flags & LYS_CONFIG_W)) { + /* print only if it contains status data in its subtree */ + LYD_TREE_DFS_BEGIN(node, elem) { + if ((elem->schema->nodetype != LYS_CONTAINER) || (elem->schema->flags & LYS_PRESENCE)) { + if (elem->schema->flags & LYS_CONFIG_R) { + return 1; + } + } + LYD_TREE_DFS_END(node, elem) + } + } + return 0; + } + + return 1; +} + +LIBYANG_API_DEF LY_OUT_TYPE +ly_out_type(const struct ly_out *out) +{ + LY_CHECK_ARG_RET(NULL, out, LY_OUT_ERROR); + return out->type; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_clb(ly_write_clb writeclb, void *user_data, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, writeclb, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + + (*out)->type = LY_OUT_CALLBACK; + (*out)->method.clb.func = writeclb; + (*out)->method.clb.arg = user_data; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF ly_write_clb +ly_out_clb(struct ly_out *out, ly_write_clb writeclb) +{ + ly_write_clb prev_clb; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_CALLBACK, NULL); + + prev_clb = out->method.clb.func; + + if (writeclb) { + out->method.clb.func = writeclb; + } + + return prev_clb; +} + +LIBYANG_API_DEF void * +ly_out_clb_arg(struct ly_out *out, void *arg) +{ + void *prev_arg; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_CALLBACK, NULL); + + prev_arg = out->method.clb.arg; + + if (arg) { + out->method.clb.arg = arg; + } + + return prev_arg; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_fd(int fd, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, fd != -1, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + (*out)->type = LY_OUT_FD; + (*out)->method.fd = fd; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF int +ly_out_fd(struct ly_out *out, int fd) +{ + int prev_fd; + + LY_CHECK_ARG_RET(NULL, out, out->type <= LY_OUT_FDSTREAM, -1); + + if (out->type == LY_OUT_FDSTREAM) { + prev_fd = out->method.fdstream.fd; + } else { /* LY_OUT_FD */ + prev_fd = out->method.fd; + } + + if (fd != -1) { + /* replace output stream */ + if (out->type == LY_OUT_FDSTREAM) { + int streamfd; + FILE *stream; + + streamfd = dup(fd); + if (streamfd < 0) { + LOGERR(NULL, LY_ESYS, "Unable to duplicate provided file descriptor (%d) for printing the output (%s).", fd, strerror(errno)); + return -1; + } + stream = fdopen(streamfd, "a"); + if (!stream) { + LOGERR(NULL, LY_ESYS, "Unable to open provided file descriptor (%d) for printing the output (%s).", fd, strerror(errno)); + close(streamfd); + return -1; + } + /* close only the internally created stream, file descriptor is returned and supposed to be closed by the caller */ + fclose(out->method.fdstream.f); + out->method.fdstream.f = stream; + out->method.fdstream.fd = streamfd; + } else { /* LY_OUT_FD */ + out->method.fd = fd; + } + } + + return prev_fd; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_file(FILE *f, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, f, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + + (*out)->type = LY_OUT_FILE; + (*out)->method.f = f; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF FILE * +ly_out_file(struct ly_out *out, FILE *f) +{ + FILE *prev_f; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_FILE, NULL); + + prev_f = out->method.f; + + if (f) { + out->method.f = f; + } + + return prev_f; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_memory(char **strp, size_t size, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, strp, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + + (*out)->type = LY_OUT_MEMORY; + (*out)->method.mem.buf = strp; + if (!size) { + /* buffer is supposed to be allocated */ + *strp = NULL; + } else if (*strp) { + /* there is already buffer to use */ + (*out)->method.mem.size = size; + } + + return LY_SUCCESS; +} + +char * +ly_out_memory(struct ly_out *out, char **strp, size_t size) +{ + char *data; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_MEMORY, NULL); + + data = *out->method.mem.buf; + + if (strp) { + out->method.mem.buf = strp; + out->method.mem.len = out->method.mem.size = 0; + out->printed = 0; + if (!size) { + /* buffer is supposed to be allocated */ + *strp = NULL; + } else if (*strp) { + /* there is already buffer to use */ + out->method.mem.size = size; + } + } + + return data; +} + +LIBYANG_API_DEF LY_ERR +ly_out_reset(struct ly_out *out) +{ + LY_CHECK_ARG_RET(NULL, out, LY_EINVAL); + + switch (out->type) { + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + case LY_OUT_FD: + if ((lseek(out->method.fd, 0, SEEK_SET) == -1) && (errno != ESPIPE)) { + LOGERR(NULL, LY_ESYS, "Seeking output file descriptor failed (%s).", strerror(errno)); + return LY_ESYS; + } + if ((errno != ESPIPE) && (ftruncate(out->method.fd, 0) == -1)) { + LOGERR(NULL, LY_ESYS, "Truncating output file failed (%s).", strerror(errno)); + return LY_ESYS; + } + break; + case LY_OUT_FDSTREAM: + case LY_OUT_FILE: + case LY_OUT_FILEPATH: + if ((fseek(out->method.f, 0, SEEK_SET) == -1) && (errno != ESPIPE)) { + LOGERR(NULL, LY_ESYS, "Seeking output file stream failed (%s).", strerror(errno)); + return LY_ESYS; + } + if ((errno != ESPIPE) && (ftruncate(fileno(out->method.f), 0) == -1)) { + LOGERR(NULL, LY_ESYS, "Truncating output file failed (%s).", strerror(errno)); + return LY_ESYS; + } + break; + case LY_OUT_MEMORY: + if (out->method.mem.buf && *out->method.mem.buf) { + memset(*out->method.mem.buf, 0, out->method.mem.len); + } + out->printed = 0; + out->method.mem.len = 0; + break; + case LY_OUT_CALLBACK: + /* nothing to do (not seekable) */ + break; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_filepath(const char *filepath, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, filepath, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + + (*out)->type = LY_OUT_FILEPATH; + (*out)->method.fpath.f = fopen(filepath, "wb"); + if (!(*out)->method.fpath.f) { + LOGERR(NULL, LY_ESYS, "Failed to open file \"%s\" (%s).", filepath, strerror(errno)); + free(*out); + *out = NULL; + return LY_ESYS; + } + (*out)->method.fpath.filepath = strdup(filepath); + return LY_SUCCESS; +} + +LIBYANG_API_DEF const char * +ly_out_filepath(struct ly_out *out, const char *filepath) +{ + FILE *f; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_FILEPATH, filepath ? NULL : ((void *)-1)); + + if (!filepath) { + return out->method.fpath.filepath; + } + + /* replace filepath */ + f = out->method.fpath.f; + out->method.fpath.f = fopen(filepath, "wb"); + if (!out->method.fpath.f) { + LOGERR(NULL, LY_ESYS, "Failed to open file \"%s\" (%s).", filepath, strerror(errno)); + out->method.fpath.f = f; + return (void *)-1; + } + fclose(f); + free(out->method.fpath.filepath); + out->method.fpath.filepath = strdup(filepath); + + return NULL; +} + +LIBYANG_API_DEF void +ly_out_free(struct ly_out *out, void (*clb_arg_destructor)(void *arg), ly_bool destroy) +{ + if (!out) { + return; + } + + switch (out->type) { + case LY_OUT_CALLBACK: + if (clb_arg_destructor) { + clb_arg_destructor(out->method.clb.arg); + } + break; + case LY_OUT_FDSTREAM: + fclose(out->method.fdstream.f); + if (destroy) { + close(out->method.fdstream.fd); + } + break; + case LY_OUT_FD: + if (destroy) { + close(out->method.fd); + } + break; + case LY_OUT_FILE: + if (destroy) { + fclose(out->method.f); + } + break; + case LY_OUT_MEMORY: + if (destroy) { + free(*out->method.mem.buf); + } + break; + case LY_OUT_FILEPATH: + free(out->method.fpath.filepath); + fclose(out->method.fpath.f); + break; + case LY_OUT_ERROR: + LOGINT(NULL); + } + + free(out->buffered); + free(out); +} + +static LY_ERR +ly_vprint_(struct ly_out *out, const char *format, va_list ap) +{ + LY_ERR ret; + int written = 0; + char *msg = NULL, *aux; + + switch (out->type) { + case LY_OUT_FD: + written = vdprintf(out->method.fd, format, ap); + break; + case LY_OUT_FDSTREAM: + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + written = vfprintf(out->method.f, format, ap); + break; + case LY_OUT_MEMORY: + if ((written = vasprintf(&msg, format, ap)) < 0) { + break; + } + if (out->method.mem.len + written + 1 > out->method.mem.size) { + aux = ly_realloc(*out->method.mem.buf, out->method.mem.len + written + 1); + if (!aux) { + out->method.mem.buf = NULL; + out->method.mem.len = 0; + out->method.mem.size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + *out->method.mem.buf = aux; + out->method.mem.size = out->method.mem.len + written + 1; + } + if (written) { + memcpy(&(*out->method.mem.buf)[out->method.mem.len], msg, written); + } + out->method.mem.len += written; + (*out->method.mem.buf)[out->method.mem.len] = '\0'; + free(msg); + break; + case LY_OUT_CALLBACK: + if ((written = vasprintf(&msg, format, ap)) < 0) { + break; + } + written = out->method.clb.func(out->method.clb.arg, msg, written); + free(msg); + break; + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + } + + if (written < 0) { + LOGERR(NULL, LY_ESYS, "%s: writing data failed (%s).", __func__, strerror(errno)); + written = 0; + ret = LY_ESYS; + } else { + if (out->type == LY_OUT_FDSTREAM) { + /* move the original file descriptor to the end of the output file */ + lseek(out->method.fdstream.fd, 0, SEEK_END); + } + ret = LY_SUCCESS; + } + + out->printed += written; + out->func_printed += written; + return ret; +} + +LY_ERR +ly_print_(struct ly_out *out, const char *format, ...) +{ + LY_ERR ret; + va_list ap; + + va_start(ap, format); + ret = ly_vprint_(out, format, ap); + va_end(ap); + + return ret; +} + +LIBYANG_API_DEF LY_ERR +ly_print(struct ly_out *out, const char *format, ...) +{ + LY_ERR ret; + va_list ap; + + out->func_printed = 0; + + va_start(ap, format); + ret = ly_vprint_(out, format, ap); + va_end(ap); + + return ret; +} + +LIBYANG_API_DEF void +ly_print_flush(struct ly_out *out) +{ + switch (out->type) { + case LY_OUT_FDSTREAM: + /* move the original file descriptor to the end of the output file */ + lseek(out->method.fdstream.fd, 0, SEEK_END); + fflush(out->method.fdstream.f); + break; + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + fflush(out->method.f); + break; + case LY_OUT_FD: + fsync(out->method.fd); + break; + case LY_OUT_MEMORY: + case LY_OUT_CALLBACK: + /* nothing to do */ + break; + case LY_OUT_ERROR: + LOGINT(NULL); + } + + free(out->buffered); + out->buf_size = out->buf_len = 0; +} + +LY_ERR +ly_write_(struct ly_out *out, const char *buf, size_t len) +{ + LY_ERR ret = LY_SUCCESS; + size_t written = 0, new_mem_size; + + if (out->hole_count) { + /* we are buffering data after a hole */ + if (out->buf_len + len > out->buf_size) { + out->buffered = ly_realloc(out->buffered, out->buf_len + len); + if (!out->buffered) { + out->buf_len = 0; + out->buf_size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + out->buf_size = out->buf_len + len; + } + + if (len) { + memcpy(&out->buffered[out->buf_len], buf, len); + } + out->buf_len += len; + + out->printed += len; + out->func_printed += len; + return LY_SUCCESS; + } + +repeat: + switch (out->type) { + case LY_OUT_MEMORY: + new_mem_size = out->method.mem.len + len + 1; + if (new_mem_size > out->method.mem.size) { + new_mem_size = REALLOC_CHUNK(new_mem_size); + *out->method.mem.buf = ly_realloc(*out->method.mem.buf, new_mem_size); + if (!*out->method.mem.buf) { + out->method.mem.len = 0; + out->method.mem.size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + out->method.mem.size = new_mem_size; + } + if (len) { + memcpy(&(*out->method.mem.buf)[out->method.mem.len], buf, len); + } + out->method.mem.len += len; + (*out->method.mem.buf)[out->method.mem.len] = '\0'; + + written = len; + break; + case LY_OUT_FD: { + ssize_t r; + + r = write(out->method.fd, buf, len); + if (r < 0) { + ret = LY_ESYS; + } else { + written = (size_t)r; + } + break; + } + case LY_OUT_FDSTREAM: + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + written = fwrite(buf, sizeof *buf, len, out->method.f); + if (written != len) { + ret = LY_ESYS; + } + break; + case LY_OUT_CALLBACK: { + ssize_t r; + + r = out->method.clb.func(out->method.clb.arg, buf, len); + if (r < 0) { + ret = LY_ESYS; + } else { + written = (size_t)r; + } + break; + } + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + } + + if (ret) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { + ret = LY_SUCCESS; + goto repeat; + } + LOGERR(NULL, LY_ESYS, "%s: writing data failed (%s).", __func__, strerror(errno)); + written = 0; + } else if ((size_t)written != len) { + LOGERR(NULL, LY_ESYS, "%s: writing data failed (unable to write %u from %u data).", __func__, + len - (size_t)written, len); + ret = LY_ESYS; + } else { + if (out->type == LY_OUT_FDSTREAM) { + /* move the original file descriptor to the end of the output file */ + lseek(out->method.fdstream.fd, 0, SEEK_END); + } + ret = LY_SUCCESS; + } + + out->printed += written; + out->func_printed += written; + return ret; +} + +LIBYANG_API_DEF LY_ERR +ly_write(struct ly_out *out, const char *buf, size_t len) +{ + out->func_printed = 0; + + return ly_write_(out, buf, len); +} + +LIBYANG_API_DEF size_t +ly_out_printed(const struct ly_out *out) +{ + return out->func_printed; +} + +LY_ERR +ly_write_skip(struct ly_out *out, size_t count, size_t *position) +{ + switch (out->type) { + case LY_OUT_MEMORY: + if (out->method.mem.len + count > out->method.mem.size) { + *out->method.mem.buf = ly_realloc(*out->method.mem.buf, out->method.mem.len + count); + if (!(*out->method.mem.buf)) { + out->method.mem.len = 0; + out->method.mem.size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + out->method.mem.size = out->method.mem.len + count; + } + + /* save the current position */ + *position = out->method.mem.len; + + /* skip the memory */ + out->method.mem.len += count; + break; + case LY_OUT_FD: + case LY_OUT_FDSTREAM: + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + case LY_OUT_CALLBACK: + /* buffer the hole */ + if (out->buf_len + count > out->buf_size) { + out->buffered = ly_realloc(out->buffered, out->buf_len + count); + if (!out->buffered) { + out->buf_len = 0; + out->buf_size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + out->buf_size = out->buf_len + count; + } + + /* save the current position */ + *position = out->buf_len; + + /* skip the memory */ + out->buf_len += count; + + /* increase hole counter */ + ++out->hole_count; + break; + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + } + + /* update printed bytes counter despite we actually printed just a hole */ + out->printed += count; + out->func_printed += count; + return LY_SUCCESS; +} + +LY_ERR +ly_write_skipped(struct ly_out *out, size_t position, const char *buf, size_t count) +{ + LY_ERR ret = LY_SUCCESS; + + assert(count); + + switch (out->type) { + case LY_OUT_MEMORY: + /* write */ + memcpy(&(*out->method.mem.buf)[position], buf, count); + break; + case LY_OUT_FD: + case LY_OUT_FDSTREAM: + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + case LY_OUT_CALLBACK: + if (out->buf_len < position + count) { + LOGMEM(NULL); + return LY_EMEM; + } + + /* write into the hole */ + memcpy(&out->buffered[position], buf, count); + + /* decrease hole counter */ + --out->hole_count; + + if (!out->hole_count) { + /* all holes filled, we can write the buffer, + * printed bytes counter is updated by ly_write_() */ + ret = ly_write_(out, out->buffered, out->buf_len); + out->buf_len = 0; + } + break; + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + } + + if (out->type == LY_OUT_FILEPATH) { + /* move the original file descriptor to the end of the output file */ + lseek(out->method.fdstream.fd, 0, SEEK_END); + } + return ret; +} |