/* hivexsh - Hive shell.
 * Copyright (C) 2009 Red Hat Inc.
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <locale.h>

#ifdef HAVE_LIBINTL_H
#include <libintl.h>
#endif

#ifdef HAVE_LIBREADLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif

#ifdef HAVE_GETTEXT
#include "gettext.h"
#define _(str) dgettext(PACKAGE, (str))
//#define N_(str) dgettext(PACKAGE, (str))
#else
#define _(str) str
//#define N_(str) str
#endif

#define STREQ(a,b) (strcmp((a),(b)) == 0)
#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
#define STRNEQ(a,b) (strcmp((a),(b)) != 0)
//#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
//#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
//#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
//#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
//#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)

#include "c-ctype.h"
#include "xstrtol.h"

#include "hivex.h"
#include "byte_conversions.h"

#define HIVEX_MAX_VALUES         1000

static int quit = 0;
static int is_tty;
static int unsafe = 0;
static hive_h *h = NULL;
static char *prompt_string = NULL; /* Normal prompt string. */
static char *loaded = NULL;     /* Basename of loaded file, if any. */
static hive_node_h cwd;         /* Current node. */
static int open_flags = 0;      /* Flags used when loading a hive file. */

static void usage (void) __attribute__((noreturn));
static void print_node_path (hive_node_h, FILE *);
static void set_prompt_string (void);
static void initialize_readline (void);
static void cleanup_readline (void);
static void add_history_line (const char *);
static char *rl_gets (const char *prompt);
static void sort_strings (char **strings, int len);
static int get_xdigit (char c);
static int dispatch (char *cmd, char *args);
static int cmd_add (char *name);
static int cmd_cd (char *path);
static int cmd_close (char *path);
static int cmd_commit (char *path);
static int cmd_del (char *args);
static int cmd_help (char *args);
static int cmd_load (char *hivefile);
static int cmd_ls (char *args);
static int cmd_lsval (char *args);
static int cmd_setval (char *args);

static void
usage (void)
{
  fprintf (stderr, "hivexsh [-dfwu] [hivefile]\n");
  exit (EXIT_FAILURE);
}

int
main (int argc, char *argv[])
{
  setlocale (LC_ALL, "");
#ifdef HAVE_BINDTEXTDOMAIN
  bindtextdomain (PACKAGE, LOCALEBASEDIR);
  textdomain (PACKAGE);
#endif

  int c;
  const char *filename = NULL;

  set_prompt_string ();

  while ((c = getopt (argc, argv, "df:wu")) != EOF) {
    switch (c) {
    case 'd':
      open_flags |= HIVEX_OPEN_DEBUG;
      break;
    case 'f':
      filename = optarg;
      break;
    case 'w':
      open_flags |= HIVEX_OPEN_WRITE;
      break;
    case 'u':
      open_flags |= HIVEX_OPEN_UNSAFE;
      unsafe = 1;
      break;
    default:
      usage ();
    }
  }

  if (optind < argc) {
    if (optind + 1 != argc)
      usage ();
    if (cmd_load (argv[optind]) == -1)
      exit (EXIT_FAILURE);
  }

  /* -f filename parameter */
  if (filename) {
    close (0);
    if (open (filename, O_RDONLY) == -1) {
      perror (filename);
      exit (EXIT_FAILURE);
    }
  }

  /* Main loop. */
  is_tty = isatty (0);
  initialize_readline ();

  if (is_tty)
    printf (_(
"\n"
"Welcome to hivexsh, the hivex interactive shell for examining\n"
"Windows Registry binary hive files.\n"
"\n"
"Type: 'help' for help summary\n"
"      'quit' to quit the shell\n"
"\n"));

  while (!quit) {
    char *buf = rl_gets (prompt_string);
    if (!buf) {
      quit = 1;
      if (is_tty)
        printf ("\n");
      break;
    }

    while (*buf && c_isspace (*buf))
      buf++;

    /* Ignore blank line. */
    if (!*buf) continue;

    /* If the next character is '#' then this is a comment. */
    if (*buf == '#') continue;

    /* Parsing is very simple - much simpler than guestfish.  This is
     * because Registry keys often contain spaces, and we don't want
     * to bother with quoting.  Therefore here we just split at the
     * first whitespace into "cmd<whitespace>arg(s)".  We let the
     * command decide how to deal with arg(s), if at all.
     */
    size_t len = strcspn (buf, " \t");

    if (len == 0) continue;

    char *cmd = buf;
    char *args;

    if (buf[len] == '\0') {
      /* This is mostly safe.  Although the cmd_* functions do sometimes
       * modify args, then shouldn't do so when args is "".
       */
      args = (char *) "";
      goto got_command;
    }

    buf[len] = '\0';
    args = buf + len + 1 + strspn (&buf[len+1], " \t");

    len = strlen (args);
    while (len > 0 && c_isspace (args[len-1])) {
      args[len-1] = '\0';
      len--;
    }

  got_command:
    /*printf ("command: '%s'  args: '%s'\n", cmd, args)*/;
    int r = dispatch (cmd, args);
    if (!is_tty && r == -1)
      exit (EXIT_FAILURE);
  }

  cleanup_readline ();
  free (prompt_string);
  free (loaded);
  if (h) hivex_close (h);
  exit (0);
}

