Files
RTLPlayground/tools/fileadder.c
T
d00f 5ffeb3d16a build: make the static-file build repeatable
Two things kept the same source from producing the same image.

fileadder walked the input directory with readdir(), which returns entries
in whatever order the filesystem hands them over. The same tree therefore
embedded the files in a different order on a different filesystem, and the
image differed byte for byte. scandir() with alphasort() fixes the order.

version.h was listed in .PHONY, which makes it permanently out of date and
propagates that to everything including it, so a plain build recompiled the
four largest translation units even when nothing had changed. Dropping it
from .PHONY alone would go too far the other way: with no prerequisites the
recipe would never run again and the version string would freeze at its
first value. A FORCE prerequisite keeps the recipe running every time, and
the generated header is only moved into place when its content actually
differs, so the timestamp survives an unchanged rebuild.

Measured on this tree: with FORCE the version follows GIT_VERSION and an
unchanged rebuild leaves version.h untouched; without it the header keeps
the value from the first build.
2026-09-29 05:27:07 +02:00

452 lines
12 KiB
C

/*
* Adds data files into specified locations of an image, optionally creates
* an index in the form of a header file
*/
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <argp.h>
#include <stdbool.h>
#include <zlib.h>
#define OFFSET 2
// Use a 4MB buffer, the same as the flash rom size
#define BUFFER_SIZE 0x400000
char buffer[BUFFER_SIZE];
FILE *inptr, *dataptr, *ofile;
int outptr;
#define PATH_SIZE 20480
#define INDEX_SIZE 20480
#define DEF_SIZE 20480
char pathbuffer[PATH_SIZE];
char ibuf[INDEX_SIZE];
char dbuf[DEF_SIZE];
char fbuf[DEF_SIZE];
char xbuf[DEF_SIZE];
int defbuf_p;
int ibuf_p;
int fbuf_p;
int xbuf_p;
bool addsDir = false;
int callNum = 0;
struct arguments arguments;
const char *argp_program_version = "fileadder 0.1";
const char *argp_program_bug_address = "<git@logicog.de>";
static char doc[] = "Adds a file or a directory of files into an image";
static char args_doc[] = "addfile [options] INPUT_IMAGE";
static struct argp_option options[] = {
{ "size", 's', "SIZE", 0, "Resize image"},
{ "output", 'o', "FILE", 0, "Output image file name instead of overwriting input image"},
{ "data", 'd', "FILE", 0, "File or directory to add to image"},
{ "address", 'a', "SIZE", 0, "Address where data is placed, default is 0x1000000 if option is used, otherwise 0x1fd000"},
{ "prefix", 'p', "FILE", 0, "Prefix for header and index file generation"},
{ "bank", 'b', "BANKNAME", 0, "Generate #pragma with given bank-name"},
{ "gzip", 'z', 0, 0, "gzip-compress the added files"},
{ 0 }
};
struct arguments {
int size, address;
char *output_file;
char *data_file;
char *prefix;
char *bank;
bool overwrite;
bool add_zero;
bool gzip;
};
static error_t parse_opt(int key, char *arg, struct argp_state *state)
{
struct arguments *arguments = state->input;
switch (key) {
case 'a':
arguments->address = arg? atoi(arg): 0x100000; // Default address
break;
case 's':
arguments->size = arg? atoi(arg): 0x200000; // Default size is 2MB
break;
case 'o':
arguments->output_file = arg;
arguments->overwrite = false;
break;
case 'd':
arguments->data_file = arg;
break;
case 'p':
arguments->prefix = arg;
break;
case 'n':
arguments->add_zero = false;
break;
case 'b':
arguments->bank = arg? arg : "BANK1";
break;
case 'z':
arguments->gzip = true;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = {
options, parse_opt, args_doc, doc, 0, 0, 0
};
int addfile(const char *name, int addr)
{
int dataptr = open(name, 0);
if (!dataptr) {
fprintf(stderr, "%s: ", name);
perror("Cannot open file for reading");
return 0;
}
int data_read = read(dataptr, &buffer[addr], sizeof(buffer) - addr);
if (data_read < 0) {
perror("Error reading file");
return 0;
}
close(dataptr);
return data_read;
}
/*
* gzip-compresses the data at buffer[addr] in place (gzip format, as
* expected by the Content-Encoding: gzip header the httpd serves it
* with). Returns the compressed size.
*/
int gzipBuffer(int addr, int len)
{
uLong bound = compressBound(len);
unsigned char *out = malloc(bound);
if (!out) {
perror("malloc");
return 0;
}
z_stream zs;
memset(&zs, 0, sizeof(zs));
if (deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
Z_DEFAULT_STRATEGY) != Z_OK) {
fprintf(stderr, "gzip: deflateInit2 failed\n");
free(out);
return 0;
}
zs.next_in = (Bytef *)&buffer[addr];
zs.avail_in = len;
zs.next_out = out;
zs.avail_out = bound;
if (deflate(&zs, Z_FINISH) != Z_STREAM_END) {
fprintf(stderr, "gzip: deflate failed\n");
deflateEnd(&zs);
free(out);
return 0;
}
int out_len = zs.total_out;
deflateEnd(&zs);
memcpy(&buffer[addr], out, out_len);
free(out);
return out_len;
}
int hasSuffix(const char *str, const char *suffix)
{
if (!str || !suffix)
return 0;
size_t lenstr = strlen(str);
size_t lensuffix = strlen(suffix);
if (lensuffix > lenstr)
return 0;
return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
}
char *getMime(const char *name)
{
if (hasSuffix(name, ".html"))
return "mime_HTML";
else if (hasSuffix(name, ".svg"))
return "mime_SVG";
else if (hasSuffix(name, ".ico"))
return "mime_SVG";
else if (hasSuffix(name, ".png"))
return "mime_PNG";
else if (hasSuffix(name, ".js"))
return "mime_JS";
else if (hasSuffix(name, ".css"))
return "mime_CSS";
return "mime_TXT";
}
int addidx(const char *name, int addr, int len)
{
char s[256];
int i = 0;
while (name[i]) {
s[i] = name[i] == '.'? '_' : name[i];
i++;
}
s[i] = '\0';
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_START_%s 0x%x\n", s, addr);
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_SIZE_%s %d\n", s, len);
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/%s\", FDATA_START_%s, FDATA_SIZE_%s, %s, %d},\n", name, s, s, getMime(name), arguments.gzip? 1 : 0);
if (!strcmp(name, "index.html"))
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/\", FDATA_START_%s, FDATA_SIZE_%s, mime_HTML, %d},\n", s, s, arguments.gzip? 1 : 0);
return 0;
}
/*
* Replaces calls of the type #{function} in .html files being added
* It is assumed that the possibly expanded string will fit into the buffer
* Takes the length of the data and returns its new length
*/
int replaceCalls(int pos, int len)
{
int i = 0;
char function_buf[256];
while (i < len) {
if (buffer[pos + i] == '#' && i + 1 < len && buffer[pos + i + 1] == '{') {
int j = 2;
while (i + j < len) {
if (buffer[pos + i + j] == '}')
break;
function_buf[j - 2] = buffer[pos + i + j];
j++;
if (j >= 255) {
function_buf[255] = '\0';
fprintf(stderr, "Function not terminated %s ", function_buf);
exit(5);
}
}
function_buf[j - 2] = '\0';
if (i + j >= len)
return len;
// Because the buffers overlap we cannot use strcpy
memmove(&buffer[pos + i + 5], &buffer[pos + i + j], len - (i + j));
sprintf(&buffer[pos + i + 2], "%03d", callNum);
buffer[pos + i + 5] = '}';
len -= j - 5;
i += 6;
fbuf_p += snprintf(&fbuf[fbuf_p], DEF_SIZE - fbuf_p, " %s,\n", function_buf);
xbuf_p += snprintf(&xbuf[xbuf_p], DEF_SIZE - xbuf_p, "extern uint16_t %s(void);\n", function_buf);
callNum++;
}
i++;
}
return len;
}
int main(int argc, char **argv)
{
int arg_index;
char tmpfilename[] = "image_XXXXXX";
struct stat s;
arguments.add_zero = true;
arguments.address = 0x1fd000;
arguments.size = 0;
arguments.overwrite = true;
arguments.prefix = 0;
arguments.bank = NULL;
arguments.gzip = false;
argp_parse(&argp, argc, argv, 0, &arg_index, &arguments);
if (!arg_index)
argp_usage (0);
memset(buffer, 0, BUFFER_SIZE);
size_t filesize = 0;
if (argv[arg_index]) {
inptr = fopen(argv[arg_index], "rb");
if (inptr == NULL) {
printf("Cannot open input file %s\n", argv[arg_index]);
return 5;
}
fseek(inptr, 0L, SEEK_END);
filesize = ftell(inptr);
rewind(inptr);
printf("Input file size: %ld\n", filesize);
if (filesize > BUFFER_SIZE) {
printf("File too large.\n");
return 5;
}
size_t bytes_read = fread(buffer, 1, sizeof(buffer), inptr);
printf("Bytes read: %ld\n", bytes_read);
if (bytes_read != filesize) {
printf("Error reading input file.\n");
return 5;
}
fclose(inptr);
}
// Add preambles to index and header file
defbuf_p = ibuf_p = xbuf_p = fbuf_p = 0;
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "// This file is automatically generated, do not edit!\n\n");
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#ifndef FDATA_DEFS_H\n");
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_DEFS_H\n\n");
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#include <stdint.h>\n\n");
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p,
"typedef enum mime_type_e {\n mime_HTML = 0,\n mime_SVG,\n mime_ICO,\n mime_PNG,\n mime_JS,\n mime_CSS,\n mime_TXT\n} mime_type_t;\n\n");
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "struct f_data {\n __code const char *file;\n uint32_t start;\n uint16_t len;\n mime_type_t mime;\n uint8_t gzip;\n};\n\n");
// defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "typedef uint16_t (* fcall_ptr)(void);\n\n");
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, "// This file is automatically generated, do not edit!\n\n");
if (arguments.prefix)
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, "#include \"%s.h\"\n\n", arguments.prefix);
if (arguments.bank)
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, "#pragma codeseg %s\n#pragma constseg %s\n\n", arguments.bank, arguments.bank);
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " __code const char * __code const mime_strings[] = {\n \"text/html\",\n \"image/svg+xml\",\n"
" \"image/svg+xml\",\n \"image/png\",\n \"text/javascript\",\n \"text/css\",\n \"text/plain\"};\n\n");
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, "__code const struct f_data f_data[] = {\n");
// fbuf_p += snprintf(&fbuf[fbuf_p], DEF_SIZE - fbuf_p, "\n__code fcall_ptr f_calls[] = {\n");
// Now that the beginning of the buffer is filled with out image, optionally resize the image
if (filesize)
filesize = arguments.size? arguments.size : filesize;
if (stat(arguments.data_file, &s) == 0 && s.st_mode & S_IFDIR) {
printf("Adding entries in directory %s\n", arguments.data_file);
addsDir = true;
struct dirent **entries;
int n_entries = scandir(arguments.data_file, &entries, NULL, alphasort);
if (n_entries < 0) {
fprintf(stderr, "%s: ", arguments.data_file);
perror("Error opening directory");
return 5;
}
int addr = arguments.address;
for (int e = 0; e < n_entries; e++) {
struct dirent *in_file = entries[e];
snprintf(pathbuffer, PATH_SIZE, "%s/%s", arguments.data_file, in_file->d_name);
if (stat(pathbuffer, &s) < 0) {
fprintf(stderr, "%s: ", pathbuffer);
perror("Stat failed");
continue;
}
if (! (s.st_mode & S_IFREG))
continue;
size_t data_read = addfile(pathbuffer, addr);
size_t file_len = data_read;
if (data_read) {
if (arguments.add_zero) {
/* NUL-terminate the data so the file table and the
* strlen()-based size computation are deterministic
* (the buffer past the file is otherwise uninitialised) */
buffer[addr + data_read] = '\0';
data_read++;
}
printf("Data inserted from %s at 0x%x, size: %ld\n", pathbuffer, addr, data_read);
}
int old_len = data_read;
data_read = replaceCalls(addr, file_len);
if (old_len > data_read)
memset(buffer + addr + data_read, 0, old_len - data_read);
if (arguments.gzip)
data_read = gzipBuffer(addr, data_read);
addidx(in_file->d_name, addr, data_read);
addr += data_read;
}
for (int e = 0; e < n_entries; e++)
free(entries[e]);
free(entries);
} else {
size_t data_read = addfile(arguments.data_file, arguments.address);
if (data_read) {
if (arguments.add_zero) {
buffer[arguments.address + data_read + 1] = '\0';
data_read++;
}
printf("Data inserted at 0x%x, size: %ld\n", arguments.address, data_read);
}
}
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {0, 0, 0}\n};\n");
// fbuf_p += snprintf(&fbuf[fbuf_p], DEF_SIZE - fbuf_p, "};\n");
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#endif\n");
if (filesize) {
if (!arguments.overwrite)
outptr = creat(arguments.output_file, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
else
outptr = mkstemp(tmpfilename);
if (!outptr) {
printf("Cannot open %s\n", arguments.output_file);
return 5;
}
size_t written = write(outptr, buffer, filesize);
if (written != filesize) {
printf("Error writing output file.\n");
return 5;
}
close(outptr);
if (arguments.overwrite)
rename(tmpfilename, argv[arg_index]);
}
if (arguments.prefix) {
snprintf(pathbuffer, PATH_SIZE, "%s.%s", arguments.prefix, "h");
ofile = fopen(pathbuffer, "w");
if (!ofile) {
fprintf(stderr, "%s: ", pathbuffer);
perror("Open failed");
return 5;
}
fputs(dbuf, ofile);
fclose(ofile);
snprintf(pathbuffer, PATH_SIZE, "%s.%s", arguments.prefix, "c");
ofile = fopen(pathbuffer, "w");
if (!ofile) {
fprintf(stderr, "%s: ", pathbuffer);
perror("Open failed");
return 5;
}
fputs(ibuf, ofile);
fputs(xbuf, ofile);
fputs(fbuf, ofile);
fclose(ofile);
} else if (addsDir){
puts(dbuf);
puts(xbuf);
puts(ibuf);
puts(fbuf);
}
return 0;
}