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:
@@ -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 :
0 commit comments