diff options
Diffstat (limited to 'src/output.c')
-rw-r--r-- | src/output.c | 2003 |
1 files changed, 2003 insertions, 0 deletions
diff --git a/src/output.c b/src/output.c new file mode 100644 index 0000000..7cd63fa --- /dev/null +++ b/src/output.c @@ -0,0 +1,2003 @@ +/*-------------------------------------------------------------------- + * Copyright © 2016 James Hunt <jamesodhunt@ubuntu.com>. + * + * 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 <stdbool.h> +#include <syslog.h> +#include <assert.h> + +#include "pr_list.h" +#include "string-util.h" +#include "output.h" + +/* Default character to use for indent */ +#define DEFAULT_INDENT_CHAR " " + +/* If an indent is required, use this many INDENT_CHARs by default */ +#define DEFAULT_INDENT_AMOUNT 2 + +#define PROCENV_DEFAULT_CRUMB_SEPARATOR ":" +#define PROCENV_DEFAULT_TEXT_SEPARATOR ": " + +/* + * XXX: WARNING! Gross magic number hack! + * + * - This value must equal the maximum number of entries in any particular + * OutputFormats translation table. + * + * - This must be updated to reflect the + * maximum values in any TranslateMapEntry, plus 1. + */ +#define TRANSLATE_MAP_ENTRIES (5+1) + +extern pstring *doc; +extern struct procenv_user user; + +/** + * output: + * + * Where to send output. + **/ +Output output = OUTPUT_STDOUT; + +/** + * output_format: + * + * Format output will be displayed in. + **/ +OutputFormat output_format = OUTPUT_FORMAT_TEXT; + +/** + * output_file: + * + * Name or output file to send output to if not NULL. + **/ +const char *output_file = NULL; + +/** + * output_file_append: + * + * If true, append to @output_file rather than truncating. + **/ +static int output_file_append = false; + +/** + * text_separator: + * + * Separator used for text output format to separate a name from a + * value. + **/ +static const char *text_separator = PROCENV_DEFAULT_TEXT_SEPARATOR; + +/** + * output_fd: + * + * File descriptor associated with output_file. + **/ +int output_fd = -1; + +static struct procenv_map output_map[] = { + { OUTPUT_FILE , "file" }, + { OUTPUT_STDERR , "stderr" }, + { OUTPUT_STDOUT , "stdout" }, + { OUTPUT_SYSLOG , "syslog" }, + { OUTPUT_TERM , "terminal" }, + + { 0 , NULL } +}; + +static struct procenv_map output_format_map[] = { + { OUTPUT_FORMAT_TEXT , "text" }, + { OUTPUT_FORMAT_CRUMB , "crumb" }, + { OUTPUT_FORMAT_JSON , "json" }, + { OUTPUT_FORMAT_XML , "xml" }, + + { 0 , NULL } +}; + +typedef struct translate_map_entry { + wchar_t from; + wchar_t *to; +} TranslateMapEntry; + +typedef struct translate_table { + OutputFormat output_format; + TranslateMapEntry map[TRANSLATE_MAP_ENTRIES]; +} TranslateTable; + +/* + * Note the gross hack to avoid need for flexible arrays. + */ +static TranslateTable translate_table[] = { + { + OUTPUT_FORMAT_XML, + { + { L'\'', L"'" }, + { L'"', L""" }, + { L'&', L"&" }, + { L'<', L"<" }, + { L'>', L">" }, + + /* terminator */ + { L'\0', NULL } + } + }, + { + OUTPUT_FORMAT_JSON, + { + { L'"', L"\\\"" }, + { L'\\', L"\\\\" }, + + /* XXX: the hack! */ + { L'\0', NULL }, + { L'\0', NULL }, + { L'\0', NULL }, + + /* terminator */ + { L'\0', NULL } + } + }, +}; + +/** + * last_element: Type of previous element handled. + **/ +ElementType last_element = ELEMENT_TYPE_NONE; + +/** + * current_element: Type of element currently being handled. + **/ +ElementType current_element = ELEMENT_TYPE_NONE; + +/** + * crumb_list: + * + * List used to store breadcrumbs when OUTPUT_FORMAT_CRUMB being used. + **/ +static PRList *crumb_list = NULL; + +/** + * indent: + * + * Current output indent value. + **/ +static int indent = 0; + +/** + * indent_amount: + * + * Number of INDENT_CHARs to emit for an indent. + **/ +static int indent_amount = DEFAULT_INDENT_AMOUNT; + +/** + * indent_char, wide_indent_char: + * + * Character to use for indenting and wide-char equivalent. + **/ +const char *indent_char = DEFAULT_INDENT_CHAR; +wchar_t wide_indent_char; + +/** + * crumb_separator: + * + * Separator used for text output format to separate a name from a + * value. + **/ +const char *crumb_separator = PROCENV_DEFAULT_CRUMB_SEPARATOR; + +void +header (const char *name) +{ + assert (name); + common_assert (); + + section_open (name); +} + +void +footer (void) +{ + common_assert (); + section_close (); +} + +/** + * master_footer: + * + * @doc: document to write footer to. + * + * Main footer which is displayed once. + **/ +void +master_footer (pstring **doc) +{ + common_assert (); + + switch (output_format) { + + case OUTPUT_FORMAT_CRUMB: /* FALL */ + case OUTPUT_FORMAT_TEXT: + /* Tweak */ + wappend (doc, L"\n"); + break; + + case OUTPUT_FORMAT_JSON: + object_close (false); + + /* Tweak */ + wappend (doc, L"\n"); + break; + + case OUTPUT_FORMAT_XML: + /* Tweak */ + wappend (doc, L"\n"); + dec_indent (); + wappendf (doc, L"</%s>\n", PACKAGE_NAME); + break; + + default: + assert_not_reached (); + break; + } +} + +/** + * object_open: + * + * @retain: if true, do not disrupt the current element such that the + * opening of the object will be invisible to the state machine, but + * will still produce the required output. + * + * Note: @retain is only meaningful for OUTPUT_FORMAT_JSON. + * + * Handle opening an object. + **/ +void +object_open (int retain) +{ + common_assert (); + + if (output_format == OUTPUT_FORMAT_JSON) { + if (retain) { + format_element (); + } else { + change_element (ELEMENT_TYPE_OBJECT_OPEN); + } + } else { + /* Objects are only required for handling JSON. In + * fact, they cause problems for other output formats + * that do not have visible "objects" in that they cause + * the state table to lose track of the previous element + * since it is actually the previous-previous element + * (as the pointless object is the previous element). + * + * As such, ignore them. + */ + } + + switch (output_format) { + + case OUTPUT_FORMAT_CRUMB: /* FALL */ + case OUTPUT_FORMAT_TEXT: + /* NOP */ + break; + + case OUTPUT_FORMAT_JSON: + wappend (&doc, L"{"); + break; + + case OUTPUT_FORMAT_XML: + /* NOP */ + break; + + default: + assert_not_reached (); + break; + } +} + +/** + * object_close: + * + * @retain: if true, do not disrupt the current element such that the + * object closure will be invisible to the state machine, but will still + * produce @retain: if true, do not disrupt the current element. + * + * Note: @retain is only meaningful for OUTPUT_FORMAT_JSON. + * + * Handle closing an object. + **/ +void +object_close (int retain) +{ + common_assert (); + + if (output_format == OUTPUT_FORMAT_JSON) { + if (retain) { + format_element (); + } else { + change_element (ELEMENT_TYPE_OBJECT_CLOSE); + } + } else { + /* Objects are only required for handling JSON. In + * fact, they cause problems for other output formats + * that do not have visible "objects" in that they cause + * the state table to lose track of the previous element + * since it is actually the previous-previous element + * (as the pointless object is the previous element). + * + * As such, ignore them. + */ + } + + switch (output_format) { + + case OUTPUT_FORMAT_CRUMB: /* FALL */ + case OUTPUT_FORMAT_TEXT: + /* NOP */ + break; + + case OUTPUT_FORMAT_JSON: + wappend (&doc, L"}"); + break; + + case OUTPUT_FORMAT_XML: + /* NOP */ + break; + + default: + assert_not_reached (); + break; + } +} + +/** + * section_open: + * + * @name: name of section. + * + * Start a new section which will contain >0 entry() calls. + **/ +void +section_open (const char *name) +{ + assert (name); + common_assert (); + + change_element (ELEMENT_TYPE_SECTION_OPEN); + + switch (output_format) { + + case OUTPUT_FORMAT_TEXT: + wappendf (&doc, L"%s:", name); + break; + + case OUTPUT_FORMAT_CRUMB: + add_breadcrumb (name); + break; + + case OUTPUT_FORMAT_JSON: + wappendf (&doc, L"\"%s\" : {", name); + break; + + case OUTPUT_FORMAT_XML: + wappendf (&doc, L"<section name=\"%s\">", name); + break; + + default: + assert_not_reached (); + break; + } +} + +void +section_close (void) +{ + common_assert (); + + change_element (ELEMENT_TYPE_SECTION_CLOSE); + + switch (output_format) { + + case OUTPUT_FORMAT_TEXT: + /* NOP */ + break; + + case OUTPUT_FORMAT_CRUMB: + remove_breadcrumb (); + break; + + case OUTPUT_FORMAT_JSON: + wappend (&doc, L"}"); + break; + + case OUTPUT_FORMAT_XML: + wappend (&doc, L"</section>"); + break; + + default: + assert_not_reached (); + break; + } +} + +/** + * container_open: + * + * @name: name of container. + * + * Start a new container which will contain >0 entry() calls. + * + * This is primarily to handle JSON arrays. + **/ +void +container_open (const char *name) +{ + assert (name); + common_assert (); + + change_element (ELEMENT_TYPE_CONTAINER_OPEN); + + switch (output_format) { + + case OUTPUT_FORMAT_TEXT: + wappendf (&doc, L"%s:", name); + break; + + case OUTPUT_FORMAT_CRUMB: + add_breadcrumb (name); + break; + + case OUTPUT_FORMAT_JSON: + wappendf (&doc, L"\"%s\" : [", name); + break; + + case OUTPUT_FORMAT_XML: + wappendf (&doc, L"<container name=\"%s\">", name); + break; + + default: + assert_not_reached (); + break; + } +} + +/** + * container_close: + * + * Finish with a container. + **/ +void +container_close (void) +{ + common_assert (); + + change_element (ELEMENT_TYPE_CONTAINER_CLOSE); + + switch (output_format) { + + case OUTPUT_FORMAT_TEXT: + /* NOP */ + break; + + case OUTPUT_FORMAT_CRUMB: + remove_breadcrumb (); + break; + + case OUTPUT_FORMAT_JSON: + wappend (&doc, L"]"); + break; + + case OUTPUT_FORMAT_XML: + wappend (&doc, L"</container>"); + break; + + default: + assert_not_reached (); + break; + } +} + +/** + * _show: + * + * @prefix: string prefix to write, + * @indent: number of spaces to indent output to, + * @fmt: printf-style format with associated arguments that comprises + * the value part. + * + * Write output to @string, indented by @indent spaces. A trailing newline + * will be added. + * + * Note that error scenarios cannot call die() as by definition output + * may not be possible. + **/ +void +_show (const char *prefix, int indent, const char *fmt, ...) +{ + va_list ap; + char *buffer = NULL; + + assert (fmt); + + if (indent) + appendf (&buffer, "%*s", indent, indent_char); + + if (prefix && *prefix) + appendf (&buffer, "%s: ", prefix); + + va_start (ap, fmt); + appendva (&buffer, fmt, ap); + va_end (ap); + + append (&buffer, "\n"); + + _show_output (buffer); + + free (buffer); +} + +/** + * entry: + * + * @name: name of thing to display, + * @fmt: printf-style format with associated arguments that comprises + * the value part. + * + * Add name/value pair represented by @name and value comprising + * printf-format string to the @doc global. The value added will be + * indented appropriately. + **/ +void +entry (const char *name, const char *fmt, ...) +{ + pstring *encoded_name = NULL; + pstring *encoded_value = NULL; + va_list ap; + + assert (name); + assert (fmt); + + common_assert (); + + change_element (ELEMENT_TYPE_ENTRY); + + encoded_name = char_to_pstring (name); + if (! encoded_name) + die ("failed to encode name"); + + /* annoyingly, we must encode here; we cannot simply call + * encode_string() once just prior to showing the output + * document since if the output format is XML, we'd end + * up encoding the XML tags themselves, not just the values + * within! + */ + if (encode_string (&encoded_name) < 0) + die ("failed to encode name"); + + /* expand format */ + va_start (ap, fmt); + wmappendva (&encoded_value, fmt, ap); + va_end (ap); + + if (encode_string (&encoded_value) < 0) + die ("failed to encode value"); + + switch (output_format) { + + case OUTPUT_FORMAT_CRUMB: + assert (crumb_list); + + /* Add the bread crumbs */ + PR_LIST_FOREACH (crumb_list, iter) { + char *crumb = (char *)iter->data; + wappendf (&doc, + L"%s%s", + crumb, + crumb_separator); + } + + wappendf (&doc, + L"%ls%s%ls\n", + encoded_name->buf, + text_separator, + encoded_value->buf); + break; + + case OUTPUT_FORMAT_TEXT: + wappendf (&doc, + L"%ls%s%ls", + encoded_name->buf, + text_separator, + encoded_value->buf); + break; + + case OUTPUT_FORMAT_JSON: + wappendf (&doc, + L"\"%ls\" : \"%ls\"", + encoded_name->buf, + encoded_value->buf); + break; + + case OUTPUT_FORMAT_XML: + wappendf (&doc, + L"<entry name=\"%ls\">%ls</entry>", + encoded_name->buf, + encoded_value->buf); + break; + + default: + assert_not_reached (); + break; + } + + pstring_free (encoded_name); + pstring_free (encoded_value); +} + +/** + * _show_output: + * + * @string: String to display. + * + * Write output @string to appropriate location based on Output + * destination. + **/ +void +_show_output_pstring (const pstring *pstr) +{ + char *str; + + assert (pstr); + + str = pstring_to_char (pstr); + + _show_output (str); + + free (str); +} + +void +_show_output (const char *str) +{ + int ret; + + assert (str); + + switch (output) { + case OUTPUT_SYSLOG: + syslog (LOG_INFO, "%s", str); + ret = 0; + break; + + case OUTPUT_STDOUT: + ret = fputs (str, stdout); + break; + + case OUTPUT_STDERR: + ret = fputs (str, stderr); + break; + + case OUTPUT_TERM: + assert (user.tty_fd != -1); + ret = write (user.tty_fd, str, strlen (str)); + if (ret < 0) { + fprintf (stderr, "ERROR: failed to write to terminal: %s\n", + strerror (errno)); + exit (EXIT_FAILURE); + } + break; + + case OUTPUT_FILE: + { + int flags = (O_WRONLY | O_CREAT); + + if (output_file_append) + flags |= O_APPEND; + + assert (output_file); + if (output_fd < 0) { + output_fd = open (output_file, flags, + (S_IRWXU|S_IRGRP|S_IROTH)); + if (output_fd < 0) { + fprintf (stderr, "ERROR: failed to open file '%s': %s\n", + output_file, strerror (errno)); + exit (EXIT_FAILURE); + } + } + ret = write (output_fd, str, strlen (str)); + if (ret < 0) { + fprintf (stderr, "ERROR: failed to write to file '%s': %s\n", + output_file, strerror (errno)); + exit (EXIT_FAILURE); + } + } + break; + + default: + fprintf (stderr, "ERROR: invalid output type: %d\n", output); + exit (EXIT_FAILURE); + break; + } + + if (ret < 0) { + fprintf (stderr, "ERROR: failed to output message\n"); + exit (EXIT_FAILURE); + } +} + +void +set_indent_amount (int amount) +{ + indent_amount = amount; +} + +int +get_indent_amount (void) +{ + return indent_amount; +} + + +const char * +get_text_separator (void) +{ + return text_separator; +} + +void +set_text_separator (const char *s) +{ + text_separator = s; +} + +void +set_indent_char (const char *c) +{ + if (! c) + return; + + // FIXME: free on shutdown ! + indent_char = strdup (c); +} + +const char * +get_indent_char (void) +{ + return indent_char; +} + +void +set_crumb_separator (const char *c) +{ + if (! c) + return; + + crumb_separator = strdup (c); +} + +void +set_output_file (const char *f) +{ + if (! f) + return; + + output_file = f; +} + +void +set_output_file_append (void) +{ + output_file_append = true; +} + +const char * +get_crumb_separator (void) +{ + return crumb_separator; +} + +void +reset_indent (void) +{ + indent = 0; +} + +/** + * inc_indent: + * + * Increase indent. + **/ +void +inc_indent (void) +{ + assert (indent >= 0); + + indent += indent_amount; +} + +/** + * dec_indent: + * + * Decrease indent. + **/ +void +dec_indent (void) +{ + assert (indent >= 0); + + indent -= indent_amount; + + assert (indent >= 0); +} + +/** + * add_indent: + * + * Insert the current indent to the output document. + **/ +void +add_indent (pstring **doc) +{ + common_assert (); + + if (! indent) + return; + + if (! strcmp (indent_char, DEFAULT_INDENT_CHAR)) { + wappendf (doc, L"%*s", indent, indent_char); + } else { + pstring *buffer = NULL; + + // Expand the buffer to the appropriate + // length by filling it with spaces and a random + // character. + wappendf (&buffer, L"%*lc", indent, wide_indent_char); + + + /* Now, replace the spaces and the random character with + * the chosen character. This convoluted approach is + * necessary as printf-type functions don't allow the + * padding character to be specified. + */ + wmemset (buffer->buf, wide_indent_char, wcslen (buffer->buf)); + + pappend (doc, buffer); + pstring_free (buffer); + } +} + +/** + * master_header: + * + * @doc: document to write footer to. + * + * Main header which is displayed once. + **/ +void +master_header (pstring **doc) +{ + common_assert (); + + switch (output_format) { + + case OUTPUT_FORMAT_CRUMB: /* FALL */ + case OUTPUT_FORMAT_TEXT: + /* NOP */ + break; + + case OUTPUT_FORMAT_JSON: + object_open (false); + break; + + case OUTPUT_FORMAT_XML: + wappend (doc, L"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + wappendf (doc, L"<%s version=\"%s\" package_string=\"%s\" " + "mode=\"%s%s\" format_version=\"%d\">\n", + PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_STRING, + user.euid ? _(NON_STR) "-" : "", + PRIVILEGED_STR, + PROCENV_FORMAT_VERSION); + + inc_indent (); + + break; + + default: + assert_not_reached (); + break; + } +} + +void +add_breadcrumb (const char *name) +{ + assert (name); + + if (! crumb_list) + crumb_list = pr_list_new (NULL); + + assert (crumb_list); + + pr_list_prepend_str (crumb_list, name); +} + +void +remove_breadcrumb (void) +{ + PRList *entry; + + assert (crumb_list); + + entry = pr_list_remove (crumb_list->prev); + assert (entry); + + free ((char *)entry->data); + free (entry); +} + +static void +clear_breadcrumbs (void) +{ + assert (crumb_list); + + while (crumb_list->prev != crumb_list) + remove_breadcrumb (); +} + +static void +free_breadcrumbs (void) +{ + if (output_format == OUTPUT_FORMAT_CRUMB && crumb_list) { + clear_breadcrumbs (); + free (crumb_list); + } +} + +/** + * change_element: + * + * Handle changing to a new element type. Depending on the output + * format, this may require separators and newlines to be emitted to + * produce well-formatted output. + **/ +void +change_element (ElementType new) +{ + common_assert (); + + last_element = current_element; + + current_element = new; + + format_element (); +} + +void +format_element (void) +{ + switch (output_format) { + + case OUTPUT_FORMAT_TEXT: + format_text_element (); + break; + + case OUTPUT_FORMAT_CRUMB: + /* NOP */ + break; + + case OUTPUT_FORMAT_JSON: + format_json_element (); + break; + + case OUTPUT_FORMAT_XML: + format_xml_element (); + break; + + default: + assert_not_reached (); + break; + } +} + +void +format_text_element (void) +{ + common_assert (); + switch (last_element) { + + case ELEMENT_TYPE_ENTRY: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + case ELEMENT_TYPE_SECTION_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_SECTION_OPEN: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_CLOSE: + /* NOP */ + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_SECTION_CLOSE: + { + switch (current_element) { + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_CONTAINER_OPEN: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: /* FALL */ + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + /* NOP */ + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + { + switch (current_element) { + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_NONE: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_OPEN: /* FALL */ + case ELEMENT_TYPE_OBJECT_CLOSE: + break; + + default: + assert_not_reached (); + break; + } + } + break; + + default: + assert_not_reached (); + break; + } +} + +void +format_json_element (void) +{ + common_assert (); + + switch (last_element) { + + case ELEMENT_TYPE_ENTRY: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L",\n"); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + case ELEMENT_TYPE_OBJECT_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_SECTION_OPEN: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_CLOSE: + case ELEMENT_TYPE_OBJECT_OPEN: + /* NOP */ + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + assert_not_reached (); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_SECTION_CLOSE: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L",\n"); + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_OPEN: + /* NOP */ + break; + + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_CONTAINER_OPEN: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_OBJECT_OPEN: + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_CLOSE: + case ELEMENT_TYPE_SECTION_CLOSE: + assert_not_reached (); + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + /* NOP */ + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_OBJECT_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L",\n"); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + case ELEMENT_TYPE_OBJECT_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_OBJECT_OPEN: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_CLOSE: + /* NOP */ + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_OBJECT_CLOSE: + { + switch (current_element) { + case ELEMENT_TYPE_CONTAINER_CLOSE: + case ELEMENT_TYPE_SECTION_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_OPEN: + case ELEMENT_TYPE_SECTION_OPEN: + wappend (&doc, L",\n"); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_NONE: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + case ELEMENT_TYPE_OBJECT_OPEN: /* FALL */ + case ELEMENT_TYPE_OBJECT_CLOSE: + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + default: + assert_not_reached (); + break; + } +} + +void +format_xml_element (void) +{ + common_assert (); + + switch (last_element) { + + case ELEMENT_TYPE_ENTRY: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_OBJECT_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + case ELEMENT_TYPE_OBJECT_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_SECTION_OPEN: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_CLOSE: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + assert_not_reached (); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_SECTION_CLOSE: + { + switch (current_element) { + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_CLOSE: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_CONTAINER_OPEN: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_SECTION_CLOSE: + assert_not_reached (); + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_OPEN: + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + { + switch (current_element) { + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + case ELEMENT_TYPE_OBJECT_CLOSE: + wappend (&doc, L"\n"); + dec_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_OBJECT_OPEN: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_CONTAINER_OPEN: + wappend (&doc, L"\n"); + inc_indent (); + add_indent (&doc); + break; + + case ELEMENT_TYPE_OBJECT_CLOSE: + /* NOP */ + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_OBJECT_CLOSE: + { + switch (current_element) { + case ELEMENT_TYPE_OBJECT_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_CLOSE: + /* NOP */ + break; + + case ELEMENT_TYPE_CONTAINER_CLOSE: + case ELEMENT_TYPE_ENTRY: + wappend (&doc, L"\n"); + dec_indent (); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + case ELEMENT_TYPE_NONE: + { + switch (current_element) { + case ELEMENT_TYPE_ENTRY: /* FALL */ + case ELEMENT_TYPE_CONTAINER_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_OPEN: /* FALL */ + case ELEMENT_TYPE_SECTION_CLOSE: /* FALL */ + case ELEMENT_TYPE_CONTAINER_CLOSE: /* FALL */ + case ELEMENT_TYPE_OBJECT_OPEN: /* FALL */ + case ELEMENT_TYPE_OBJECT_CLOSE: + add_indent (&doc); + break; + + default: + assert_not_reached (); + break; + } + } + break; + + default: + assert_not_reached (); + break; + } +} + +void +set_output_value (const char *name) +{ + struct procenv_map *p; + + assert (name); + + for (p = output_map; p && p->name; p++) { + if (! strcmp (name, p->name)) { + output = p->num; + return; + } + } + die ("invalid output value: '%s'", name); +} + +void +set_output_value_raw (Output o) +{ + output = o; +} + +void +set_output_format (const char *name) +{ + struct procenv_map *p; + + assert (name); + + for (p = output_format_map; p && p->name; p++) { + if (! strcmp (name, p->name)) { + output_format = p->num; + return; + } + } + die ("invalid output format value: '%s'", name); +} + +const char * +get_output_format_name (void) +{ + struct procenv_map *p; + + for (p = output_format_map; p && p->name; p++) { + if (output_format == p->num) + return p->name; + } + + bug ("invalid output format: %d", output_format); + + /* compiler appeasement */ + return NULL; +} + +void +handle_indent_char (void) +{ + size_t len; + + const char *new = indent_char; + + len = mbsrtowcs (NULL, &new, 0, NULL); + if (len != 1) + die ("invalid indent character"); + + if (mbsrtowcs (&wide_indent_char, &new, len, NULL) != len) + die ("failed to convert indent character"); +} + +void +output_init (void) +{ + if (output == OUTPUT_SYSLOG) + openlog (PACKAGE_NAME, LOG_CONS | LOG_PID, LOG_USER); +} + +void +output_finalise (void) +{ + if (output_fd != -1) + close (output_fd); + + free_breadcrumbs (); + + if (output == OUTPUT_SYSLOG) + closelog (); +} + +/* Performs simple substitution on the input */ +static pstring * +output_translate (const pstring *pstr) +{ + pstring *result = NULL; + const wchar_t *start; + const wchar_t *p; + TranslateTable *table; + size_t i; + size_t len; + size_t extra; + size_t bytes; + size_t amount; + wchar_t from; + + assert (pstr); + assert (output_format != OUTPUT_FORMAT_TEXT); + assert (output_format != OUTPUT_FORMAT_CRUMB); + + /* Find the correct translation table for the chosen output format */ + for (i = 0; i < sizeof (translate_table) / sizeof (translate_table[0]); i++) { + table = &translate_table[i]; + if (table && table->output_format == output_format) + break; + } + + if (! table) + return NULL; + + len = pstr->len; + start = pstr->buf; + + /* First, calculate the amount of space needed for the expanded + * buffer. + */ + extra = 0; + while (start && *start) { + for (i = 0; i < TRANSLATE_MAP_ENTRIES; i++) { + from = table->map[i].from; + if (*start == from) { + /* Subtract one to take account of the + * pre-existing character we're going to + * replace. + */ + extra += (wcslen (table->map[i].to) - 1); + } + } + start++; + } + + if (! extra) { + /* No translation required. + * + * FIXME: this is inefficient - we should really have + * the function accept a 'pstring **' to avoid + * re-copying. + */ + return pstring_create (pstr->buf); + } + + len += extra; + + result = pstring_new (); + if (! result) + return NULL; + + /* Note that this includes the space for the terminator + * (since a pstring's len includes the terminator) + */ + bytes = len * sizeof (wchar_t); + + result->buf = malloc (bytes); + if (! result->buf) { + pstring_free (result); + return NULL; + } + + /* We're using wcsncat() so we'd better make sure there is a + * nul for it to find! + * + * Note: we could have used calloc to do this for us, but + * the code is clearer using the @bytes idiom. + */ + memset (result->buf, '\0', bytes); + + result->size = bytes; + + /* Sanity check for upcoming overrun check */ + assert (result->buf[len-1] == L'\0'); + + /* Now, iterate the string again, performing the actual + * replacements. + */ + p = start = pstr->buf; + + while (p && *p) { + for (i = 0; i < TRANSLATE_MAP_ENTRIES; i++) { + wchar_t *to; + size_t len; + + from = table->map[i].from; + + if (*p != from) + continue; + + to = table->map[i].to; + + amount = p - start; + + /* Copy from start to match */ + wcsncat (result->buf + result->len, start, amount); + + result->len += amount; + + /* Copy replacement text */ + len = wcslen (to); + wcsncat (result->buf + result->len, to, len); + result->len += len; + + /* Jump over the matching character */ + start = p + 1; + + break; + } + p++; + } + + /* Copy remaining non-matching chars */ + amount = p - start; + wcsncat (result->buf + result->len, start, amount); + result->len += amount; + + /* Account for terminator */ + result->len += 1; + + /* check for buffer overrun */ + assert (result->buf[len-1] == L'\0'); + + return result; +} + +/** + * encode_string: + * + * @str: string to encode. + * + * Returns: 0 on success, -1 on failure. + * + * Convert the specified string to its encoded form. If no encoding is + * necessary, the string will not be modified. + * + * Notes: + * + * - By encoding, we mean replacing literals with their + * format-langage-specific encodings. For example for XML output, + * '<' is converted to '<'. + * + * - It is the callers responsibility to free @str iff this function + * is successful. any previous value of @str will be freed by + * encode_string(). + * + * BUGS: this is just horribly, horribly gross :( + **/ +int +encode_string (pstring **pstr) +{ + int ret = 0; + pstring *new = NULL; + wchar_t *p, *q; + size_t non_printables; + size_t len = 0; + size_t bytes; + + assert (pstr); + assert (*pstr); + + if ((*pstr)->len <= 1) { + /* Nothing to do */ + return 0; + } + + switch (output_format) { + + case OUTPUT_FORMAT_CRUMB: /* FALL */ + case OUTPUT_FORMAT_TEXT: + /* Nothing to do */ + ret = 0; + break; + + case OUTPUT_FORMAT_JSON: /* FALL THROUGH */ + case OUTPUT_FORMAT_XML: + new = output_translate (*pstr); + if (new) { + pstring_free (*pstr); + *pstr = new; + new = NULL; + } else { + ret = -1; + } + break; + + default: + assert_not_reached (); + break; + } + + if (ret < 0) + return ret; + + /* Now, search for evil non-printable characters and encode those + * appropriately. + */ + for (p = (*pstr)->buf, non_printables = 0; p && *p; p++) { + if (! iswprint (*p)) + non_printables++; + } + + if (non_printables && + (output_format == OUTPUT_FORMAT_XML + || output_format == OUTPUT_FORMAT_JSON)) { + + size_t new_size = 0; + + wchar_t *json_format = L"\\u%4.4x"; + + /* XXX: + * + * Although this format spec _may_ produce valid XML, + * the rules are arcane and some(?) control characters + * cannot be used within an XML document, hence the + * "may". + * + * Aside from simply discarding non-printable characters + * (thus distorting the output), we are left with + * attempting to produce some sort of encoded + * representation which may well choke a validating + * parser. + * + * Realistically, the problem is confined to handling + * control characters set in environment variables when + * attempting to output XML. This may occur if you run + * GNU Screen since it sets $TERMCAP which includes + * binary characters. + * + * FIXME: + * + * If you hit this issue, raise a bug so we can consider + * simply discarding all non-printables when attempting + * XML output. + */ + wchar_t *xml_format = L"&#x%2.2x;"; + + len = (*pstr)->len; + + /* Calculate expanded size of string by removing + * count of non-printable byte and adding back the + * number of bytes required to encode them in expanded + * form. + */ + switch (output_format) { + case OUTPUT_FORMAT_XML: + new_size = (len - non_printables) + (non_printables * wcslen (L"&#x..;")); + break; + + case OUTPUT_FORMAT_JSON: + new_size = (len - non_printables) + (non_printables * wcslen (L"\\u....")); + break; + default: + break; + } + + new = pstring_new (); + if (! new) + return -1; + + bytes = (1 + new_size) * sizeof (wchar_t); + + new->buf = malloc (bytes); + if (! new->buf) { + free (new); + return -1; + } + + new->size = bytes; + + memset (new->buf, '\0', bytes); + + for (p = (*pstr)->buf, q = new->buf; p && *p; p++) { + if (iswprint (*p)) { + *q = *p; + q++; + new->len++; + } else { + ret = swprintf (q, + new_size, + output_format == OUTPUT_FORMAT_JSON + ? json_format : xml_format, + *p); + q += ret; + } + } + + /* include terminator */ + new->len = wcslen (new->buf) + 1; + + pstring_free (*pstr); + *pstr = new; + } + + return ret; +} |