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