/****************************************************************************
 * tools/gencromfs.c
 *
 *   Copyright (C) 2018, 2020 Gregory Nutt. All rights reserved.
 *   Authors: Gregory Nutt <gnutt@nuttx.org>
 *            David Sidrane <david.sidrane@nscdg.com>
 *
 * The function lzf_compress() comes from the file lzf_c.c (which substantial
 * modification):
 *
 *   Copyright (c) 2000-2010 Marc Alexander Lehmann <schmorp@schmorp.de>
 *
 * Which has a compatible BSD license and included here under the NuttX BSD
 * license:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#define _GNU_SOURCE 1

#include <sys/stat.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <limits.h>
#include <errno.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* HOST_BIGENDIAN or TGT_BIGENDIAN may be defined on the command line to
 * adapt to different host/target combinations.  Otherwise, both are assumed
 * to be the same endian-ness.
 */

#undef HOST_TGTSWAP

#if (defined(HOST_BIGENDIAN) && !defined(TGT_BIGENDIAN)) || \
    (!defined(HOST_BIGENDIAN) && defined(TGT_BIGENDIAN))
#  define HOST_TGTSWAP 1
#endif

#define UNUSED(a) ((void)(a))

/* mkstemp() has been giving me errors on Cygwin */

#undef USE_MKSTEMP

#define TMP_NAMLEN         32         /* Actually only 22 */
#ifdef USE_MKSTEMP
#  define TMP_NAME         "/tmp/gencromfs-XXXXXX"
#else
#  define TMP_NAME         "/tmp/gencromfs-%06u"
#endif

#define NUTTX_IXOTH        (1 << 0)   /* Must match NuttX's S_IXOTH */
#define NUTTX_IWOTH        (1 << 1)   /* Must match NuttX's S_IWOTH */
#define NUTTX_IROTH        (1 << 2)   /* Must match NuttX's S_IROTH */

#define NUTTX_IRXOTH       (NUTTX_IROTH | NUTTX_IXOTH)

#define NUTTX_IXGRP        (1 << 3)   /* Must match NuttX's S_IXGRP */
#define NUTTX_IWGRP        (1 << 4)   /* Must match NuttX's S_IWGRP */
#define NUTTX_IRGRP        (1 << 5)   /* Must match NuttX's S_IRGRP */

#define NUTTX_IRXGRP       (NUTTX_IRGRP | NUTTX_IXGRP)

#define NUTTX_IXUSR        (1 << 6)   /* Must match NuttX's S_IXUSR */
#define NUTTX_IWUSR        (1 << 7)   /* Must match NuttX's S_IWUSR */
#define NUTTX_IRUSR        (1 << 8)   /* Must match NuttX's S_IRUSR */

#define NUTTX_IRXUSR       (NUTTX_IRUSR | NUTTX_IXUSR)

#define NUTTX_IFDIR        (4 << 12)   /* Must match NuttX's S_IFDIR */
#define NUTTX_IFREG        (8 << 12)   /* Must match NuttX's S_IFREG */
#define NUTTX_IFLNK        (10 << 12)  /* Must match NuttX's S_IFLNK */

#define DIR_MODEFLAGS      (NUTTX_IFDIR | NUTTX_IRXUSR | NUTTX_IRXGRP | NUTTX_IRXOTH)
#define DIRLINK_MODEFLAGS  (NUTTX_IFLNK | NUTTX_IRXUSR | NUTTX_IRXGRP | NUTTX_IRXOTH)
#define FILE_MODEFLAGS     (NUTTX_IFREG | NUTTX_IRUSR | NUTTX_IRGRP | NUTTX_IROTH)

#define CROMFS_MAGIC       0x4d4f5243
#define CROMFS_BLOCKSIZE   512

#define LZF_BUFSIZE        512
#define LZF_HLOG           13
#define LZF_HSIZE          (1 << LZF_HLOG)

#define LZF_TYPE0_HDR      0
#define LZF_TYPE1_HDR      1
#define LZF_TYPE0_HDR_SIZE 5
#define LZF_TYPE1_HDR_SIZE 7

#define LZF_FRST(p)        (((p[0]) << 8) | p[1])
#define LZF_NEXT(v,p)      (((v) << 8) | p[2])
#define LZF_NDX(h)         ((((h ^ (h << 5)) >> (3*8 - LZF_HLOG)) - h*5) & (LZF_HSIZE - 1))

#define LZF_MAX_LIT        (1 <<  5)
#define LZF_MAX_OFF        (1 << LZF_HLOG)
#define LZF_MAX_REF        ((1 << 8) + (1 << 3))

