Skip to content

Commit 674935b

Browse files
authored
bpo-18819: tarfile: only set device fields for device files (GH-18080)
The GNU docs describe the `devmajor` and `devminor` fields of the tar header struct only in the context of character and block special files, suggesting that in other cases they are not populated. Typical utilities behave accordingly; this patch teaches `tarfile` to do the same.
1 parent 4fac7ed commit 674935b

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed

Lib/tarfile.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,14 @@ def _create_header(info, format, encoding, errors):
930930
"""Return a header block. info is a dictionary with file
931931
information, format must be one of the *_FORMAT constants.
932932
"""
933+
has_device_fields = info.get("type") in (CHRTYPE, BLKTYPE)
934+
if has_device_fields:
935+
devmajor = itn(info.get("devmajor", 0), 8, format)
936+
devminor = itn(info.get("devminor", 0), 8, format)
937+
else:
938+
devmajor = stn("", 8, encoding, errors)
939+
devminor = stn("", 8, encoding, errors)
940+
933941
parts = [
934942
stn(info.get("name", ""), 100, encoding, errors),
935943
itn(info.get("mode", 0) & 0o7777, 8, format),
@@ -943,8 +951,8 @@ def _create_header(info, format, encoding, errors):
943951
info.get("magic", POSIX_MAGIC),
944952
stn(info.get("uname", ""), 32, encoding, errors),
945953
stn(info.get("gname", ""), 32, encoding, errors),
946-
itn(info.get("devmajor", 0), 8, format),
947-
itn(info.get("devminor", 0), 8, format),
954+
devmajor,
955+
devminor,
948956
stn(info.get("prefix", ""), 155, encoding, errors)
949957
]
950958

Lib/test/test_tarfile.py

+46
Original file line numberDiff line numberDiff line change
@@ -1549,6 +1549,52 @@ def test_longnamelink_1025(self):
15491549
("longlnk/" * 127) + "longlink_")
15501550

15511551

1552+
class DeviceHeaderTest(WriteTestBase, unittest.TestCase):
1553+
1554+
prefix = "w:"
1555+
1556+
def test_headers_written_only_for_device_files(self):
1557+
# Regression test for bpo-18819.
1558+
tempdir = os.path.join(TEMPDIR, "device_header_test")
1559+
os.mkdir(tempdir)
1560+
try:
1561+
tar = tarfile.open(tmpname, self.mode)
1562+
try:
1563+
input_blk = tarfile.TarInfo(name="my_block_device")
1564+
input_reg = tarfile.TarInfo(name="my_regular_file")
1565+
input_blk.type = tarfile.BLKTYPE
1566+
input_reg.type = tarfile.REGTYPE
1567+
tar.addfile(input_blk)
1568+
tar.addfile(input_reg)
1569+
finally:
1570+
tar.close()
1571+
1572+
# devmajor and devminor should be *interpreted* as 0 in both...
1573+
tar = tarfile.open(tmpname, "r")
1574+
try:
1575+
output_blk = tar.getmember("my_block_device")
1576+
output_reg = tar.getmember("my_regular_file")
1577+
finally:
1578+
tar.close()
1579+
self.assertEqual(output_blk.devmajor, 0)
1580+
self.assertEqual(output_blk.devminor, 0)
1581+
self.assertEqual(output_reg.devmajor, 0)
1582+
self.assertEqual(output_reg.devminor, 0)
1583+
1584+
# ...but the fields should not actually be set on regular files:
1585+
with open(tmpname, "rb") as infile:
1586+
buf = infile.read()
1587+
buf_blk = buf[output_blk.offset:output_blk.offset_data]
1588+
buf_reg = buf[output_reg.offset:output_reg.offset_data]
1589+
# See `struct posixheader` in GNU docs for byte offsets:
1590+
# <https://www.gnu.org/software/tar/manual/html_node/Standard.html>
1591+
device_headers = slice(329, 329 + 16)
1592+
self.assertEqual(buf_blk[device_headers], b"0000000\0" * 2)
1593+
self.assertEqual(buf_reg[device_headers], b"\0" * 16)
1594+
finally:
1595+
support.rmtree(tempdir)
1596+
1597+
15521598
class CreateTest(WriteTestBase, unittest.TestCase):
15531599

15541600
prefix = "x:"

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ Brad Chapman
286286
Greg Chapman
287287
Mitch Chapman
288288
Matt Chaput
289+
William Chargin
289290
Yogesh Chaudhari
290291
David Chaum
291292
Nicolas Chauvat
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Omit ``devmajor`` and ``devminor`` fields for non-device files in
2+
:mod:`tarfile` archives, enabling bit-for-bit compatibility with GNU
3+
``tar(1)``.

0 commit comments

Comments
 (0)