tools/mkconstfs: Add an improved tool.

The new tool (mkconstfs2) features:

* more robust filename handling: no need for mangling,
  and works on Windows.
* Better output generation: nothing is written in case
  of failures.
* Allows more control over the files that are included:
 - does not traverse directories, filenames must be explicitly
   given.
 - The "root" can be explicitly given (thus the tool can get
   the same result independently of the CWD).

Thanks to MichelRottleuthner for making it work with Windows paths.
This commit is contained in:
Juan Carrano 2018-06-01 17:04:50 +02:00
parent c68ebca154
commit 6cfafc8923
2 changed files with 182 additions and 0 deletions

View File

@ -14,3 +14,8 @@ structures that can be mounted using constfs.
[...] [...]
vfs_mount((vfs_mount_t *)&_constfs); vfs_mount((vfs_mount_t *)&_constfs);
# mkconstfs2
This is an alternative tool that takes a list of files instead of a whole
directory.

177
dist/tools/mkconstfs/mkconstfs2.py vendored Executable file
View File

@ -0,0 +1,177 @@
#!/usr/bin/env python3
import os
import sys
import argparse
import pathlib
import posixpath
import io
import itertools
import mmap
import shutil
from binascii import hexlify
C_HEADER = """/* This file was automatically generated by mkconstfs2.
* !!!! DO NOT EDIT !!!!!
*/
#include <stdint.h>
#include "fs/constfs.h"
"""
FILE_TEMPLATE = """ {{
.path = "{target_name}",
.data = {buff_name},
.size = sizeof({buff_name})
}},
"""
C_FOOTER = """
static const constfs_t _fs_data = {{
.files = _files,
.nfiles = sizeof(_files) / sizeof(_files[0]),
}};
vfs_mount_t {constfs_name} = {{
.fs = &constfs_file_system,
.mount_point = "{mount_pount}",
.private_data = (void *)&_fs_data,
}};
"""
FILES_DECL = """
static const constfs_file_t _files[] = {
"""
BLOB_DECL = """
/** {fname} **/
static const uint8_t {varname}[] = {{
"""
def _relpath_p(path, start):
return posixpath.relpath(pathlib.Path(os.path.abspath(path)).as_posix(),
pathlib.Path(os.path.abspath(start)).as_posix())
def mkconstfs(files, root_path, mount_point, constfs_name):
"""Generate a C file containing a constant file system
Return
------
chunks: Iterator yielding fragments of the of the output file.
"""
filemap = {f: (_mkident(i), _relpath_p(f, root_path))
for i, f in enumerate(files)}
yield C_HEADER
yield from itertools.chain.from_iterable(
print_file_data(local_f, *f_data) for local_f, f_data in filemap.items())
yield FILES_DECL
yield from (FILE_TEMPLATE.format(target_name=_addroot(relp),
buff_name=ident)
for ident, relp in sorted(filemap.values()))
yield "};\n"
yield C_FOOTER.format(constfs_name=constfs_name, mount_pount=mount_point)
def _addroot(fname):
return "/" + fname if not fname.startswith("/") else fname
def _mkident(k):
return "_file{:02X}".format(k)
def print_file_data(local_fname, varname, target_fname=""):
"""Convert a file into a static C array:
Parameters
----------
local_fname: real Path (where the file is on this machine's fs)
target_fname: name that the file will have in the constfs.
output_file: File-like object where the array will be written.
Return
------
chunks: Iterator yielding fragments of the of the output text.
"""
yield BLOB_DECL.format(fname=target_fname, varname=varname)
def byte2s(b):
return "0x{},".format(hexlify(b).decode('utf-8'))
def chunk(iterable, blocksize):
"""Break a single iterable into chunks of maximum size 'blocksize'"""
return (x for _, x in itertools.groupby(enumerate(iterable),
lambda x: x[0]//blocksize))
with open(local_fname, 'rb') as f, mmap.mmap(f.fileno(), 0,
access=mmap.ACCESS_READ
) as bfile:
yield from map(lambda x: x[1],
itertools.chain.from_iterable(
map(lambda l: itertools.chain(l, [(0, "\n")]),
chunk(map(byte2s, bfile), 16)
)
)
)
yield "};\n"
def main():
parser = argparse.ArgumentParser(
description="Embed files into a constant file system")
parser.add_argument("-m", '--mount', metavar="mountpoint",
help="Where to mount the resulting fs", default="/")
parser.add_argument("-o", '--output', metavar="output_file",
help="Write the output to a file instead of stdout. "
"The file is only written if the command is successful "
"(i.e. there is no partial output")
parser.add_argument("-r", '--root', metavar="root_base_path",
type=pathlib.Path,
help="Paths on the constf will be generated for the real "
"path of the files by considering this path to be the root "
"By default the current directory (.) is used",
default=pathlib.Path())
parser.add_argument("name", help="Name for the vfs_mount_t structure")
parser.add_argument("files", nargs="+", type=pathlib.Path,
help="Files to be included.")
ns = parser.parse_args()
f_chunks = mkconstfs(ns.files, ns.root, ns.mount, ns.name)
if ns.output:
tmp_out = io.StringIO()
else:
tmp_out = sys.stdout
tmp_out.writelines(f_chunks)
if ns.output:
with open(ns.output, "w+") as f:
tmp_out.seek(0)
shutil.copyfileobj(tmp_out, f)
return 0
if __name__ == "__main__":
exit(main())