| /**************************************************************************** |
| * tools/gencromfs.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * 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> |
| #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); |
| 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); |
| 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; |
| } |