1#!/usr/bin/env python 2 3## 4# Copyright (c) 2007 - 2010, 2013 Apple Inc. 5# 6# This is the MIT license. This software may also be distributed under the 7# same terms as Python (the PSF license). 8# 9# Permission is hereby granted, free of charge, to any person obtaining a 10# copy of this software and associated documentation files (the "Software"), 11# to deal in the Software without restriction, including without limitation 12# the rights to use, copy, modify, merge, publish, distribute, sublicense, 13# and/or sell copies of the Software, and to permit persons to whom the 14# Software is furnished to do so, subject to the following conditions: 15# 16# The above copyright notice and this permission notice shall be included in 17# all copies or substantial portions of the Software. 18# 19# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 25# IN THE SOFTWARE. 26## 27 28import sys 29import os 30import getopt 31import xattr 32import binascii 33import string 34 35def usage(e=None): 36 if e: 37 print e 38 print "" 39 40 name = os.path.basename(sys.argv[0]) 41 print "usage: %s [-l] [-r] [-s] [-v] [-x] file [file ...]" % (name,) 42 print " %s -p [-l] [-r] [-s] [-v] [-x] attr_name file [file ...]" % (name,) 43 print " %s -w [-r] [-s] [-x] attr_name attr_value file [file ...]" % (name,) 44 print " %s -d [-r] [-s] attr_name file [file ...]" % (name,) 45 print " %s -c [-r] [-s] file [file ...]" % (name,) 46 print "" 47 print "The first form lists the names of all xattrs on the given file(s)." 48 print "The second form (-p) prints the value of the xattr attr_name." 49 print "The third form (-w) sets the value of the xattr attr_name to the string attr_value." 50 print "The fourth form (-d) deletes the xattr attr_name." 51 print "The fifth form (-c) deletes (clears) all xattrs." 52 print "" 53 print "options:" 54 print " -h: print this help" 55 print " -l: print long format (attr_name: attr_value and hex output has offsets and" 56 print " ascii representation)" 57 print " -r: act recursively" 58 print " -s: act on the symbolic link itself rather than what the link points to" 59 print " -v: also print filename (automatic with -r and with multiple files)" 60 print " -x: attr_value is represented as a hex string for input and output" 61 62 if e: 63 sys.exit(64) 64 else: 65 sys.exit(0) 66 67#ifdef PY3K 68_FILTER=b''.join(x >= 32 and x < 127 and chr(x).encode('utf-8') or b'.' for x in range(256)) 69_WHITESPACE=b''.join(chr(x).encode() for x in range(33)) 70#else 71_FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) 72#endif 73 74def _dump(src, length=16, longfmt=0): 75 result=[] 76 for i in xrange(0, len(src), length): 77 s = src[i:i+length] 78#ifdef PY3K 79 hexa = ' '.join(["%02X"%x for x in s]) 80#else 81 hexa = ' '.join(["%02X"%ord(x) for x in s]) 82#endif 83 if longfmt: 84#ifdef PY3K 85 printable = s.translate(_FILTER).decode('utf-8') 86#else 87 printable = s.translate(_FILTER) 88#endif 89 result.append("%08X %-*s |%s|" % (i, length*3, hexa, printable)) 90 else: 91 result.append(hexa) 92 if longfmt: 93 result.append("%08x" % len(src)) 94 return '\n'.join(result) 95 96status = 0 97 98def main(): 99 global status 100 try: 101 (optargs, args) = getopt.getopt(sys.argv[1:], "cdhlprsvwx", ["help"]) 102 except getopt.GetoptError, e: 103 usage(e) 104 105 attr_name = None 106 attr_value = None 107 clear = False 108 delete = False 109 dohex = False 110 long_format = False 111 main_arg = None 112 options = 0 # 0 or XATTR_NOFOLLOW 113 read = False 114 recursive = False 115 verbose = False 116 write = False 117 118 for opt, arg in optargs: 119 if opt == "-c": 120 if main_arg and main_arg != opt: 121 usage("Can't specify %s with %s" % (main_arg, opt)) 122 main_arg = opt 123 clear = True 124 elif opt == "-d": 125 if main_arg and main_arg != opt: 126 usage("Can't specify %s with %s" % (main_arg, opt)) 127 main_arg = opt 128 delete = True 129 elif opt in ("-h", "--help"): 130 usage() 131 elif opt == "-l": 132 long_format = True 133 elif opt == "-p": 134 if main_arg and main_arg != opt: 135 usage("Can't specify %s with %s" % (main_arg, opt)) 136 main_arg = opt 137 read = True 138 elif opt == "-r": 139 recursive = True 140 elif opt == "-s": 141 options = xattr.XATTR_NOFOLLOW 142 elif opt == "-v": 143 verbose = True 144 elif opt == "-w": 145 if main_arg and main_arg != opt: 146 usage("Can't specify %s with %s" % (main_arg, opt)) 147 main_arg = opt 148 write = True 149 elif opt == "-x": 150 dohex = True 151 152 if write or delete: 153 if long_format: 154 usage("-l not allowed with -w or -p") 155 156 if read or write or delete: 157 if not args: 158 usage("No attr_name") 159#ifdef PY3K 160 # using os.fsencode() to retrieve the original bytes of the argument 161 attr_name = os.fsencode(args.pop(0)) 162#else 163 attr_name = args.pop(0) 164#endif 165 166 if write: 167 if not args: 168 usage("No attr_value") 169#ifdef PY3K 170 # using os.fsencode() to retrieve the original bytes of the argument 171 attr_value = os.fsencode(args.pop(0)) 172#else 173 attr_value = args.pop(0) 174#endif 175 176 if len(args) > 1: 177 multiple_files = True 178 else: 179 multiple_files = False 180 181 for filename in args: 182 def doSinglePathChange(filename,attr_name,attr_value,read,write,delete,recursive): 183 def onError(e): 184 global status 185 if not os.path.exists(filename): 186 sys.stderr.write("xattr: No such file: %s\n" % (filename,)) 187 else: 188 sys.stderr.write("xattr: " + str(e) + "\n") 189 status = 1 190 191 def notPrintable(s): 192 try: 193 s.decode('utf-8') 194#ifdef PY3K 195 if s.find(b'\0') >= 0: 196#else 197 if s.find('\0') >= 0: 198#endif 199 return True 200 return False 201 except UnicodeError: 202 return True 203 204 if verbose or recursive or multiple_files: 205 file_prefix = "%s: " % filename 206 else: 207 file_prefix = "" 208 209 if recursive and os.path.isdir(filename) and not os.path.islink(filename): 210 listdir = os.listdir(filename) 211 for subfilename in listdir: 212 doSinglePathChange(filename+'/'+subfilename,attr_name,attr_value,read,write,delete,recursive) 213 214 try: 215 attrs = xattr.xattr(filename, options) 216 except (IOError, OSError), e: 217 onError(e) 218 return 219 220 if write: 221 try: 222 if dohex: 223 # strip whitespace and unhexlify 224#ifdef PY3K 225 attr_value = binascii.unhexlify(attr_value.translate(None, _WHITESPACE)) 226#else 227 attr_value = binascii.unhexlify(attr_value.translate(string.maketrans('', ''), string.whitespace)) 228#endif 229 attrs[attr_name] = attr_value 230 except (IOError, OSError, TypeError), e: 231 onError(e) 232 return 233 234 elif delete: 235 try: 236 del attrs[attr_name] 237 except (IOError, OSError), e: 238 onError(e) 239 return 240 except KeyError: 241 if not recursive: 242#ifdef PY3K 243 onError("%s: No such xattr: %s" % (filename, attr_name.decode('utf-8'),)) 244#else 245 onError("%s: No such xattr: %s" % (filename, attr_name,)) 246#endif 247 return 248 249 elif clear: 250 try: 251 attrs.clear() 252 except (IOError, OSError), e: 253 onError(e) 254 return 255 256 else: 257 try: 258 if read: 259 attr_names = (attr_name,) 260 else: 261#ifdef PY3K 262 attr_names = [a for a in attrs.keys()] 263#else 264 attr_names = [a.encode('utf-8') for a in attrs.keys()] 265#endif 266 except (IOError, OSError), e: 267 onError(e) 268 return 269 270 for attr_name in attr_names: 271 try: 272 if long_format: 273 if dohex or notPrintable(attrs[attr_name]): 274#ifdef PY3K 275 print "%s%s:" % (file_prefix, attr_name.decode('utf-8')) 276#else 277 print "%s%s:" % (file_prefix, attr_name) 278#endif 279 print _dump(attrs[attr_name], longfmt=1) 280 else: 281#ifdef PY3K 282 print "%s%s: %s" % (file_prefix, attr_name.decode('utf-8'), attrs[attr_name].decode('utf-8')) 283#else 284 print "%s%s: %s" % (file_prefix, attr_name, attrs[attr_name]) 285#endif 286 else: 287 if read: 288 if dohex or notPrintable(attrs[attr_name]): 289 if len(file_prefix) > 0: 290 print file_prefix 291 print _dump(attrs[attr_name]) 292 else: 293#ifdef PY3K 294 print "%s%s" % (file_prefix, attrs[attr_name].decode('utf-8')) 295#else 296 print "%s%s" % (file_prefix, attrs[attr_name]) 297#endif 298 else: 299#ifdef PY3K 300 print "%s%s" % (file_prefix, attr_name.decode('utf-8')) 301#else 302 print "%s%s" % (file_prefix, attr_name) 303#endif 304 except (IOError, OSError), e: 305 onError(e) 306 return 307 except KeyError: 308#ifdef PY3K 309 onError("%s: No such xattr: %s" % (filename, attr_name.decode('utf-8'))) 310#else 311 onError("%s: No such xattr: %s" % (filename, attr_name)) 312#endif 313 return 314 315 return 316 317 doSinglePathChange(filename,attr_name,attr_value,read,write,delete,recursive) 318 sys.exit(status) 319 320if __name__ == "__main__": 321 main() 322