poke-devel
[Top][All Lists]
Advanced

[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: Mon, 08 Mar 2021 03:31:40 +0000

Hi Jose,

Patch v3 at bottom!

>>> 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.
>
>I was thinking on the scenario of a new node type being added to the
>spec.

That makes sense. I'll make the change!

>>> 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.
>
>Yes... I would have an anonymous
>
>  byte[0] @ total_len;
>
>in the struct types, and a method `get_data_offset'.

Ah ya data_end doesn't need to have a name. Will fix.

>> +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;
>> +    }
>> +};
>
>I think in situations like this it is better to have a method to return
>the offset of data, `get_data_offset', than a method that maps an array
>of bytes.
>
>The reason is that `data' is a payload in this case, i.e. it can contain
>all sort of things, unrestricted.  Therefore, the user will most
>probably want to map stuff there: maybe an array of bytes yes, but also
>maybe an JPEG file.
>
>If you give the user an array of bytes and she is interested in mapping
>something else in that area, she will just use get_data'offset and
>get_data'size as coordinates for mapping, and the array of bytes will be
>mapped for nothing.

Ok I like that. Letting the user map the data as whatever they want does
seem to be the more poke'ish way of doing things.

I can add the get_data_offset function, but I wonder if it would
make more sense to just have the user do inode.data'offset instead?
What do you think best practice is there?

I thought about getting rid of the data field, but it's the only
field in the struct that has a 'offset attribute, so I can't get
a global offset of the data without it.

>> +/*
>> + * 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;
>
>Why data_end needs to be a named field?

Will fix!

>> +    // padding
>> +    byte[0] @ total_len + alignto(total_len, JFFS2_ALIGNMENT);
>> +
>> +    method get_name = string:
>> +    {
>> +        if (name_len != 0#B) {
>> +            return catos(name);
>> +        } else {
>> +            return "";
>> +        }
>> +    }
>
>catos does the right thing with empty arrays of characters:
>
>(poke) var a = char[]()
>(poke) a
>[]
>(poke) catos (a)
>""
>
>So I think you can simplify that.

I think you may have missed the name field here:

+    char[name_len] name if name_len != 0#B;

Per your suggestion in the last review I made this an optional
field contingen on name_len. That's why I had to add the
if...else here.

>Also, you may want to add set_name methods too, using stoca.  This is
>an example from id3v1.pk:
>
> method set_comment = (string val) void:
> {
>   try stoca (val, data.comment, ' ');
>   catch if E_elem { stoca (val, data.extended.comment, ' '); }
> }

I thought about adding setters for this and also for dirent names, but
I came to the conclusion that it wouldn't be very useful as
the name field is not of fixed length, so changing it could change
the size of the node.

The correct way to make changes to any file in a JFFS2 file system
is to create a new node with an incremented version field at the tail
of the file system. Doing that requires some knowledge of the physical
attributes of the flash chip that the filesystem was intended to be
placed on, namely the eraseblock size and page size. In the future,
I could make those values (optional?) struct arguments and
add some helper functions for creating/adding nodes. So unless
you object, I think I would leave that feature for a jffs2
pickle 2.0.

>> +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;
>
>Why having records and records_end?
>I would just have a get_records_offset method.

A "get_records_offset" method could be useful, so I'll add that. But I also
think that the "get_records" method is useful, since it's not likely the
user will want to map the record data as anything other than an array of
summary records.

>> +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;
>
>Ditto here for `data'.

Will do!

---- End of Review Comments ----

Summary of V3 patch changes:

    * Made nodetype, inode comp type, and dirent type arrays lower case
    * Removed "data_end" field from JFFS2_Inode, JFFS2_XAttr, and
      JFFS2_Summary, and added get_data_offset methods
    * docstring improvements


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





reply via email to

[Prev in Thread] Current Thread [Next in Thread]