#define HEX_PER_LINE       8

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* Maximum size of an offset.  This should normally be size_t since this is
 * an in-memory file system.  However, size_t is 32-bits on most 32-bit
 * target machines but 64-bits on 64-host machines.  We restrict offsets to
 * 32-bits for commonality (limiting the size of the CROMFS image to 4Gb)
 *
 * Similarly, the NuttX mode_t is only 16-bits so uint16_t is explicitly used
 * for NuttX file modes.
 */

/* CROMFS structures */

struct cromfs_volume_s
{
  uint32_t cv_magic;      /* Must be first.  Must be CROMFS_MAGIC */
  uint16_t cv_nnodes;     /* Total number of nodes in-use */
  uint16_t cv_nblocks;    /* Total number of data blocks in-use */
  uint32_t cv_root;       /* Offset to the first node in the root file system */
  uint32_t cv_fsize;      /* Size of the compressed file system image */
  uint32_t cv_bsize;      /* Optimal block size for transfers */
};

struct cromfs_node_s
{
  uint16_t cn_mode;       /* File type, attributes, and access mode bits */
  uint16_t cn_pad;        /* Not used */
  uint32_t cn_name;       /* Offset from the beginning of the volume header to the
                           * node name string.  NUL-terminated. */
  uint32_t cn_size;       /* Size of the uncompressed data (in bytes) */
  uint32_t cn_peer;       /* Offset to next node in this directory (for readdir()) */
  union
  {
    uint32_t cn_child;    /* Offset to first node in sub-directory (directories only) */
    uint32_t cn_link;     /* Offset to an arbitrary node (for hard link) */
    uint32_t cn_blocks;   /* Offset to first block of compressed data (for read) */
  } u;
};

/* LZF headers */

struct lzf_header_s       /* Common data header */
{
  uint8_t lzf_magic[2];   /* [0]='Z', [1]='V' */
  uint8_t lzf_type;       /* LZF_TYPE0_HDR or LZF_TYPE1_HDR */
};

struct lzf_type0_header_s /* Uncompressed data header */
{
  uint8_t lzf_magic[2];   /* [0]='Z', [1]='V' */
  uint8_t lzf_type;       /* LZF_TYPE0_HDR */
  uint8_t lzf_len[2];     /* Data length (big-endian) */
};

struct lzf_type1_header_s /* Compressed data header */
{
  uint8_t lzf_magic[2];   /* [0]='Z', [1]='V' */
  uint8_t lzf_type;       /* LZF_TYPE1_HDR */
  uint8_t lzf_clen[2];    /* Compressed data length (big-endian) */
  uint8_t lzf_ulen[2];    /* Uncompressed data length (big-endian) */
};

/* LZF data buffer */

union lzf_result_u
{
  struct
  {
    uint8_t lzf_magic[2];     /* [0]='Z', [1]='V' */
    uint8_t lzf_type;         /* LZF_TYPE0_HDR or LZF_TYPE1_HDR */
  } cmn;                      /* Common data header */
  struct
  {
    uint8_t lzf_magic[2];     /* [0]='Z', [1]='V' */
    uint8_t lzf_type;         /* LZF_TYPE0_HDR */
    uint8_t lzf_len[2];       /* Data length (big-endian) */
    uint8_t lzf_buffer[LZF_BUFSIZE];
  } uncompressed;             /* Uncompressed data header */
  struct
  {
    uint8_t lzf_magic[2];     /* [0]='Z', [1]='V' */
    uint8_t lzf_type;         /* LZF_TYPE1_HDR */
    uint8_t lzf_clen[2];      /* Compressed data length (big-endian) */
    uint8_t lzf_ulen[2];      /* Uncompressed data length (big-endian) */
    uint8_t lzf_buffer[LZF_BUFSIZE + 16];
  } compressed;
};

/* LZF hash table */

static uint8_t *g_lzf_hashtab[LZF_HSIZE];

/* Type of the callback from traverse_directory() */

typedef int (*traversal_callback_t)(const char *dirpath, const char *name,
                                    void *arg, bool lastentry);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static char *g_progname;       /* Name of this program */
static char *g_dirname;        /* Source directory path */
static char *g_outname;        /* Output file path */

static FILE *g_outstream;      /* Main output stream */
static FILE *g_tmpstream;      /* Temporary file output stream */

static const char g_delim[] =
  "**************************************"
  "**************************************";

