1414"""This tool build tar files from a list of inputs."""
1515
1616import argparse
17+ import base64
1718import os
1819import tarfile
1920import tempfile
@@ -36,27 +37,47 @@ def normpath(path):
3637 return os.path.normpath(path).replace(os.path.sep, '/')
3738
3839
40+ def parse_xattr(xattr_list):
41+ xattrs = {}
42+ if xattr_list:
43+ for item in xattr_list:
44+ idx = item.index("=")
45+ if idx < 0:
46+ raise ValueError("Unexpected xattr item format {} (want key=value)".format(item))
47+ key = item[:idx]
48+ raw = item[idx+1:]
49+ if raw.startswith("0x"):
50+ xattrs[key] = bytes.fromhex(raw[2:]).decode('latin-1')
51+ elif raw.startswith('"') and raw.endswith('"') and len(raw) >= 2:
52+ xattrs[key] = raw[1:-1]
53+ else:
54+ xattrs[key] = base64.b64decode(raw).decode('latin-1')
55+ return xattrs
56+
57+
3958class TarFile(object):
4059 """A class to generates a TAR file."""
4160
4261 class DebError(Exception):
4362 pass
4463
45- def __init__(self, output, directory, compression, compressor, default_mtime):
64+ def __init__(self, output, directory, compression, compressor, default_mtime, use_xattr ):
4665 # Directory prefix on all output paths
4766 d = directory.strip('/')
4867 self.directory = (d + '/') if d else None
4968 self.output = output
5069 self.compression = compression
5170 self.compressor = compressor
5271 self.default_mtime = default_mtime
72+ self.use_xattr = use_xattr
5373
5474 def __enter__(self):
5575 self.tarfile = tar_writer.TarFileWriter(
5676 self.output,
5777 self.compression,
5878 self.compressor,
59- default_mtime=self.default_mtime)
79+ default_mtime=self.default_mtime,
80+ use_xattr=self.use_xattr)
6081 return self
6182
6283 def __exit__(self, t, v, traceback):
@@ -74,7 +95,7 @@ def normalize_path(self, path: str) -> str:
7495 dest = self.directory + dest
7596 return dest
7697
77- def add_file(self, f, destfile, mode=None, ids=None, names=None):
98+ def add_file(self, f, destfile, mode=None, ids=None, names=None, xattr=None ):
7899 """Add a file to the tar file.
79100
80101 Args:
@@ -85,6 +106,7 @@ def add_file(self, f, destfile, mode=None, ids=None, names=None):
85106 ids: (uid, gid) for the file to set ownership
86107 names: (username, groupname) for the file to set ownership. `f` will be
87108 copied to `self.directory/destfile` in the layer.
109+ xattr: (strings) xattr list in getfattr-like output style.
88110 """
89111 dest = self.normalize_path(destfile)
90112 # If mode is unspecified, derive the mode from the file's mode.
@@ -101,14 +123,16 @@ def add_file(self, f, destfile, mode=None, ids=None, names=None):
101123 uid=ids[0],
102124 gid=ids[1],
103125 uname=names[0],
104- gname=names[1])
126+ gname=names[1],
127+ xattr=xattr)
105128
106129 def add_empty_file(self,
107130 destfile,
108131 mode=None,
109132 ids=None,
110133 names=None,
111- kind=tarfile.REGTYPE):
134+ kind=tarfile.REGTYPE,
135+ xattr=None):
112136 """Add a file to the tar file.
113137
114138 Args:
@@ -118,6 +142,7 @@ def add_empty_file(self,
118142 names: (username, groupname) for the file to set ownership.
119143 kind: type of the file. tarfile.DIRTYPE for directory. An empty file
120144 will be created as `destfile` in the layer.
145+ xattr: (strings) xattr list in getfattr-like output style.
121146 """
122147 dest = destfile.lstrip('/') # Remove leading slashes
123148 # If mode is unspecified, assume read only
@@ -136,9 +161,10 @@ def add_empty_file(self,
136161 uid=ids[0],
137162 gid=ids[1],
138163 uname=names[0],
139- gname=names[1])
164+ gname=names[1],
165+ xattr=xattr)
140166
141- def add_empty_dir(self, destpath, mode=None, ids=None, names=None):
167+ def add_empty_dir(self, destpath, mode=None, ids=None, names=None, xattr=None ):
142168 """Add a directory to the tar file.
143169
144170 Args:
@@ -147,9 +173,10 @@ def add_empty_dir(self, destpath, mode=None, ids=None, names=None):
147173 ids: (uid, gid) for the file to set ownership
148174 names: (username, groupname) for the file to set ownership. An empty
149175 file will be created as `destfile` in the layer.
176+ xattr: (strings) xattr list in getfattr-like output style.
150177 """
151178 self.add_empty_file(
152- destpath, mode=mode, ids=ids, names=names, kind=tarfile.DIRTYPE)
179+ destpath, mode=mode, ids=ids, names=names, kind=tarfile.DIRTYPE, xattr=xattr )
153180
154181 def add_tar(self, tar):
155182 """Merge a tar file into the destination tar file.
@@ -163,7 +190,7 @@ def add_tar(self, tar):
163190 """
164191 self.tarfile.add_tar(tar, numeric=True, prefix=self.directory)
165192
166- def add_link(self, symlink, destination, mode=None, ids=None, names=None):
193+ def add_link(self, symlink, destination, mode=None, ids=None, names=None, xattr=None ):
167194 """Add a symbolic link pointing to `destination`.
168195
169196 Args:
@@ -174,6 +201,7 @@ def add_link(self, symlink, destination, mode=None, ids=None, names=None):
174201 ids: (uid, gid) for the file to set ownership
175202 names: (username, groupname) for the file to set ownership. An empty
176203 file will be created as `destfile` in the layer.
204+ xattr: (strings) xattr list in getfattr-like output style.
177205 """
178206 dest = self.normalize_path(symlink)
179207 self.tarfile.add_file(
@@ -184,7 +212,8 @@ def add_link(self, symlink, destination, mode=None, ids=None, names=None):
184212 uid=ids[0],
185213 gid=ids[1],
186214 uname=names[0],
187- gname=names[1])
215+ gname=names[1],
216+ xattr=xattr)
188217
189218 def add_deb(self, deb):
190219 """Extract a debian package in the output tar.
@@ -211,7 +240,7 @@ def add_deb(self, deb):
211240 self.add_tar(tmpfile[1])
212241 os.remove(tmpfile[1])
213242
214- def add_tree(self, tree_top, destpath, mode=None, ids=None, names=None):
243+ def add_tree(self, tree_top, destpath, mode=None, ids=None, names=None, xattr=None ):
215244 """Add a tree artifact to the tar file.
216245
217246 Args:
@@ -222,6 +251,7 @@ def add_tree(self, tree_top, destpath, mode=None, ids=None, names=None):
222251 ids: (uid, gid) for the file to set ownership
223252 names: (username, groupname) for the file to set ownership. `f` will be
224253 copied to `self.directory/destfile` in the layer.
254+ xattr: (strings) xattr list in getfattr-like output style.
225255 """
226256 # We expect /-style paths.
227257 tree_top = normpath(tree_top)
@@ -374,6 +404,14 @@ def main():
374404 '--owner_names', action='append',
375405 help='Specify the owner names of individual files, e.g. '
376406 'path/to/file=root.root.')
407+ parser.add_argument(
408+ '--xattr', action='append',
409+ help='Specify the xattr of all files, e.g. '
410+ 'security.capability=0x0100000200000001000000000000000000000000')
411+ parser.add_argument(
412+ '--file_xattr', action='append',
413+ help='Specify the xattr of individual files, e.g. '
414+ 'path/to/file=security.capability=0x0100000200000001000000000000000000000000')
377415 parser.add_argument('--stamp_from', default='',
378416 help='File to find BUILD_STAMP in')
379417 options = parser.parse_args()
@@ -404,6 +442,18 @@ def main():
404442 f = f[1:]
405443 names_map[f] = (user, group)
406444
445+ default_xattr = parse_xattr(options.xattr)
446+ xattr_map = {}
447+ if options.file_xattr:
448+ xattr_by_file = {}
449+ for file_xattr in options.file_xattr:
450+ (f, xattr) = helpers.SplitNameValuePairAtSeparator(file_xattr, '=')
451+ xattrs = xattr_by_file.get(f, [])
452+ xattrs.append(xattr)
453+ xattr_by_file[f] = xattrs
454+ for f in xattr_by_file:
455+ xattr_map[f] = parse_xattr(xattr_by_file[f])
456+
407457 default_ids = options.owner.split('.', 1)
408458 default_ids = (int(default_ids[0]), int(default_ids[1]))
409459 ids_map = {}
@@ -425,7 +475,8 @@ def main():
425475 directory = helpers.GetFlagValue(options.directory),
426476 compression = options.compression,
427477 compressor = options.compressor,
428- default_mtime=default_mtime) as output:
478+ default_mtime=default_mtime,
479+ use_xattr=bool(xattr_map or default_xattr)) as output:
429480
430481 def file_attributes(filename):
431482 if filename.startswith('/'):
@@ -434,6 +485,7 @@ def file_attributes(filename):
434485 'mode': mode_map.get(filename, default_mode),
435486 'ids': ids_map.get(filename, default_ids),
436487 'names': names_map.get(filename, default_ownername),
488+ 'xattr': {**xattr_map.get(filename, {}), **default_xattr}
437489 }
438490
439491 if options.manifest:
0 commit comments