Skip to content

Commit

Permalink
Add tar xattr support
Browse files Browse the repository at this point in the history
  • Loading branch information
bozaro committed Jun 5, 2023
1 parent ec4ad13 commit ba6e562
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 10 deletions.
59 changes: 52 additions & 7 deletions pkg/private/tar/build_tar.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""This tool build tar files from a list of inputs."""

import argparse
import base64
import os
import tarfile
import tempfile
Expand All @@ -36,27 +37,47 @@ def normpath(path):
return os.path.normpath(path).replace(os.path.sep, '/')


def parse_xattr(xattr_list):
xattrs = {}
if xattr_list:
for item in xattr_list:
idx = item.index("=")
if idx < 0:
raise ValueError("Unexpected xattr item format {} (want key=value)".format(item))
key = item[:idx]
raw = item[idx+1:]
if raw.startswith("0x"):
xattrs[key] = bytes.fromhex(raw[2:]).decode('latin-1')
elif raw.startswith('"') and raw.endswith('"') and len(raw) >= 2:
xattrs[key] = raw[1:-1]
else:
xattrs[key] = base64.b64decode(raw).decode('latin-1')
return xattrs


class TarFile(object):
"""A class to generates a TAR file."""

class DebError(Exception):
pass

def __init__(self, output, directory, compression, compressor, default_mtime):
def __init__(self, output, directory, compression, compressor, default_mtime, use_xattr):
# Directory prefix on all output paths
d = directory.strip('/')
self.directory = (d + '/') if d else None
self.output = output
self.compression = compression
self.compressor = compressor
self.default_mtime = default_mtime
self.use_xattr = use_xattr

def __enter__(self):
self.tarfile = tar_writer.TarFileWriter(
self.output,
self.compression,
self.compressor,
default_mtime=self.default_mtime)
default_mtime=self.default_mtime,
use_xattr=self.use_xattr)
return self

def __exit__(self, t, v, traceback):
Expand All @@ -74,7 +95,7 @@ def normalize_path(self, path: str) -> str:
dest = self.directory + dest
return dest

def add_file(self, f, destfile, mode=None, ids=None, names=None):
def add_file(self, f, destfile, mode=None, ids=None, names=None, xattr=None):
"""Add a file to the tar file.
Args:
Expand All @@ -101,7 +122,8 @@ def add_file(self, f, destfile, mode=None, ids=None, names=None):
uid=ids[0],
gid=ids[1],
uname=names[0],
gname=names[1])
gname=names[1],
xattr=xattr)

def add_empty_file(self,
destfile,
Expand Down Expand Up @@ -163,7 +185,7 @@ def add_tar(self, tar):
"""
self.tarfile.add_tar(tar, numeric=True, prefix=self.directory)

def add_link(self, symlink, destination, mode=None, ids=None, names=None):
def add_link(self, symlink, destination, mode=None, ids=None, names=None, xattr=None):
"""Add a symbolic link pointing to `destination`.
Args:
Expand All @@ -184,7 +206,8 @@ def add_link(self, symlink, destination, mode=None, ids=None, names=None):
uid=ids[0],
gid=ids[1],
uname=names[0],
gname=names[1])
gname=names[1],
xattr=xattr)