static const char g_license[] =
  " *\n"
  " * Licensed to the Apache Software Foundation (ASF) under one or more\n"
  " * contributor license agreements. See the NOTICE file distributed with\n"
  " * this work for additional information regarding copyright ownership.\n"
  " * The ASF licenses this file to you under the Apache License, Version\n"
  " * 2.0 (the \"License\"); you mayn`t use this file except in compliance\n"
  " * with the License. You may obtain a copy of the License at\n"
  " *\n"
  " *   http://www.apache.org/licenses/LICENSE-2.0\n"
  " *\n"
  " * Unless required by applicable law or agreed to in writing, software\n"
  " * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
  " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n"
  " * implied. See the License for the specific language governing\n"
  " * permissions and limitations under the License.\n"
  " *\n";

static uint32_t g_offset;        /* Current image offset */
static uint32_t g_diroffset;     /* Offset for '.' */
static uint32_t g_parent_offset; /* Offset for '..' */

static unsigned int g_nnodes;  /* Number of nodes generated */
static unsigned int g_nblocks; /* Number of blocks of data generated */
static unsigned int g_nhex;    /* Number of hex characters on output line */
#ifndef USE_MKSTEMP
static unsigned int g_ntmps;   /* Number temporary files */
#endif

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static void show_usage(void);
static void verify_directory(void);
static void verify_outfile(void);
static void init_outfile(void);
static FILE *open_tmpfile(void);
#ifndef USE_MKSTEMP
static void unlink_tmpfiles(void);
#endif
static void append_tmpfile(FILE *dest, FILE *src);
static void dump_hexbuffer(FILE *stream, const void *buffer,
                           unsigned int nbytes);
static void dump_nextline(FILE *stream);
static size_t lzf_compress(const uint8_t *inbuffer, unsigned int inlen,
                           union lzf_result_u *result);
static uint16_t get_mode(mode_t mode);
#ifdef HOST_TGTSWAP
static inline uint16_t tgt_uint16(uint16_t a);
static inline uint32_t tgt_uint32(uint32_t a);
#  define TGT_UINT16(a) tgt_uint16(a)
#  define TGT_UINT32(a) tgt_uint32(a)
#else
#  define TGT_UINT16(a) (a)
#  define TGT_UINT32(a) (a)
#endif
static void gen_dirlink(const char *name, uint32_t tgtoffs, bool dirempty);
static void gen_directory(const char *path, const char *name, mode_t mode,
                          bool lastentry);
static void gen_file(const char *path, const char *name, mode_t mode,
                          bool lastentry);
static int  dir_notempty(const char *dirpath, const char *name,
                         void *arg, bool lastentry);
static int  process_direntry(const char *dirpath, const char *name,
                             void *arg, bool lastentry);
static int  traverse_directory(const char *dirpath,
                               traversal_callback_t callback, void *arg);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static void show_usage(void)
{
  fprintf(stderr, "USAGE: %s <dir-path> <out-file>\n", g_progname);
  exit(1);
}

static void verify_directory(void)
{
  struct stat buf;
  int len;
  int ret;

  /* Trim any trailing '/' characters from the directory path. */

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

  if (len < 1)
    {
      fprintf(stderr, "ERROR: Source <dir-path> %s is invalid\n",
              g_dirname);
      show_usage();
    }

  /* stat the source directory containing the file system image */

  ret = stat(g_dirname, &buf);
  if (ret < 0)
    {
      int errcode = errno;
      if (errcode == ENOENT)
        {
          fprintf(stderr, "ERROR: Source <dir-path> %s does not exist\n",
                  g_dirname);
        }
      else
        {
          fprintf(stderr, "ERROR: stat(%s) failed: %s\n",
                  g_dirname, strerror(errcode));
        }

      show_usage();
    }

  /* Verify that the source is, indeed, a directory */

  else if (!S_ISDIR(buf.st_mode))
    {
      fprintf(stderr, "ERROR: Source <dir-path> %s is not a directory\n",
              g_dirname);
    }
}

static void verify_outfile(void)
{
  struct stat buf;
  int ret;

  /* stat the destination file */

  ret = stat(g_outname, &buf);
  if (ret < 0)
    {
      int errcode = errno;
      if (errcode != ENOENT)
        {
          fprintf(stderr, "ERROR: stat(%s) failed: %s\n",
                  g_outname, strerror(errcode));
          show_usage();
        }
    }

  /* Something exists at this path.  Verify that the destination is a regular
   * file
   */

  else if (!S_ISREG(buf.st_mode))
    {
      fprintf(stderr, "ERROR: Destination <out-file> %s exists\n",
              g_outname);
      show_usage();
    }
  else
    {
      printf("Existing file %s will be replaced\n", g_outname);
    }
}