/* Set the prompt string.  This is called whenever it could change, eg.
 * after loading a file or changing directory.
 */
static void
set_prompt_string (void)
{
  free (prompt_string);
  prompt_string = NULL;

  FILE *fp;
  char *ptr;
  size_t size;
  fp = open_memstream (&ptr, &size);
  if (fp == NULL) {
    perror ("open_memstream");
    exit (EXIT_FAILURE);
  }

  if (h) {
    assert (loaded != NULL);
    assert (cwd != 0);

    fputs (loaded, fp);
    print_node_path (cwd, fp);
  }

  fprintf (fp, "> ");
  fclose (fp);
  prompt_string = ptr;
}

/* Print the \full\path of a node. */
static void
print_node_path (hive_node_h node, FILE *fp)
{
  hive_node_h root = hivex_root (h);

  if (node == root) {
    fputc ('\\', fp);
    return;
  }

  hive_node_h parent = hivex_node_parent (h, node);
  if (parent == 0) {
    fprintf (stderr, _("hivexsh: error getting parent of node %zu\n"), node);
    return;
  }
  print_node_path (parent, fp);

  if (parent != root)
    fputc ('\\', fp);

  char *name = hivex_node_name (h, node);
  if (name == NULL) {
    fprintf (stderr, _("hivexsh: error getting node name of node %zx\n"), node);
    return;
  }

  fputs (name, fp);
  free (name);
}

static char *line_read = NULL;

static char *
rl_gets (const char *prompt)
{
#ifdef HAVE_LIBREADLINE

  if (is_tty) {
    if (line_read) {
      free (line_read);
      line_read = NULL;
    }

    line_read = readline (prompt);

    if (line_read && *line_read)
      add_history_line (line_read);

    return line_read;
  }

#endif /* HAVE_LIBREADLINE */

  static char buf[8192];
  int len;

  if (is_tty)
    printf ("%s", prompt);
  line_read = fgets (buf, sizeof buf, stdin);

  if (line_read) {
    len = strlen (line_read);
    if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
  }

  return line_read;
}

#ifdef HAVE_LIBREADLINE
static char histfile[1024];
static int nr_history_lines = 0;
#endif

static void
initialize_readline (void)
{
#ifdef HAVE_LIBREADLINE
  const char *home;

  home = getenv ("HOME");
  if (home) {
    snprintf (histfile, sizeof histfile, "%s/.hivexsh", home);
    using_history ();
    (void) read_history (histfile);
  }

  rl_readline_name = "hivexsh";
#endif
}

