Skip to content

Commit

Permalink
New pysquashfsimage command, strings for names and paths, argparse (#…
Browse files Browse the repository at this point in the history
…22)

* Simplify some code

- replace `fill` by the `from_bytes` class method
- file names and paths are now instances of str just like symlinks

* Use argparse

* Add pysquashfsimage command
  • Loading branch information
AT0myks authored Apr 16, 2023
1 parent 9755074 commit 4b4b5e2
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 109 deletions.
142 changes: 68 additions & 74 deletions PySquashfsImage/PySquashfsImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,6 @@ def SQUASHFS_XATTR_OFFSET(A):
]


def str2byt(data):
if isinstance(data, str):
return data.encode("latin-1")
return data


def byt2str(data):
if isinstance(data, bytes):
return data.decode("latin-1")
Expand Down Expand Up @@ -340,12 +334,13 @@ def read(self, myfile):
for field, value in zip(self.FIELDS, values):
setattr(self, field, value)

def fill(self, buffer, ofs):
"""Set values read from a buffer. Return the amount of bytes read."""
values = struct.unpack(self.FORMAT, buffer[ofs : ofs + self.SIZE])
for field, value in zip(self.FIELDS, values):
setattr(self, field, value)
return self.SIZE
@classmethod
def from_bytes(cls, buffer, offset=0):
inst = cls()
values = struct.unpack_from(cls.FORMAT, buffer, offset)
for field, value in zip(cls.FIELDS, values):
setattr(inst, field, value)
return inst


class _Squashfs_super_block(_Squashfs_commons):
Expand Down Expand Up @@ -393,9 +388,6 @@ def __init__(self):
self.unused = 0
self.fragment = None

def fill(self, buffer, ofs):
return ofs + super().fill(buffer, ofs)


class SquashInode:

Expand Down Expand Up @@ -467,7 +459,7 @@ def __init__(self):
def _set_values(self, fmt, fields, buff, offset):
"""Return the amount of bytes read from the buffer."""
size = struct.calcsize(fmt)
values = struct.unpack(fmt, buff[offset : offset + size])
values = struct.unpack_from(fmt, buff, offset)
for field, value in zip(fields, values):
setattr(self, field, value)
return size
Expand Down Expand Up @@ -538,12 +530,16 @@ def __init__(self):
self.inode_number = 0
self.type = 0
self.size = 0
self.name = []
self.name = None
self.s_file = None

def fill(self, buffer, ofs):
ofs += super().fill(buffer, ofs)
self.name = buffer[ofs : ofs + self.size + 1]
@classmethod
def from_bytes(cls, buffer, offset=0):
# super without arguments is not Python 2 compatible.
inst = super(_Dir_entry, cls).from_bytes(buffer, offset)
offset += cls.SIZE
inst.name = byt2str(buffer[offset : offset + inst.size + 1])
return inst


class _Dir_header(_Squashfs_commons):
Expand Down Expand Up @@ -597,7 +593,7 @@ def __init__(self):

class SquashedFile:

def __init__(self, name, parent):
def __init__(self, name, parent=None):
self.name = name
self.children = []
self.inode = None
Expand All @@ -607,7 +603,7 @@ def getPath(self):
if self.parent is None:
return self.name
else:
return self.parent.getPath() + "/" + byt2str(self.name)
return self.parent.getPath() + "/" + self.name

def getXattr(self):
return self.inode.getXattr()
Expand Down Expand Up @@ -642,9 +638,9 @@ def dirlist(self, path):
return node.children

def select(self, path):
if path == b"/":
path = b""
lpath = [str2byt(i) for i in path.split('/')]
if path == "/":
path = ''
lpath = path.split('/')
start = self
ofs = 0
if not lpath[0]:
Expand Down Expand Up @@ -764,7 +760,7 @@ def __init__(self, filepath=None, offset=0):
self.total_inodes = 0
self.directory_table = b''
self.inode_to_file = {}
self.root = SquashedFile("", None)
self.root = SquashedFile("")
self.image_file = None
self.offset = offset
if filepath is not None:
Expand Down Expand Up @@ -800,7 +796,7 @@ def getCompressor(self, compression_id):