static void init_outfile(void)
{
  fprintf(g_outstream, "/%s\n", g_delim);
  fprintf(g_outstream, " * %s\n", g_outname);
  fprintf(g_outstream, "%s", g_license);
  fprintf(g_outstream, " %s/\n\n", g_delim);

  fprintf(g_outstream, "/%s\n", g_delim);
  fprintf(g_outstream, " * Included Files\n");
  fprintf(g_outstream, " %s/\n\n", g_delim);
  fprintf(g_outstream, "#include <stdint.h>\n\n");

  fprintf(g_outstream, "/%s\n", g_delim);
  fprintf(g_outstream, " * Private Data\n");
  fprintf(g_outstream, " %s/\n\n", g_delim);
}

static FILE *open_tmpfile(void)
{
  FILE *tmpstream;
#ifdef USE_MKSTEMP
  int fd;

  fd = mkstemp(TMP_NAME);
  if (fd < 0)
    {
      fprintf(stderr, "Failed to create temporary file: %s\n",
              strerror(errno));
      exit(1);
    }

  tmpstream = fdopen(fd, "w+");
  if (!tmpstream)
    {
      fprintf(stderr, "fdopen for tmp file failed: %s\n", strerror(errno));
      exit(1);
    }

#else
  char tmpname[TMP_NAMLEN];

  snprintf(tmpname, TMP_NAMLEN, TMP_NAME, g_ntmps);
  g_ntmps++;

  tmpstream = fopen(tmpname, "w+");
  if (!tmpstream)
    {
      fprintf(stderr, "fopen for tmp file %s failed: %s\n",
              tmpname, strerror(errno));
      exit(1);
    }
#endif

  return tmpstream;
}

#ifndef USE_MKSTEMP
static void unlink_tmpfiles(void)
{
  char tmpname[TMP_NAMLEN];
  unsigned int i;

  for (i = 0; i < g_ntmps; i++)
    {
      snprintf(tmpname, TMP_NAMLEN, TMP_NAME, i);
      unlink(tmpname);
    }
}
#endif

static void append_tmpfile(FILE *dest, FILE *src)
{
  uint8_t iobuffer[1024];
  size_t nread;

  /* Rewind the source directory to be beginning.  We assume that the dest
   * is already at the end.
   */

  rewind(src);

  /* Then append the source to the destination */

  do
    {
      nread = fread(iobuffer, 1, 1024, src);
      if (nread > 0)
        {
          fwrite(iobuffer, 1, nread, dest);
        }
    }
  while (nread > 0);

  /* We can now close the src temporary file */

  fclose(src);
}

static void dump_hexbuffer(FILE *stream, const void *buffer,
                           unsigned int nbytes)
{
  uint8_t *ptr = (uint8_t *)buffer;

  while (nbytes > 0)
    {
      if (g_nhex == 0)
        {
          fprintf(stream, " ");
        }

      fprintf(stream, " 0x%02x,", *ptr++);

      if (++g_nhex >= HEX_PER_LINE)
        {
          fprintf(stream, "\n");
          g_nhex = 0;
        }

      nbytes--;
    }
}

static void dump_nextline(FILE *stream)
{
  if (g_nhex > 0)
    {
      fprintf(stream, "\n");
      g_nhex = 0;
    }
}

static size_t lzf_compress(const uint8_t *inbuffer, unsigned int inlen,
                           union lzf_result_u *result)
{
  const uint8_t *inptr  = inbuffer;
        uint8_t *outptr = result->compressed.lzf_buffer;
  const uint8_t *inend  = inptr + inlen;
        uint8_t *outend = outptr + LZF_BUFSIZE;
  const uint8_t *ref;
  uintptr_t off;
  ssize_t cs;
  ssize_t retlen;
  unsigned int hval;
  int lit;

  if (inlen == 0)
    {
      cs = 0;
      goto genhdr;
    }

  memset(g_lzf_hashtab, 0, sizeof(g_lzf_hashtab));
  lit = 0; /* Start run */
  outptr++;

