summaryrefslogtreecommitdiff
path: root/tools/libar/ar.py
blob: a9861560abb5c74ef45da54eb5b60c1fda4139bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
"""

Simple Library for reading and parsing archive files generate by the linker.

"""

import struct

from typing import List, Tuple
from dataclasses import dataclass, field

#
# Source: 'https://en.wikipedia.org/wiki/Ar_(Unix)'
#


def _utf8(s):
    result = s.decode('utf-8')
    if not result:
        return ""
    return result


@dataclass
class Header:
    """ Header """

    signature: int = 0

    def read(self, file):
        self.signature = struct.unpack(">Q", file.read(8))[0]


@dataclass
class FileBlock:
    """ File Block """

    name: str = None
    mtime: int = 0
    uid: int = 0
    gid: int = 0
    perms: int = 0
    size: int = 0
    data: bytes = None

    def read(self, file):
        buffer = file.read(60)
        if len(buffer) != 60:
            return False

        name, mtime, uid, gid, perms, size, magic = (
            struct.unpack('16s12s6s6s8s10s2s', buffer))
        self.name = _utf8(name)
        self.mtime = int(mtime, 10) if len(mtime.strip()) > 0 else 0
        self.uid = int(uid, 10) if len(uid.strip()) > 0 else 0
        self.gid = int(gid, 10) if len(gid.strip()) > 0 else 0
        self.perms = int(perms, 8) if len(perms.strip()) > 0 else 0
        self.size = int(size, 10)
        self.data = file.read(self.size)
        if self.size % 2 != 0:
            file.read(1)
        return len(self.data) == self.size


@dataclass
class AR:
    """ Archive with files """

    files: List[Tuple[str, bytearray]] = field(default_factory=list)


def read(path) -> AR:
    """
    Read and parse archive files generate by linkers.
    """

    archive = AR()
    with open(path, 'rb') as file:
        header = Header()
        header.read(file)
        if header.signature != 0x213C617263683E0A:
            raise ARException(
                f"invalid AR file signature: {header.signature:016X}")

        while True:
            block = FileBlock()
            if not block.read(file):
                break
            if block.name.strip() == "/":
                continue
            if block.name.strip() == "//":
                string_table = _utf8(block.data)
                continue

            # parse file name
            ss = block.name.split("/")
            name = ss[0]
            if len(ss[0]) == 0:
                str_index = ss[1].strip()
                assert len(str_index) > 0
                index = int(str_index)
                name = string_table[index:].split("/")[0]

            name = name.lstrip().rstrip(' ')
            if name == "" or name == "/":
                continue

            archive.files.append((name, block.data))

    return archive