static void
cleanup_readline (void)
{
#ifdef HAVE_LIBREADLINE
  int fd;

  if (histfile[0] != '\0') {
    fd = open (histfile, O_WRONLY|O_CREAT, 0644);
    if (fd == -1) {
      perror (histfile);
      return;
    }
    close (fd);

    (void) append_history (nr_history_lines, histfile);
  }
#endif
}

static void
add_history_line (const char *line)
{
#ifdef HAVE_LIBREADLINE
  add_history (line);
  nr_history_lines++;
#endif
}

static int
compare (const void *vp1, const void *vp2)
{
  char * const *p1 = (char * const *) vp1;
  char * const *p2 = (char * const *) vp2;
  return strcasecmp (*p1, *p2);
}

static void
sort_strings (char **strings, int len)
{
  qsort (strings, len, sizeof (char *), compare);
}

static int
get_xdigit (char c)
{
  switch (c) {
  case '0'...'9': return c - '0';
  case 'a'...'f': return c - 'a' + 10;
  case 'A'...'F': return c - 'A' + 10;
  default: return -1;
  }
}

static int
dispatch (char *cmd, char *args)
{
  if (STRCASEEQ (cmd, "help"))
    return cmd_help (args);
  else if (STRCASEEQ (cmd, "load"))
    return cmd_load (args);
  else if (STRCASEEQ (cmd, "exit") ||
           STRCASEEQ (cmd, "q") ||
           STRCASEEQ (cmd, "quit")) {
    quit = 1;
    return 0;
  }

  /* If no hive file is loaded (!h) then only the small selection of
   * commands above will work.
   */
  if (!h) {
    fprintf (stderr, _("hivexsh: you must load a hive file first using 'load hivefile'\n"));
    return -1;
  }

  if (STRCASEEQ (cmd, "add"))
    return cmd_add (args);
  else if (STRCASEEQ (cmd, "cd"))
    return cmd_cd (args);
  else if (STRCASEEQ (cmd, "close") || STRCASEEQ (cmd, "unload"))
    return cmd_close (args);
  else if (STRCASEEQ (cmd, "commit"))
    return cmd_commit (args);
  else if (STRCASEEQ (cmd, "del"))
    return cmd_del (args);
  else if (STRCASEEQ (cmd, "ls"))
    return cmd_ls (args);
  else if (STRCASEEQ (cmd, "lsval"))
    return cmd_lsval (args);
  else if (STRCASEEQ (cmd, "setval"))
    return cmd_setval (args);
  else {
    fprintf (stderr, _("hivexsh: unknown command '%s', use 'help' for help summary\n"),
             cmd);
    return -1;
  }
}

static int
cmd_load (char *hivefile)
{
  if (STREQ (hivefile, "")) {
    fprintf (stderr, _("hivexsh: load: no hive file name given to load\n"));
    return -1;
  }

  if (h) hivex_close (h);
  h = NULL;

  free (loaded);
  loaded = NULL;

  cwd = 0;

  h = hivex_open (hivefile, open_flags);
  if (h == NULL) {
    fprintf (stderr,
             _(
"hivexsh: failed to open hive file: %s: %m\n"
"\n"
"If you think this file is a valid Windows binary hive file (_not_\n"
"a regedit *.reg file) then please run this command again using the\n"
"hivexsh option '-d' and attach the complete output _and_ the hive file\n"
"which fails into a bug report at https://bugzilla.redhat.com/\n"
"\n"),
             hivefile);
    return -1;
  }

  /* Get the basename of the file for the prompt. */
  char *p = strrchr (hivefile, '/');
  if (p)
    loaded = strdup (p+1);
  else
    loaded = strdup (hivefile);
  if (!loaded) {
    perror ("strdup");
    exit (EXIT_FAILURE);
  }

  cwd = hivex_root (h);

  set_prompt_string ();

  return 0;
}

static int
cmd_close (char *args)
{
  if (STRNEQ (args, "")) {
    fprintf (stderr, _("hivexsh: '%s' command should not be given arguments\n"),
             "close");
    return -1;
  }

  if (h) hivex_close (h);
  h = NULL;

  free (loaded);
  loaded = NULL;

  cwd = 0;

  set_prompt_string ();

  return 0;
}

