summaryrefslogtreecommitdiffstats
path: root/src/util/safe-format-string.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/safe-format-string.c')
-rw-r--r--src/util/safe-format-string.c309
1 files changed, 309 insertions, 0 deletions
diff --git a/src/util/safe-format-string.c b/src/util/safe-format-string.c
new file mode 100644
index 000000000..11532d42e
--- /dev/null
+++ b/src/util/safe-format-string.c
@@ -0,0 +1,309 @@
+/*
+ * This file originated in the realmd project
+ *
+ * Copyright 2013 Red Hat Inc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw@redhat.com>
+ */
+
+/*
+ * Some snippets of code from gnulib, but have since been refactored
+ * to within an inch of their life...
+ *
+ * vsprintf with automatic memory allocation.
+ * Copyright (C) 1999, 2002-2003 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Library General Public License as published
+ * by the Free Software Foundation; either version 2, 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
+ * Library General Public License for more details.
+ */
+
+#include "config.h"
+
+#include "safe-format-string.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
+
+static void
+safe_padding (int count,
+ int *total,
+ void (* copy_fn) (void *, const char *, size_t),
+ void *data)
+{
+ char eight[] = " ";
+ int num;
+
+ while (count > 0) {
+ num = MIN (count, 8);
+ copy_fn (data, eight, num);
+ count -= num;
+ *total += num;
+ }
+}
+
+static void
+dummy_copy_fn (void *data,
+ const char *piece,
+ size_t len)
+{
+
+}
+
+int
+safe_format_string_cb (void (* copy_fn) (void *, const char *, size_t),
+ void *data,
+ const char *format,
+ const char * const args[],
+ int num_args)
+{
+ int at_arg = 0;
+ const char *cp;
+ int precision;
+ int width;
+ int len;
+ const char *value;
+ int total;
+ int left;
+ int i;
+
+ if (!copy_fn)
+ copy_fn = dummy_copy_fn;
+
+ total = 0;
+ cp = format;
+
+ while (*cp) {
+
+ /* Piece of raw string */
+ if (*cp != '%') {
+ len = strcspn (cp, "%");
+ copy_fn (data, cp, len);
+ total += len;
+ cp += len;
+ continue;
+ }
+
+ cp++;
+
+ /* An literal percent sign? */
+ if (*cp == '%') {
+ copy_fn (data, "%", 1);
+ total++;
+ cp++;
+ continue;
+ }
+
+ value = NULL;
+ left = 0;
+ precision = -1;
+ width = -1;
+
+ /* Test for positional argument. */
+ if (*cp >= '0' && *cp <= '9') {
+ /* Look-ahead parsing, otherwise skipped */
+ if (cp[strspn (cp, "0123456789")] == '$') {
+ unsigned int n = 0;
+ for (i = 0; i < 6 && *cp >= '0' && *cp <= '9'; i++, cp++) {
+ n = 10 * n + (*cp - '0');
+ }
+ /* Positional argument 0 is invalid. */
+ if (n == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ /* Positional argument N too high */
+ if (n > num_args) {
+ errno = EINVAL;
+ return -1;
+ }
+ value = args[n - 1];
+ cp++; /* $ */
+ }
+ }
+
+ /* Read the supported flags. */
+ for (; ; cp++) {
+ if (*cp == '-')
+ left = 1;
+ /* Supported but ignored */
+ else if (*cp != ' ')
+ break;
+ }
+
+ /* Parse the width. */
+ if (*cp >= '0' && *cp <= '9') {
+ width = 0;
+ for (i = 0; i < 6 && *cp >= '0' && *cp <= '9'; i++, cp++) {
+ width = 10 * width + (*cp - '0');
+ }
+ }
+
+ /* Parse the precision. */
+ if (*cp == '.') {
+ precision = 0;
+ for (i = 0, cp++; i < 6 && *cp >= '0' && *cp <= '9'; cp++, i++) {
+ precision = 10 * precision + (*cp - '0');
+ }
+ }
+
+ /* Read the conversion character. */
+ switch (*cp++) {
+ case 's':
+ /* Non-positional argument */
+ if (value == NULL) {
+ /* Too many arguments used */
+ if (at_arg == num_args) {
+ errno = EINVAL;
+ return -1;
+ }
+ value = args[at_arg++];
+ }
+ break;
+
+ /* No other conversion characters are supported */
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* How many characters are we printing? */
+ len = strlen (value);
+ if (precision >= 0)
+ len = MIN (precision, len);
+
+ /* Do we need padding? */
+ safe_padding (left ? 0 : width - len, &total, copy_fn, data);
+
+ /* The actual data */;
+ copy_fn (data, value, len);
+ total += len;
+
+ /* Do we need padding? */
+ safe_padding (left ? width - len : 0, &total, copy_fn, data);
+ }
+
+ return total;
+}
+
+static const char **
+valist_to_args (va_list va,
+ int *num_args)
+{
+ int alo_args;
+ const char **args;
+ const char *arg;
+ void *mem;
+
+ *num_args = alo_args = 0;
+ args = NULL;
+
+ for (;;) {
+ arg = va_arg (va, const char *);
+ if (arg == NULL)
+ break;
+ if (*num_args == alo_args) {
+ alo_args += 8;
+ mem = realloc (args, sizeof (const char *) * alo_args);
+ if (!mem) {
+ free (args);
+ return NULL;
+ }
+ args = mem;
+ }
+ args[(*num_args)++] = arg;
+ }
+
+ return args;
+}
+
+struct sprintf_ctx {
+ char *data;
+ size_t length;
+ size_t alloc;
+};
+
+static void
+snprintf_copy_fn (void *data,
+ const char *piece,
+ size_t length)
+{
+ struct sprintf_ctx *cx = data;
+
+ /* Don't copy if too much data */
+ if (cx->length > cx->alloc)
+ length = 0;
+ else if (cx->length + length > cx->alloc)
+ length = cx->alloc - cx->length;
+
+ if (length > 0)
+ memcpy (cx->data + cx->length, piece, length);
+
+ /* Null termination happens later */
+ cx->length += length;
+}
+
+int
+safe_format_string (char *str,
+ size_t len,
+ const char *format,
+ ...)
+{
+ struct sprintf_ctx cx;
+ int num_args;
+ va_list va;
+ const char **args;
+ int error = 0;
+ int ret;
+
+ cx.data = str;
+ cx.length = 0;
+ cx.alloc = len;
+
+ va_start (va, format);
+ args = valist_to_args (va, &num_args);
+ va_end (va);
+
+ if (args == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (len)
+ cx.data[0] = '\0';
+
+ ret = safe_format_string_cb (snprintf_copy_fn, &cx, format, args, num_args);
+ if (ret < 0) {
+ error = errno;
+ } else if (len > 0) {
+ cx.data[MIN (cx.length, len - 1)] = '\0';
+ }
+
+ free (args);
+
+ if (error)
+ errno = error;
+ return ret;
+}