Skip to content

Commit fa72185

Browse files
committed
Return U-Boot-related information
1 parent 562c449 commit fa72185

File tree

5 files changed

+168
-39
lines changed

5 files changed

+168
-39
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ provided on PyPI.
5454

5555
### Command line
5656

57-
#### List
57+
#### Info
5858

5959
```
6060
$ reolinkfw info file_or_url
@@ -74,6 +74,10 @@ $ reolinkfw info RLC-410-5MP_20_20052300.zip -i 2
7474
"detail_machine_type": "IPC_51516M5M",
7575
"type": "IPC",
7676
"version_file": "20_20052300",
77+
"os": "Linux",
78+
"architecture": "MIPS",
79+
"kernel_image_name": "Linux-4.1.0",
80+
"uboot_version": "U-Boot 2014.07 (Feb 26 2019 - 18:20:07)",
7781
"filesystems": [
7882
{
7983
"name": "fs",

reolinkfw/__init__.py

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import io
33
import posixpath
44
import re
5-
from enum import Enum
65
from pathlib import Path
76
from typing import Optional
87
from urllib.parse import parse_qsl, urlparse
@@ -13,16 +12,14 @@
1312
from lxml.html import document_fromstring
1413
from pakler import PAK, Section, is_pak_file
1514
from pycramfs import Cramfs
16-
from pycramfs.const import MAGIC_BYTES as CRAMFS_MAGIC
1715
from PySquashfsImage import SquashFsImage
18-
from PySquashfsImage.const import SQUASHFS_MAGIC
19-
from ubireader.ubi.defines import UBI_EC_HDR_MAGIC as UBI_MAGIC
2016
from ubireader.ubifs import ubifs, walk
21-
from ubireader.ubifs.defines import UBIFS_NODE_MAGIC as UBIFS_MAGIC
2217
from ubireader.ubifs.output import _process_reg_file
2318

19+
from reolinkfw.uboot import get_arch_name, get_uboot_version, get_uimage_header
2420
from reolinkfw.util import (
2521
DummyLEB,
22+
FileType,
2623
get_cache_file,
2724
get_fs_from_ubi,
2825
has_cache,
@@ -39,20 +36,6 @@
3936
FS_SECTIONS = ROOTFS_SECTIONS + ["app"]
4037

4138

42-
class FileSystem(Enum):
43-
CRAMFS = CRAMFS_MAGIC
44-
SQUASHFS = SQUASHFS_MAGIC.to_bytes(4, "little")
45-
UBI = UBI_MAGIC # Not a file system
46-
UBIFS = UBIFS_MAGIC
47-
48-
@classmethod
49-
def from_magic(cls, key, default=None):
50-
try:
51-
return cls(key)
52-
except ValueError:
53-
return default
54-
55-
5639
async def download(url):
5740
"""Return resource as bytes.
5841
@@ -118,10 +101,10 @@ def get_files_from_ubifs(binbytes):
118101

119102
def get_files_from_ubi(fd, size, offset=0):
120103
fsbytes = get_fs_from_ubi(fd, size, offset)
121-
fs = FileSystem.from_magic(fsbytes[:4])
122-
if fs == FileSystem.UBIFS:
104+
fs = FileType.from_magic(fsbytes[:4])
105+
if fs == FileType.UBIFS:
123106
return get_files_from_ubifs(fsbytes)
124-
elif fs == FileSystem.SQUASHFS:
107+
elif fs == FileType.SQUASHFS:
125108
return get_files_from_squashfs(io.BytesIO(fsbytes))
126109
raise Exception("Unknown file system in UBI")
127110

@@ -150,7 +133,7 @@ def get_fs_info(pak: PAK, fs_sections: list[Section]) -> list[dict[str, str]]:
150133
result = []
151134
for section in fs_sections:
152135
pak._fd.seek(section.start)
153-
fs = FileSystem.from_magic(pak._fd.read(4))
136+
fs = FileType.from_magic(pak._fd.read(4))
154137
result.append({
155138
"name": section.name,
156139
"type": fs.name.lower() if fs is not None else "unknown"
@@ -163,17 +146,25 @@ async def get_info_from_pak(pak: PAK):
163146
fs_sections = [s for s in pak.sections if s.name in FS_SECTIONS]
164147
app = fs_sections[-1]
165148
pak._fd.seek(app.start)
166-
fs = FileSystem.from_magic(pak._fd.read(4))
167-
if fs == FileSystem.CRAMFS:
149+
fs = FileType.from_magic(pak._fd.read(4))
150+
if fs == FileType.CRAMFS:
168151
files = await asyncio.to_thread(get_files_from_cramfs, pak._fd, app.start, False)
169-
elif fs == FileSystem.UBI:
152+
elif fs == FileType.UBI:
170153
files = await asyncio.to_thread(get_files_from_ubi, pak._fd, app.len, app.start)
171-
elif fs == FileSystem.SQUASHFS:
154+
elif fs == FileType.SQUASHFS:
172155
files = await asyncio.to_thread(get_files_from_squashfs, pak._fd, app.start, False)
173156
else:
174157
return {"error": "Unrecognized image type", "sha256": ha}
175-
info = get_info_from_files(files)
176-
return {**info, "filesystems": get_fs_info(pak, fs_sections), "sha256": ha}
158+
uimage = get_uimage_header(pak)
159+
return {
160+
**get_info_from_files(files),
161+
"os": "Linux" if uimage.os == 5 else "Unknown",
162+
"architecture": get_arch_name(uimage.arch),
163+
"kernel_image_name": uimage.name,
164+
"uboot_version": get_uboot_version(pak),
165+
"filesystems": get_fs_info(pak, fs_sections),
166+
"sha256": ha
167+
}
177168

178169

179170
async def direct_download_url(url):

reolinkfw/extract.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,32 @@
1010
from ubireader.ubifs import ubifs
1111
from ubireader.ubifs.output import extract_files as extract_ubifs
1212

13-
from reolinkfw import FS_SECTIONS, ROOTFS_SECTIONS, FileSystem
14-
from reolinkfw.util import DummyLEB, get_fs_from_ubi
13+
from reolinkfw import FS_SECTIONS, ROOTFS_SECTIONS
14+
from reolinkfw.util import DummyLEB, FileType, get_fs_from_ubi
1515

1616

1717
def extract_file_system(pak: PAK, section: Section, dest: Path = None):
1818
dest = (Path.cwd() / "reolink_fs") if dest is None else dest
1919
dest.mkdir(parents=True, exist_ok=True)
2020
pak._fd.seek(section.start)
21-
fs = FileSystem.from_magic(pak._fd.read(4))
22-
if fs == FileSystem.UBI:
21+
fs = FileType.from_magic(pak._fd.read(4))
22+
if fs == FileType.UBI:
2323
fs_bytes = get_fs_from_ubi(pak._fd, section.len, section.start)
24-
fs = FileSystem.from_magic(fs_bytes[:4])
25-
if fs == FileSystem.UBIFS:
24+
fs = FileType.from_magic(fs_bytes[:4])
25+
if fs == FileType.UBIFS:
2626
with DummyLEB.from_bytes(fs_bytes) as leb:
2727
with redirect_stdout(StringIO()):
2828
# If files already exist they are not written again.
2929
extract_ubifs(ubifs(leb), dest)
30-
elif fs == FileSystem.SQUASHFS:
30+
elif fs == FileType.SQUASHFS:
3131
with SquashFsImage.from_bytes(fs_bytes) as image:
3232
extract_squashfs(image.root, dest, True)
3333
else:
3434
raise Exception("Unknown file system in UBI")
35-
elif fs == FileSystem.SQUASHFS:
35+
elif fs == FileType.SQUASHFS:
3636
with SquashFsImage(pak._fd, section.start, False) as image:
3737
extract_squashfs(image.root, dest, True)
38-
elif fs == FileSystem.CRAMFS:
38+
elif fs == FileType.CRAMFS:
3939
with Cramfs.from_fd(pak._fd, section.start, False) as image:
4040
extract_cramfs(image.rootdir, dest, True)
4141
else:

reolinkfw/uboot.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import re
2+
from ctypes import BigEndianStructure, c_char, c_uint32, c_uint8, sizeof
3+
from enum import IntEnum
4+
5+
from pakler import PAK
6+
7+
from reolinkfw.util import FileType
8+
9+
UBOOT_MAGIC = 0x27051956
10+
11+
12+
class Arch(IntEnum):
13+
ARM = 2
14+
MIPS = 5
15+
ARM64 = 22
16+
17+
18+
class LegacyImageHeader(BigEndianStructure):
19+
_fields_ = [
20+
("_magic", c_uint32),
21+
("_hcrc", c_uint32),
22+
("_time", c_uint32),
23+
("_size", c_uint32),
24+
("_load", c_uint32),
25+
("_ep", c_uint32),
26+
("_dcrc", c_uint32),
27+
("_os", c_uint8),
28+
("_arch", c_uint8),
29+
("_type", c_uint8),
30+
("_comp", c_uint8),
31+
("_name", c_char * 32),
32+
]
33+
34+
@property
35+
def magic(self) -> int:
36+
return self._magic
37+
38+
@property
39+
def hcrc(self) -> int:
40+
return self._hcrc
41+
42+
@property
43+
def time(self) -> int:
44+
return self._time
45+
46+
@property
47+
def size(self) -> int:
48+
return self._size
49+
50+
@property
51+
def load(self) -> int:
52+
return self._load
53+
54+
@property
55+
def ep(self) -> int:
56+
return self._ep
57+
58+
@property
59+
def dcrc(self) -> int:
60+
return self._dcrc
61+
62+
@property
63+
def os(self) -> int:
64+
return self._os
65+
66+
@property
67+
def arch(self) -> Arch:
68+
return Arch(self._arch)
69+
70+
@property
71+
def type(self) -> int:
72+
return self._type
73+
74+
@property
75+
def comp(self) -> int:
76+
return self._comp
77+
78+
@property
79+
def name(self) -> str:
80+
return self._name.decode()
81+
82+
@classmethod
83+
def from_fd(cls, fd):
84+
return cls.from_buffer_copy(fd.read(sizeof(cls)))
85+
86+
87+
def get_uboot_version(pak: PAK):
88+
for section in pak.sections:
89+
if section.len and "uboot" in section.name.lower():
90+
# This section is always named 'uboot' or 'uboot1'.
91+
pak._fd.seek(section.start)
92+
match = re.search(b"U-Boot [0-9]{4}\.[0-9]{2}.*? \(.*?\)", pak._fd.read(section.len))
93+
return match.group().decode() if match is not None else None
94+
return None
95+
96+
97+
def get_uimage_header(pak: PAK) -> LegacyImageHeader:
98+
for section in pak.sections:
99+
pak._fd.seek(section.start)
100+
if section.len and FileType.from_magic(pak._fd.read(4)) == FileType.UIMAGE:
101+
# This section is always named 'KERNEL' or 'kernel'.
102+
pak._fd.seek(section.start)
103+
return LegacyImageHeader.from_fd(pak._fd)
104+
raise Exception("No kernel section found")
105+
106+
107+
def get_arch_name(arch: Arch) -> str:
108+
if arch == Arch.ARM:
109+
return "ARM"
110+
elif arch == Arch.MIPS:
111+
return "MIPS"
112+
elif arch == Arch.ARM64:
113+
return "AArch64"
114+
raise Exception("Unknown architecture")

reolinkfw/util.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import hashlib
22
import io
33
from contextlib import contextmanager
4+
from enum import Enum
45
from functools import partial
56
from os import scandir
67
from pathlib import Path
@@ -9,8 +10,12 @@
910
from zipfile import is_zipfile
1011

1112
from pakler import PAK, is_pak_file
13+
from pycramfs.const import MAGIC_BYTES as CRAMFS_MAGIC
14+
from PySquashfsImage.const import SQUASHFS_MAGIC
1215
from ubireader.ubi import ubi
16+
from ubireader.ubi.defines import UBI_EC_HDR_MAGIC as UBI_MAGIC
1317
from ubireader.ubi_io import ubi_file, leb_virtual_file
18+
from ubireader.ubifs.defines import UBIFS_NODE_MAGIC as UBIFS_MAGIC
1419
from ubireader.utils import guess_peb_size
1520

1621
from reolinkfw.tmpfile import TempFile
@@ -19,6 +24,21 @@
1924
ONEGIB = 1024**3
2025

2126

27+
class FileType(Enum):
28+
CRAMFS = CRAMFS_MAGIC
29+
SQUASHFS = SQUASHFS_MAGIC.to_bytes(4, "little")
30+
UBI = UBI_MAGIC
31+
UBIFS = UBIFS_MAGIC
32+
UIMAGE = 0x27051956.to_bytes(4, "big")
33+
34+
@classmethod
35+
def from_magic(cls, key, default=None):
36+
try:
37+
return cls(key)
38+
except ValueError:
39+
return default
40+
41+
2242
class DummyLEB:
2343
"""A class that emulates ubireader's `leb_virtual_file`."""
2444

0 commit comments

Comments
 (0)