[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Patch] Added a JFFS2 pickle
From: |
Matt Ihlenfield |
Subject: |
Re: [Patch] Added a JFFS2 pickle |
Date: |
Thu, 04 Mar 2021 20:49:04 +0000 |
Hi Jose,
Thanks for reviewing this! A v2 of the patch is at the end.
------- Review comments: -------
> In other pickles we have used the convention of:
>
> 1. using "dump" instead of "print" in functions that are not used in
> struct pretty-printers.
>
> 2. To put the dump functions in a separated FOO-dump.pk pickle.
>
> What do you think?
My original intention was for jffs2_print_fs_tree to be a method (print_fs_tree)
on JFFS2_FS, but I didn't want to stuff all the JFFS2_Entry and
JFFS2_Entry_Table
stuff in to that struct, so I kept them separate. I think I'd prefer to keep
the function in jffs2.pk and rename it to jffs2_dump_fs_tree, which I think is
what 1. is suggesting?
> But I am not sure I understand the "variable endianness" part? What do
> you mean exactly?
I just meant that a JFFS2 file system could be little endian or big endian.
The original JFFS2 paper says that it's host-endian, but mkfs.jffs2 and sumtool
both let you specify an endianness when creating jffs2 file systems.
Ill remove the `as uint<16>` stuff from v2 of the patch though. I realized it
was
something that I had done early on when I was confused about how poke
handled endianness.
> In pickles distributed with poke we use lower-case identifiers for
> variables in general, and upper-case identifiers for variables holding
> constants.
>
> So the above would be:
>
> var jffs2_nodetypes = [
> JFFS2_NODETYPE_DIRENT,
> JFFS2_NODETYPE_INODE,
> JFFS2_NODETYPE_CLEANMARKER,
> JFFS2_NODETYPE_PADDING,
> JFFS2_NODETYPE_SUMMARY,
> JFFS2_NODETYPE_XATTR,
> JFFS2_NODETYPE_XREF
> ];
These arrays are just there to make my field constraints a little cleaner. A
user
probably shouldn't change them, so I was thinking of them as constants. But I
can
still make the change if you'd prefer it.
> In pickles distributed with poke we use the following style:
>
> type JFFS2_Dirent =
> struct
> {
> [...]
> };
Will fix
> You may want to use an anonymous field there, to make displaying values
> of this struct less verbose:
>
> uint<8>[2];
Will fix
> Hmm, why not just using this to define the`name' field: char[nsize] name;
> Then a) You won't need to define a method`get_name' and
> b) The struct type will be "agnostic" i.e. it will work as well mapped
> and not mapped.
Will fix
> Here the situation may be different, because the data described by the
> inode is not really part of the inode.
>
> But in that case, why having `data' defined in the inode at all?
I would say the data is part of the inode for jffs2, but the data is
potentially large,
so mapping all the data in all the inodes takes some time. I think I could make
the
JFFS2_Inode struct more independent by adding this field after the data field:
byte[0] data_end @ total_len;
I could do something similar with the JFFS2_Summary and JFFS2_XAttr structs too.
> Why not having two fields`name' and`data' in the struct definition?
>
> char[name_len] name;
> byte[0] data;
>
> If extended attributes don't always have names, you could make it an
> optional field.
Will do.
> Same comment than above regarding`name'. Why not having it defined directly
> as`char[nsize] name' ?
Will do.
> This is an interesting idiom you are introducing here. Never saw it
> before :)
My new idiom seems to be the result of not reading the manual well enough :)
> I'm thinking that we may be over-replicating the union logic here
> unnecessarily. Why not just turn JFFS2_Sum_Rec into an union:
This is much much nicer. Don't know why I didn't think of it... I'll make this
change and do the same thing to the inode node
> The method `get_node_type' may not be necessary at all, since the usual
> way in poke to check whether a given union value is of some alternative
> foo is to:
>
> try [.. do something with value.FOO ..]; catch if E_elem {...}
I decided to go with a get_node_type function because it makes checking for
node types
in jffs2_create_entry_tables and JFFS2_FS.get_nodes_by_type easier. A user could
still do the try...catch method if they wanted to.
> The size of JFFS2_Summary won't include the records... are the records
> conceptually part of the summary?
The records should be a part of the summary. This is a similar case to the
Inode node:
the records are a part of the summary node, but there could be alot of data in
the
records that would take some time to map. So I opted not to map the records by
default. To keep all the data within the summary node I would use the same
method as I did in the inode node: place a byte[0] data_end @ total_len
field after the data field.
>That loop can be simplified:
>
>for (node in nodes where node.get_node_type() == node_type)
> res_nodes += [node];
Will do.
Summary of changes to V1 of the patch:
* pickles/jffs2.pk:
* Changed "jffs2_print_fs_tree" to "jffs2_dump_fs_tree"
* Removed unneeded uint<16> casts from constants
* Changed the node_type constraints to initializers, and removed the
constraint from "unknown" nodes.
* Made "unused" struct fields anonymous
* Fixed suggested code style changes
* Added total_len constraint to JFFS2_Unk_Node
* Mapped full name in JFFS2_Dirent and JFFS2_Sum_Dirent
* Fixed some doc string typos
* Added "data_end" fields to structs with data that isn't mapped by
default
* Mapped the "name" field in the xattr struct
* Made JFFS2_Sum_Rec and JFFS2_Node unions
* Simplified loop in JFFS2_FS.get_nodes_by_type
------- End of review comments -------
2021-03-04 Matt Ihlenield <mtihlenfield@protonmail.com>
* pickles/jffs2.pk: New pickle that decodes JFFS2 filesystems
* pickles/Makefile.am: Add new pickle file
---
pickles/Makefile.am | 2 +-
pickles/jffs2.pk | 642 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 643 insertions(+), 1 deletion(-)
create mode 100644 pickles/jffs2.pk
diff --git a/pickles/Makefile.am b/pickles/Makefile.am
index 232646c5..bc00d3d9 100644
--- a/pickles/Makefile.am
+++ b/pickles/Makefile.am
@@ -3,4 +3,4 @@ dist_pickles_DATA = elf.pk ctf.pk leb128.pk bpf.pk btf.pk
btf-dump.pk bmp.pk \
color.pk rgb24.pk id3v1.pk \
dwarf.pk dwarf-common.pk dwarf-frame.pk dwarf-pubnames.pk \
dwarf-types.pk time.pk argp.pk pktest.pk mbr.pk ustar.pk \
- mcr.pk dwarf-expr.pk dwarf-info.pk id3v2.pk
+ mcr.pk dwarf-expr.pk dwarf-info.pk id3v2.pk jffs2.pk
diff --git a/pickles/jffs2.pk b/pickles/jffs2.pk
new file mode 100644
index 00000000..a4f0a5d4
--- /dev/null
+++ b/pickles/jffs2.pk
@@ -0,0 +1,642 @@
+/* JFFS2 Implementation for GNU poke */
+
+/* Copyright (C) 2021 Matthew T. Ihlenfield. */
+
+/* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Based on
https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/jffs2.h
+ * and
https://kernel.googlesource.com/pub/scm/linux/kernel/git/rw/mtd-utils/+/refs/heads/master/mkfs.jffs2.c
+ * and https://www.sourceware.org/jffs2/jffs2.pdf
+ *
+ * Note: This was tested with file systems created by mkfs.jffs2 and sumtool
+ */
+
+var JFFS2_MAGIC = 0x1985;
+var JFFS2_MAGIC_OLD = 0x1984;
+
+var JFFS2_NODE_ACCURATE = 0x2000;
+
+/*
+ * Part of the node_type that tells the OS what to do if it doesn't
+ * know what the node type is
+ */
+// INCOMPAT: Refuse to mount the file system
+var JFFS2_FEATURE_INCOMPAT = 0xc000;
+// ROCOMPAT: Ignore the node, but mount the FS read only
+var JFFS2_FEATURE_ROCOMPAT = 0x8000;
+// RWCOMPAT_COPY: Ignore the node, and move it somewhere else on garbage
collection
+var JFFS2_FEATURE_RWCOMPAT_COPY = 0x4000;
+// RWCOMPAT_DELETE: Ignore the node and delete it on next garbage collection
+var JFFS2_FEATURE_RWCOMPAT_DELETE = 0x0000;
+
+var JFFS2_NODETYPE_DIRENT = (JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 1);
+var JFFS2_NODETYPE_INODE = (JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 2);
+var JFFS2_NODETYPE_CLEANMARKER = (JFFS2_FEATURE_RWCOMPAT_DELETE |
JFFS2_NODE_ACCURATE | 3);
+var JFFS2_NODETYPE_PADDING = (JFFS2_FEATURE_RWCOMPAT_DELETE |
JFFS2_NODE_ACCURATE | 4) ;
+var JFFS2_NODETYPE_SUMMARY = (JFFS2_FEATURE_RWCOMPAT_DELETE |
JFFS2_NODE_ACCURATE | 6);
+var JFFS2_NODETYPE_XATTR = (JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 8);
+var JFFS2_NODETYPE_XREF = (JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 9);
+
+var JFFS2_NODETYPES = [
+ JFFS2_NODETYPE_DIRENT,
+ JFFS2_NODETYPE_INODE,
+ JFFS2_NODETYPE_CLEANMARKER,
+ JFFS2_NODETYPE_PADDING,
+ JFFS2_NODETYPE_SUMMARY,
+ JFFS2_NODETYPE_XATTR,
+ JFFS2_NODETYPE_XREF
+];
+
+// inode compression types
+var JFFS2_COMPR_NONE = 0x00;
+var JFFS2_COMPR_ZERO = 0x01;
+var JFFS2_COMPR_RTIME = 0x02;
+var JFFS2_COMPR_RUBINMIPS = 0x03;
+var JFFS2_COMPR_COPY = 0x04;
+var JFFS2_COMPR_DYNRUBIN = 0x05;
+var JFFS2_COMPR_ZLIB = 0x06;
+var JFFS2_COMPR_LZO = 0x07;
+
+var JFFS2_INODE_COMP_TYPES = [
+ JFFS2_COMPR_NONE,
+ JFFS2_COMPR_ZERO,
+ JFFS2_COMPR_RTIME,
+ JFFS2_COMPR_RUBINMIPS,
+ JFFS2_COMPR_COPY,
+ JFFS2_COMPR_DYNRUBIN,
+ JFFS2_COMPR_ZLIB,
+ JFFS2_COMPR_LZO
+];
+
+// dirent types
+// These are the same as the linux dirent values and could
+// probably be moved to a dirent pickle someday
+var JFFS2_DT_UNKNOWN = 0x0;
+var JFFS2_DT_FIFO = 0x1;
+var JFFS2_DT_CHR = 0x2;
+var JFFS2_DT_DIR = 0x4;
+var JFFS2_DT_BLK = 0x6;
+var JFFS2_DT_REG = 0x8;
+var JFFS2_DT_LNK = 0xa;
+var JFFS2_DT_SOCK = 0xc;
+var JFFS2_DT_WHT = 0xe;
+
+var JFFS2_DIRENT_TYPES = [
+ JFFS2_DT_UNKNOWN,
+ JFFS2_DT_FIFO,
+ JFFS2_DT_CHR,
+ JFFS2_DT_DIR,
+ JFFS2_DT_BLK,
+ JFFS2_DT_REG,
+ JFFS2_DT_LNK,
+ JFFS2_DT_SOCK,
+ JFFS2_DT_WHT
+];
+
+var JFFS2_ALIGNMENT = 4#B;
+var JFFS2_HEADER_SIZE = 0xc#B;
+
+fun jffs2_nodetype_to_str = (uint<16> node_type) string: {
+ if (node_type == JFFS2_NODETYPE_DIRENT) {
+ return "DIRENT";
+ }
+
+ if (node_type == JFFS2_NODETYPE_INODE) {
+ return "INODE";
+ }
+
+ if (node_type == JFFS2_NODETYPE_CLEANMARKER) {
+ return "CLEANMARKER";
+ }
+
+ if (node_type == JFFS2_NODETYPE_PADDING) {
+ return "PADDING";
+ }
+
+ if (node_type == JFFS2_NODETYPE_SUMMARY) {
+ return "SUMMARY";
+ }
+
+ if (node_type == JFFS2_NODETYPE_XATTR) {
+ return "XATTR";
+ }
+
+ if (node_type == JFFS2_NODETYPE_XREF) {
+ return "XREF";
+ }
+
+ return "UNKNOWN";
+};
+
+
+/*
+ * Similar to a linux dirent - represents an entry within
+ * a directory. Could be a regular file, dir, device, link,
+ * etc...
+ */
+type JFFS2_Dirent =
+struct
+{
+ uint<16> magic : magic in [JFFS2_MAGIC, JFFS2_MAGIC_OLD];
+ uint<16> node_type = JFFS2_NODETYPE_DIRENT;
+ offset<uint<32>, B> total_len : total_len >= JFFS2_HEADER_SIZE;
+ uint<32> node_header_crc;
+ uint<32> pino; // Parent inode number
+ uint<32> version;
+ uint<32> ino; // inode number
+ uint<32> mctime; // time of the last modification in secs
+ offset<uint<8>, B> nsize; // length of the file name
+ uint<8> dir_type : dir_type in JFFS2_DIRENT_TYPES;
+ uint<8>[2];
+ uint<32> node_crc; // crc of data from node magic to unused
+ uint<32> name_crc; // crc of name[nsize]
+ char[nsize] name; // file name
+
+ // padding
+ byte[0] @ total_len + alignto(total_len, JFFS2_ALIGNMENT);
+
+ method get_name = string:
+ {
+ return catos(name);
+ }
+};
+
+/*
+ * Holds file data and metadata
+ * A fresh FS will have (file_size / page_size) inodes for each
+ * file, each one representing 1 page of data, and each one with an incremented
+ * version value. Each time a change is made to a file (either its data or
metedata),
+ * a new inode is created with offset set to the location in the file the
+ * data was changed and version incremented by 1. Obselete inodes are
eventually
+ * garbage collected
+ */
+type JFFS2_Inode =
+struct
+{
+ uint<16> magic : magic in [JFFS2_MAGIC, JFFS2_MAGIC_OLD];
+ uint<16> node_type = JFFS2_NODETYPE_INODE;
+ offset<uint<32>, B> total_len : total_len >= JFFS2_HEADER_SIZE;
+ uint<32> node_header_crc;
+ uint<32> ino; // inode number
+ uint<32> version; // log version
+ uint<32> mode; // stat
+ uint<16> uid; // user id
+ uint<16> gid; // group id
+ uint<32> isize; // file size
+ uint<32> atime; // last access time
+ uint<32> mtime; // last modification time
+ uint<32> ctime; // last time of status change
+ offset<uint<32>, B> offset; // where to begin write
+ offset<uint<32>, B> csize; // compressed data size
+ offset<uint<32>, B> dsize; // uncompressed data size
+ uint<8> compr : compr in JFFS2_INODE_COMP_TYPES;
+ uint<8> usercompr : usercompr in JFFS2_INODE_COMP_TYPES;
+ uint<16> flags;
+ uint<32> data_crc; // crc of data[csize]
+ uint<32> node_crc; // crc from jffs2 to magic to flags
+ byte[0] data; // This could be large, don't want to map it unless needed
+ byte[0] data_end @ total_len;
+
+ // padding
+ byte[0] @ total_len + alignto(total_len, JFFS2_ALIGNMENT);
+
+ method get_data = byte[]:
+ {
+ return byte[csize] @ data'offset;
+ }
+};
+
+/*
+ * Holds a single extended attribute name/value pair
+ *
+ * Associated to a file (ino) via an XRef node.
+ * This allows XAttr nodes to be reused by multiple files
+ */
+type JFFS2_XAttr =
+struct
+{
+ uint<16> magic : magic in [JFFS2_MAGIC, JFFS2_MAGIC_OLD];
+ uint<16> node_type = JFFS2_NODETYPE_XATTR;
+ offset<uint<32>, B> total_len : total_len >= JFFS2_HEADER_SIZE;
+ uint<32> node_header_crc;
+ uint<32> xid;
+ uint<32> version;
+ uint<8> xprefix;
+ offset<uint<8>, B> name_len;
+ offset<uint<16>, B> value_len;
+ uint<32> data_crc;
+ uint<32> node_crc;
+ char[name_len] name if name_len != 0#B;
+ byte[0] data;
+ byte[0] data_end @ total_len;
+
+ // padding
+ byte[0] @ total_len + alignto(total_len, JFFS2_ALIGNMENT);
+
+ method get_name = string:
+ {
+ if (name_len != 0#B) {
+ return catos(name);
+ } else {
+ return "";
+ }
+ }
+
+ method get_data = byte[]:
+ {
+ return byte[value_len] @ data'offset;
+ }
+};
+
+/*
+ * Associates an XAttr to a file (ino)
+ */
+type JFFS2_XRef =
+struct
+{
+ uint<16> magic : magic in [JFFS2_MAGIC, JFFS2_MAGIC_OLD];
+ uint<16> node_type = JFFS2_NODETYPE_XREF;
+ offset<uint<32>, B> total_len : total_len >= JFFS2_HEADER_SIZE;
+ uint<32> node_header_crc;
+ uint<32> ino; // ino to add attr to
+ uint<32> xid; // attr to add
+ uint<32> xseqno;
+ uint<32> node_crc;
+};
+
+/*
+ * Represets in an inode in a summary node
+ */
+type JFFS2_Sum_Inode =
+struct
+{
+ uint<16> node_type = JFFS2_NODETYPE_INODE;
+ uint<32> inode; /* inode number */
+ uint<32> version; /* inode version */
+ offset<uint<32>, B> offset; /* offset on jeb */
+ offset<uint<32>, B> totlen; /* record length */
+};
+
+/*
+ * Represets in a dirent in a summary node
+ */
+type JFFS2_Sum_Dirent =
+struct
+{
+ uint<16> node_type = JFFS2_NODETYPE_DIRENT;
+ uint<32> totlen; /* record length */
+ offset<uint<32>, B> offset; /* ofset on jeb */
+ uint<32> pino; /* parent inode */
+ uint<32> version; /* dirent version */
+ uint<32> ino; /* == zero for unlink */
+ offset<uint<8>, B> nsize; /* dirent name size */
+ uint<8> dir_type; /* dirent type */
+ char[nsize] name; /* dirent name */
+
+ method get_name = string:
+ {
+ return catos(name);
+ }
+};
+
+/*
+ * Represets an xattr in a summary node
+ */
+type JFFS2_Sum_XAttr =
+struct
+{
+ uint<16> node_type = JFFS2_NODETYPE_XATTR;
+ uint<32> xid; /* xattr identifier */
+ uint<32> version; /* version number */
+ uint<32> offset; /* offset on jeb */
+ uint<32> totlen; /* node length */
+};
+
+/*
+ * Represets in an xref in a summary node
+ */
+type JFFS2_Sum_XRef =
+struct
+{
+ uint<16> node_type = JFFS2_NODETYPE_XREF;
+ uint<32> offset; /* offset on jeb */
+};
+
+/*
+ * Represets in an unknown node in a summary node
+ */
+type JFFS2_Sum_Unk =
+struct
+{
+ uint<16> node_type; // Could be anything
+};
+
+/*
+ * JFFS2 Summary node record
+ * Exists within a summary node, but represents a JFFS2
+ * in the surrounding erase block
+ */
+type JFFS2_Sum_Rec =
+union {
+ JFFS2_Sum_Dirent dirent;
+ JFFS2_Sum_Inode inode;
+ JFFS2_Sum_XAttr xattr;
+ JFFS2_Sum_XRef xref;
+ JFFS2_Sum_Unk unknown;
+
+ method get_node_type = uint<16>:
+ {
+ try return dirent.node_type; catch if E_elem {}
+ try return inode.node_type; catch if E_elem {}
+ try return xattr.node_type; catch if E_elem {}
+ try return xref.node_type; catch if E_elem {}
+
+ return unknown.node_type;
+ }
+};
+
+/*
+ * An Erase Block Summary (EBS) provides a "summary" of a JFFS2 erase
+ * block. If a jffs2 fs has summaries they will be at the end of
+ * each erase block, and will contain a summary record for each JFFS2
+ * node (dirents, inodes, xattrs, and xrefs) in the erase block.
+ * They were added to make mounting JFFS2 file system faster: instead
+ * of parsing through all the data in an erase block, you could jump
+ * to the end and parse the summary node
+ */
+type JFFS2_Summary =
+struct
+{
+ uint<16> magic : magic in [JFFS2_MAGIC, JFFS2_MAGIC_OLD];
+ uint<16> node_type = JFFS2_NODETYPE_SUMMARY;
+ offset<uint<32>, B> total_len : total_len >= JFFS2_HEADER_SIZE;
+ uint<32> node_header_crc;
+ uint<32> sum_num;
+ uint<32> cln_mkr;
+ uint<32> padded;
+ uint<32> sum_crc;
+ uint<32> node_crc;
+ byte[0] records; // This will be large. Don't want to map it unless needed
+ byte[0] records_end @ total_len;
+
+ // padding
+ byte[0] @ total_len + alignto(total_len, JFFS2_ALIGNMENT);
+
+ method get_records = JFFS2_Sum_Rec[]:
+ {
+ return JFFS2_Sum_Rec[sum_num] @ records'offset;
+ }
+
+};
+
+/*
+ * Used to for nodes with no extra data, or for unknown nodes.
+ *
+ * If a JFFS2 implementation encounters a node_type it isn't familiar
+ * with, it checks the FEATURE bits in the node_type to see how to handle
+ * it:
+ *
+ */
+type JFFS2_Unk_Node =
+struct
+{
+ uint<16> magic : magic in [JFFS2_MAGIC, JFFS2_MAGIC_OLD];
+ uint<16> node_type; // could be anything
+ offset<uint<32>, B> total_len : total_len >= JFFS2_HEADER_SIZE;
+ uint<32> node_header_crc;
+
+ byte[0] data;
+ byte[0] data_end @ total_len;
+
+ // padding
+ byte[0] @ total_len + alignto(total_len, JFFS2_ALIGNMENT);
+
+ method get_data = byte[]:
+ {
+ return byte[data_end'offset - data'offset] @ data'offset;
+ }
+};
+
+/*
+ * This is essentially a union of all the node types that allows
+ * you to get the node_type, total_len, and header_crc without
+ * knowing what the node is
+ */
+type JFFS2_Node =
+union {
+ JFFS2_Dirent dirent;
+ JFFS2_Inode inode;
+ JFFS2_XAttr xattr;
+ JFFS2_XRef xref;
+ JFFS2_Summary summary;
+ JFFS2_Unk_Node padding;
+ JFFS2_Unk_Node clean_marker;
+ JFFS2_Unk_Node unknown;
+
+ method get_node_type = uint<16>:
+ {
+ try return dirent.node_type; catch if E_elem {}
+ try return inode.node_type; catch if E_elem {}
+ try return xattr.node_type; catch if E_elem {}
+ try return xref.node_type; catch if E_elem {}
+ try return summary.node_type; catch if E_elem {}
+ try return padding.node_type; catch if E_elem {}
+ try return clean_marker.node_type; catch if E_elem {}
+
+ return unknown.node_type;
+ }
+};
+
+/*
+ * JFFS2 is a logging file system that exists on flash as
+ * a circular buffer of node structures. When changes are made to
+ * a file, new nodes are added to the end of the buffer
+ * with incremented "version" fields. Old nodes are garbage collected.
+ */
+type JFFS2_FS =
+struct
+{
+ JFFS2_Node[] nodes;
+
+ method get_nodes_by_type = (uint<16> node_type) JFFS2_Node[]:
+ {
+ var res_nodes = JFFS2_Node[]();
+ for (node in nodes where node.get_node_type() == node_type) {
+ res_nodes += [node];
+ }
+
+ return res_nodes;
+ }
+};
+
+/* --- Nothing below here is part of the JFFS2 spec. Just utility types and
funcitons --- */
+
+type JFFS2_Entry =
+struct
+{
+ string name;
+ uint<32> ino;
+ uint<32> pino;
+ uint<8> entry_type;
+ uint<32>[] children; // TODO change this to JFFS2_Entry[] when
self-referential structs are supported
+
+ method add_child = (uint<32> ino) void:
+ {
+ children += [ino];
+ }
+
+ method get_children = uint<32>[]:
+ {
+ return children;
+ }
+};
+
+type JFFS2_Entry_Table =
+struct
+{
+ JFFS2_Entry[] table;
+
+ method init = (string fs_name) void:
+ {
+ table += [JFFS2_Entry {
+ name = fs_name,
+ ino = 1,
+ pino = 0,
+ entry_type = JFFS2_DT_DIR
+ }];
+ }
+
+ method add_entry = (JFFS2_Dirent dirent) void:
+ {
+ table += [JFFS2_Entry {
+ name = dirent.get_name(),
+ ino = dirent.ino,
+ pino = dirent.pino,
+ entry_type = dirent.dir_type
+ }];
+ }
+
+ method get_entry = (uint<32> ino) JFFS2_Entry:
+ {
+ for (entry in table) {
+ if (entry.ino == ino) {
+ return entry;
+ }
+ }
+
+ raise Exception {
+ code = EC_inval,
+ msg = "No such JFFS2 inode"
+ };
+ }
+
+ method get_entries = JFFS2_Entry[]:
+ {
+ return table;
+ }
+
+ method get_root = JFFS2_Entry:
+ {
+ return get_entry(1);
+ }
+};
+
+fun jffs2_create_entry_tables = (JFFS2_FS fs) JFFS2_Entry_Table[]:
+{
+ var tables = [JFFS2_Entry_Table {}];
+ var fs_index = 0;
+ var inos = uint<32>[]();
+
+ tables[fs_index].init("fs_" + ltos(fs_index));
+
+ // First pass, just create the tables
+ for (node in fs.nodes) {
+ if (node.get_node_type() != JFFS2_NODETYPE_DIRENT) {
+ continue;
+ }
+
+ var dirent = node.dirent;
+
+ if (dirent.ino == 0) {
+ // An ino of 0 means an unlinked file.
+ continue;
+ }
+
+ if (dirent.ino in inos) {
+ // Found a new fs
+ tables += [JFFS2_Entry_Table {}];
+ fs_index += 1;
+ tables[fs_index].init("fs_" + ltos(fs_index));
+ inos = uint<32>[]();
+ }
+
+ inos += [dirent.ino];
+ tables[fs_index].add_entry(dirent);
+
+ }
+
+ // Second pass, add children to entries
+ for (table in tables) {
+ for (entry in table.get_entries()) {
+ if (entry.pino == 0) {
+ // root entry. skip it
+ continue;
+ }
+
+ var parent = table.get_entry(entry.pino);
+ parent.add_child(entry.ino);
+ }
+ }
+
+ return tables;
+}
+
+fun jffs2_dump_fs_tree = (JFFS2_FS fs) void:
+{
+ fun walk_dir = (JFFS2_Entry_Table table, JFFS2_Entry dir, string prefix)
void:
+ {
+ var idx = 0;
+ for (ino in dir.children) {
+ var pointer = "";
+ var prefix_ext = "";
+
+ if (idx == (dir.children'length - 1)) {
+ pointer = "└── ";
+ prefix_ext = " ";
+ } else {
+ pointer = "├── ";
+ prefix_ext = "│ ";
+ }
+
+ var entry = table.get_entry(ino);
+
+ printf("%s%s%s\n", prefix, pointer, entry.name);
+
+ if (entry.entry_type == JFFS2_DT_DIR) {
+ walk_dir(table, entry, prefix + prefix_ext);
+ }
+
+ idx += 1;
+ }
+ }
+
+ var tables = jffs2_create_entry_tables(fs);
+ for (table in tables) {
+ var root = table.get_root();
+ printf("%s\n", root.name);
+ walk_dir(table, root, "");
+ }
+}
--
2.25.1
Matt Ihlenfield