hxtools/sdevel/bin2c.c

645 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2004-2008,2013-2015 Jan Engelhardt
/*
* bin2c - convert arbitrary files into C variable definitions
*/
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <libHX/ctype_helper.h>
#include <libHX/defs.h>
#include <libHX/option.h>
#include <libHX/string.h>
#if CHAR_BIT > 8
# error Sorry, we do not support CHAR_BIT > 8 yet.
#endif
/**
* @cfp: file handle for output of the definition
* (this can be the same as @hfile!)
* @hfp: file handle for output of the declaration
* @vname: name for the variable inside the outputted program
* @ifp: input file handle
* @ifile: input file name (as on the command line)
* @ifile_path: real file path (after possibly prepending @btc_prefix_directory)
* @isize: size of the input file in bytes
*/
struct btc_state {
FILE *cfp, *hfp;
hxmc_t *vname;
FILE *ifp;
const char *ifile, *ifile_path, *guard_name;
size_t isize;
};
struct btc_operations {
void (*global_header)(struct btc_state *);
void (*global_footer)(struct btc_state *);
void (*file_predecl)(struct btc_state *);
void (*file_content)(struct btc_state *);
void (*func_header)(struct btc_state *);
};
static hxmc_t *btc_cfile, *btc_hfile;
static hxmc_t *btc_guard_name;
static char *btc_prefix_directory;
static unsigned int btc_verbose, btc_emit_wxbitmap, btc_emit_ultra;
static const struct btc_operations *btc_ops;
static int btc_strip = -1;
static char *btc_tblquote(const void *vsrc, size_t input_size)
{
const unsigned char *src = vsrc;
size_t quoted_size = input_size * 5;
char *out, *p;
p = out = malloc(quoted_size + 1);
if (out == NULL)
abort();
for (; input_size-- > 0; ++src) {
if (*src == 0) {
*p++ = '0';
} else if (*src <= 07) {
*p++ = '0';
*p++ = '0' + (*src & 0007);
} else if (HX_isprint(*src) && *src != '\'' && *src != '\\') {
*p++ = '\'';
*p++ = *src;
*p++ = '\'';
} else if (*src <= 077) {
*p++ = '0';
*p++ = '0' + ((*src & 0070) >> 3);
*p++ = '0' + (*src & 0007);
} else {
*p++ = '0';
*p++ = '0' + ((*src & 0700) >> 6);
*p++ = '0' + ((*src & 0070) >> 3);
*p++ = '0' + (*src & 0007);
}
*p++ = ',';
}
*p = '\0';
return out;
}
/*
* character/sequence chance expansion
* 00,22,5C 3/256 2 (\x)
* 01..07,digit 7/256*10/256 4 (\000 .. \007)
* 01..07,nondigit 7/256*246/256 2 (\0 .. \7)
* 08..1F,digit 24/256*10/256 4 (\010 .. \031)
* 08..1F,nondigit 24/256*246/256 3 (\10 .. \31)
* 20..21,23..3E,40..5B,5D..7E 92/256 1
* 3F:3F 1/256*1/256 3 (\077)
* 3F:!3F 1/256*255/256 1 (?)
* 7F..FF 129/256 4 (\177 .. \377)
* average bytes per byte around 2.74411 (the 3F cases are hard to determine)
*/
static char *btc_memquote(const void *vsrc, size_t input_size)
{
size_t quoted_size = input_size * 4 + 1;
char *out, *p;
const unsigned char *src = vsrc;
bool qthis, qnext;
p = out = malloc(quoted_size + 1);
qnext = input_size > 1 && (src[0] == '\"' || src[0] == '\\' || !HX_isprint(src[0]));
for (; input_size-- > 0; ++src) {
qthis = qnext;
qnext = input_size > 0 && (!HX_isprint(src[1]) ||
src[1] == '\"' || src[1] == '\\' ||
(src[1] == '?' && src[0] == '?' && !qthis));
if (!qthis) {
*p++ = *src;
continue;
}
bool full = input_size == 0 || (!qnext && HX_isdigit(src[1]));
*p++ = '\\';
if (*src == '\"' || *src == '\\') {
*p++ = *src;
continue;
}
if (full || *src > 0070)
*p++ = '0' + ((*src & 0700) >> 6);
if (full || *src > 0007)
*p++ = '0' + ((*src & 0070) >> 3);
*p++ = '0' + (*src & 0007);
}
*p = '\0';
return out;
}
static char *btc_strquote(const char *src)
{
return btc_memquote(src, strlen(src));
}
static void btc_generic_global_header(struct btc_state *state)
{
fprintf(state->hfp, "/* Autogenerated by hxtools bin2c */\n");
if (state->guard_name)
fprintf(state->hfp, "#ifndef %s\n#define %s 1\n\n",
state->guard_name, state->guard_name);
fprintf(state->hfp, "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n");
}
static void btc_generic_global_footer(struct btc_state *state)
{
fprintf(state->hfp, "\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n");
if (state->guard_name)
fprintf(state->hfp, "\n\n#endif /* %s */\n", state->guard_name);
}
/**
* Output the binary stream in a "normal" fashion.
*/
static void btc_stdc_file_content(struct btc_state *state)
{
char input_buf[4096], *output_buf;
size_t input_len;
fprintf(state->cfp, "/* Autogenerated from %s */\n", state->ifile);
if (state->cfp != state->hfp) {
fprintf(state->hfp, "extern const unsigned char bin2c_%s[%" HX_SIZET_FMT "u];\n",
state->vname, state->isize);
fprintf(state->cfp, "const unsigned char bin2c_%s[%" HX_SIZET_FMT "u] = {",
state->vname, state->isize);
} else {
fprintf(state->cfp,
"static const unsigned char bin2c_%s[%" HX_SIZET_FMT "u] = {",
state->vname, state->isize);
}
while ((input_len = fread(input_buf, 1, sizeof(input_buf),
state->ifp)) > 0)
{
output_buf = btc_tblquote(input_buf, input_len);
if (output_buf == NULL)
abort();
fwrite(output_buf, strlen(output_buf), 1, state->cfp);
free(output_buf);
}
fprintf(state->cfp, "};\n");
}
static const struct btc_operations btc_stdc_ops = {
.global_header = btc_generic_global_header,
.global_footer = btc_generic_global_footer,
.file_content = btc_stdc_file_content,
};
/**
* Output the binary stream as a string literal.
* "Ultra mode".
*
* C++ is very picky about sizes. "ABC" is of type char[4] and must be
* assigned to a char[N] with N >= 4. In C, "ABC" could be assigned to
* char[N] with N >= 3 too.
* Yes, this means the array contains a trailing \0 byte.
* You can either live with it (JPEG and PNG seem to permit trailing garbage),
* or substract 1 when using sizeof/ARRAY_SIZE.
*/
static void btc_ultra_file_content(struct btc_state *state)
{
char input_buf[4096], *output_buf;
size_t input_len;
fprintf(state->cfp, "/* Autogenerated from %s */\n", state->ifile);
if (state->cfp != state->hfp) {
fprintf(state->hfp, "extern const unsigned char bin2c_%s[%" HX_SIZET_FMT "u];\n",
state->vname, state->isize + 1);
fprintf(state->cfp, "const unsigned char bin2c_%s[%" HX_SIZET_FMT "u] = \"",
state->vname, state->isize + 1);
} else {
fprintf(state->cfp,
"static const unsigned char bin2c_%s[%" HX_SIZET_FMT "u] = \"",
state->vname, state->isize + 1);
}
while ((input_len = fread(input_buf, 1, sizeof(input_buf),
state->ifp)) > 0)
{
output_buf = btc_memquote(input_buf, input_len);
if (output_buf == NULL)
abort();
fwrite(output_buf, strlen(output_buf), 1, state->cfp);
free(output_buf);
}
fprintf(state->cfp, "\";\n");
}
static const struct btc_operations btc_ultra_ops = {
.global_header = btc_generic_global_header,
.global_footer = btc_generic_global_footer,
.file_content = btc_ultra_file_content,
};
static void btc_wxbitmap_global_header(struct btc_state *state)
{
fprintf(state->hfp, "/* Autogenerated by hxtools bin2c */\n");
if (state->guard_name != NULL)
fprintf(state->hfp, "#ifndef %s\n#define %s 1\n\n",
state->guard_name, state->guard_name);
fprintf(state->hfp, "class wxBitmap;\n\n");
fprintf(state->hfp, "extern \"C\" void bin2c_init_%s(void);\n",
state->guard_name);
fprintf(state->cfp, "#include <wx/bitmap.h>\n"
"#include <wx/image.h>\n"
"#include <wx/mstream.h>\n");
}
static void btc_wxbitmap_global_footer(struct btc_state *state)
{
fprintf(state->cfp, /* { */ "}\n");
if (state->guard_name != NULL)
fprintf(state->hfp, "\n\n#endif /* %s */\n", state->guard_name);
}
static void btc_wxbitmap_file_predecl(struct btc_state *state)
{
fprintf(state->cfp, "wxBitmap *bin2c_%s;\n", state->vname);
fprintf(state->hfp, "extern wxBitmap *bin2c_%s;\n",
state->vname);
}
static void btc_wxbitmap_func_header(struct btc_state *state)
{
fprintf(state->cfp, "void bin2c_init_%s(void)\n{\n" /* } */,
state->guard_name);
}
static void btc_wxbitmap_file_content(struct btc_state *state)
{
char input_buf[4096], *output_buf;
size_t input_len;
fprintf(state->cfp, "\t{\n\t\twxMemoryInputStream sm(\"");
while ((input_len = fread(input_buf, 1, sizeof(input_buf),
state->ifp)) > 0)
{
output_buf = btc_memquote(input_buf, input_len);
if (output_buf == NULL)
abort();
fwrite(output_buf, strlen(output_buf), 1, state->cfp);
free(output_buf);
}
fprintf(state->cfp, "\", %" HX_SIZET_FMT "u);\n\t\tbin2c_%s = new wxBitmap(wxImage(sm, wxBITMAP_TYPE_ANY), -1);\n\t}\n",
state->isize, state->vname);
}
static const struct btc_operations btc_wxbitmap_ops = {
.global_header = btc_wxbitmap_global_header,
.global_footer = btc_wxbitmap_global_footer,
.file_predecl = btc_wxbitmap_file_predecl,
.file_content = btc_wxbitmap_file_content,
.func_header = btc_wxbitmap_func_header,
};
/**
* Construct a variable identifier from the given input filename.
* Just substitute all non-alphanumeric characters by underscore.
*/
static hxmc_t *btc_construct_vname(const char *cfile)
{
const char *eof;
hxmc_t *vname;
int strip = btc_strip;
char *p;
/* move @cfile forward as many paths as btc_strip specifies */
for (; strip > 0; --strip) {
while (*cfile != '/' && *cfile != '\0')
++cfile;
while (*cfile == '/')
++cfile;
}
/* move @eof backward as many paths as btc_strip specifies */
eof = cfile + strlen(cfile) - 1;
for (; strip < 0; ++strip) {
while (eof >= cfile && *eof == '/')
--eof;
while (eof >= cfile && *eof != '/')
--eof;
cfile = eof + 1;
}
vname = HXmc_strinit(cfile);
if (vname == NULL)
return NULL;
if (!HX_isalpha(*cfile) && *cfile != '_') {
/*
* The identifier must begin with [a-z_]; in any case, it
* cannot begin with a digit or any special character.
*/
if (HXmc_strpcat(&vname, "_") == NULL)
/* can happen but unlikely: just abort w/o errormsg */
abort();
p = vname + 1;
} else {
p = vname;
}
for (; *cfile != '\0'; ++cfile)
*p++ = HX_isalnum(*cfile) ? *cfile : '_';
return vname;
}
/**
* Process a single input file.
*/
static int btc_process_single(struct btc_state *state)
{
struct stat sb;
if (stat(state->ifile_path, &sb) != 0) {
fprintf(stderr, "ERROR: Cannot stat %s: %s\n",
state->ifile, strerror(errno));
return -errno;
}
state->ifp = fopen(state->ifile_path, "r");
if (state->ifp == NULL) {
fprintf(stderr, "bin2c: ERROR: Could not open %s for "
"reading: %s\n", state->ifile, strerror(errno));
return -errno;
}
state->isize = sb.st_size;
state->vname = btc_construct_vname(state->ifile);
btc_ops->file_content(state);
HXmc_free(state->vname);
fclose(state->ifp);
return 0;
}
static int btc_process_single_pf(struct btc_state *state)
{
if (btc_prefix_directory == NULL) {
state->ifile_path = state->ifile;
return btc_process_single(state);
}
hxmc_t *newfile = HXmc_strinit(btc_prefix_directory);
int ret = 0;
if (newfile == NULL || HXmc_strcat(&newfile, "/") == NULL ||
HXmc_strcat(&newfile, state->ifile) == NULL) {
ret = -errno;
} else {
state->ifile_path = newfile;
ret = btc_process_single(state);
}
HXmc_free(newfile);
return ret;
}
/**
* Process all files given in @argv.
*/
static int btc_start(const char **argv)
{
struct btc_state state;
const char **arg;
char *result;
int ret = 0;
state.hfp = fopen(btc_hfile, "w");
if (state.hfp == NULL) {
fprintf(stderr, "bin2c: ERROR: Could nt open %s for writing: "
"%s\n", btc_hfile, strerror(errno));
return -errno;
}
if (btc_cfile != NULL) {
state.cfp = fopen(btc_cfile, "w");
if (state.cfp == NULL) {
fprintf(stderr, "bin2c: ERROR: Could not open %s for "
"writing: %s\n", btc_cfile, strerror(errno));
return -errno;
}
fprintf(state.cfp, "/* Autogenerated by hxtools bin2c */\n");
result = btc_strquote(btc_hfile);
fprintf(state.cfp, "#include \"%s\"\n", result);
free(result);
} else {
state.cfp = state.hfp;
}
state.guard_name = btc_guard_name;
btc_ops->global_header(&state);
if (btc_ops->file_predecl != NULL)
for (arg = argv + 1; *arg != NULL; ++arg) {
state.ifile = *arg;
state.vname = btc_construct_vname(state.ifile);
btc_ops->file_predecl(&state);
HXmc_free(state.vname);
}
if (btc_ops->func_header != NULL)
btc_ops->func_header(&state);
for (arg = argv + 1; *arg != NULL; ++arg) {
state.ifile = *arg;
ret = btc_process_single_pf(&state);
if (ret != 0)
break;
}
btc_ops->global_footer(&state);
fclose(state.hfp);
if (state.cfp != state.hfp)
fclose(state.cfp);
if (ret != EXIT_SUCCESS) {
if (btc_cfile != NULL)
unlink(btc_cfile);
if (btc_hfile != NULL)
unlink(btc_hfile);
}
return ret;
}
static const struct HXoption btc_option_table[] = {
{.sh = 'C', .type = HXTYPE_MCSTR, .ptr = &btc_cfile,
.help = "Filename for the output .c file", .htyp = "FILE"},
{.sh = 'D', .type = HXTYPE_STRING, .ptr = &btc_prefix_directory,
.help = "Prefix input filenames with this directory", .htyp = "DIR"},
{.sh = 'G', .type = HXTYPE_MCSTR, .ptr = &btc_guard_name,
.help = "Name for the header's include guard"},
{.sh = 'H', .type = HXTYPE_MCSTR, .ptr = &btc_hfile,
.help = "Filename for the output .h file", .htyp = "FILE"},
{.sh = 'p', .type = HXTYPE_INT, .ptr = &btc_strip,
.help = "Strip N path components (keep -N if N is negative)", .htyp = "N"},
{.sh = 'v', .type = HXTYPE_NONE, .ptr = &btc_verbose,
.help = "Be verbose during operation"},
{.ln = "wxbitmap", .type = HXTYPE_NONE, .ptr = &btc_emit_wxbitmap,
.help = "Generate wxBitmap variables rather than plain data"},
{.ln = "ultra", .type = HXTYPE_NONE, .ptr = &btc_emit_ultra,
.help = "Generate variables using +1-sized(!) string literals"},
HXOPT_AUTOHELP,
HXOPT_TABLEEND,
};
/**
* Detect any C/non-C file naming and either return the corresponding
* header file suffix, or report the suffix problem.
*/
static const char *btc_known_c_suffix(const char *s)
{
bool modern_cp, archaic_cp;
if (strcmp(s, ".c") == 0)
return ".h";
modern_cp = strcmp(s, ".cpp") == 0 || strcmp(s, ".cxx") == 0;
archaic_cp = strcmp(s, ".cc") == 0 || strcmp(s, ".C") == 0 ||
strcmp(s, ".cp") == 0 || strcmp(s, ".CPP") == 0;
if (modern_cp || archaic_cp)
fprintf(stderr, "bin2c: WARNING: bin2c outputs C code, not "
"C++ -- It is wrong to call the output file %s!\n", s);
else
fprintf(stderr, "bin2c: WARNING: The suffix %s is "
"unknown!\n", s);
return NULL;
}
/**
* Detect any C++/non-C++ file naming and either return the corresponding
* header file suffix, or report the suffix problem.
*/
static const char *btc_known_cpp_suffix(const char *s)
{
if (strcmp(s, ".c") == 0)
fprintf(stderr, "bin2c: WARNING: bin2c is set to outputs "
"C++/wxWidgets code, not C -- It is wrong to call the "
"output file %s!\n", s);
if (strcmp(s, ".cpp") == 0)
return ".hpp";
else if (strcmp(s, ".cxx") == 0)
return ".hxx";
else
fprintf(stderr, "bin2c: WARNING: The suffix %s is "
"unknown!\n", s);
/* The other extensions are a bunch of whack */
return NULL;
}
/**
* Construct a filename from the given .c-ish filename.
* Warn about C++ naming, because we are really generating a C program,
* not a C++ program.
*/
static hxmc_t *btc_construct_hname(const char *cfile)
{
hxmc_t *hfile = HXmc_strinit(cfile);
const char *suffix, *repl;
if (hfile == NULL)
return NULL;
suffix = strrchr(hfile, '.');
if (suffix == NULL) {
if (HXmc_strcat(&hfile, ".h") == NULL)
goto out;
return hfile;
}
if (btc_emit_wxbitmap)
repl = btc_known_cpp_suffix(suffix);
else
repl = btc_known_c_suffix(suffix);
if (repl == NULL) {
if (HXmc_strcat(&hfile, ".h") == NULL)
goto out;
return hfile;
}
/* We have a replacement… */
if (HXmc_trunc(&hfile, suffix - hfile) == NULL)
abort(); /* never to happen */
if (HXmc_strcat(&hfile, repl) == NULL)
goto out;
return hfile;
out:
HXmc_free(hfile);
return NULL;
}
/**
* Construct an identifer name for the guarding #define from the filename.
* Transformation: a-z -> A-Z, 0-9 -> (keep), * -> underscore.
*/
static hxmc_t *btc_construct_guard(const char *s)
{
char *p, *guard = HXmc_strinit(s);
if (guard == NULL)
return NULL;
if (!HX_isalpha(*s) && *s != '_') {
/*
* The identifier must begin with [a-z_]; in any case, it
* cannot begin with a digit or any special character.
*/
if (HXmc_strpcat(&guard, "_") == NULL)
/* can happen but unlikely: just abort w/o errormsg */
abort();
p = guard + 1;
} else {
p = guard;
}
for (; *s != '\0'; ++s)
*p++ = HX_isalnum(*s) ? HX_toupper(*s) : '_';
return guard;
}
/**
* Parse options and fill in any defaults for the variables.
*/
static bool btc_get_options(int *argc, const char ***argv)
{
int ret;
ret = HX_getopt(btc_option_table, argc, argv, HXOPT_USAGEONERR);
if (ret != HXOPT_ERR_SUCCESS)
return false;
if (btc_cfile != NULL && btc_hfile == NULL)
btc_hfile = btc_construct_hname(btc_cfile);
if (btc_hfile == NULL) {
fprintf(stderr, "bin2c: you need to specify -C or -H, or both\n");
return false;
}
if (btc_cfile != NULL && btc_guard_name == NULL)
btc_guard_name = btc_construct_guard(btc_hfile);
if (btc_verbose) {
printf("C program file: %s\n",
(btc_cfile != NULL) ? btc_cfile : "");
printf("C header file: %s\n", btc_hfile);
printf("Header guard name: %s\n",
(btc_guard_name != NULL) ? btc_guard_name : "");
}
btc_ops = btc_emit_wxbitmap ? &btc_wxbitmap_ops :
btc_emit_ultra ? &btc_ultra_ops : &btc_stdc_ops;
return true;
}
int main(int argc, const char **argv)
{
int ret;
if (!btc_get_options(&argc, &argv))
return EXIT_FAILURE;
ret = btc_start(argv);
HXmc_free(btc_cfile);
HXmc_free(btc_hfile);
HXmc_free(btc_guard_name);
return (ret == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}