Skip to content

Commit ba6e562

Browse files
committed
Add tar xattr support
1 parent ec4ad13 commit ba6e562

File tree

3 files changed

+76
-10
lines changed

3 files changed

+76
-10
lines changed

pkg/private/tar/build_tar.py

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"""This tool build tar files from a list of inputs."""
1515

1616
import argparse
17+
import base64
1718
import os
1819
import tarfile
1920
import 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+
3958
class 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:
@@ -101,7 +122,8 @@ def add_file(self, f, destfile, mode=None, ids=None, names=None):
101122
uid=ids[0],
102123
gid=ids[1],
103124
uname=names[0],
104-
gname=names[1])
125+
gname=names[1],
126+
xattr=xattr)
105127

106128
def add_empty_file(self,
107129
destfile,
@@ -163,7 +185,7 @@ def add_tar(self, tar):
163185
"""
164186
self.tarfile.add_tar(tar, numeric=True, prefix=self.directory)
165187

166-
def add_link(self, symlink, destination, mode=None, ids=None, names=None):
188+
def add_link(self, symlink, destination, mode=None, ids=None, names=None, xattr=None):
167189
"""Add a symbolic link pointing to `destination`.
168190
169191
Args:
@@ -184,7 +206,8 @@ def add_link(self, symlink, destination, mode=None, ids=None, names=None):
184206
uid=ids[0],
185207
gid=ids[1],
186208
uname=names[0],
187-
gname=names[1])
209+
gname=names[1],
210+
xattr=xattr)
188211

189212
def add_deb(self, deb):
190213
"""Extract a debian package in the output tar.
@@ -374,6 +397,14 @@ def main():
374397
'--owner_names', action='append',
375398
help='Specify the owner names of individual files, e.g. '
376399
'path/to/file=root.root.')
400+
parser.add_argument(
401+
'--xattr', action='append',
402+
help='Specify the xattr of all files, e.g. '
403+
'security.capability=0x0100000200000001000000000000000000000000')
404+
parser.add_argument(
405+
'--file_xattr', action='append',
406+
help='Specify the xattr of individual files, e.g. '
407+
'path/to/file=security.capability=0x0100000200000001000000000000000000000000')
377408
parser.add_argument('--stamp_from', default='',
378409
help='File to find BUILD_STAMP in')
379410
options = parser.parse_args()
@@ -404,6 +435,18 @@ def main():
404435
f = f[1:]
405436
names_map[f] = (user, group)
406437

438+
default_xattr = parse_xattr(options.xattr)
439+
xattr_map = {}
440+
if options.file_xattr:
441+
xattr_by_file = {}
442+
for file_xattr in options.file_xattr:
443+
(f, xattr) = helpers.SplitNameValuePairAtSeparator(file_xattr, '=')
444+
xattrs = xattr_by_file.get(f, [])
445+
xattrs.append(xattr)
446+
xattr_by_file[f] = xattrs
447+
for f in xattr_by_file:
448+
xattr_map[f] = parse_xattr(xattr_by_file[f])
449+
407450
default_ids = options.owner.split('.', 1)
408451
default_ids = (int(default_ids[0]), int(default_ids[1]))
409452
ids_map = {}
@@ -425,7 +468,8 @@ def main():
425468
directory = helpers.GetFlagValue(options.directory),
426469
compression = options.compression,
427470
compressor = options.compressor,
428-
default_mtime=default_mtime) as output:
471+
default_mtime=default_mtime,
472+
use_xattr=bool(xattr_map or default_xattr)) as output:
429473

430474
def file_attributes(filename):
431475
if filename.startswith('/'):
@@ -434,6 +478,7 @@ def file_attributes(filename):
434478
'mode': mode_map.get(filename, default_mode),
435479
'ids': ids_map.get(filename, default_ids),
436480
'names': names_map.get(filename, default_ownername),
481+
'xattr': {**xattr_map.get(filename, {}), **default_xattr}
437482
}
438483

439484
if options.manifest:

pkg/private/tar/tar.bzl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ def _pkg_tar_impl(ctx):
181181
"--owner_names",
182182
"%s=%s" % (_quote(key), ctx.attr.ownernames[key]),
183183
)
184+
if ctx.attr.xattr:
185+
for item in ctx.attr.xattr:
186+
args.add("--xattr", item)
187+
if ctx.attr.xattrs:
188+
for file in ctx.attr.xattrs:
189+
xattr = ctx.attr.xattrs[file]
190+
for item in xattr:
191+
args.add("--file_xattr", "%s=%s" % (_quote(file), item))
184192
for empty_file in ctx.attr.empty_files:
185193
add_empty_file(content_map, empty_file, ctx.label)
186194
for empty_dir in ctx.attr.empty_dirs or []:
@@ -264,6 +272,8 @@ pkg_tar_impl = rule(
264272
"ownername": attr.string(default = "."),
265273
"owners": attr.string_dict(),
266274
"ownernames": attr.string_dict(),
275+
"xattr": attr.string_list(),
276+
"xattrs": attr.string_list_dict(),
267277
"extension": attr.string(default = "tar"),
268278
"symlinks": attr.string_dict(),
269279
"empty_files": attr.string_list(),

pkg/private/tar/tar_writer.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def __init__(self,
4747
compression='',
4848
compressor='',
4949
default_mtime=None,
50-
preserve_tar_mtimes=True):
50+
preserve_tar_mtimes=True,
51+
use_xattr=False):
5152
"""TarFileWriter wraps tarfile.open().
5253
5354
Args:
@@ -60,6 +61,7 @@ def __init__(self,
6061
preserve_tar_mtimes: if true, keep file mtimes from input tar file.
6162
"""
6263
self.preserve_mtime = preserve_tar_mtimes
64+
self.use_xattr = use_xattr
6365
if default_mtime is None:
6466
self.default_mtime = 0
6567
elif default_mtime == 'portable':
@@ -98,7 +100,7 @@ def __init__(self,
98100
self.name = name
99101

100102
self.tar = tarfile.open(name=name, mode=mode, fileobj=self.fileobj,
101-
format=tarfile.GNU_FORMAT)
103+
format=tarfile.PAX_FORMAT if use_xattr else tarfile.GNU_FORMAT)
102104
self.members = set()
103105
self.directories = set()
104106
# Preseed the added directory list with things we should not add. If we
@@ -191,7 +193,8 @@ def add_file(self,
191193
uname='',
192194
gname='',
193195
mtime=None,
194-
mode=None):
196+
mode=None,
197+
xattr=None):
195198
"""Add a file to the current tar.
196199
197200
Args:
@@ -234,6 +237,14 @@ def add_file(self,
234237
tarinfo.mode = mode
235238
if link:
236239
tarinfo.linkname = link
240+
if xattr:
241+
if not self.use_xattr:
242+
raise self.Error('This tar file was created without `use_xattr` flag but try to create file with xattr: {}, {}'.
243+
format(name, xattr))
244+
pax_headers = {}
245+
for key in xattr:
246+
pax_headers["SCHILY.xattr." + key] = xattr[key]
247+
tarinfo.pax_headers = pax_headers
237248
if content:
238249
content_bytes = content.encode('utf-8')
239250
tarinfo.size = len(content_bytes)

0 commit comments

Comments
 (0)