static int
cmd_commit (char *path)
{
  if (STREQ (path, ""))
    path = NULL;

  if (hivex_commit (h, path, 0) == -1) {
    perror ("hivexsh: commit");
    return -1;
  }

  return 0;
}

static int
cmd_cd (char *path)
{
  if (STREQ (path, "")) {
    print_node_path (cwd, stdout);
    fputc ('\n', stdout);
    return 0;
  }

  if (path[0] == '\\' && path[1] == '\\') {
    fprintf (stderr, _("%s: %s: \\ characters in path are doubled - are you escaping the path parameter correctly?\n"), "hivexsh", path);
    return -1;
  }

  hive_node_h new_cwd = cwd;
  hive_node_h root = hivex_root (h);

  if (path[0] == '\\') {
    new_cwd = root;
    path++;
  }

  while (path[0]) {
    size_t len = strcspn (path, "\\");
    if (len == 0) {
      path++;
      continue;
    }

    char *elem = path;
    path = path[len] == '\0' ? &path[len] : &path[len+1];
    elem[len] = '\0';

    if (len == 1 && STREQ (elem, "."))
      continue;

    if (len == 2 && STREQ (elem, "..")) {
      if (new_cwd != root)
        new_cwd = hivex_node_parent (h, new_cwd);
      continue;
    }

    errno = 0;
    new_cwd = hivex_node_get_child (h, new_cwd, elem);
    if (new_cwd == 0) {
      if (errno)
        perror ("hivexsh: cd");
      else
        fprintf (stderr, _("hivexsh: cd: subkey '%s' not found\n"),
                 elem);
      return -1;
    }
  }

  if (new_cwd != cwd) {
    cwd = new_cwd;
    set_prompt_string ();
  }

  return 0;
}

static int
cmd_help (char *args)
{
  printf (_(
"Navigate through the hive's keys using the 'cd' command, as if it\n"
"contained a filesystem, and use 'ls' to list the subkeys of the\n"
"current key.  Full documentation is in the hivexsh(1) manual page.\n"));

  return 0;
}

static int
cmd_ls (char *args)
{
  if (STRNEQ (args, "")) {
    fprintf (stderr, _("hivexsh: '%s' command should not be given arguments\n"),
             "ls");
    return -1;
  }

  /* Get the subkeys. */
  hive_node_h *children = hivex_node_children (h, cwd);
  if (children == NULL) {
    perror ("ls");
    return -1;
  }

  /* Get names for each subkey. */
  size_t len;
  for (len = 0; children[len] != 0; ++len)
    ;

  /* No children, no need to print anything. */
  if (len == 0)
      return 0;

  char **names = calloc (len, sizeof (char *));
  if (names == NULL) {
    perror ("malloc");
    exit (EXIT_FAILURE);
  }

  int ret = -1;
  size_t i;
  for (i = 0; i < len; ++i) {
    names[i] = hivex_node_name (h, children[i]);
    if (names[i] == NULL) {
      perror ("hivex_node_name");
      goto error;
    }
  }

  /* Sort the names. */
  sort_strings (names, len);

  for (i = 0; i < len; ++i)
    printf ("%s\n", names[i]);

  ret = 0;
 error:
  free (children);
  for (i = 0; i < len; ++i)
    free (names[i]);
  free (names);
  return ret;
}