  hval = LZF_FRST(inptr);
  while (inptr < inend - 2)
    {
      uint8_t **hslot;

      hval   = LZF_NEXT(hval, inptr);
      hslot  = &g_lzf_hashtab[LZF_NDX(hval)];
      ref    = *hslot;
      *hslot = (uint8_t *)inptr;

      if (ref < inptr && /* the next test will actually take care of this, but this is faster */
          (off = inptr - ref - 1) < LZF_MAX_OFF &&
          ref > (uint8_t *)inbuffer &&
          ref[2] == inptr[2] &&
          ((ref[1] << 8) | ref[0]) == ((inptr[1] << 8) | inptr[0]))
        {
          /* Match found at *ref++ */

          unsigned int len = 2;
          unsigned int maxlen = inend - inptr - len;
          maxlen = maxlen > LZF_MAX_REF ? LZF_MAX_REF : maxlen;

          /* First a faster conservative test */

          if ((outptr + 3 + 1) >= outend)
            {
              /* Second the exact but rare test */

              if (outptr - !lit + 3 + 1 >= outend)
                {
                  cs = 0;
                  goto genhdr;
                }
            }

          outptr[(-lit) - 1] = lit - 1; /* Stop run */
          outptr -= !lit;               /* Undo run if length is zero */

          for (; ; )
            {
              if (maxlen > 16)
                {
                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }

                  len++;
                  if (ref[len] != inptr[len])
                    {
                      break;
                    }
                }

              do
                {
                  len++;
                }
              while (len < maxlen && ref[len] == inptr[len]);

              break;
            }

          len -= 2; /* len is now #octets - 1 */
          inptr++;

          if (len < 7)
            {
              *outptr++ = (off >> 8) + (len << 5);
            }
          else
            {
              *outptr++ = (off >> 8) + (7 << 5);
              *outptr++ = len - 7;
            }

          *outptr++ = off;

          lit = 0; outptr++; /* start run */

          inptr += len + 1;

          if (inptr >= inend - 2)
            {
              break;
            }

          inptr -= len + 1;

          do
            {
              hval = LZF_NEXT(hval, inptr);
              g_lzf_hashtab[LZF_NDX(hval)] = (uint8_t *)inptr;
              inptr++;
            }
          while (len--);
        }
      else
        {
          /* One more literal byte we must copy */

          if (outptr >= outend)
            {
              cs = 0;
              goto genhdr;
            }

          lit++;
          *outptr++ = *inptr++;

          if (lit == LZF_MAX_LIT)
            {
              outptr[(-lit) - 1] = lit - 1; /* Stop run */
              lit = 0;                      /* Start run */
              outptr++;
            }
        }
    }

  /* At most 3 bytes can be missing here */

  if (outptr + 3 > outend)
    {
      cs = 0;
      goto genhdr;
    }

  while (inptr < inend)
    {
      lit++; *outptr++ = *inptr++;

      if (lit == LZF_MAX_LIT)
        {
          outptr[(-lit) - 1] = lit - 1; /* Stop run */
          lit = 0;                      /* Start run */
          outptr++;
        }
    }

  outptr[(-lit) - 1] = lit - 1; /* End run */
  outptr -= !lit;               /* Undo run if length is zero */

  cs = outptr - (uint8_t *)result->compressed.lzf_buffer;

genhdr:
  if (cs > 0)
    {
      /* Write compressed header */

      result->compressed.lzf_magic[0]   = 'Z';
      result->compressed.lzf_magic[1]   = 'V';
      result->compressed.lzf_type       = LZF_TYPE1_HDR;
      result->compressed.lzf_clen[0]    = cs >> 8;
      result->compressed.lzf_clen[1]    = cs & 0xff;
      result->compressed.lzf_ulen[0]    = inlen >> 8;
      result->compressed.lzf_ulen[1]    = inlen & 0xff;
      retlen                            = cs + LZF_TYPE1_HDR_SIZE;
    }
  else
    {
      /* Write uncompressed header */

      result->uncompressed.lzf_magic[0] = 'Z';
      result->uncompressed.lzf_magic[1] = 'V';
      result->uncompressed.lzf_type     = LZF_TYPE0_HDR;
      result->uncompressed.lzf_len[0]   = inlen >> 8;
      result->uncompressed.lzf_len[1]   = inlen & 0xff;

      /* Copy uncompressed data into the result buffer */

      memcpy(result->uncompressed.lzf_buffer, inbuffer, inlen);
      retlen                            = inlen + LZF_TYPE0_HDR_SIZE;
    }

  return retlen;
}

static uint16_t get_mode(mode_t mode)
{
  uint16_t ret = 0;

  /* Convert mode to CROMFS NuttX read-only mode */

  if ((mode & S_IXOTH) != 0)
    {
      ret |= NUTTX_IXOTH;
    }

  if ((mode & S_IROTH) != 0)
    {
      ret |= NUTTX_IROTH;
    }

  if ((mode & S_IXGRP) != 0)
    {
      ret |= NUTTX_IXGRP;
    }

  if ((mode & S_IRGRP) != 0)
    {
      ret |= NUTTX_IRGRP;
    }

  if ((mode & S_IXUSR) != 0)
    {
      ret |= NUTTX_IXUSR;
    }

  if ((mode & S_IRUSR) != 0)
    {
      ret |= NUTTX_IRUSR;
    }

  return ret;
}

#ifdef HOST_TGTSWAP
static inline uint16_t tgt_uint16(uint16_t a)
{
  /* [15:8][7:0] -> [7:0][15:8] */

  return (a >> 8) | (a << 8);
}

