nuttx/tools/gencromfs.c

1340 lines
36 KiB
C
Raw Normal View History

/****************************************************************************
* tools/gencromfs.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* 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>
2018-12-26 21:34:02 +01:00
#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);
2023-04-12 14:32:35 +02:00
if (ret < 0)
{
fprintf(stderr, "ERROR: asprintf() failed\n");
exit(1);
}
/* 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);
2023-04-12 14:32:35 +02:00
if (ret < 0)
{
fprintf(stderr, "ERROR: asprintf() failed\n");
exit(1);
}
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 aligned_data(4) 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;
}