mirror of
https://github.com/linuxboot/heads.git
synced 2025-05-18 08:42:54 +00:00
174 lines
8.1 KiB
Python
Executable File
174 lines
8.1 KiB
Python
Executable File
#!/usr/bin/python3
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
# This code is based on MFSUtil by Youness Alaoui (see `doc/LICENSE.orig` for original copyright)
|
|
|
|
import sys, argparse, textwrap
|
|
from lib.mfs import MFS
|
|
from lib.cfg import CFG
|
|
import zipfile, posixpath
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='MFS and CFG file manipulation utility.',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
The default output is to stdout.
|
|
Either one of --mfs or --cfg must be specified to indicate on which \
|
|
type of file to work (MFS or CFG).
|
|
You can specify one of the mutually exclusive actions : \
|
|
--dump --zip, --extract, --add, --remove.
|
|
For the --extract, --add, --remove actions, if --mfs is specified, \
|
|
then --file-id is required, if --cfg is specified, then --file-path is required.
|
|
When adding a file to a CFG file, the --mode, --opt, --uid and --gid options can be added.
|
|
The --mode option needs to be a string in the form 'dAEIrwxrwxrwx' where \
|
|
unused bits can be either a space or a dash, like --mode ' rwx---rwx' for example.
|
|
The --opt option needs to be a string in the form '?!MF' where unused bits can be \
|
|
either a space or a dash.
|
|
When adding a directory, both the file path needs to end with a '/' character and the --mode needs to start with 'd'.
|
|
""")
|
|
|
|
parser.add_argument("-o", "--output", dest="output", default='-',
|
|
help="Output file to write", metavar="FILE")
|
|
parser.add_argument("-i", "--file-id", dest="file_id", type=int,
|
|
help="ID of the file to manipulate in the MFS file", metavar="ID")
|
|
parser.add_argument("-f", "--file-path", dest="file_path",
|
|
help="Path of the file to manipulate in the CFG file", metavar="PATH")
|
|
parser.add_argument("--mode", dest="mode", default="---rwxrwxrwx",
|
|
help="Mode for file being added to CFG", metavar="MODE")
|
|
parser.add_argument("--opt", dest="opt", default="----",
|
|
help="Deplyoment option for file being added to CFG", metavar="OPT")
|
|
parser.add_argument("--uid", dest="uid", default=0, type=int,
|
|
help="User ID for file being added to CFG", metavar="UID")
|
|
parser.add_argument("--gid", dest="gid", default=0, type=int,
|
|
help="Group ID for file being added to CFG", metavar="GID")
|
|
parser.add_argument("--recursive", dest="recursive", action="store_true",
|
|
help="Recursive deletion for a file path in CFG")
|
|
parser.add_argument("--alignment", dest="alignment", type=int, default=0,
|
|
help="Alignment type for CFG files. (default: 0).\n"
|
|
"0 : packed.\n"
|
|
"1 : align all files on chunk start.\n"
|
|
"2 : align end of files on end of chunk.")
|
|
parser.add_argument("--deoptimize", dest="optimize", action="store_false",
|
|
help="De-optimize chain sequences when adding a file to MFS.")
|
|
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
group.add_argument("-m", "--mfs", dest="mfs", type=argparse.FileType('rb'),
|
|
help="MFS file to read from", metavar="FILE")
|
|
group.add_argument("-c", "--cfg", dest="cfg", type=argparse.FileType('rb'),
|
|
help="CFG file to read from", metavar="FILE")
|
|
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
group.add_argument("-d", "--dump", dest='dump', action="store_true",
|
|
help="Dump information about the MFS file, or the CFG file")
|
|
group.add_argument("-z", "--zip", dest='zip', action="store_true",
|
|
help="Store the MFS contents to a ZIP file")
|
|
group.add_argument("-x", "--extract", dest='extract', action="store_true",
|
|
help="Extract a file from the MFS file, or a file from the CFG file")
|
|
group.add_argument("-a", "--add", dest='add', type=argparse.FileType('rb'),
|
|
help="Add a file to the MFS file or a file to the CFG file",
|
|
metavar="FILENAME")
|
|
group.add_argument("-r", "--remove", dest='remove', action="store_true",
|
|
help="Remove a file from the MFS file, or a file from the CFG file")
|
|
args = parser.parse_args()
|
|
|
|
if (args.add or args.remove or args.extract) and (args.cfg and args.file_path is None):
|
|
parser.error("--add/--remove/--extract on a --cfg file requires the --file-path option")
|
|
if (args.add or args.remove or args.extract) and (args.mfs and args.file_id is None):
|
|
parser.error("--add/--remove/--extract on a --mfs file requires the --file-id option")
|
|
|
|
if args.mfs is not None:
|
|
data = args.mfs.read()
|
|
mfs = MFS(data)
|
|
|
|
if args.dump:
|
|
with argparse.FileType("wb")(args.output) as f: f.write("%s" % mfs)
|
|
elif args.extract:
|
|
file = mfs.getSystemVolume().getFile(args.file_id)
|
|
if file:
|
|
with argparse.FileType("wb")(args.output) as f: f.write(file.data)
|
|
else:
|
|
print("File ID %d does not exist in the MFS System Volume" % args.file_id)
|
|
sys.exit(-1)
|
|
elif args.remove:
|
|
mfs.getSystemVolume().removeFile(args.file_id)
|
|
mfs.generate()
|
|
with argparse.FileType("wb")(args.output) as f: f.write(mfs.data)
|
|
elif args.add:
|
|
file = mfs.getSystemVolume().getFile(args.file_id)
|
|
if file:
|
|
print("File ID %d already exists in the MFS System Volume" % args.file_id)
|
|
sys.exit(-1)
|
|
data = args.add.read()
|
|
mfs.getSystemVolume().addFile(args.file_id, data, args.optimize)
|
|
mfs.generate()
|
|
with argparse.FileType("wb")(args.output) as f: f.write(mfs.data)
|
|
elif args.zip:
|
|
z = zipfile.ZipFile(args.output, "w", zipfile.ZIP_STORED)
|
|
for id in xrange(mfs.getSystemVolume().numFiles):
|
|
file = mfs.getSystemVolume().getFile(id)
|
|
if file:
|
|
zi = zipfile.ZipInfo("file_%d.bin" % id)
|
|
zi.external_attr = (0o644 << 16)
|
|
z.writestr(zi, file.data)
|
|
z.close()
|
|
else:
|
|
data = args.cfg.read()
|
|
cfg = CFG(data)
|
|
if args.dump:
|
|
with argparse.FileType("wb")(args.output) as f: f.write("%s" % cfg)
|
|
cfg.generate(args.alignment)
|
|
#with argparse.FileType("wb")(args.output) as f: f.write(cfg.data)
|
|
assert cfg.data == data
|
|
elif args.zip:
|
|
z = zipfile.ZipFile(args.output, "w", zipfile.ZIP_STORED)
|
|
for file in cfg.files:
|
|
path = file.path
|
|
if file.isDirectory():
|
|
path += posixpath.sep
|
|
attr = (0o40755 << 16) | 0x30
|
|
else:
|
|
attr = (0o644 << 16)
|
|
zi = zipfile.ZipInfo(path)
|
|
zi.external_attr = attr
|
|
z.writestr(zi, file.data)
|
|
z.close()
|
|
elif args.extract:
|
|
file = cfg.getFile(args.file_path)
|
|
if file is None:
|
|
print("File path '%s' does not exist in the CFG file" % args.file_path)
|
|
sys.exit(-1)
|
|
with argparse.FileType("wb")(args.output) as f: f.write(file.data)
|
|
elif args.remove:
|
|
res = cfg.removeFile(args.file_path, args.recursive)
|
|
if not res:
|
|
if cfg.getFile(args.file_path) is None:
|
|
print("File path '%s' does not exist in the CFG file" % args.file_path)
|
|
else:
|
|
print("File path '%s' is a non-empty directory in the CFG file (use --recursive)" % args.file_path)
|
|
sys.exit(-1)
|
|
cfg.generate(args.alignment)
|
|
with argparse.FileType("wb")(args.output) as f: f.write(cfg.data)
|
|
elif args.add:
|
|
file = cfg.getFile(args.file_path)
|
|
if file:
|
|
print("File path '%s' already exists in the CFG file" % args.file_path)
|
|
sys.exit(-1)
|
|
data = args.add.read()
|
|
mode = CFG.strToMode(args.mode)
|
|
opt = CFG.strToOpt(args.opt)
|
|
if args.file_path[-1] == '/':
|
|
assert mode & 0x1000 == 0x1000
|
|
else:
|
|
assert mode & 0x1000 == 0
|
|
|
|
if not cfg.addFile(args.file_path, data, mode, opt, args.uid, args.gid):
|
|
print("Error adding file to path '%s' in the CFG file " \
|
|
"(parent doesn't exist or is not a directory?)" % args.file_path)
|
|
sys.exit(-1)
|
|
cfg.generate(args.alignment)
|
|
with argparse.FileType("wb")(args.output) as f: f.write(cfg.data)
|
|
|
|
|
|
|
|
if __name__=="__main__":
|
|
main()
|