#!/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 #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())