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