1#!/usr/bin/env python
2
3##
4# Copyright (c) 2007 - 2009 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] [-v] [-x] file [file ...]" % (name,)
42    print "       %s -p [-l] [-r] [-v] [-x] attr_name file [file ...]" % (name,)
43    print "       %s -w [-r] [-v] [-x] attr_name attr_value file [file ...]" % (name,)
44    print "       %s -d [-r] [-v] attr_name file [file ...]" % (name,)
45    print ""
46    print "The first form lists the names of all xattrs on the given file(s)."
47    print "The second form (-p) prints the value of the xattr attr_name."
48    print "The third form (-w) sets the value of the xattr attr_name to the string attr_value."
49    print "The fourth form (-d) deletes the xattr attr_name."
50    print ""
51    print "options:"
52    print "  -h: print this help"
53    print "  -r: act recursively"
54    print "  -l: print long format (attr_name: attr_value and hex output has offsets and"
55    print "      ascii representation)"
56    print "  -v: also print filename (automatic with -r and with multiple files)"
57    print "  -x: attr_value is represented as a hex string for input and output"
58
59    if e:
60        sys.exit(64)
61    else:
62        sys.exit(0)
63
64_FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
65
66def _dump(src, length=16, long=0):
67    result=[]
68    for i in xrange(0, len(src), length):
69        s = src[i:i+length]
70        hexa = ' '.join(["%02X"%ord(x) for x in s])
71	if long:
72	    printable = s.translate(_FILTER)
73	    result.append("%08X  %-*s |%s|" % (i, length*3, hexa, printable))
74	else:
75	    result.append(hexa)
76    if long:
77	result.append("%08x" % len(src))
78    return '\n'.join(result)
79
80status = 0
81
82def main():
83    global status
84    try:
85        (optargs, args) = getopt.getopt(sys.argv[1:], "hlpwdrvx", ["help"])
86    except getopt.GetoptError, e:
87        usage(e)
88
89    attr_name   = None
90    attr_value  = None
91    long_format = False
92    read        = False
93    hex         = False
94    write       = False
95    delete      = False
96    recursive   = False
97    verbose     = False
98
99    for opt, arg in optargs:
100        if opt in ("-h", "--help"):
101            usage()
102        elif opt == "-l":
103            long_format = True
104        elif opt == "-p":
105            read = True
106        elif opt == "-w":
107            write = True
108        elif opt == "-d":
109            delete = True
110        elif opt == "-r":
111            recursive = True
112        elif opt == "-v":
113            verbose = True
114        elif opt == "-x":
115            hex = True
116
117    if write or delete:
118        if long_format:
119            usage("-l not allowed with -w or -p")
120
121    if read or write or delete:
122        if not args:
123            usage("No attr_name")
124        attr_name = args.pop(0)
125
126    if write:
127        if not args:
128            usage("No attr_value")
129        attr_value = args.pop(0)
130
131    if len(args) > 1:
132        multiple_files = True
133    else:
134        multiple_files = False
135
136    for filename in args:
137        def doSinglePathChange(filename,attr_name,attr_value,read,write,delete,recursive):
138            def onError(e):
139                global status
140                if not os.path.exists(filename):
141                    sys.stderr.write("xattr: No such file: %s\n" % (filename,))
142                else:
143                    sys.stderr.write("xattr: " + str(e) + "\n")
144                status = 1
145
146	    def hasNulls(s):
147		try:
148		    if s.find('\0') >= 0:
149			return True
150		    return False
151		except UnicodeDecodeError:
152		    return True
153
154	    if verbose or recursive or multiple_files:
155		file_prefix = "%s: " % filename
156	    else:
157		file_prefix = ""
158
159            if recursive and os.path.isdir(filename) and not os.path.islink(filename):
160                listdir = os.listdir(filename)
161                for subfilename in listdir:
162                    doSinglePathChange(filename+'/'+subfilename,attr_name,attr_value,read,write,delete,recursive)
163
164            try:
165                attrs = xattr.xattr(filename)
166            except (IOError, OSError), e:
167                onError(e)
168                return
169
170            if write:
171                try:
172                    if hex:
173                        # strip whitespace and unhexlify
174                        attr_value = binascii.unhexlify(attr_value.translate(string.maketrans('', ''), string.whitespace))
175                    attrs[attr_name] = attr_value
176                except (IOError, OSError, TypeError), e:
177                    onError(e)
178                    return
179
180            elif delete:
181                try:
182                    del attrs[attr_name]
183                except (IOError, OSError), e:
184                    onError(e)
185                    return
186                except KeyError:
187                    if not recursive:
188                        onError("%s: No such xattr: %s" % (filename, attr_name,))
189                        return
190
191            else:
192                try:
193                    if read:
194                        attr_names = (attr_name,)
195                    else:
196                        attr_names = [a.encode('utf8') for a in attrs.keys()]
197                except (IOError, OSError), e:
198                    onError(e)
199                    return
200
201                for attr_name in attr_names:
202                    try:
203                        if long_format:
204                            if hex or hasNulls(attrs[attr_name]):
205				print "%s%s:" % (file_prefix, attr_name)
206				print _dump(attrs[attr_name], long=1)
207			    else:
208				print "%s%s: %s" % (file_prefix, attr_name, attrs[attr_name])
209                        else:
210                            if read:
211				if hex or hasNulls(attrs[attr_name]):
212				    if len(file_prefix) > 0:
213					print file_prefix
214				    print _dump(attrs[attr_name])
215				else:
216				    print "%s%s" % (file_prefix, attrs[attr_name])
217                            else:
218                                print "%s%s" % (file_prefix, attr_name)
219                    except (IOError, OSError), e:
220                        onError(e)
221                        return
222                    except KeyError:
223                        onError("%s: No such xattr: %s" % (filename, attr_name))
224                        return
225
226            return
227
228        doSinglePathChange(filename,attr_name,attr_value,read,write,delete,recursive)
229    sys.exit(status)
230
231if __name__ == "__main__":
232    main()
233