def add_deb(self, deb):
"""Extract a debian package in the output tar.
Expand Down Expand Up @@ -374,6 +397,14 @@ def main():
'--owner_names', action='append',
help='Specify the owner names of individual files, e.g. '
'path/to/file=root.root.')
parser.add_argument(
'--xattr', action='append',
help='Specify the xattr of all files, e.g. '
'security.capability=0x0100000200000001000000000000000000000000')
parser.add_argument(
'--file_xattr', action='append',
help='Specify the xattr of individual files, e.g. '
'path/to/file=security.capability=0x0100000200000001000000000000000000000000')
parser.add_argument('--stamp_from', default='',
help='File to find BUILD_STAMP in')
options = parser.parse_args()
Expand Down Expand Up @@ -404,6 +435,18 @@ def main():
f = f[1:]
names_map[f] = (user, group)

default_xattr = parse_xattr(options.xattr)
xattr_map = {}
if options.file_xattr:
xattr_by_file = {}
for file_xattr in options.file_xattr:
(f, xattr) = helpers.SplitNameValuePairAtSeparator(file_xattr, '=')
xattrs = xattr_by_file.get(f, [])
xattrs.append(xattr)
xattr_by_file[f] = xattrs
for f in xattr_by_file:
xattr_map[f] = parse_xattr(xattr_by_file[f])

default_ids = options.owner.split('.', 1)
default_ids = (int(default_ids[0]), int(default_ids[1]))
ids_map = {}
Expand All @@ -425,7 +468,8 @@ def main():
directory = helpers.GetFlagValue(options.directory),
compression = options.compression,
compressor = options.compressor,
default_mtime=default_mtime) as output:
default_mtime=default_mtime,
use_xattr=bool(xattr_map or default_xattr)) as output:

def file_attributes(filename):
if filename.startswith('/'):
Expand All @@ -434,6 +478,7 @@ def file_attributes(filename):
'mode': mode_map.get(filename, default_mode),
'ids': ids_map.get(filename, default_ids),
'names': names_map.get(filename, default_ownername),
'xattr': {**xattr_map.get(filename, {}), **default_xattr}
}

if options.manifest:
Expand Down
10 changes: 10 additions & 0 deletions pkg/private/tar/tar.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ def _pkg_tar_impl(ctx):
"--owner_names",
"%s=%s" % (_quote(key), ctx.attr.ownernames[key]),
)
if ctx.attr.xattr:
for item in ctx.attr.xattr:
args.add("--xattr", item)
if ctx.attr.xattrs:
for file in ctx.attr.xattrs:
xattr = ctx.attr.xattrs[file]
for item in xattr:
args.add("--file_xattr", "%s=%s" % (_quote(file), item))
for empty_file in ctx.attr.empty_files:
add_empty_file(content_map, empty_file, ctx.label)
for empty_dir in ctx.attr.empty_dirs or []:
Expand Down Expand Up @@ -264,6 +272,8 @@ pkg_tar_impl = rule(
"ownername": attr.string(default = "."),
"owners": attr.string_dict(),
"ownernames": attr.string_dict(),
"xattr": attr.string_list(),
"xattrs": attr.string_list_dict(),
"extension": attr.string(default = "tar"),
"symlinks": attr.string_dict(),
"empty_files": attr.string_list(),
Expand Down
17 changes: 14 additions & 3 deletions pkg/private/tar/tar_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def __init__(self,
compression='',
compressor='',
default_mtime=None,
preserve_tar_mtimes=True):
preserve_tar_mtimes=True,
use_xattr=False):
"""TarFileWriter wraps tarfile.open().
Args:
Expand All @@ -60,6 +61,7 @@ def __init__(self,
preserve_tar_mtimes: if true, keep file mtimes from input tar file.
"""
self.preserve_mtime = preserve_tar_mtimes
self.use_xattr = use_xattr
if default_mtime is None:
self.default_mtime = 0
elif default_mtime == 'portable':
Expand Down Expand Up @@ -98,7 +100,7 @@ def __init__(self,
self.name = name

self.tar = tarfile.open(name=name, mode=mode, fileobj=self.fileobj,
format=tarfile.GNU_FORMAT)
format=tarfile.PAX_FORMAT if use_xattr else tarfile.GNU_FORMAT)
self.members = set()
self.directories = set()
# Preseed the added directory list with things we should not add. If we
Expand Down Expand Up @@ -191,7 +193,8 @@ def add_file(self,
uname='',
gname='',
mtime=None,
mode=None):
mode=None,
xattr=None):
"""Add a file to the current tar.
Args:
Expand Down Expand Up @@ -234,6 +237,14 @@ def add_file(self,
tarinfo.mode = mode
if link:
tarinfo.linkname = link
if xattr:
if not self.use_xattr:
raise self.Error('This tar file was created without `use_xattr` flag but try to create file with xattr: {}, {}'.
format(name, xattr))
pax_headers = {}
for key in xattr:
pax_headers["SCHILY.xattr." + key] = xattr[key]
tarinfo.pax_headers = pax_headers
if content:
content_bytes = content.encode('utf-8')
tarinfo.size = len(content_bytes)
Expand Down

0 comments on commit ba6e562

Please sign in to comment.