/*
 * avrdude - A Downloader/Uploader for AVR device programmers
 * Copyright (C) 2000-2004  Brian S. Dean <bsd@bsdhome.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 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, see <http://www.gnu.org/licenses/>.
 */

/* $Id$ */

#include "ac_cfg.h"

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <limits.h>
#include <unistd.h>
#include <errno.h>

#if defined(HAVE_LIBREADLINE)
#include <readline/readline.h>
#include <readline/history.h>

#ifdef _MSC_VER
#include "msvc/unistd.h"
#else
#include <unistd.h>
#endif

#ifdef WIN32
#include <windows.h>
#else
#include <sys/select.h>
#endif
#endif


#include "avrdude.h"
#include "term.h"

struct command {
  char *name;
  int (*func)(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
  size_t fnoff;
  char *desc;
};


static int cmd_dump   (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_write  (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_flush  (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_abort  (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_erase  (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_pgerase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_sig    (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_part   (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_help   (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_quit   (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_send   (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_parms  (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_vtarg  (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_varef  (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_fosc   (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_sck    (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_spi    (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_pgm    (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_verbose(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);
static int cmd_quell  (PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]);

#define _fo(x) offsetof(PROGRAMMER, x)

struct command cmd[] = {
  { "dump",  cmd_dump,  _fo(read_byte_cached),  "%s <memory> [<addr> <len> | <addr> ... | <addr> | ...]" },
  { "read",  cmd_dump,  _fo(read_byte_cached),  "alias for dump" },
  { "write", cmd_write, _fo(write_byte_cached), "write <memory> <addr> <data>[,] {<data>[,]}" },
  { "",      cmd_write, _fo(write_byte_cached), "write <memory> <addr> <len> <data>[,] {<data>[,]} ..." },
  { "flush", cmd_flush, _fo(flush_cache),       "synchronise flash & EEPROM writes with the device" },
  { "abort", cmd_abort, _fo(reset_cache),       "abort flash & EEPROM writes (reset the r/w cache)" },
  { "erase", cmd_erase, _fo(chip_erase_cached), "perform a chip erase" },
  { "pgerase", cmd_pgerase, _fo(page_erase),    "pgerase <memory> <addr>" },
  { "sig",   cmd_sig,   _fo(open),              "display device signature bytes" },
  { "part",  cmd_part,  _fo(open),              "display the current part information" },
  { "send",  cmd_send,  _fo(cmd),               "send a raw command: %s <b1> <b2> <b3> <b4>" },
  { "parms", cmd_parms, _fo(print_parms),       "display adjustable parameters" },
  { "vtarg", cmd_vtarg, _fo(set_vtarget),       "set <V[target]>" },
  { "varef", cmd_varef, _fo(set_varef),         "set <V[aref]>" },
  { "fosc",  cmd_fosc,  _fo(set_fosc),          "set <oscillator frequency>" },
  { "sck",   cmd_sck,   _fo(set_sck_period),    "set <SCK period>" },
  { "spi",   cmd_spi,   _fo(setpin),            "enter direct SPI mode" },
  { "pgm",   cmd_pgm,   _fo(setpin),            "return to programming mode" },
  { "verbose", cmd_verbose, _fo(open),          "change verbosity" },
  { "quell", cmd_quell, _fo(open),              "set quell level for progress bars" },
  { "help",  cmd_help,  _fo(open),              "show help message" },
  { "?",     cmd_help,  _fo(open),              "same as help" },
  { "quit",  cmd_quit,  _fo(open),              "quit after writing out cache for flash & EEPROM" },
  { "q",     cmd_quit,  _fo(open),              "abbreviation for quit" },
};

#define NCMDS ((int)(sizeof(cmd)/sizeof(struct command)))



static int spi_mode = 0;

static int nexttok(char *buf, char **tok, char **next) {
  unsigned char *q, *n;

  q = (unsigned char *) buf;
  while (isspace(*q))
    q++;

  /* isolate first token */
  n = q;
  uint8_t quotes = 0;
  while (*n && (!isspace(*n) || quotes)) {
    // Poor man's quote and escape processing
    if (*n == '"' || *n == '\'')
      quotes++;
    else if(*n == '\\' && n[1])
      n++;
    else if (isspace(*n) && (n > q+1) && (n[-1] == '"' || n[-1] == '\''))
      break;
    n++;
  }

  if (*n) {
    *n = 0;
    n++;
  }

  /* find start of next token */
  while (isspace(*n))
    n++;

  *tok  = (char *) q;
  *next = (char *) n;

  return 0;
}


static int hexdump_line(char *buffer, unsigned char *p, int n, int pad) {
  char *hexdata = "0123456789abcdef";
  char *b = buffer;
  int i = 0;
  int j = 0;

  for (i=0; i<n; i++) {
    if (i && ((i % 8) == 0))
      b[j++] = ' ';
    b[j++] = hexdata[(p[i] & 0xf0) >> 4];
    b[j++] = hexdata[(p[i] & 0x0f)];
    if (i < 15)
      b[j++] = ' ';
  }

  for (i=j; i<pad; i++)
    b[i] = ' ';

  b[i] = 0;

  for (i=0; i<pad; i++) {
    if (!((b[i] == '0') || (b[i] == ' ')))
      return 0;
  }

  return 1;
}


static int chardump_line(char *buffer, unsigned char *p, int n, int pad) {
  int i;
  unsigned char b[128];

  // Sanity check
  n = n < 1? 1: n > (int) sizeof b? (int) sizeof b: n;

  memcpy(b, p, n);
  for (int i = 0; i < n; i++)
    buffer[i] = isascii(b[i]) && isspace(b[i])? ' ':
      isascii(b[i]) && isgraph(b[i])? b[i]: '.';

  for (i = n; i < pad; i++)
    buffer[i] = ' ';

  buffer[i] = 0;

  return 0;
}


static int hexdump_buf(FILE *f, AVRMEM *m, int startaddr, unsigned char *buf, int len) {
  char dst1[80];
  char dst2[80];

  int addr = startaddr;
  unsigned char *p = (unsigned char *) buf;
  while (len) {
    int n = 16;
    if (n > len)
      n = len;
    if(addr + n > m->size)
      n = m->size - addr;

    hexdump_line(dst1, p, n, 48);
    chardump_line(dst2, p, n, 16);
    term_out("%0*x  %s  |%s|\n", m->size > 0x10000 ? 5: 4, addr, dst1, dst2);
    len -= n;
    addr += n;
    if (addr >= m->size)
      addr = 0;
    p += n;
  }

  return 0;
}


static int cmd_dump(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  static struct mem_addr_len {
    int addr;
    int len;
    AVRMEM *mem;
  } read_mem[32];
  static int i;

  if ((argc < 2 && read_mem[0].mem == NULL) || argc > 4) {
    msg_error(
      "Usage: %s <memory> <addr> <len>\n"
      "       %s <memory> <addr> ...\n"
      "       %s <memory> <addr>\n"
      "       %s <memory> ...\n"
      "       %s <memory>\n",
      argv[0], argv[0], argv[0], argv[0], argv[0]);
    return -1;
  }

  enum { read_size = 256 };
  char *memtype;
  if(argc > 1)
    memtype = argv[1];
  else
    memtype = (char*)read_mem[i].mem->desc;
  AVRMEM *mem = avr_locate_mem(p, memtype);
  if (mem == NULL) {
    pmsg_error("(dump) %s memory type not defined for part %s\n", memtype, p->desc);
    return -1;
  }

  int maxsize = mem->size;
  if(maxsize <= 0) { // Sanity check
    pmsg_error("cannot read memory %s of size %d\n", mem->desc, maxsize);
    return -1;
  }

  // Iterate through the read_mem structs to find relevant address and length info
  for(i = 0; i < 32; i++) {
    if(read_mem[i].mem == NULL)
      read_mem[i].mem = mem;
    if(read_mem[i].mem == mem) {
      if(read_mem[i].len == 0)
        read_mem[i].len = maxsize > read_size? read_size: maxsize;
      break;
    }
  }

  if(i >= 32) { // Catch highly unlikely case
    pmsg_error("read_mem[] under-dimensioned; increase and recompile\n");
    return -1;
  }

  // Get start address if present
  char *end_ptr;
  if (argc >= 3 && strcmp(argv[2], "...") != 0) {
    unsigned long ul = strtoul(argv[2], &end_ptr, 0);
    if(*end_ptr || (end_ptr == argv[2])) {
      pmsg_error("(dump) cannot parse address %s\n", argv[2]);
      return -1;
    }
    if(ul > INT_MAX || ul >= (unsigned long) maxsize) {
      pmsg_error("(dump) %s address 0x%lx is out of range [0, 0x%0*x]\n", mem->desc, ul,
        mem->size > 0x10000? 5: 4, maxsize-1);
      return -1;
    }
    read_mem[i].addr = (int) ul;
  }

  // Get no. bytes to read if present
  if (argc >= 3) {
    if (strcmp(argv[argc - 1], "...") == 0) {
      if (argc == 3)
        read_mem[i].addr = 0;
      read_mem[i].len = maxsize - read_mem[i].addr;
    } else if (argc == 4) {
      unsigned long ul = strtoul(argv[3], &end_ptr, 0);
      if (*end_ptr || (end_ptr == argv[3])) {
        pmsg_error("(dump) cannot parse length %s\n", argv[3]);
        return -1;
      }
      if (ul == 0 || ul > INT_MAX) // Not positive if used as int, make it 1
        ul = 1;
      read_mem[i].len = (int) ul;
    }
  }
  // Wrap around if the memory address is greater than the maximum size
  if(read_mem[i].addr >= maxsize)
    read_mem[i].addr = 0; // Wrap around

  // Trim len if nessary to prevent reading from the same memory address twice
  if (read_mem[i].len > maxsize)
    read_mem[i].len = maxsize;

  uint8_t *buf = malloc(read_mem[i].len);
  if (buf == NULL) {
    pmsg_error("(dump) out of memory\n");
    return -1;
  }

  if(argc < 4 && verbose)
    term_out(">>> %s %s 0x%x 0x%x\n", argv[0], read_mem[i].mem->desc, read_mem[i].addr, read_mem[i].len);

  report_progress(0, 1, "Reading");
  for (int j = 0; j < read_mem[i].len; j++) {
    int addr = (read_mem[i].addr + j) % mem->size;
    int rc = pgm->read_byte_cached(pgm, p, read_mem[i].mem, addr, &buf[j]);
    if (rc != 0) {
      report_progress(1, -1, NULL);
      pmsg_error("(dump) error reading %s address 0x%05lx of part %s\n", mem->desc, (long) read_mem[i].addr + j, p->desc);
      if (rc == -1)
        imsg_error("%*sread operation not supported on memory type %s\n", 7, "", mem->desc);
      free(buf);
      return -1;
    }
    report_progress(j, read_mem[i].len, NULL);
  }
  report_progress(1, 1, NULL);

  hexdump_buf(stdout, mem, read_mem[i].addr, buf, read_mem[i].len);
  term_out("\n");

  free(buf);

  read_mem[i].addr = (read_mem[i].addr + read_mem[i].len) % maxsize;

  return 0;
}


static size_t maxstrlen(int argc, char **argv) {
  size_t max = 0;

  for(int i=0; i<argc; i++)
    if(strlen(argv[i]) > max)
      max = strlen(argv[i]);

  return max;
}


// Change data item p of size bytes from big endian to little endian and vice versa
static void change_endian(void *p, int size) {
  uint8_t tmp, *w = p;

  for(int i=0; i<size/2; i++)
    tmp = w[i], w[i] = w[size-i-1], w[size-i-1] = tmp;
}


// Looks like a double mantissa in hex or dec notation
static int is_mantissa_only(char *p) {
  char *digs;

  if(*p == '+' || *p == '-')
    p++;

  if(*p == '0' && (p[1] == 'x' || p[1] == 'X')) {
    p += 2;
    digs = "0123456789abcdefABCDEF";
  } else
    digs = "0123456789";

  if(!*p)
    return 0;

  while(*p)
    if(!strchr(digs, *p++))
      return 0;

  return 1;
}


static int cmd_write(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  if (argc < 4) {
    msg_error(
      "Usage: write <memory> <addr> <data>[,] {<data>[,]}\n"
      "       write <memory> <addr> <len> <data>[,] {<data>[,]} ...\n"
      "\n"
      "Ellipsis ... writes <len> bytes padded by repeating the last <data> item.\n"
      "\n"
      "<data> can be hexadecimal, octal or decimal integers, floating point numbers\n"
      "or C-style strings and characters. For integers, an optional case-insensitive\n"
      "suffix specifies the data size: HH 8 bit, H/S 16 bit, L 32 bit, LL 64 bit.\n"
      "Suffix D indicates a 64-bit double, F a 32-bit float, whilst a floating point\n"
      "number without suffix defaults to 32-bit float. Hexadecimal floating point\n"
      "notation is supported. An ambiguous trailing suffix, eg, 0x1.8D, is read as\n"
      "no-suffix float where D is part of the mantissa; use a zero exponent 0x1.8p0D\n"
      "to clarify.\n"
      "\n"
      "An optional U suffix makes integers unsigned. Ordinary 0x hex integers are\n"
      "always treated as unsigned. +0x or -0x hex numbers are treated as signed\n"
      "unless they have a U suffix. Unsigned integers cannot be larger than 2^64-1.\n"
      "If n is an unsigned integer then -n is also a valid unsigned integer as in C.\n"
      "Signed integers must fall into the [-2^63, 2^63-1] range or a correspondingly\n"
      "smaller range when a suffix specifies a smaller type.\n"
      "\n"
      "Ordinary 0x hex integers with n hex digits (counting leading zeros) use the\n"
      "smallest size of one, two, four and eight bytes that can accommodate any\n"
      "n-digit hex integer. If an integer suffix specifies a size explicitly the\n"
      "corresponding number of least significant bytes are written, and a warning\n"
      "shown if the number does not fit into the desired representation. Otherwise,\n"
      "unsigned integers occupy the smallest of one, two, four or eight bytes\n"
      "needed. Signed numbers are allowed to fit into the smallest signed or\n"
      "smallest unsigned representation: For example, 255 is stored as one byte as\n"
      "255U would fit in one byte, though as a signed number it would not fit into a\n"
      "one-byte interval [-128, 127]. The number -1 is stored in one byte whilst -1U\n"
      "needs eight bytes as it is the same as 0xFFFFffffFFFFffffU.\n"
    );
    return -1;
  }

  int i;
  uint8_t write_mode;           // Operation mode, "standard" or "fill"
  uint8_t start_offset;         // Which argc argument
  int len;                      // Number of bytes to write to memory
  char *memtype = argv[1];      // Memory name string
  AVRMEM *mem = avr_locate_mem(p, memtype);
  if (mem == NULL) {
    pmsg_error("(write) %s memory type not defined for part %s\n", memtype, p->desc);
    return -1;
  }
  int maxsize = mem->size;

  char *end_ptr;
  int addr = strtoul(argv[2], &end_ptr, 0);
  if (*end_ptr || (end_ptr == argv[2])) {
    pmsg_error("(write) cannot parse address %s\n", argv[2]);
    return -1;
  }

  if (addr < 0 || addr >= maxsize) {
    pmsg_error("(write) %s address 0x%05x is out of range [0, 0x%05x]\n", mem->desc, addr, maxsize-1);
    return -1;
  }

  // Allocate a buffer guaranteed to be large enough
  uint8_t *buf = calloc(mem->size + 8 + maxstrlen(argc-3, argv+3)+1, sizeof(uint8_t));
  if (buf == NULL) {
    pmsg_error("(write) out of memory\n");
    return -1;
  }

  // Find the first argument to write to flash and how many arguments to parse and write
  if (strcmp(argv[argc - 1], "...") == 0) {
    write_mode = WRITE_MODE_FILL;
    start_offset = 4;
    len = strtoul(argv[3], &end_ptr, 0);
    if (*end_ptr || (end_ptr == argv[3])) {
      pmsg_error("(write ...) cannot parse length %s\n", argv[3]);
      free(buf);
      return -1;
    }
  } else {
    write_mode = WRITE_MODE_STANDARD;
    start_offset = 3;
    len = argc - start_offset;
  }

  // Structure related to data that is being written to memory
  struct Data {
    // Data info
    int bytes_grown;
    uint8_t size;
    char *str_ptr;
    // Data union
    union {
      float f;
      double d;
      int64_t ll;
      uint64_t ull;
      uint8_t a[8];
    };
  } data = {
    .bytes_grown = 0,
    .size        = 0,
    .str_ptr     = NULL,
    .ull         = 1
  };

  if(sizeof(long long) != sizeof(int64_t) || (data.a[0]^data.a[7]) != 1)
    pmsg_error("(write) assumption on data types not met? "
      "Check source and recompile\n");
  bool is_big_endian = data.a[7];

  for (i = start_offset; i < len + start_offset; i++) {
    // Handle the next argument
    if (i < argc - start_offset + 3) {
      char *argi = argv[i];
      size_t arglen = strlen(argi);

      data.size = 0;

      // Free string pointer if already allocated
      if(data.str_ptr) {
        free(data.str_ptr);
        data.str_ptr = NULL;
      }

      // Remove trailing comma to allow cut and paste of lists
      if(arglen > 0 && argi[arglen-1] == ',')
        argi[--arglen] = 0;

      // Try integers and assign data size
      errno = 0;
      data.ull = strtoull(argi, &end_ptr, 0);
      if (!(end_ptr == argi || errno)) {
        unsigned int nu=0, nl=0, nh=0, ns=0, nx=0;
        char *p;

        // Parse suffixes: ULL, LL, UL, L ... UHH, HH
        for(p=end_ptr; *p; p++)
          switch(toupper(*p)) {
          case 'U': nu++; break;
          case 'L': nl++; break;
          case 'H': nh++; break;
          case 'S': ns++; break;
          default: nx++;
          }

        if(nx==0 && nu<2 && nl<3 && nh<3 && ns<2) { // Could be valid integer suffix
          if(nu==0 || toupper(*end_ptr) == 'U' || toupper(p[-1]) == 'U') { // If U, then must be at start or end
            bool is_hex = strncasecmp(argi, "0x", 2) == 0; // Ordinary hex: 0x... without explicit +/- sign
            bool is_signed = !(nu || is_hex);              // Neither explicitly unsigned nor ordinary hex
            bool is_outside_int64_t = 0;
            bool is_out_of_range = 0;
            int nhexdigs = p-argi-2;

            if(is_signed) {     // Is input in range for int64_t?
              errno = 0; (void) strtoll(argi, NULL, 0);
              is_outside_int64_t = errno == ERANGE;
            }

            if(nl==0 && ns==0 && nh==0) { // No explicit data size
              // Ordinary hex numbers have implicit size given by number of hex digits, including leading zeros
              if(is_hex) {
                data.size = nhexdigs > 8? 8: nhexdigs > 4? 4: nhexdigs > 2? 2: 1;

              } else if(is_signed) {
                // Smallest size that fits signed or unsigned (asymmetric to meet user expectation)
                data.size =
                  is_outside_int64_t? 8:
                  data.ll < INT32_MIN || data.ll > (long long) UINT32_MAX? 8:
                  data.ll < INT16_MIN || data.ll > (long long) UINT16_MAX? 4:
                  data.ll < INT8_MIN  || data.ll > (long long) UINT8_MAX? 2: 1;

              } else {
                // Smallest size that fits unsigned representation
                data.size =
                  data.ull > UINT32_MAX? 8:
                  data.ull > UINT16_MAX? 4:
                  data.ull > UINT8_MAX? 2: 1;
              }
            } else if(nl==0 && nh==2 && ns==0) { // HH
              data.size = 1;
              if(is_outside_int64_t || (is_signed && (data.ll < INT8_MIN  || data.ll > INT8_MAX))) {
                is_out_of_range = 1;
                data.ll = (int8_t) data.ll;
              }
            } else if(nl==0 && ((nh==1 && ns==0) || (nh==0 && ns==1))) { // H or S
              data.size = 2;
              if(is_outside_int64_t || (is_signed && (data.ll < INT16_MIN  || data.ll > INT16_MAX))) {
                is_out_of_range = 1;
                data.ll = (int16_t) data.ll;
              }
            } else if(nl==1 && nh==0 && ns==0) { // L
              data.size = 4;
              if(is_outside_int64_t || (is_signed && (data.ll < INT32_MIN  || data.ll > INT32_MAX))) {
                is_out_of_range = 1;
                data.ll = (int32_t) data.ll;
              }
            } else if(nl==2 && nh==0 && ns==0) { // LL
              data.size = 8;
              if(is_outside_int64_t || is_signed)
                is_out_of_range = 1;
            }

            if(is_out_of_range)
              pmsg_error("(write) %s out of int%d_t range, "
                "interpreted as %d-byte %lld; consider 'U' suffix\n", argi, data.size*8, data.size, (long long int) data.ll);
          }
        }
      }

      if(!data.size) {          // Try double now that input was rejected as integer
        data.d = strtod(argi, &end_ptr);
        if (end_ptr != argi && toupper(*end_ptr) == 'D' && end_ptr[1] == 0)
          data.size = 8;
      }

      if(!data.size) {          // Try float
        data.f = strtof(argi, &end_ptr);
        if (end_ptr != argi && toupper(*end_ptr) == 'F' && end_ptr[1] == 0)
          data.size = 4;
        if (end_ptr != argi && *end_ptr == 0) // No suffix defaults to float but ...
          // ... do not accept valid mantissa-only floats that are integer rejects (eg, 078 or ULL overflows)
          if (!is_mantissa_only(argi))
            data.size = 4;
      }

      if(!data.size && arglen > 1) { // Try C-style string or single character
        if ((*argi == '\'' && argi[arglen-1] == '\'') || (*argi == '\"' && argi[arglen-1] == '\"')) {
          char *s = calloc(arglen-1, 1);
          if (s == NULL) {
            pmsg_error("(write str) out of memory\n");
            free(buf);
            return -1;
          }
          // Strip start and end quotes, and unescape C string
          strncpy(s, argi+1, arglen-2);
          cfg_unescape(s, s);
          if (*argi == '\'') {  // Single C-style character
            if(*s && s[1])
              pmsg_error("(write) only using first character of %s\n", argi);
            data.ll = *s;
            data.size = 1;
            free(s);
          } else {              // C-style string
            data.str_ptr = s;
          }
        }
      }

      if(!data.size && !data.str_ptr) {
        pmsg_error("(write) cannot parse data %s\n", argi);
        free(buf);
        return -1;
      }

      // Assume endianness is the same for double and int, and ensure little endian representation
      if(is_big_endian && data.size > 1)
        change_endian(data.a, data.size);
    }

    if(data.str_ptr) {
      for(size_t j = 0; j < strlen(data.str_ptr); j++)
        buf[i - start_offset + data.bytes_grown++] = (uint8_t)data.str_ptr[j];
    } else if(data.size > 0) {
      for(int k=0; k<data.size; k++)
        buf[i - start_offset + data.bytes_grown + k] = data.a[k];
      data.bytes_grown += data.size-1;
    }

    // Make sure buf does not overflow
    if (i - start_offset + data.bytes_grown > maxsize)
      break;
  }

  // When in "fill" mode, the maximum size is already predefined
  if (write_mode == WRITE_MODE_FILL)
    data.bytes_grown = 0;

  if ((addr + len + data.bytes_grown) > maxsize) {
    pmsg_error("(write) selected address and # bytes exceed "
      "range for %s memory\n", memtype);
    free(buf);
    return -1;
  }

  if(data.str_ptr)
    free(data.str_ptr);

  pmsg_notice2("(write) writing %d byte%s starting from address 0x%02lx",
    len + data.bytes_grown, update_plural(len + data.bytes_grown), (long) addr);
  if (write_mode == WRITE_MODE_FILL)
    msg_notice2("; remaining space filled with %s", argv[argc - 2]);
  msg_notice2("\n");

  pgm->err_led(pgm, OFF);
  bool werror = false;
  report_progress(0, 1, avr_has_paged_access(pgm, mem)? "Caching": "Writing");
  for (i = 0; i < len + data.bytes_grown; i++) {
    int rc = pgm->write_byte_cached(pgm, p, mem, addr+i, buf[i]);
    if (rc == LIBAVRDUDE_SOFTFAIL) {
      pmsg_warning("(write) programmer write protects %s address 0x%04x\n", mem->desc, addr+i);
    } else if(rc) {
      pmsg_error("(write) error writing 0x%02x at 0x%05lx, rc=%d\n", buf[i], (long) addr+i, (int) rc);
      if (rc == -1)
        imsg_error("%*swrite operation not supported on memory type %s\n", 8, "", mem->desc);
      werror = true;
    } else {
      uint8_t b;
      rc = pgm->read_byte_cached(pgm, p, mem, addr+i, &b);
      if (b != buf[i]) {
        pmsg_error("(write) verification error writing 0x%02x at 0x%05lx cell=0x%02x\n", buf[i], (long) addr+i, b);
        werror = true;
      }
   }

    if (werror)
      pgm->err_led(pgm, ON);

    report_progress(i, len + data.bytes_grown, NULL);
  }
  report_progress(1, 1, NULL);

  free(buf);

  return 0;
}


static int cmd_flush(PROGRAMMER *pgm, AVRPART *p, int ac, char *av[]) {
  pgm->flush_cache(pgm, p);
  return 0;
}


static int cmd_abort(PROGRAMMER *pgm, AVRPART *p, int ac, char *av[]) {
  pgm->reset_cache(pgm, p);
  return 0;
}


static int cmd_send(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  unsigned char cmd[4], res[4];
  char *e;
  int i;
  int len;

  if (spi_mode && (pgm->spi == NULL)) {
    pmsg_error("(send) the %s programmer does not support direct SPI transfers\n", pgm->type);
    return -1;
  }


  if ((argc > 5) || ((argc < 5) && (!spi_mode))) {
    msg_error(spi_mode?
      "Usage: send <byte1> [<byte2> [<byte3> [<byte4>]]]\n":
      "Usage: send <byte1> <byte2> <byte3> <byte4>\n");
    return -1;
  }

  /* number of bytes to write at the specified address */
  len = argc - 1;

  /* load command bytes */
  for (i=1; i<argc; i++) {
    cmd[i-1] = strtoul(argv[i], &e, 0);
    if (*e || (e == argv[i])) {
      pmsg_error("(send) cannot parse byte %s\n", argv[i]);
      return -1;
    }
  }

  pgm->err_led(pgm, OFF);

  if (spi_mode)
    pgm->spi(pgm, cmd, res, argc-1);
  else
    pgm->cmd(pgm, cmd, res);

  /*
   * display results
   */
  term_out("results:");
  for (i=0; i<len; i++)
    term_out(" %02x", res[i]);
  term_out("\n\n");

  return 0;
}


static int cmd_erase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  term_out("erasing chip ...\n");

  // Erase chip and clear cache
  int rc = pgm->chip_erase_cached(pgm, p);

  if(rc == LIBAVRDUDE_SOFTFAIL) {
    pmsg_info("(erase) emulating chip erase by writing 0xff to flash ");
    AVRMEM *flm = avr_locate_mem(p, "flash");
    if(!flm) {
      msg_error("but flash not defined for part %s?\n", p->desc);
      return -1;
    }
    int addr, beg = 0, end = flm->size-1;
    if(pgm->readonly) {
      for(addr=beg; addr < flm->size; addr++)
        if(!pgm->readonly(pgm, p, flm, addr)) {
          beg = addr;
          break;
        }
      if(addr >= flm->size) {
        msg_info("but all flash is write protected\n");
        return 0;
      }
      for(addr=end; addr >= 0; addr--)
        if(!pgm->readonly(pgm, p, flm, addr)) {
          end = addr;
          break;
        }
    }

    msg_info("[0x%04x, 0x%04x]; undo with abort\n", beg, end);
    for(int addr=beg; addr <= end; addr++)
      if(!pgm->readonly || !pgm->readonly(pgm, p, flm, addr))
        if(pgm->write_byte_cached(pgm, p, flm, addr, 0xff) == -1)
          return -1;
    return 0;
  }

  if(rc) {
    pmsg_error("(erase) programmer %s failed erasing the chip\n", (char *) ldata(lfirst(pgm->id)));
    return -1;
  }

  return 0;
}


static int cmd_pgerase(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  if(argc < 3) {
    msg_error("Usage: pgerase <memory> <addr>\n");
    return -1;
  }

  char *memtype = argv[1];
  AVRMEM *mem = avr_locate_mem(p, memtype);
  if(!mem) {
    pmsg_error("(pgerase) %s memory type not defined for part %s\n", memtype, p->desc);
    return -1;
  }
  if(!avr_has_paged_access(pgm, mem)) {
    pmsg_error("(pgerase) %s memory cannot be paged addressed by %s\n", memtype, (char *) ldata(lfirst(pgm->id)));
    return -1;
  }

  int maxsize = mem->size;

  char *end_ptr;
  int addr = strtoul(argv[2], &end_ptr, 0);
  if(*end_ptr || (end_ptr == argv[2])) {
    pmsg_error("(pgerase) cannot parse address %s\n", argv[2]);
    return -1;
  }

  if (addr < 0 || addr >= maxsize) {
    pmsg_error("(pgerase) %s address 0x%05x is out of range [0, 0x%05x]\n", mem->desc, addr, maxsize-1);
    return -1;
  }

  if(pgm->page_erase_cached(pgm, p, mem, (unsigned int) addr) < 0) {
    pmsg_error("(pgerase) unable to erase %s page at 0x%05x\n", mem->desc, addr);
    return -1;
  }

  return 0;
}


static int cmd_part(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  term_out("\n");
  avr_display(stdout, p, "", 0);
  term_out("\n");

  return 0;
}


static int cmd_sig(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int i;
  int rc;
  AVRMEM *m;

  rc = avr_signature(pgm, p);
  if (rc != 0) {
    pmsg_error("(sig) error reading signature data, rc=%d\n", rc);
  }

  m = avr_locate_mem(p, "signature");
  if (m == NULL) {
    pmsg_error("(sig) signature data not defined for device %s\n", p->desc);
  }
  else {
    term_out("Device signature = 0x");
    for (i=0; i<m->size; i++)
      term_out("%02x", m->buf[i]);
    term_out("\n\n");
  }

  return 0;
}


static int cmd_quit(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  /* FUSE bit verify will fail if left in SPI mode */
  if (spi_mode) {
    cmd_pgm(pgm, p, 0, NULL);
  }
  return 1;
}


static int cmd_parms(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  pgm->print_parms(pgm, stdout);
  term_out("\n");
  return 0;
}


static int cmd_vtarg(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int rc;
  double v;
  char *endp;

  if (argc != 2) {
    msg_error("Usage: vtarg <value>\n");
    return -1;
  }
  v = strtod(argv[1], &endp);
  if (endp == argv[1]) {
    pmsg_error("(vtarg) cannot parse voltage %s\n", argv[1]);
    return -1;
  }
  if ((rc = pgm->set_vtarget(pgm, v)) != 0) {
    pmsg_error("(vtarg) unable to set V[target] (rc = %d)\n", rc);
    return -3;
  }
  return 0;
}


static int cmd_fosc(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int rc;
  double v;
  char *endp;

  if (argc != 2) {
    msg_error("Usage: fosc <value>[M|k] | off\n");
    return -1;
  }
  v = strtod(argv[1], &endp);
  if (endp == argv[1]) {
    if (strcmp(argv[1], "off") == 0)
      v = 0.0;
    else {
      pmsg_error("(fosc) cannot parse frequency %s\n", argv[1]);
      return -1;
    }
  }
  if (*endp == 'm' || *endp == 'M')
    v *= 1e6;
  else if (*endp == 'k' || *endp == 'K')
    v *= 1e3;
  if ((rc = pgm->set_fosc(pgm, v)) != 0) {
    pmsg_error("(fosc) unable to set oscillator frequency (rc = %d)\n", rc);
    return -3;
  }
  return 0;
}


static int cmd_sck(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int rc;
  double v;
  char *endp;

  if (argc != 2) {
    msg_error("Usage: sck <value>\n");
    return -1;
  }
  v = strtod(argv[1], &endp);
  if (endp == argv[1]) {
    pmsg_error("(sck) cannot parse period %s\n", argv[1]);
    return -1;
  }
  v *= 1e-6;                    // Convert from microseconds to seconds
  if ((rc = pgm->set_sck_period(pgm, v)) != 0) {
    pmsg_error("(sck) unable to set SCK period (rc = %d)\n", rc);
    return -3;
  }
  return 0;
}


static int cmd_varef(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int rc;
  unsigned int chan;
  double v;
  char *endp;

  if (argc != 2 && argc != 3) {
    msg_error("Usage: varef [channel] <value>\n");
    return -1;
  }
  if (argc == 2) {
    chan = 0;
    v = strtod(argv[1], &endp);
    if (endp == argv[1]) {
      pmsg_error("(varef) cannot parse voltage %s\n", argv[1]);
      return -1;
    }
  } else {
    chan = strtoul(argv[1], &endp, 10);
    if (endp == argv[1]) {
      pmsg_error("(varef) cannot parse channel %s\n", argv[1]);
      return -1;
    }
    v = strtod(argv[2], &endp);
    if (endp == argv[2]) {
      pmsg_error("(varef) cannot parse voltage %s\n", argv[2]);
      return -1;
    }
  }
  if ((rc = pgm->set_varef(pgm, chan, v)) != 0) {
    pmsg_error("(varef) unable to set V[aref] (rc = %d)\n", rc);
    return -3;
  }
  return 0;
}


static int cmd_help(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int i;

  term_out("Valid commands:\n");
  for (i=0; i<NCMDS; i++) {
    if(!*(void (**)(void)) ((char *) pgm + cmd[i].fnoff))
      continue;
    term_out("  %-7s : ", cmd[i].name);
    term_out(cmd[i].desc, cmd[i].name);
    term_out("\n");
  }
  term_out("\n"
    "Note that not all programmer derivatives support all commands. Flash and\n"
    "EEPROM type memories are normally read and written using a cache via paged\n"
    "read and write access; the cache is synchronised on quit or flush commands.\n"
    "The part command displays valid memory types for use with dump and write.\n\n");
  return 0;
}

static int cmd_spi(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  pgm->setpin(pgm, PIN_AVR_RESET, 1);
  spi_mode = 1;
  return 0;
}

static int cmd_pgm(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  pgm->setpin(pgm, PIN_AVR_RESET, 0);
  spi_mode = 0;
  pgm->initialize(pgm, p);
  return 0;
}

static int cmd_verbose(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int nverb;
  char *endp;

  if (argc != 1 && argc != 2) {
    msg_error("Usage: verbose [<value>]\n");
    return -1;
  }
  if (argc == 1) {
    msg_error("Verbosity level: %d\n", verbose);
    return 0;
  }
  nverb = strtol(argv[1], &endp, 0);
  if (endp == argv[1] || *endp) {
    pmsg_error("(verbose) cannot parse verbosity level %s\n", argv[1]);
    return -1;
  }
  if (nverb < 0) {
    pmsg_error("(verbose) level must not be negative: %d\n", nverb);
    return -1;
  }
  verbose = nverb;
  term_out("New verbosity level: %d\n", verbose);

  return 0;
}

static int cmd_quell(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int nquell;
  char *endp;

  if (argc != 1 && argc != 2) {
    msg_error("Usage: quell [<value>]\n");
    return -1;
  }
  if (argc == 1) {
    msg_error("Quell level: %d\n", quell_progress);
    return 0;
  }
  nquell = strtol(argv[1], &endp, 0);
  if (endp == argv[1] || *endp) {
    pmsg_error("(quell) cannot parse quell level %s\n", argv[1]);
    return -1;
  }
  if (nquell < 0) {
    pmsg_error("(quell) level must not be negative: %d\n", nquell);
    return -1;
  }
  quell_progress = nquell;
  term_out("New quell level: %d\n", quell_progress);

  if(quell_progress > 0)
    update_progress = NULL;
  else
    terminal_setup_update_progress();

  return 0;
}

static int tokenize(char *s, char ***argv) {
  int     i, n, l, nargs;
  int     len, slen;
  char  *buf;
  int     bufsize;
  char **bufv;
  char  *bufp;
  char  *q, *r;
  char  *nbuf;
  char **av;

  slen = strlen(s);

  /*
   * initialize allow for 20 arguments, use realloc to grow this if
   * necessary
   */
  nargs   = 20;
  bufsize = slen + 20;
  buf     = malloc(bufsize);
  bufv    = (char **) malloc(nargs*sizeof(char *));
  for (i=0; i<nargs; i++) {
    bufv[i] = NULL;
  }
  buf[0] = 0;

  n    = 0;
  l    = 0;
  nbuf = buf;
  r    = s;
  while (*r) {
    nexttok(r, &q, &r);
    strcpy(nbuf, q);
    bufv[n]  = nbuf;
    len      = strlen(q);
    l       += len + 1;
    nbuf    += len + 1;
    nbuf[0]  = 0;
    n++;
    if ((n % 20) == 0) {
      char *buf_tmp;
      char **bufv_tmp;
      /* realloc space for another 20 args */
      bufsize += 20;
      nargs   += 20;
      bufp     = buf;
      buf_tmp  = realloc(buf, bufsize);
      if (buf_tmp == NULL) {
        free(buf);
        free(bufv);
        return -1;
      }
      buf = buf_tmp;
      bufv_tmp = realloc(bufv, nargs*sizeof(char *));
      if (bufv_tmp == NULL) {
        free(buf);
        free(bufv);
        return -1;
      }
      bufv = bufv_tmp;
      nbuf     = &buf[l];
      /* correct bufv pointers */
      ptrdiff_t k = buf - bufp;
      for (i=0; i<n; i++) {
          bufv[i] = bufv[i] + k;
      }
      for (i=n; i<nargs; i++)
        bufv[i] = NULL;
    }
  }

  /*
   * We have parsed all the args, n == argc, bufv contains an array of
   * pointers to each arg, and buf points to one memory block that
   * contains all the args, back to back, seperated by a nul
   * terminator.  Consilidate bufv and buf into one big memory block
   * so that the code that calls us, will have an easy job of freeing
   * this memory.
   */
  av = (char **) malloc(slen + n + (n+1)*sizeof(char *));
  q  = (char *)&av[n+1];
  memcpy(q, buf, l);
  for (i=0; i<n; i++) {
    ptrdiff_t offset = bufv[i] - buf;
    av[i] = q + offset;
  }
  av[i] = NULL;

  free(buf);
  free(bufv);

  *argv = av;

  return n;
}


static int do_cmd(PROGRAMMER *pgm, AVRPART *p, int argc, char *argv[]) {
  int i;
  int hold, matches;
  size_t len;

  len = strlen(argv[0]);
  matches = 0;
  for (i=0; i<NCMDS; i++) {
    if(!*(void (**)(void)) ((char *) pgm + cmd[i].fnoff))
      continue;
    if(len && strncasecmp(argv[0], cmd[i].name, len)==0) { // Partial initial match
      hold = i;
      matches++;
      if(cmd[i].name[len] == 0) { // Exact match
        matches = 1;
        break;
      }
    }
  }

  if(matches == 1)
    return cmd[hold].func(pgm, p, argc, argv);

  pmsg_error("(cmd) command %s is %s\n", argv[0], matches > 1? "ambiguous": "invalid");
  return -1;
}


char *terminal_get_input(const char *prompt) {
  char input[256];

  term_out("%s", prompt);
  if(fgets(input, sizeof(input), stdin)) {
    int len = strlen(input);
    if(len > 0 && input[len-1] == '\n')
      input[len-1] = 0;
    return cfg_strdup(__func__, input);
  }

  return NULL;
}


static int process_line(char *cmdbuf, PROGRAMMER *pgm, struct avrpart *p) {
  int argc, rc;
  char **argv = NULL, *q;

  // Find the start of the command, skipping any white space
  q = cmdbuf;
  while(*q && isspace((unsigned char) *q))
    q++;

  // Skip blank lines and comments
  if (!*q || (*q == '#'))
    return 0;

  // Tokenize command line
  argc = tokenize(q, &argv);

  if(!argv)
    return -1;

  // Run the command
  rc = do_cmd(pgm, p, argc, argv);
  free(argv);

  return rc;
}



#if defined(HAVE_LIBREADLINE)

static PROGRAMMER *term_pgm;
static struct avrpart *term_p;

static int term_running;

// Any character in standard input available (without sleeping)?
static int readytoread() {
#ifdef _MSC_VER
    return rl_input_available();
#elif defined(WIN32)
  HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);

  while(1) {
    INPUT_RECORD input[1] = { 0 };
    DWORD dwNumberOfEventsRead = 0;

    if(!PeekConsoleInputA(hStdin, input, ARRAYSIZE(input), &dwNumberOfEventsRead)) {
      DWORD dwError = GetLastError();

      // Stdin redirected from a pipe or file (FIXME: reading from a pipe may sleep)
      if(dwError == ERROR_INVALID_HANDLE)
        return 1;

      pmsg_warning("PeekConsoleInputA() failed with error code %u\n", (unsigned int) dwError);
      return -1;
    }

    if(dwNumberOfEventsRead <= 0) // Nothing in the input buffer
      return 0;

    // Filter out all the events that readline does not handle ...
    if((input[0].EventType & KEY_EVENT) != 0 && input[0].Event.KeyEvent.bKeyDown)
      return 1;

    // Drain other events not handled by readline
    if(!ReadConsoleInputA(hStdin, input, ARRAYSIZE(input), &dwNumberOfEventsRead)) {
      pmsg_warning("ReadConsoleInputA() failed with error code %u\n", (unsigned int) GetLastError());
      return -1;
    }
  }
#else
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);

    return select(1, &fds, NULL, NULL, &tv) > 0;
#endif
}

// Callback processes commands whenever readline() has finished
void term_gotline(char *cmdstr) {
  if(cmdstr) {
    if(*cmdstr) {
      add_history(cmdstr);
      // Only quit returns a value > 0
      if(process_line(cmdstr, term_pgm, term_p) > 0)
        term_running = 0;
    }
    free(cmdstr);
    /*
     * This is a workaround for a bug apparently present in the
     * readline compat layer of libedit which is natively present in
     * NetBSD and MacOS.
     *
     * see https://github.com/avrdudes/avrdude/issues/1173
     */
    if(term_running) {
      rl_callback_handler_remove();
      rl_callback_handler_install("avrdude> ", term_gotline);
    }
  } else {
    // End of file or terminal ^D
    term_out("\n");
    cmd_quit(term_pgm, term_p, 0, NULL);
    term_running = 0;
  }
  if(!term_running)
    rl_callback_handler_remove();
}


int terminal_mode_interactive(PROGRAMMER *pgm, struct avrpart *p) {
  term_pgm = pgm;               // For callback routine
  term_p = p;

  rl_callback_handler_install("avrdude> ", term_gotline);

  term_running = 1;
  for(int n=1; term_running; n++) {
    if(n%16 == 0) {             // Every 100 ms (16*6.25 us) reset bootloader watchdog timer
      if(pgm->term_keep_alive)
        pgm->term_keep_alive(pgm, NULL);
    }
    usleep(6250);
    if(readytoread() > 0 && term_running)
      rl_callback_read_char();
  }

  return pgm->flush_cache(pgm, p);
}

#endif


int terminal_mode_noninteractive(PROGRAMMER *pgm, struct avrpart *p) {
  char *cmdbuf;
  int rc = 0;

  while((cmdbuf = terminal_get_input("avrdude> "))) {
    int rc = process_line(cmdbuf, pgm, p);
    free(cmdbuf);
    if(rc > 0)
      break;
  }

  if(rc <= 0)
    cmd_quit(pgm, p, 0, NULL);
  return pgm->flush_cache(pgm, p);
}

int terminal_mode(PROGRAMMER *pgm, struct avrpart *p) {
#if defined(HAVE_LIBREADLINE)
  // GNU libreadline can also work if input is a pipe.
  // EditLine (NetBSD, MacOS) has issues with that, so only use it when
  // running interactively.
  // EditLine uses version 4.2 (0x0402).
  if (isatty(fileno(stdin)) || rl_readline_version > 0x0500)
    return terminal_mode_interactive(pgm, p);
#endif
  return terminal_mode_noninteractive(pgm, p);
}

static void update_progress_tty(int percent, double etime, const char *hdr, int finish) {
  static char *header;
  static int last, done = 1;
  int i;

  setvbuf(stderr, (char *) NULL, _IONBF, 0);

  if(hdr) {
    msg_info("\n");
    last = done = 0;
    if(header)
      free(header);
    header = cfg_strdup("update_progress_tty()",  hdr);
  }

  percent = percent > 100? 100: percent < 0? 0: percent;

  if(!done) {
    if(!header)
      header = cfg_strdup("update_progress_tty()", "report");

    int showperc = finish >= 0? percent: last;

    char hashes[51];
    memset(hashes, finish >= 0? ' ': '-', 50);
    for(i=0; i<showperc; i+=2)
      hashes[i/2] = '#';
    hashes[50] = 0;

    msg_info("\r%s | %s | %d%% %0.2f s ", header, hashes, showperc, etime);
    if(percent == 100) {
      if(finish)
        msg_info("\n\n");
      done = 1;
    }
  }
  last = percent;

  setvbuf(stderr, (char *) NULL, _IOLBF, 0);
}

static void update_progress_no_tty(int percent, double etime, const char *hdr, int finish) {
  static int last, done = 1;

  setvbuf(stderr, (char *) NULL, _IONBF, 0);

  percent = percent > 100? 100: percent < 0? 0: percent;

  if(hdr) {
    msg_info("\n%s | ", hdr);
    last = done = 0;
  }

  if(!done) {
    for(int cnt = percent/2; cnt > last/2; cnt--)
      msg_info(finish >= 0? "#": "-");

    if(percent == 100) {
      msg_info(" | %d%% %0.2fs", finish >= 0? 100: last, etime);
      if(finish)
        msg_info("\n\n");
      done = 1;
    }
  }
  last = percent;

  setvbuf(stderr, (char *) NULL, _IOLBF, 0);
}

void terminal_setup_update_progress() {
  if (isatty (STDERR_FILENO))
    update_progress = update_progress_tty;
  else {
    update_progress = update_progress_no_tty;
    /* disable all buffering of stderr for compatibility with
       software that captures and redirects output to a GUI
       i.e. Programmers Notepad */
    setvbuf( stderr, NULL, _IONBF, 0 );
    setvbuf( stdout, NULL, _IONBF, 0 );
  }
}