static inline uint32_t tgt_uint32(uint32_t a)
{
  /* [31:24][23:16][15:8][7:0] -> [7:0][15:8][23:16][31:24] */

  return (a >> 24) | ((a >> 8) & 0x0000ff00) |
         ((a << 8) & 0x00ff0000) | (a << 24);
}
#endif

static void gen_dirlink(const char *name, uint32_t tgtoffs, bool dirempty)
{
  struct cromfs_node_s node;
  int namlen;

  namlen          = strlen(name) + 1;

  /* Generate the hardlink node */

  fprintf(g_tmpstream, "\n  /* Offset %6lu:  Hard link %s */\n\n",
          (unsigned long)g_offset, name);

  node.cn_mode    = TGT_UINT16(DIRLINK_MODEFLAGS);
  node.cn_pad     = 0;

  g_offset       += sizeof(struct cromfs_node_s);
  node.cn_name    = TGT_UINT32(g_offset);
  node.cn_size    = 0;

  g_offset       += namlen;
  node.cn_peer    = TGT_UINT32(dirempty ? 0 : g_offset);
  node.u.cn_link  = TGT_UINT32(tgtoffs);

  dump_hexbuffer(g_tmpstream, &node, sizeof(struct cromfs_node_s));
  dump_hexbuffer(g_tmpstream, name, namlen);
  dump_nextline(g_tmpstream);

  g_nnodes++;
}

static void gen_directory(const char *path, const char *name, mode_t mode,
                          bool lastentry)
{
  struct cromfs_node_s node;
  uint32_t save_offset        = g_offset;
  uint32_t save_diroffset     = g_diroffset;
  uint32_t save_parent_offset = g_parent_offset;
  FILE *save_tmpstream        = g_tmpstream;
  FILE *subtree_stream;
  int namlen;
  int result;

  namlen          = strlen(name) + 1;

  /* Open a new temporary file */

  subtree_stream  = open_tmpfile();
  g_tmpstream     = subtree_stream;

  /* Update the offset to account for the file node which we have not yet
   * written (we can't, we don't have enough information yet)
   */

  g_offset       += sizeof(struct cromfs_node_s) + namlen;

  /* Update offsets for the subdirectory */

  g_parent_offset = g_diroffset;  /* New offset for '..' */
  g_diroffset     = g_offset;     /* New offset for '.' */

  /* We are going to traverse the new directory twice; the first time just
   * see if the directory is empty.  The second time is the real thing.
   */

  result = traverse_directory(path, dir_notempty, NULL);

  /* Generate the '.' and '..' links for the directory (in the new temporary
   * file).
   */

  gen_dirlink(".", g_diroffset, false);
  gen_dirlink("..", g_parent_offset, result == 0);
  if (result != 0)
    {
      /* Then recurse to generate all of the nodes for the subtree */

      traverse_directory(path, process_direntry, NULL);
    }

  /* When traverse_directory() returns, all of the nodes in the sub-tree
   * under 'name' will have been written to the new tmpfile.  g_offset is
   * correct, but other settings are not.
   *
   * Restore the state.
   */

  g_tmpstream     = save_tmpstream;
  g_diroffset     = save_diroffset;
  g_parent_offset = save_parent_offset;

  /* Generate the directory node */

  fprintf(g_tmpstream, "\n  /* Offset %6lu:  Directory %s */\n\n",
          (unsigned long)save_offset, path);

  node.cn_mode    = TGT_UINT16(NUTTX_IFDIR | get_mode(mode));
  node.cn_pad     = 0;

  save_offset    += sizeof(struct cromfs_node_s);
  node.cn_name    = TGT_UINT32(save_offset);
  node.cn_size    = 0;

  save_offset    += namlen;
  node.cn_peer    = TGT_UINT32(lastentry ? 0 : g_offset);
  node.u.cn_child = TGT_UINT32(save_offset);

  dump_hexbuffer(g_tmpstream, &node, sizeof(struct cromfs_node_s));
  dump_hexbuffer(g_tmpstream, name, namlen);
  dump_nextline(g_tmpstream);

  g_nnodes++;

  /* Now append the sub-tree nodes in the new tmpfile to the previous
   * tmpfile
   */

  append_tmpfile(g_tmpstream, subtree_stream);
}