static int
cmd_lsval (char *key)
{
  if (STRNEQ (key, "")) {
    hive_value_h value;

    errno = 0;
    if (STREQ (key, "@"))       /* default key written as "@" */
      value = hivex_node_get_value (h, cwd, "");
    else
      value = hivex_node_get_value (h, cwd, key);

    if (value == 0) {
      if (errno)
        goto error;
      /* else key not found */
      fprintf (stderr, _("%s: %s: key not found\n"), "hivexsh", key);
      return -1;
    }

    /* Print the value. */
    hive_type t;
    size_t len;
    if (hivex_value_type (h, value, &t, &len) == -1)
      goto error;

    switch (t) {
    case hive_t_string:
    case hive_t_expand_string:
    case hive_t_link: {
      char *str = hivex_value_string (h, value);
      if (!str)
        goto error;

      puts (str); /* note: this adds a single \n character */
      free (str);
      break;
    }

    case hive_t_dword:
    case hive_t_dword_be: {
      int32_t j = hivex_value_dword (h, value);
      printf ("%" PRIi32 "\n", j);
      break;
    }

    case hive_t_qword: {
      int64_t j = hivex_value_qword (h, value);
      printf ("%" PRIi64 "\n", j);
      break;
    }

    case hive_t_multiple_strings: {
      char **strs = hivex_value_multiple_strings (h, value);
      if (!strs)
        goto error;
      size_t j;
      for (j = 0; strs[j] != NULL; ++j) {
        puts (strs[j]);
        free (strs[j]);
      }
      free (strs);
      break;
    }

    case hive_t_none:
    case hive_t_binary:
    case hive_t_resource_list:
    case hive_t_full_resource_description:
    case hive_t_resource_requirements_list:
    default: {
      size_t r;
      char *data;

      data = hivex_value_value (h, value, &t, &len);
      if (!data)
        goto error;

      r = fwrite (data, 1, len, stdout);
      free (data);
      if (r != len)
        goto error;

      break;
    }
    } /* switch */
  } else {
    /* No key specified, so print all keys in this node.  We do this
     * in a format which looks like the output of regedit, although
     * this isn't a particularly useful format.
     */
    hive_value_h *values;

    values = hivex_node_values (h, cwd);
    if (values == NULL)
      goto error;

    size_t i;
    for (i = 0; values[i] != 0; ++i) {
      char *k = hivex_value_key (h, values[i]);
      if (!k) goto error;

      if (*k) {
        putchar ('"');
        size_t j;
        for (j = 0; k[j] != 0; ++j) {
          if (k[j] == '"' || k[j] == '\\')
            putchar ('\\');
          putchar (k[j]);
        }
        putchar ('"');
      } else
        printf ("\"@\"");       /* default key in regedit files */
      putchar ('=');
      free (k);

      hive_type t;
      size_t len;

      if (hivex_value_type (h, values[i], &t, &len) == -1)
        goto error;

      switch (t) {
      case hive_t_string:
      case hive_t_expand_string:
      case hive_t_link: {
        char *str = hivex_value_string (h, values[i]);
        if (!str) {
          if (unsafe)
            continue;
          else
            goto error;
        }

        if (t != hive_t_string)
          printf ("str(%u):", t);
        putchar ('"');
        size_t j;
        for (j = 0; str[j] != 0; ++j) {
          if (str[j] == '"' || str[j] == '\\')
            putchar ('\\');
          putchar (str[j]);
        }
        putchar ('"');
        free (str);
        break;
      }

      case hive_t_dword:
      case hive_t_dword_be: {
        int32_t j = hivex_value_dword (h, values[i]);
        printf ("dword:%08" PRIx32, (uint32_t) j);
        break;
      }

      case hive_t_qword: /* sic */
      case hive_t_none:
      case hive_t_binary:
      case hive_t_multiple_strings:
      case hive_t_resource_list:
      case hive_t_full_resource_description:
      case hive_t_resource_requirements_list:
      default: {
        unsigned char *data =
          (unsigned char *) hivex_value_value (h, values[i], &t, &len);
        if (!data) {
          if (unsafe)
            continue;
          else
            goto error;
        }

        printf ("hex(%u):", t);
        size_t j;
        for (j = 0; j < len; ++j) {
          if (j > 0)
            putchar (',');
          printf ("%02x", data[j]);
        }

        free (data);

        break;
      }
      } /* switch */

      putchar ('\n');
    } /* for */

    free (values);
  }

  return 0;

 error:
  perror ("hivexsh: lsval");
  return -1;
}

