1from __future__ import print_function
2
3# lldb test suite imports
4from lldbsuite.test.decorators import *
5from lldbsuite.test.lldbtest import TestBase
6
7# gdb-remote-specific imports
8import lldbgdbserverutils
9from gdbremote_testcase import GdbRemoteTestCaseBase
10
11import binascii
12import os
13import stat
14import struct
15import typing
16
17
18class GDBStat(typing.NamedTuple):
19    st_dev: int
20    st_ino: int
21    st_mode: int
22    st_nlink: int
23    st_uid: int
24    st_gid: int
25    st_rdev: int
26    st_size: int
27    st_blksize: int
28    st_blocks: int
29    st_atime: int
30    st_mtime: int
31    st_ctime: int
32
33
34def uint32_or_zero(x):
35    return x if x < 2**32 and x >= 0 else 0
36
37
38def uint32_or_max(x):
39    return x if x < 2**32 and x >= 0 else 2**32 - 1
40
41
42def uint32_trunc(x):
43    return x & (2**32 - 1)
44
45
46class TestGdbRemotePlatformFile(GdbRemoteTestCaseBase):
47
48    @skipIfWindows
49    @add_test_categories(["llgs"])
50    def test_platform_file_rdonly(self):
51        self.vFile_test(read=True)
52
53    @skipIfWindows
54    @add_test_categories(["llgs"])
55    def test_platform_file_wronly(self):
56        self.vFile_test(write=True)
57
58    @skipIfWindows
59    @add_test_categories(["llgs"])
60    def test_platform_file_rdwr(self):
61        self.vFile_test(read=True, write=True)
62
63    @skipIfWindows
64    @add_test_categories(["llgs"])
65    def test_platform_file_wronly_append(self):
66        self.vFile_test(write=True, append=True)
67
68    @skipIfWindows
69    @add_test_categories(["llgs"])
70    def test_platform_file_rdwr_append(self):
71        self.vFile_test(read=True, write=True, append=True)
72
73    @skipIfWindows
74    @add_test_categories(["llgs"])
75    def test_platform_file_wronly_trunc(self):
76        self.vFile_test(write=True, trunc=True)
77
78    @skipIfWindows
79    @add_test_categories(["llgs"])
80    def test_platform_file_rdwr_trunc(self):
81        self.vFile_test(read=True, write=True, trunc=True)
82
83    @skipIfWindows
84    @add_test_categories(["llgs"])
85    def test_platform_file_wronly_creat(self):
86        self.vFile_test(write=True, creat=True)
87
88    @skipIfWindows
89    @add_test_categories(["llgs"])
90    def test_platform_file_wronly_creat_excl(self):
91        self.vFile_test(write=True, creat=True, excl=True)
92
93    @skipIfWindows
94    @add_test_categories(["llgs"])
95    def test_platform_file_wronly_fail(self):
96        server = self.connect_to_debug_monitor()
97        self.assertIsNotNone(server)
98
99        temp_path = self.getBuildArtifact("test")
100        self.assertFalse(os.path.exists(temp_path))
101
102        # attempt to open the file without O_CREAT
103        self.do_handshake()
104        self.test_sequence.add_log_lines(
105            ["read packet: $vFile:open:%s,1,0#00" % (
106                binascii.b2a_hex(temp_path.encode()).decode(),),
107             {"direction": "send",
108             "regex": r"^\$F-1,[0-9a-fA-F]+#[0-9a-fA-F]{2}$"}],
109            True)
110        self.expect_gdbremote_sequence()
111
112    @skipIfWindows
113    @add_test_categories(["llgs"])
114    def test_platform_file_wronly_creat_excl_fail(self):
115        server = self.connect_to_debug_monitor()
116        self.assertIsNotNone(server)
117
118        temp_file = self.getBuildArtifact("test")
119        with open(temp_file, "wb"):
120            pass
121
122        # attempt to open the file with O_CREAT|O_EXCL
123        self.do_handshake()
124        self.test_sequence.add_log_lines(
125            ["read packet: $vFile:open:%s,a01,0#00" % (
126                binascii.b2a_hex(temp_file.encode()).decode(),),
127             {"direction": "send",
128             "regex": r"^\$F-1,[0-9a-fA-F]+#[0-9a-fA-F]{2}$"}],
129            True)
130        self.expect_gdbremote_sequence()
131
132    @skipIfWindows
133    @add_test_categories(["llgs"])
134    def test_platform_file_size(self):
135        server = self.connect_to_debug_monitor()
136        self.assertIsNotNone(server)
137
138        temp_path = self.getBuildArtifact("test")
139        test_data = b"test data of some length"
140        with open(temp_path, "wb") as temp_file:
141            temp_file.write(test_data)
142
143        self.do_handshake()
144        self.test_sequence.add_log_lines(
145            ["read packet: $vFile:size:%s#00" % (
146                binascii.b2a_hex(temp_path.encode()).decode(),),
147             {"direction": "send",
148             "regex": r"^\$F([0-9a-fA-F]+)+#[0-9a-fA-F]{2}$",
149             "capture": {1: "size"}}],
150            True)
151        context = self.expect_gdbremote_sequence()
152        self.assertEqual(int(context["size"], 16), len(test_data))
153
154    @skipIfWindows
155    @add_test_categories(["llgs"])
156    def test_platform_file_mode(self):
157        server = self.connect_to_debug_monitor()
158        self.assertIsNotNone(server)
159
160        temp_path = self.getBuildArtifact("test")
161        test_mode = 0o751
162
163        with open(temp_path, "wb") as temp_file:
164            os.chmod(temp_file.fileno(), test_mode)
165
166        self.do_handshake()
167        self.test_sequence.add_log_lines(
168            ["read packet: $vFile:mode:%s#00" % (
169                binascii.b2a_hex(temp_path.encode()).decode(),),
170             {"direction": "send",
171             "regex": r"^\$F([0-9a-fA-F]+)+#[0-9a-fA-F]{2}$",
172             "capture": {1: "mode"}}],
173            True)
174        context = self.expect_gdbremote_sequence()
175        self.assertEqual(int(context["mode"], 16), test_mode)
176
177    @skipIfWindows
178    @add_test_categories(["llgs"])
179    def test_platform_file_mode_fail(self):
180        server = self.connect_to_debug_monitor()
181        self.assertIsNotNone(server)
182
183        temp_path = self.getBuildArtifact("nonexist")
184
185        self.do_handshake()
186        self.test_sequence.add_log_lines(
187            ["read packet: $vFile:mode:%s#00" % (
188                binascii.b2a_hex(temp_path.encode()).decode(),),
189             {"direction": "send",
190             "regex": r"^\$F-1,0*2+#[0-9a-fA-F]{2}$"}],
191            True)
192        self.expect_gdbremote_sequence()
193
194    @skipIfWindows
195    @add_test_categories(["llgs"])
196    def test_platform_file_exists(self):
197        server = self.connect_to_debug_monitor()
198        self.assertIsNotNone(server)
199
200        temp_path = self.getBuildArtifact("test")
201        with open(temp_path, "wb"):
202            pass
203
204        self.do_handshake()
205        self.test_sequence.add_log_lines(
206            ["read packet: $vFile:exists:%s#00" % (
207                binascii.b2a_hex(temp_path.encode()).decode(),),
208             "send packet: $F,1#00"],
209            True)
210        self.expect_gdbremote_sequence()
211
212    @skipIfWindows
213    @add_test_categories(["llgs"])
214    def test_platform_file_exists_not(self):
215        server = self.connect_to_debug_monitor()
216        self.assertIsNotNone(server)
217
218        test_path = self.getBuildArtifact("nonexist")
219        self.do_handshake()
220        self.test_sequence.add_log_lines(
221            ["read packet: $vFile:exists:%s#00" % (
222                binascii.b2a_hex(test_path.encode()).decode(),),
223             "send packet: $F,0#00"],
224            True)
225        self.expect_gdbremote_sequence()
226
227    @skipIfWindows
228    @add_test_categories(["llgs"])
229    def test_platform_file_fstat(self):
230        server = self.connect_to_debug_monitor()
231        self.assertIsNotNone(server)
232
233        with tempfile.NamedTemporaryFile() as temp_file:
234            temp_file.write(b"some test data for stat")
235            temp_file.flush()
236
237            self.do_handshake()
238            self.test_sequence.add_log_lines(
239                ["read packet: $vFile:open:%s,0,0#00" % (
240                    binascii.b2a_hex(temp_file.name.encode()).decode(),),
241                 {"direction": "send",
242                 "regex": r"^\$F([0-9a-fA-F]+)#[0-9a-fA-F]{2}$",
243                 "capture": {1: "fd"}}],
244                True)
245
246            context = self.expect_gdbremote_sequence()
247            self.assertIsNotNone(context)
248            fd = int(context["fd"], 16)
249
250            self.reset_test_sequence()
251            self.test_sequence.add_log_lines(
252                ["read packet: $vFile:fstat:%x#00" % (fd,),
253                 {"direction": "send",
254                 "regex": r"^\$F([0-9a-fA-F]+);(.*)#[0-9a-fA-F]{2}$",
255                 "capture": {1: "size", 2: "data"}}],
256                True)
257            context = self.expect_gdbremote_sequence()
258            self.assertEqual(int(context["size"], 16), 64)
259            # NB: we're using .encode() as a hack because the test suite
260            # is wrongly using (unicode) str instead of bytes
261            gdb_stat = GDBStat(
262                *struct.unpack(">IIIIIIIQQQIII",
263                               self.decode_gdbremote_binary(context["data"])
264                                .encode("iso-8859-1")))
265            sys_stat = os.fstat(temp_file.fileno())
266
267            self.assertEqual(gdb_stat.st_dev, uint32_or_zero(sys_stat.st_dev))
268            self.assertEqual(gdb_stat.st_ino, uint32_or_zero(sys_stat.st_ino))
269            self.assertEqual(gdb_stat.st_mode, uint32_trunc(sys_stat.st_mode))
270            self.assertEqual(gdb_stat.st_nlink, uint32_or_max(sys_stat.st_nlink))
271            self.assertEqual(gdb_stat.st_uid, uint32_or_zero(sys_stat.st_uid))
272            self.assertEqual(gdb_stat.st_gid, uint32_or_zero(sys_stat.st_gid))
273            self.assertEqual(gdb_stat.st_rdev, uint32_or_zero(sys_stat.st_rdev))
274            self.assertEqual(gdb_stat.st_size, sys_stat.st_size)
275            self.assertEqual(gdb_stat.st_blksize, sys_stat.st_blksize)
276            self.assertEqual(gdb_stat.st_blocks, sys_stat.st_blocks)
277            self.assertEqual(gdb_stat.st_atime,
278                             uint32_or_zero(int(sys_stat.st_atime)))
279            self.assertEqual(gdb_stat.st_mtime,
280                             uint32_or_zero(int(sys_stat.st_mtime)))
281            self.assertEqual(gdb_stat.st_ctime,
282                             uint32_or_zero(int(sys_stat.st_ctime)))
283
284            self.reset_test_sequence()
285            self.test_sequence.add_log_lines(
286                ["read packet: $vFile:close:%x#00" % (fd,),
287                 "send packet: $F0#00"],
288                True)
289            self.expect_gdbremote_sequence()
290
291    def expect_error(self):
292        self.test_sequence.add_log_lines(
293            [{"direction": "send",
294             "regex": r"^\$F-1,[0-9a-fA-F]+#[0-9a-fA-F]{2}$"}],
295            True)
296        self.expect_gdbremote_sequence()
297
298    def vFile_test(self, read=False, write=False, append=False, trunc=False,
299                   creat=False, excl=False):
300        if read and write:
301            mode = 2
302        elif write:
303            mode = 1
304        else:  # read
305            mode = 0
306        if append:
307            mode |= 8
308        if creat:
309            mode |= 0x200
310        if trunc:
311            mode |= 0x400
312        if excl:
313            mode |= 0x800
314
315        old_umask = os.umask(0o22)
316        try:
317            server = self.connect_to_debug_monitor()
318        finally:
319            os.umask(old_umask)
320        self.assertIsNotNone(server)
321
322        # create a temporary file with some data
323        temp_path = self.getBuildArtifact("test")
324        test_data = 'some test data longer than 16 bytes\n'
325
326        if creat:
327            self.assertFalse(os.path.exists(temp_path))
328        else:
329            with open(temp_path, "wb") as temp_file:
330                temp_file.write(test_data.encode())
331
332        # open the file for reading
333        self.do_handshake()
334        self.test_sequence.add_log_lines(
335            ["read packet: $vFile:open:%s,%x,1a0#00" % (
336                binascii.b2a_hex(temp_path.encode()).decode(),
337                mode),
338             {"direction": "send",
339             "regex": r"^\$F([0-9a-fA-F]+)#[0-9a-fA-F]{2}$",
340             "capture": {1: "fd"}}],
341            True)
342
343        context = self.expect_gdbremote_sequence()
344        self.assertIsNotNone(context)
345        fd = int(context["fd"], 16)
346
347        # read data from the file
348        self.reset_test_sequence()
349        self.test_sequence.add_log_lines(
350            ["read packet: $vFile:pread:%x,11,10#00" % (fd,)],
351            True)
352        if read:
353            self.test_sequence.add_log_lines(
354                [{"direction": "send",
355                 "regex": r"^\$F([0-9a-fA-F]+);(.*)#[0-9a-fA-F]{2}$",
356                 "capture": {1: "size", 2: "data"}}],
357                True)
358            context = self.expect_gdbremote_sequence()
359            self.assertIsNotNone(context)
360            if trunc:
361                self.assertEqual(context["size"], "0")
362                self.assertEqual(context["data"], "")
363            else:
364                self.assertEqual(context["size"], "11")  # hex
365                self.assertEqual(context["data"], test_data[0x10:0x10 + 0x11])
366        else:
367            self.expect_error()
368
369        # another offset
370        if read and not trunc:
371            self.reset_test_sequence()
372            self.test_sequence.add_log_lines(
373                ["read packet: $vFile:pread:%x,6,3#00" % (fd,),
374                 {"direction": "send",
375                 "regex": r"^\$F([0-9a-fA-F]+);(.+)#[0-9a-fA-F]{2}$",
376                 "capture": {1: "size", 2: "data"}}],
377                True)
378            context = self.expect_gdbremote_sequence()
379            self.assertIsNotNone(context)
380            self.assertEqual(context["size"], "6")  # hex
381            self.assertEqual(context["data"], test_data[3:3 + 6])
382
383        # write data to the file
384        self.reset_test_sequence()
385        self.test_sequence.add_log_lines(
386            ["read packet: $vFile:pwrite:%x,6,somedata#00" % (fd,)],
387            True)
388        if write:
389            self.test_sequence.add_log_lines(
390                ["send packet: $F8#00"],
391                True)
392            self.expect_gdbremote_sequence()
393        else:
394            self.expect_error()
395
396        # close the file
397        self.reset_test_sequence()
398        self.test_sequence.add_log_lines(
399            ["read packet: $vFile:close:%x#00" % (fd,),
400             "send packet: $F0#00"],
401            True)
402        self.expect_gdbremote_sequence()
403
404        if write:
405            # check if the data was actually written
406            with open(temp_path, "rb") as temp_file:
407                if creat:
408                    self.assertEqual(os.fstat(temp_file.fileno()).st_mode & 0o7777,
409                                     0o640)
410                data = test_data.encode()
411                if trunc or creat:
412                    data = b"\0" * 6 + b"somedata"
413                elif append:
414                    data += b"somedata"
415                else:
416                    data = data[:6] + b"somedata" + data[6 + 8:]
417                self.assertEqual(temp_file.read(), data)
418