Skip to content

Commit 7128d42

Browse files
dpwejimmo
authored andcommitted
utarfile: Support creating/appending tar files.
This adds a utarfile-write extension package that adds the ability to create and append to tar files. Work done by Doug Ellis <[email protected]>. Signed-off-by: Jim Mussared <[email protected]>
1 parent a1b9aa9 commit 7128d42

File tree

8 files changed

+315
-101
lines changed

8 files changed

+315
-101
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
""" tar append writes additional files to the end of an existing tar file."""
2+
import os
3+
import sys
4+
import utarfile
5+
6+
if len(sys.argv) < 2:
7+
raise ValueError("Usage: %s appendfile.tar newinputfile1 ..." % sys.argv[0])
8+
9+
tarfile = sys.argv[1]
10+
if not tarfile.endswith(".tar"):
11+
raise ValueError("Filename %s does not end with .tar" % tarfile)
12+
13+
with utarfile.TarFile(sys.argv[1], "a") as t:
14+
for filename in sys.argv[2:]:
15+
t.add(filename)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
""" tar create writes a new tar file containing the specified files."""
2+
import sys
3+
import utarfile
4+
5+
if len(sys.argv) < 2:
6+
raise ValueError("Usage: %s outputfile.tar inputfile1 ..." % sys.argv[0])
7+
8+
tarfile = sys.argv[1]
9+
if not tarfile.endswith(".tar"):
10+
raise ValueError("Filename %s does not end with .tar" % tarfile)
11+
12+
with utarfile.TarFile(sys.argv[1], "w") as t:
13+
for filename in sys.argv[2:]:
14+
t.add(filename)
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
metadata(description="Adds write (create/append) support to utarfile.", version="0.1")
2+
3+
require("utarfile")
4+
package("utarfile")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Additions to the TarFile class to support creating and appending tar files.
2+
3+
The methods defined below in are injected into the TarFile class in the
4+
utarfile package.
5+
"""
6+
7+
import uctypes
8+
import os
9+
10+
# Extended subset of tar header fields including the ones we'll write.
11+
# http://www.gnu.org/software/tar/manual/html_node/Standard.html
12+
_TAR_HEADER = {
13+
"name": (uctypes.ARRAY | 0, uctypes.UINT8 | 100),
14+
"mode": (uctypes.ARRAY | 100, uctypes.UINT8 | 7),
15+
"uid": (uctypes.ARRAY | 108, uctypes.UINT8 | 7),
16+
"gid": (uctypes.ARRAY | 116, uctypes.UINT8 | 7),
17+
"size": (uctypes.ARRAY | 124, uctypes.UINT8 | 12),
18+
"mtime": (uctypes.ARRAY | 136, uctypes.UINT8 | 12),
19+
"chksum": (uctypes.ARRAY | 148, uctypes.UINT8 | 8),
20+
"typeflag": (uctypes.ARRAY | 156, uctypes.UINT8 | 1),
21+
}
22+
23+
24+
_NUL = const(b"\0") # the null character
25+
_BLOCKSIZE = const(512) # length of processing blocks
26+
_RECORDSIZE = const(_BLOCKSIZE * 20) # length of records
27+
28+
29+
# Write a string into a bytearray by copying each byte.
30+
def _setstring(b, s, maxlen):
31+
for i, c in enumerate(s.encode("utf-8")[:maxlen]):
32+
b[i] = c
33+
34+
35+
def _open_write(self, name, mode, fileobj):
36+
if mode == "w":
37+
if not fileobj:
38+
self.f = open(name, "wb")
39+
else:
40+
self.f = fileobj
41+
elif mode == "a":
42+
if not fileobj:
43+
self.f = open(name, "r+b")
44+
else:
45+
self.f = fileobj
46+
# Read through the existing file.
47+
while self.next():
48+
pass
49+
# Position at start of end block.
50+
self.f.seek(self.offset)
51+
else:
52+
raise ValueError("mode " + mode + " not supported.")
53+
54+
55+
def _close_write(self):
56+
# Must be called to complete writing a tar file.
57+
if self.mode == "w":
58+
self.f.write(_NUL * (_BLOCKSIZE * 2))
59+
self.offset += _BLOCKSIZE * 2
60+
remainder = self.offset % _RECORDSIZE
61+
if remainder:
62+
self.f.write(_NUL * (_RECORDSIZE - remainder))
63+
64+
65+
def addfile(self, tarinfo, fileobj=None):
66+
# Write the header: 100 bytes of name, 8 bytes of mode in octal...
67+
buf = bytearray(_BLOCKSIZE)
68+
name = tarinfo.name
69+
size = tarinfo.size
70+
if tarinfo.isdir():
71+
size = 0
72+
if not name.endswith("/"):
73+
name += "/"
74+
hdr = uctypes.struct(uctypes.addressof(buf), _TAR_HEADER, uctypes.LITTLE_ENDIAN)
75+
_setstring(hdr.name, name, 100)
76+
_setstring(hdr.mode, "%06o " % (tarinfo.mode & 0o7777), 7)
77+
_setstring(hdr.uid, "%06o " % tarinfo.uid, 7)
78+
_setstring(hdr.gid, "%06o " % tarinfo.gid, 7)
79+
_setstring(hdr.size, "%011o " % size, 12)
80+
_setstring(hdr.mtime, "%011o " % tarinfo.mtime, 12)
81+
_setstring(hdr.typeflag, "5" if tarinfo.isdir() else "0", 1)
82+
# Checksum is calculated with checksum field all blanks.
83+
_setstring(hdr.chksum, " " * 8, 8)
84+
# Calculate and insert the actual checksum.
85+
chksum = sum(buf)
86+
_setstring(hdr.chksum, "%06o\0" % chksum, 7)
87+
# Emit the header.
88+
self.f.write(buf)
89+
self.offset += len(buf)
90+
91+
# Copy the file contents, if any.
92+
if fileobj:
93+
n_bytes = self.f.write(fileobj.read())
94+
self.offset += n_bytes
95+
remains = -n_bytes & (_BLOCKSIZE - 1) # == 0b111111111
96+
if remains:
97+
buf = bytearray(remains)
98+
self.f.write(buf)
99+
self.offset += len(buf)
100+
101+
102+
def add(self, name, recursive=True):
103+
from . import TarInfo
104+
105+
tarinfo = TarInfo(name)
106+
try:
107+
stat = os.stat(name)
108+
tarinfo.mode = stat[0]
109+
tarinfo.uid = stat[4]
110+
tarinfo.gid = stat[5]
111+
tarinfo.size = stat[6]
112+
tarinfo.mtime = stat[8]
113+
except OSError:
114+
print("Cannot stat", name, " - skipping.")
115+
return
116+
if not (tarinfo.isdir() or tarinfo.isreg()):
117+
# We only accept directories or regular files.
118+
print(name, "is not a directory or regular file - skipping.")
119+
return
120+
if tarinfo.isdir():
121+
self.addfile(tarinfo)
122+
if recursive:
123+
for f in os.ilistdir(name):
124+
self.add(name + "/" + f[0], recursive)
125+
else: # type == REGTYPE
126+
self.addfile(tarinfo, open(name, "rb"))
+7-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import sys
22
import os
3-
import shutil
43
import utarfile
54

5+
if len(sys.argv) < 2:
6+
raise ValueError("Usage: %s inputfile.tar" % sys.argv[0])
7+
68
t = utarfile.TarFile(sys.argv[1])
79
for i in t:
8-
print(i)
10+
print(i.name)
911
if i.type == utarfile.DIRTYPE:
10-
os.makedirs(i.name)
12+
os.mkdir(i.name)
1113
else:
1214
f = t.extractfile(i)
13-
shutil.copyfileobj(f, open(i.name, "wb"))
15+
with open(i.name, "wb") as of:
16+
of.write(f.read())

micropython/utarfile/manifest.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
metadata(description="Lightweight tarfile module subset", version="0.3.2")
1+
metadata(description="Read-only implementation of Python's tarfile.", version="0.4.0")
22

33
# Originally written by Paul Sokolovsky.
44

5-
module("utarfile.py")
5+
package("utarfile")

micropython/utarfile/utarfile.py

-95
This file was deleted.

0 commit comments

Comments
 (0)