def initialize(self, myfile):
self.__read_super(myfile)
self.created_inode = [None for _ in range(self.sBlk.inodes)]
self.created_inode = [None] * self.sBlk.inodes
self.block_size = self.sBlk.block_size
self.block_log = self.sBlk.block_log
self.fragment_buffer_size <<= 20 - self.block_log
Expand Down Expand Up @@ -833,8 +829,7 @@ def getFileContent(self, inode):
continue
c_byte = SQUASHFS_COMPRESSED_SIZE_BLOCK(cur_blk)
if cur_blk != 0: # non sparse file
buffer = self.read_data_block(self.image_file, start, cur_blk)
content += buffer
content += self.read_data_block(self.image_file, start, cur_blk)
start += c_byte
if inode.frag_bytes != 0:
start, size = self.read_fragment(inode.fragment)
Expand All @@ -857,17 +852,12 @@ def read_block(self, myfile, start):
offset = 2
if SQUASHFS_CHECK_DATA(self.sBlk.flags):
offset = 3
myfile.seek(self.offset + start + offset)
size = SQUASHFS_COMPRESSED_SIZE(c_byte)
block = myfile.read(size)
if SQUASHFS_COMPRESSED(c_byte):
myfile.seek(self.offset + start + offset)
c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte)
buffer = myfile.read(c_byte)
block = self.comp.uncompress(buffer)
return (block, start + offset + c_byte, c_byte)
else:
myfile.seek(self.offset + start + offset)
c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte)
block = myfile.read(c_byte)
return (block, start + offset + c_byte, c_byte)
block = self.comp.uncompress(block)
return (block, start + offset + size, size)

def uncompress_inode_table(self, myfile, start, end):
bytes_ = 0
Expand All @@ -879,21 +869,20 @@ def uncompress_inode_table(self, myfile, start, end):

def read_fragment_table(self, myfile):
indexes = SQUASHFS_FRAGMENT_INDEXES(self.sBlk.fragments)
fragment_table_index = [None for _ in range(indexes)]
fragment_table_index = []
self.fragment_table = []
if self.sBlk.fragments == 0:
return True
return
myfile.seek(self.offset + self.sBlk.fragment_table_start)
for i in range(indexes):
fragment_table_index[i] = self.readLong(myfile)
for _ in range(indexes):
fragment_table_index.append(self.readLong(myfile))
table = b""
for i in range(indexes):
block = self.read_block(myfile, fragment_table_index[i])[0]
table += block
for fti in fragment_table_index:
table += self.read_block(myfile, fti)[0]
ofs = 0
while ofs < len(table):
entry = _Squashfs_fragment_entry()
ofs = entry.fill(table, ofs)
entry = _Squashfs_fragment_entry.from_bytes(table, ofs)
ofs += _Squashfs_fragment_entry.SIZE
entry.fragment = self.read_data_block(myfile, entry.start_block, entry.size)
self.fragment_table.append(entry)

Expand Down Expand Up @@ -1010,35 +999,32 @@ def squashfs_opendir(self, block_start, offset, s_file):
mydir.mtime = i.time
mydir.xattr = i.xattr
mydir.dirs = []
dirh = _Dir_header()
while bytes_ < size:
dirh.fill(self.directory_table, bytes_)
dirh = _Dir_header.from_bytes(self.directory_table, bytes_)
dir_count = dirh.count + 1
bytes_ += _Dir_header.SIZE
while dir_count != 0:
dire = _Dir_entry()
dire = _Dir_entry.from_bytes(self.directory_table, bytes_)
dir_count -= 1
dire.fill(self.directory_table, bytes_)
bytes_ += _Dir_entry.SIZE
dire.s_file = SquashedFile(dire.name, s_file)
s_file.children.append(dire.s_file)
dire.parent = mydir
dire.start_block = dirh.start_block
mydir.dirs.append(dire)
mydir.dir_count += 1
bytes_ += dire.size + 1
bytes_ += _Dir_entry.SIZE + dire.size + 1
return (mydir, i)