static void gen_file(const char *path, const char *name, mode_t mode,
                     bool lastentry)
{
  struct cromfs_node_s node;
  union lzf_result_u result;
  uint32_t nodeoffs = g_offset;
  FILE *save_tmpstream = g_tmpstream;
  FILE *outstream;
  FILE *instream;
  uint8_t iobuffer[LZF_BUFSIZE];
  size_t nread;
  size_t ntotal;
  size_t blklen;
  size_t blktotal;
  unsigned int blkno;
  int namlen;

  namlen      = strlen(name) + 1;

  /* Open a new temporary file */

  outstream   = open_tmpfile();
  g_tmpstream = outstream;
  g_offset    = nodeoffs + sizeof(struct cromfs_node_s) + namlen;

  /* Open the source data file */

  instream    = fopen(path, "r");
  if (!instream)
    {
      fprintf(stderr, "fopen for source file %s failed: %s\n",
              path, strerror(errno));
      exit(1);
    }

  /* Then read data from the file, compress it, and write it to the new
   * temporary file
   */

  blkno       = 0;
  ntotal      = 0;
  blktotal    = 0;

  do
    {
      /* Read the next chunk from the file */

      nread = fread(iobuffer, 1, LZF_BUFSIZE, instream);
      if (nread > 0)
        {
          uint16_t clen;

          /* Compress the chunk */

          blklen = lzf_compress(iobuffer, nread, &result);
          if (result.cmn.lzf_type == LZF_TYPE0_HDR)
            {
              clen = nread;
            }
          else
            {
              clen = (uint16_t)result.compressed.lzf_clen[0] << 8 |
                     (uint16_t)result.compressed.lzf_clen[1];
            }

          fprintf(g_tmpstream,
                  "\n  /* Offset %6lu:  "
                  "Block %u blklen=%lu Uncompressed=%lu Compressed=%u "
                  "*/\n\n",  (unsigned long)g_offset, blkno, (long)blklen,
                  (long)nread, clen);
          dump_hexbuffer(g_tmpstream, &result, blklen);
          dump_nextline(g_tmpstream);

          ntotal   += nread;
          blktotal += blklen;
          g_offset += blklen;

          g_nblocks++;
          blkno++;
        }
    }
  while (nread > 0);

  /* Restore the old tmpfile context */

  g_tmpstream        = save_tmpstream;

  /* Now we have enough information to generate the file node */

  fprintf(g_tmpstream, "\n  /* Offset %6lu:  File %s:  "
          "Uncompressed=%lu Compressed=%lu */\n\n",
          (unsigned long)nodeoffs, path, (unsigned long)ntotal,
          (unsigned long)blktotal);

  node.cn_mode       = TGT_UINT16(NUTTX_IFREG | get_mode(mode));
  node.cn_pad        = 0;

  nodeoffs          += sizeof(struct cromfs_node_s);
  node.cn_name       = TGT_UINT32(nodeoffs);

  node.cn_size       = TGT_UINT32(ntotal);

  nodeoffs          += namlen;
  node.u.cn_blocks   = TGT_UINT32(nodeoffs);

  nodeoffs          += blktotal;
  node.cn_peer       = TGT_UINT32(lastentry ? 0 : nodeoffs);

  dump_hexbuffer(g_tmpstream, &node, sizeof(struct cromfs_node_s));
  dump_hexbuffer(g_tmpstream, name, namlen);
  dump_nextline(g_tmpstream);

  g_nnodes++;

  /* Now append the sub-tree nodes in the new tmpfile to the previous
   * tmpfiles
   */

  append_tmpfile(g_tmpstream, outstream);
}

static int dir_notempty(const char *dirpath, const char *name,
                        void *arg, bool lastentry)
{
  struct stat buf;
  char *path;
  int ret;

  ret = asprintf(&path, "%s/%s", dirpath, name);
  UNUSED(ret);

  /* stat() should not fail for any reason */

  ret = stat(path, &buf);
  if (ret < 0)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: stat(%s) failed: %s\n",
             path, strerror(errcode));
      exit(1);
    }

  /* The directory is not empty if it contains with a file or a directory
   * entry.  Anything else will be ignored and the directly may be
   * effectively empty.
   */

  free(path);
  return (S_ISREG(buf.st_mode) || S_ISDIR(buf.st_mode));
}

static int process_direntry(const char *dirpath, const char *name,
                            void *arg, bool lastentry)
{
  struct stat buf;
  char *path;
  int ret;

  ret = asprintf(&path, "%s/%s", dirpath, name);
  UNUSED(ret);

  ret = stat(path, &buf);
  if (ret < 0)
    {
      int errcode = errno;
      if (errcode == ENOENT)
        {
          fprintf(stderr, "ERROR: Directory entry %s does not exist\n",
                  path);
        }
      else
        {
          fprintf(stderr, "ERROR: stat(%s) failed: %s\n",
                 path, strerror(errcode));
        }

      show_usage();
    }

  /* Verify that the source is, indeed, a directory */

  else if (S_ISDIR(buf.st_mode))
    {
      gen_directory(path, name, buf.st_mode, lastentry);
    }
  else if (S_ISREG(buf.st_mode))
    {
      gen_file(path, name, buf.st_mode, lastentry);
    }
  else
    {
      fprintf(stderr, "Omitting entry %s\n", path);
    }

  free(path);
  return 0;
}