static int
cmd_setval (char *nrvals_str)
{
  strtol_error xerr;

  /* Parse number of values. */
  unsigned long nrvals;
  xerr = xstrtoul (nrvals_str, NULL, 0, &nrvals, "");
  if (xerr != LONGINT_OK) {
    fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %u)\n"),
             "setval", "nrvals", "xstrtol", xerr);
    return -1;
  }
  if (nrvals > HIVEX_MAX_VALUES) {
    fprintf (stderr, _("%s: %s: integer out of range\n"),
             "setval", "nrvals");
    return -1;
  }

  struct hive_set_value *values =
    calloc (nrvals, sizeof (struct hive_set_value));
  if (values == NULL) {
    perror ("calloc");
    exit (EXIT_FAILURE);
  }

  int ret = -1;

  /* Read nrvals * 2 lines of input, nrvals * (key, value) pairs, as
   * explained in the man page.
   */
  size_t i, j;
  for (i = 0; i < nrvals; ++i) {
    /* Read key. */
    char *buf = rl_gets ("  key> ");
    if (!buf) {
      fprintf (stderr, _("hivexsh: setval: unexpected end of input\n"));
      quit = 1;
      goto error;
    }

    /* Note that buf will be overwritten by the next call to rl_gets. */
    if (STREQ (buf, "@"))
      values[i].key = strdup ("");
    else
      values[i].key = strdup (buf);
    if (values[i].key == NULL) {
      perror ("strdup");
      exit (EXIT_FAILURE);
    }

    /* Read value. */
    buf = rl_gets ("value> ");
    if (!buf) {
      fprintf (stderr, _("hivexsh: setval: unexpected end of input\n"));
      quit = 1;
      goto error;
    }

    if (STREQ (buf, "none")) {
      values[i].t = hive_t_none;
      values[i].len = 0;
    }
    else if (STRPREFIX (buf, "string:")) {
      buf += 7;
      values[i].t = hive_t_string;
      size_t nr_chars = strlen (buf);
      values[i].len = 2 * (nr_chars + 1);
      values[i].value = malloc (values[i].len);
      if (!values[i].value) {
        perror ("malloc");
        exit (EXIT_FAILURE);
      }
      for (j = 0; j < nr_chars + 1 /* sic */; ++j) {
        if (buf[j] & 0x80) {
          fprintf (stderr, _("hivexsh: string(utf16le): only 7 bit ASCII strings are supported for input\n"));
          goto error;
        }
        values[i].value[2*j] = buf[j];
        values[i].value[2*j+1] = '\0';
      }
    }
    else if (STRPREFIX (buf, "expandstring:")) {
      buf += 13;
      values[i].t = hive_t_expand_string;
      size_t nr_chars = strlen (buf);
      values[i].len = 2 * (nr_chars + 1);
      values[i].value = malloc (values[i].len);
      if (!values[i].value) {
        perror ("malloc");
        exit (EXIT_FAILURE);
      }
      for (j = 0; j < nr_chars + 1 /* sic */; ++j) {
        if (buf[j] & 0x80) {
          fprintf (stderr, _("hivexsh: string(utf16le): only 7 bit ASCII strings are supported for input\n"));
          goto error;
        }
        values[i].value[2*j] = buf[j];
        values[i].value[2*j+1] = '\0';
      }
    }
    else if (STRPREFIX (buf, "dword:")) {
      buf += 6;
      values[i].t = hive_t_dword;
      values[i].len = 4;
      values[i].value = malloc (4);
      if (!values[i].value) {
        perror ("malloc");
        exit (EXIT_FAILURE);
      }
      long n;
      xerr = xstrtol (buf, NULL, 0, &n, "");
      if (xerr != LONGINT_OK) {
        fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %u)\n"),
                 "setval", "dword", "xstrtol", xerr);
        goto error;
      }
#if SIZEOF_LONG > 4
      if (n < 0 || n > UINT32_MAX) {
        fprintf (stderr, _("%s: %s: integer out of range\n"),
                 "setval", "dword");
        goto error;
      }