def read_uids_guids(self, myfile):
indexes = SQUASHFS_ID_BLOCKS(self.sBlk.no_ids)
id_index_table = [None for _ in range(indexes)]
self.id_table = [None for _ in range(self.sBlk.no_ids)]
id_index_table = []
self.id_table = [None] * self.sBlk.no_ids
myfile.seek(self.offset + self.sBlk.id_table_start)
for i in range(indexes):
id_index_table[i] = self.makeInteger(myfile, SQUASHFS_ID_BLOCK_BYTES(1))
for i in range(indexes):
myfile.seek(self.offset + id_index_table[i])
block = self.read_block(myfile, id_index_table[i])[0]
for _ in range(indexes):
id_index_table.append(self.makeInteger(myfile, SQUASHFS_ID_BLOCK_BYTES(1)))
for i, idx in enumerate(id_index_table):
myfile.seek(self.offset + idx)
block = self.read_block(myfile, idx)[0]
offset = 0
index = i * (SQUASHFS_METADATA_SIZE // 4)
while offset < len(block):
Expand All @@ -1058,16 +1044,14 @@ def read_xattrs_from_disk(self, myfile):
for _ in range(indexes):
index.append(self.makeInteger(myfile, SQUASHFS_XATTR_BLOCK_BYTES(1)))
xattr_ids = {}
for i in range(indexes):
block = self.read_block(myfile, index[i])[0]
for i, idx in enumerate(index):
block = self.read_block(myfile, idx)[0]
cur_idx = (i * SQUASHFS_METADATA_SIZE) / 16
ofs = 0
while ofs < len(block):
xattr_id = _Xattr_id()
xattr_id.fill(block, ofs)
xattr_ids[cur_idx] = xattr_id
xattr_ids[cur_idx] = _Xattr_id.from_bytes(block, ofs)
cur_idx += 1
ofs += 16
ofs += _Xattr_id.SIZE
start = xattr_table_start
end = index[0]
i = 0
Expand Down Expand Up @@ -1104,13 +1088,20 @@ def pre_scan(self, parent_name, start_block, offset, parent):
return mydir


if __name__ == "__main__":
def main():
import argparse
import sys

image = SquashFsImage(sys.argv[1])
parser = argparse.ArgumentParser(description="Print information about squashfs images.")
parser.add_argument("file", help="squashfs filesystem")
parser.add_argument("paths", nargs='+', help="directories or files to print information about")
parser.add_argument("-V", "--version", action="version", version="%(prog)s v0.8.0")
args = parser.parse_args()

image = SquashFsImage(args.file)
if len(sys.argv) > 1:
for i in range(2, len(sys.argv)):
sqashed_filename = sys.argv[i]
for path in args.paths:
sqashed_filename = path
squashed_file = image.root.select(sqashed_filename)
print("--------------%-50.50s --------------" % sqashed_filename)
if squashed_file is None:
Expand Down Expand Up @@ -1143,7 +1134,10 @@ def pre_scan(self, parent_name, start_block, offset, parent):
print("++++++++++++++%-50.50s (%8d)++++++++++++++" % (i.getPath(), len(content)))
oname = i.name + "_saved_" + str(i.inode.inode_number)
print("written %s from %s %d" % (oname, i.name, len(content)))
of = open(oname, "wb")
of.write(content)
of.close()
with open(oname, "wb") as of:
of.write(content)
image.close()


if __name__ == "__main__":
main()
51 changes: 37 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
PySquashfsImage is a lightweight library for reading squashfs image files in Python.
It provides a way to read squashfs images header and to retrieve encapsulated binaries.
It is compatible with Python2 and Python3.
It is compatible with Python 2 and Python 3.

Examples:
---------
## Use as a library

List all elements in the image:
### List all elements in the image:
-------------------------------
```
```python
from PySquashfsImage import SquashFsImage

image = SquashFsImage('/path/to/my/image.img')
Expand All @@ -16,9 +15,9 @@ for i in image.root.findAll():
image.close()
```

Print all files and folder with human readable path:
### Print all files and folder with human readable path:
----------------------------------------------------
```
```python
from PySquashfsImage import SquashFsImage

image = SquashFsImage('/path/to/my/image.img')
Expand All @@ -27,9 +26,9 @@ for i in image.root.findAllPaths():
image.close()
```

Print only files:
### Print only files:
-----------------
```
```python
from PySquashfsImage import SquashFsImage

image = SquashFsImage('/path/to/my/image.img')
Expand All @@ -39,17 +38,41 @@ for i in image.root.findAll():
image.close()
```

Save the content of a file:
### Save the content of a file:
---------------------------
```
```python
from PySquashfsImage import SquashFsImage

image = SquashFsImage('/path/to/my/image.img')
for i in image.root.findAll():
if i.getName() == b'myfilename':
with open(b'/tmp/'+i.getName(),'wb') as f:
print(b'Saving original '+i.getPath().encode()+b' in /tmp/'+i.getName())
if i.getName() == 'myfilename':
with open('/tmp/' + i.getName(), 'wb') as f:
print('Saving original ' + i.getPath() + ' in /tmp/' + i.getName())
f.write(i.getContent())
image.close()
```

## Use as a command

```
$ pysquashfsimage -h
usage: pysquashfsimage [-h] [-V] file paths [paths ...]
positional arguments:
file squashfs filesystem
paths directories or files to print information about
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
```

For each path, if it is a directory it will print the mode and name of each
contained file, with sizes and symlinks.

If a path is a file, print its content.

Example command:
```
$ pysquashfsimage myimage.img /bin /etc/passwd
```
Loading

0 comments on commit 4b4b5e2

Please sign in to comment.