text: /****************************************************************************
   * 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;
  }
  