#endif
      uint32_t u32 = htole32 (n);
      memcpy (values[i].value, &u32, 4);
    }
    else if (STRPREFIX (buf, "qword:")) {
      buf += 6;
      values[i].t = hive_t_qword;
      values[i].len = 8;
      values[i].value = malloc (8);
      if (!values[i].value) {
        perror ("malloc");
        exit (EXIT_FAILURE);
      }
      long long n;
      xerr = xstrtoll (buf, NULL, 0, &n, "");
      if (xerr != LONGINT_OK) {
        fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %u)\n"),
                 "setval", "dword", "xstrtoll", xerr);
        goto error;
      }
#if 0
      if (n < 0 || n > UINT64_MAX) {
        fprintf (stderr, _("%s: %s: integer out of range\n"),
                 "setval", "dword");
        goto error;
      }
#endif
      uint64_t u64 = htole64 (n);
      memcpy (values[i].value, &u64, 4);
    }
    else if (STRPREFIX (buf, "hex:")) {
      /* Read the type. */
      buf += 4;
      size_t len = strcspn (buf, ":");
      char *nextbuf;
      if (buf[len] == '\0')     /* "hex:t" */
        nextbuf = &buf[len];
      else {                    /* "hex:t:..." */
        buf[len] = '\0';
        nextbuf = &buf[len+1];
      }

      long t;
      xerr = xstrtol (buf, NULL, 0, &t, "");
      if (xerr != LONGINT_OK) {
        fprintf (stderr, _("%s: %s: invalid integer parameter (%s returned %u)\n"),
                 "setval", "hex", "xstrtol", xerr);
        goto error;
      }
#if SIZEOF_LONG > 4
      if (t < 0 || t > UINT32_MAX) {
        fprintf (stderr, _("%s: %s: integer out of range\n"),
                 "setval", "hex");
        goto error;
      }
#endif
      values[i].t = t;

      /* Read the hex data. */
      buf = nextbuf;

      /* The allocation length is an overestimate, but it doesn't matter. */
      values[i].value = malloc (1 + strlen (buf) / 2);
      if (!values[i].value) {
        perror ("malloc");
        exit (EXIT_FAILURE);
      }
      values[i].len = 0;

      while (*buf) {
        int c = 0;

        for (j = 0; *buf && j < 2; buf++) {
          if (c_isxdigit (*buf)) { /* NB: ignore non-hex digits. */
            c <<= 4;
            c |= get_xdigit (*buf);
            j++;
          }
        }

        if (j == 2) values[i].value[values[i].len++] = c;
        else if (j == 1) {
          fprintf (stderr, _("hivexsh: setval: trailing garbage after hex string\n"));
          goto error;
        }
      }
    }
    else {
      fprintf (stderr,
               _("hivexsh: setval: cannot parse value string, please refer to the man page hivexsh(1) for help: %s\n"),
               buf);
      goto error;
    }
  }

  ret = hivex_node_set_values (h, cwd, nrvals, values, 0);

 error:
  /* Free values array. */
  for (i = 0; i < nrvals; ++i) {
    free (values[i].key);
    free (values[i].value);
  }
  free (values);

  return ret;
}

static int
cmd_del (char *args)
{
  if (STRNEQ (args, "")) {
    fprintf (stderr, _("hivexsh: '%s' command should not be given arguments\n"),
             "del");
    return -1;
  }

  if (cwd == hivex_root (h)) {
    fprintf (stderr, _("hivexsh: del: the root node cannot be deleted\n"));
    return -1;
  }

  hive_node_h new_cwd = hivex_node_parent (h, cwd);

  if (hivex_node_delete_child (h, cwd) == -1) {
    perror ("hivexsh: del");
    return -1;
  }

  cwd = new_cwd;
  set_prompt_string ();
  return 0;
}

static int
cmd_add (char *name)
{
  hive_node_h node = hivex_node_add_child (h, cwd, name);
  if (node == 0) {
    perror ("hivexsh: add");
    return -1;
  }
  return 0;
}
