#!/usr/bin/python # ea: add, modify or remove extended attributes across multiple files # kw (c) 2003- Roey Katz, distributed under the following license: # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # revisions: # # 20031227 rk # initial release # # plans: # - add switch to skip over files which already have this attribute already set # # note: this and kw need de-kludgification. import sys,os,xattr,sets,glob from optparse import OptionParser def parseKeys(keystring): keyDict = {} errorString = None # parse keys keys = keystring.split(',') for k in keys: try: key,value = k.split('=') except ValueError: errorString="key string was not of the form key=value"; return (None,errorString) keyDict[key.strip()] = value.strip() return (keyDict,errorString) def doesThisMatch(filename, options): keys = options.queryDict.keys() # pass 1: see if file contains all the keys specified by the user attrs = xattr.listxattr(filename) if (sets.Set(keys) & sets.Set(attrs) == sets.Set(keys)): # pass 2: see if every value is the same for k in keys: if options.queryDict[k] != xattr.getxattr(filename, k): return False else: return False return True def showFile( filename, options ): print '# %s' % filename attrs = xattr.listxattr(filename) for n in attrs: print ' %s' % n,options.verbose*xattr.getxattr(filename, n) def changeAttr( filename, options ): if options.ignoredotfiles==True and os.path.basename(filename)[0]=='.': return # 1a. just showing? if options.show: showFile( filename, options ) return # 1b. adding? if options.addList != None: # bail out if user specified simulation only if options.nothing: return if options.verbose: print filename keysDict = options.addDict for k in keysDict.keys(): xattr.setxattr(filename, k, keysDict[k]) return # 2c. querying? elif options.queryList != None: if doesThisMatch(filename, options): print filename return # 2d. deleting? elif options.deleteListList != None: if options.nothing: return # bail out if user specified simulation only if options.verbose: print filename fileKeys = sets.Set( xattr.listxattr(filename) ) for k in list(options.deleteListList & fileKeys): try: xattr.removexattr(filename, k) except IOError: pass return if __name__=='__main__': parser = OptionParser() parser.add_option("-a", "--add", action="store", dest="addList", help="add attributes from given list") parser.add_option("-q", "--query", action="store", dest="queryList", help="query files for compliance with given key/val pairs"); parser.add_option("-d", "--delete", action="store", dest="deleteList", help="delete attribute (attribute list is of the form key1,key2,...,keyN)") parser.add_option("-r", "--recursive", action="store_true", dest="recursive", default=False, help="recursive operation", ) parser.add_option("-n", "--nothing", action="store_true", dest="nothing", default=False, help="just simulate; perform no action") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="print description of the actions taken to stdout") parser.add_option("-t", "--ignore-dotfiles", action="store_true", dest="ignoredotfiles", default=False, help="ignore files beginning with \".\"") (options, args) = parser.parse_args() options.show = False if options.addList == options.deleteList == options.queryList == None: options.show = True # begin script if len(args)==0: args = glob.glob('*') # quickly build some lists if options.queryList != None: # querying? options.queryDict,errorString = parseKeys( options.queryList ) if errorString != None: parser.error( errorString ) elif options.addList != None: # adding? options.addDict,errorString = parseKeys( options.addList ) if errorString != None: options.error(errorString) elif options.deleteList != None: # deleting? options.deleteListList = sets.Set( [i.strip() for i in options.deleteList.split(',')] ) for arg in args: # go through every argument if os.path.isdir(arg) and options.recursive==True: changeAttr(arg,options) for root, dirs, files in os.walk(arg): # get list of files at every dir level for f in files+dirs: # go through list of files fname = os.path.join(root,f) changeAttr(fname,options) else: changeAttr(arg, options)