| /** |
| * @file printer.c |
| * @author Radek Krejci <rkrejci@cesnet.cz> |
| * @brief Generic libyang printers functions. |
| * |
| * Copyright (c) 2015 - 2019 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 |
| */ |
| |
| #include "common.h" |
| |
| #include <errno.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "log.h" |
| #include "printer_internal.h" |
| |
| /** |
| * @brief informational structure shared by printers |
| */ |
| struct ext_substmt_info_s ext_substmt_info[] = { |
| {NULL, NULL, 0}, /**< LYEXT_SUBSTMT_SELF */ |
| {"argument", "name", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_ARGUMENT */ |
| {"base", "name", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_BASE */ |
| {"belongs-to", "module", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_BELONGSTO */ |
| {"contact", "text", SUBST_FLAG_YIN}, /**< LYEXT_SUBSTMT_CONTACT */ |
| {"default", "value", 0}, /**< LYEXT_SUBSTMT_DEFAULT */ |
| {"description", "text", SUBST_FLAG_YIN}, /**< LYEXT_SUBSTMT_DESCRIPTION */ |
| {"error-app-tag", "value", 0}, /**< LYEXT_SUBSTMT_ERRTAG */ |
| {"error-message", "value", SUBST_FLAG_YIN}, /**< LYEXT_SUBSTMT_ERRMSG */ |
| {"key", "value", 0}, /**< LYEXT_SUBSTMT_KEY */ |
| {"namespace", "uri", 0}, /**< LYEXT_SUBSTMT_NAMESPACE */ |
| {"organization", "text", SUBST_FLAG_YIN}, /**< LYEXT_SUBSTMT_ORGANIZATION */ |
| {"path", "value", 0}, /**< LYEXT_SUBSTMT_PATH */ |
| {"prefix", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_PREFIX */ |
| {"presence", "value", 0}, /**< LYEXT_SUBSTMT_PRESENCE */ |
| {"reference", "text", SUBST_FLAG_YIN}, /**< LYEXT_SUBSTMT_REFERENCE */ |
| {"revision-date", "date", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_REVISIONDATE */ |
| {"units", "name", 0}, /**< LYEXT_SUBSTMT_UNITS */ |
| {"value", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_VALUE */ |
| {"yang-version", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_VERSION */ |
| {"modifier", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_MODIFIER */ |
| {"require-instance", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_REQINST */ |
| {"yin-element", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_YINELEM */ |
| {"config", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_CONFIG */ |
| {"mandatory", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_MANDATORY */ |
| {"ordered-by", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_ORDEREDBY */ |
| {"status", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_STATUS */ |
| {"fraction-digits", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_DIGITS */ |
| {"max-elements", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_MAX */ |
| {"min-elements", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_MIN */ |
| {"position", "value", SUBST_FLAG_ID}, /**< LYEXT_SUBSTMT_POSITION */ |
| {"unique", "tag", 0}, /**< LYEXT_SUBSTMT_UNIQUE */ |
| }; |
| |
| LY_ERR |
| ly_print(struct lyout *out, const char *format, ...) |
| { |
| int count = 0; |
| char *msg = NULL, *aux; |
| va_list ap; |
| #ifndef HAVE_VDPRINTF |
| int fd; |
| FILE *stream; |
| #endif |
| |
| LYOUT_CHECK(out, out->status); |
| |
| va_start(ap, format); |
| |
| switch (out->type) { |
| case LYOUT_FD: |
| #ifdef HAVE_VDPRINTF |
| count = vdprintf(out->method.fd, format, ap); |
| break; |
| #else |
| /* Without vdfprintf(), change the printing method to printing to a FILE stream. |
| * To preserve the original file descriptor, duplicate it and use it to open file stream. |
| * Due to a standalone LYOUT_FDSTREAM, ly*_print_fd() functions are supposed to detect the |
| * change and close the stream on their exit. */ |
| fd = dup(out->method.fd); |
| if (fd < 0) { |
| LOGERR(NULL, LY_ESYS, "Unable to duplicate provided file descriptor (%d) for printing the output (%s).", |
| out->method.fd, strerror(errno)); |
| va_end(ap); |
| out->status = LY_ESYS; |
| return LY_ESYS; |
| } |
| stream = fdopen(fd, "a"); |
| if (!stream) { |
| LOGERR(NULL, LY_ESYS, "Unable to open provided file descriptor (%d) for printing the output (%s).", |
| out->method.fd, strerror(errno)); |
| va_end(ap); |
| out->status = LY_ESYS; |
| return LY_ESYS; |
| } |
| out->method.f = stream; |
| out->type = LYOUT_FDSTREAM; |
| #endif |
| /* fall through */ |
| case LYOUT_FDSTREAM: |
| case LYOUT_STREAM: |
| count = vfprintf(out->method.f, format, ap); |
| break; |
| case LYOUT_MEMORY: |
| if ((count = vasprintf(&msg, format, ap)) < 0) { |
| break; |
| } |
| if (out->method.mem.len + count + 1 > out->method.mem.size) { |
| aux = ly_realloc(out->method.mem.buf, out->method.mem.len + count + 1); |
| if (!aux) { |
| out->method.mem.buf = NULL; |
| out->method.mem.len = 0; |
| out->method.mem.size = 0; |
| LOGMEM(NULL); |
| va_end(ap); |
| return LY_EMEM; |
| } |
| out->method.mem.buf = aux; |
| out->method.mem.size = out->method.mem.len + count + 1; |
| } |
| memcpy(&out->method.mem.buf[out->method.mem.len], msg, count); |
| out->method.mem.len += count; |
| out->method.mem.buf[out->method.mem.len] = '\0'; |
| free(msg); |
| break; |
| case LYOUT_CALLBACK: |
| if ((count = vasprintf(&msg, format, ap)) < 0) { |
| break; |
| } |
| count = out->method.clb.f(out->method.clb.arg, msg, count); |
| free(msg); |
| break; |
| } |
| |
| va_end(ap); |
| |
| if (count < 0) { |
| LOGERR(out->ctx, LY_ESYS, "%s: writing data failed (%s).", __func__, strerror(errno)); |
| out->status = LY_ESYS; |
| return LY_ESYS; |
| } else { |
| out->printed += count; |
| return LY_SUCCESS; |
| } |
| } |
| |
| void |
| ly_print_flush(struct lyout *out) |
| { |
| switch (out->type) { |
| case LYOUT_FDSTREAM: |
| case LYOUT_STREAM: |
| fflush(out->method.f); |
| break; |
| case LYOUT_FD: |
| fsync(out->method.fd); |
| break; |
| case LYOUT_MEMORY: |
| case LYOUT_CALLBACK: |
| /* nothing to do */ |
| break; |
| } |
| |
| free(out->buffered); |
| out->buf_size = out->buf_len = 0; |
| } |
| |
| LY_ERR |
| ly_write(struct lyout *out, const char *buf, size_t len) |
| { |
| int written = 0; |
| |
| LYOUT_CHECK(out, out->status); |
| |
| 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_RET(NULL); |
| } |
| out->buf_size = out->buf_len + len; |
| } |
| |
| memcpy(&out->buffered[out->buf_len], buf, len); |
| out->buf_len += len; |
| return LY_SUCCESS; |
| } |
| |
| repeat: |
| switch (out->type) { |
| case LYOUT_MEMORY: |
| if (out->method.mem.len + len + 1 > out->method.mem.size) { |
| out->method.mem.buf = ly_realloc(out->method.mem.buf, out->method.mem.len + len + 1); |
| if (!out->method.mem.buf) { |
| out->method.mem.len = 0; |
| out->method.mem.size = 0; |
| LOGMEM_RET(NULL); |
| } |
| out->method.mem.size = out->method.mem.len + len + 1; |
| } |
| 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'; |
| |
| out->printed += len; |
| return LY_SUCCESS; |
| case LYOUT_FD: |
| written = write(out->method.fd, buf, len); |
| break; |
| case LYOUT_FDSTREAM: |
| case LYOUT_STREAM: |
| written = fwrite(buf, sizeof *buf, len, out->method.f); |
| break; |
| case LYOUT_CALLBACK: |
| written = out->method.clb.f(out->method.clb.arg, buf, len); |
| break; |
| } |
| |
| if (written < 0) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| goto repeat; |
| } |
| LOGERR(out->ctx, LY_ESYS, "%s: writing data failed (%s).", __func__, strerror(errno)); |
| out->status = LY_ESYS; |
| return LY_ESYS; |
| } else if ((size_t)written != len) { |
| LOGERR(out->ctx, LY_ESYS, "%s: writing data failed (unable to write %u from %u data).", __func__, len - (size_t)written, len); |
| out->status = LY_ESYS; |
| return LY_ESYS; |
| } else { |
| out->printed += written; |
| return LY_SUCCESS; |
| } |
| } |
| |
| LY_ERR |
| ly_write_skip(struct lyout *out, size_t count, size_t *position) |
| { |
| LYOUT_CHECK(out, out->status); |
| |
| switch (out->type) { |
| case LYOUT_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; |
| out->status = LY_ESYS; |
| LOGMEM_RET(NULL); |
| } |
| 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; |
| |
| /* update printed bytes counter despite we actually printed just a hole */ |
| out->printed += count; |
| break; |
| case LYOUT_FD: |
| case LYOUT_FDSTREAM: |
| case LYOUT_STREAM: |
| case LYOUT_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; |
| out->status = LY_ESYS; |
| LOGMEM_RET(NULL); |
| } |
| 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; |
| } |
| |
| return LY_SUCCESS; |
| } |
| |
| LY_ERR |
| ly_write_skipped(struct lyout *out, size_t position, const char *buf, size_t count) |
| { |
| LY_ERR ret = LY_SUCCESS; |
| |
| LYOUT_CHECK(out, out->status); |
| |
| switch (out->type) { |
| case LYOUT_MEMORY: |
| /* write */ |
| memcpy(&out->method.mem.buf[position], buf, count); |
| break; |
| case LYOUT_FD: |
| case LYOUT_FDSTREAM: |
| case LYOUT_STREAM: |
| case LYOUT_CALLBACK: |
| if (out->buf_len < position + count) { |
| out->status = LY_ESYS; |
| LOGMEM_RET(NULL); |
| } |
| |
| /* 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; |
| } |
| |
| return ret; |
| } |