/** * @file out.c * @author Radek Krejci * @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 #include #include #include #include #include #include #include #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; }