static int traverse_directory(const char *dirpath,
                              traversal_callback_t callback, void *arg)
{
  DIR *dirp;
  struct dirent *direntry;
  char name[NAME_MAX + 1];
  int ret = 0;

  /* Open the directory */

  dirp = opendir(dirpath);
  if (dirp == NULL)
    {
      fprintf(stderr, "ERROR: opendir(%s) failed: %s\n",
              dirpath, strerror(errno));
      show_usage();
    }

  /* Visit each entry in the directory */

  direntry = readdir(dirp);
  while (direntry != NULL)
    {
      /* Preserve the name from the directory entry.  The return value
       * from readdir() only persists until the next time that readdir()
       * is called (alternatively, use readdir_r).
       */

      strncpy(name, direntry->d_name, NAME_MAX + 1);

      /* Get the next entry in advance so that we can anticipate the end of
       * the directory.
       */

      direntry = readdir(dirp);

      /* Skip the '.' and '..' hard links */

      if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
        {
          /* Process the directory entry */

          ret = callback(dirpath, name, arg, direntry == NULL);
          if (ret != 0)
            {
              break;
            }
        }
    }

  closedir(dirp);
  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int main(int argc, char **argv, char **envp)
{
  struct cromfs_volume_s vol;
  char *ptr;
  int result;

  /* Verify arguments */

  ptr = strrchr(argv[0], '/');
  g_progname = ptr == NULL ? argv[0] : ptr + 1;

  if (argc != 3)
    {
      fprintf(stderr, "Unexpected number of arguments\n");
      show_usage();
    }

  g_dirname  = argv[1];
  g_outname  = argv[2];

  verify_directory();
  verify_outfile();

  g_outstream = fopen(g_outname, "w");
  if (!g_outstream)
    {
      fprintf(stderr, "open %s failed: %s\n", g_outname, strerror(errno));
      exit(1);
    }

  g_tmpstream = open_tmpfile();

  /* Set up the initial boilerplate at the beginning of each file */

  init_outfile();

  /* Set up some initial offsets */

  g_offset        = sizeof(struct cromfs_volume_s);  /* Current image offset */
  g_diroffset     = sizeof(struct cromfs_volume_s);  /* Offset for '.' */
  g_parent_offset = sizeof(struct cromfs_volume_s);  /* Offset for '..' */

  /* We are going to traverse the new directory twice; the first time just
   * see if the directory is empty.  The second time is the real thing.
   */

  result = traverse_directory(g_dirname, dir_notempty, NULL);

  /* Generate the '.' link for the root directory (it can't have a '..') */

  gen_dirlink(".", g_diroffset, result == 0);
  if (result != 0)
    {
      /* Then traverse each entry in the directory, generating node data for
       * each directory entry encountered.
       */

      traverse_directory(g_dirname, process_direntry, NULL);
    }

  /* Now append the volume header to output file */

  fprintf(g_outstream, "/* CROMFS image */\n\n");
  fprintf(g_outstream, "const uint8_t g_cromfs_image[] =\n");
  fprintf(g_outstream, "{\n");
  fprintf(g_outstream, "  /* Offset %6lu:  Volume header */\n\n", 0ul);

  vol.cv_magic    = TGT_UINT32(CROMFS_MAGIC);
  vol.cv_nnodes   = TGT_UINT16(g_nnodes);
  vol.cv_nblocks  = TGT_UINT16(g_nblocks);
  vol.cv_root     = TGT_UINT32(sizeof(struct cromfs_volume_s));
  vol.cv_fsize    = TGT_UINT32(g_offset);
  vol.cv_bsize    = TGT_UINT32(CROMFS_BLOCKSIZE);

  dump_hexbuffer(g_outstream, &vol, sizeof(struct cromfs_volume_s));
  dump_nextline(g_outstream);
  fprintf(g_outstream, "\n  /* Offset %6lu:  Root directory */\n",
          (unsigned long)sizeof(struct cromfs_volume_s));

  /* Finally append the nodes to the output file */

  append_tmpfile(g_outstream, g_tmpstream);
  fprintf(g_outstream, "};\n");

  fclose(g_outstream);
#ifndef USE_MKSTEMP
  unlink_tmpfiles();
#endif
  return 0;
}