#!/usr/bin/python -O # # 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Usetool 0.3 - Copyright 2004 Christian Cernuschi # #---------------------------------------------------------------------------- import signal,commands,os.path,re,sys,shelve from output import xtermTitle,xtermTitleReset,yellow,bold,green,darkgreen,red,blue,darkred,darkblue,turquoise,nocolor ######################### version="0.3.20" ######################### ## Global Vars ### files location pkgdir = "/var/db/pkg" database_file=os.path.expanduser('~/.usetool_db') emergelog_file='/var/log/emerge.log' packageuse_file='/etc/portage/package.use' requested_stop=False database={} systemuse=[] package_use_dict={} options={"ignore-packageuse":False,"packageuse-output":False,"quiet":False,"database-sync":False, "find":False, "single":False, "not-using":False, "using":False, "no-version":False,"no-color":False, "difference":False,"color":False} ########################## def SIG_handler(signum,frame): """ for handling various SIG """ global requested_stop xtermTitleReset() requested_stop=True sys.exit(1) ########################## def forced_quit(): """ Exits and reset xterm title """ #printstderr(red('Forced quit!\n')) xtermTitleReset() sys.exit(1) ########################## def get_input(): """ sets options list and makes some preliminary check """ global opts,args args=[] opts=sys.argv[1:] for x in opts: if x=="--find" or x=="-f": options['find']=True elif x=="--single" or x=="-s": options['single']=True elif x=="--not-using" or x=="-nu": options['not-using']=True elif x=="--using" or x=="-u": options['using']=True elif x=="--no-version" or x=="-nv": options['no-version']=True elif x=="--difference" or x=="-d": options['difference']=True elif x=="--no-color" or x=="-nc": options['no-color']=True elif x=="--database-sync" or x=="-dbs": options['database-sync']=True elif x=="--quiet" or x=="-q": options['quiet']=True elif x=="--packageuse-output" or x=="-po": options['packageuse-output']=True elif x=="--ignore-packageuse" or x=="-ip": options['ignore-packageuse']=True elif x=="--color" or x=="-c": options['color']=True elif x=="--help" or x=="-h": usage() else: no_option=re.compile('^-') if no_option.search(x): wrong('Unknown option specified: ',x) else: args.append(x) ########################## def check_input(): """ checks input consistency between options """ errcode=0 for x in options: if x!='color' and x!='ignore-packageuse' and x!='packageuse-output' and x!='quiet' and x!="no-version" and x!="no-color" and x!="database-sync": if options[x]:errcode+=1 if errcode>1:wrong('You specified more than one search method!') if errcode==0 and not options['database-sync']:wrong('You didn\'t specify any search search method!') if (options['ignore-packageuse'] or options['packageuse-output']) and not options['difference']:wrong('-po and -ip options work only with -d search method!') if options['no-color'] and options['color']:wrong('You cannot specify --color and --no-color at the same time!') if options['packageuse-output'] and options['quiet']: options['quiet']=False printstderr('\n*** Warning: --quiet not necessary with --packageuse-output') otherwarning=True if options['packageuse-output'] and options['no-version']: if not otherwarning:print printstderr('*** Warning: --no-version not necessary with --packageuse-output') if options['difference'] or options['single']: if len(args) > 1:wrong('For this kind of search you cannot specify more than one argument!') if (options['single'] or options['using'] or options['not-using'] or options['find']) and len(args)==0:wrong('You should specify at least one argument!') if options['packageuse-output']:options['no-version']=True ########################## def wrong(string,string2=""): """ Shows error messages "string" and exits """ printstderr('\n\n') printstderr( yellow('Usetool '+version)+" - Tool for Use flags analysis") printstderr('') printstderr(red('!!! '+string)+string2) printstderr('\nType usetool -h for help screen') printstderr(' ') forced_quit() ########################## def usage(): """ prints out usage and exits """ printstderr('\n\n') printstderr( yellow('Usetool '+version)+" - Tool for Use flags analysis") printstderr('') printstderr( bold('Usage: ')+turquoise('usetool ')+green('SEARCH_METHOD [ OPTIONS ] [ DBSYNC ]')+turquoise(' [ ARGUMENTS ]')) printstderr('') printstderr(' usetool ( -u | -nu | -a | -s | -d ) [ -nc , -nv , -q , -po , -ip ] [ -dbs ] USE1 USE2 .. USE-N | PKGPATTERN') printstderr('') printstderr('') printstderr(' '+green('-h , --help')) printstderr(' shows this help screen') printstderr('') printstderr(turquoise('ARGUMENTS')) printstderr(' -One or more white space separated useflags for -f,-nu,-u search methods') printstderr(' -One useflag for -s search method') printstderr(' -None or a single package pattern for -d search method') printstderr('') printstderr(turquoise('SEARCH_METHOD')) printstderr(' '+green('-u , --using')) printstderr(' search for packages compiled with useflag(s) specified') printstderr('') printstderr(' '+green('-nu , --not-using')) printstderr(' search for packages not compiled with useflag(s) specified') printstderr('') printstderr(' '+green('-f , --find')) printstderr(' search for all installed packages that support useflag(s)') printstderr(' specified') printstderr('') printstderr(' '+green('-s , --single')) printstderr(' summary for a single useflag specified') printstderr(' It gives a more clear output for a single useflag') printstderr('') printstderr(' '+green('-d , --difference')) printstderr(' shows inconsistences between installed packages useflags and') printstderr(' the ones specified in your make.conf/package.use') printstderr(' If PKGPATTERN is given,analysis will be performed only') printstderr(' on packages that match') printstderr('') printstderr(turquoise('OPTIONS')) printstderr(' '+green('-c ,--color')) printstderr(' forced output with color') printstderr(' (if you want colors also when piping to other commands)') printstderr('') printstderr(' '+green('-nc ,--no-color')) printstderr(' output with no color') printstderr('') printstderr(' '+green('-nv ,--no-version')) printstderr(' output without package version') printstderr('') printstderr(' '+green('-q ,--quiet')) printstderr(' output reduced to essential') printstderr('') printstderr(' '+green('-po , --packageuse-output')) printstderr(' output for creating /etc/portage/package.use') printstderr(' (only for --difference search method)') printstderr('') printstderr(' '+green('-ip , --ignore-packageuse')) printstderr(' ignores /etc/portage/package.use') printstderr(' (only for --difference search method)') printstderr('') printstderr(turquoise('DBSYNC')) printstderr('') printstderr(' '+green('-dbs,--database-sync')) printstderr(' resyncs database against installed packages') printstderr(' (default autosync if run by root or portage user)') printstderr('') printstderr('') printstderr('feedback,bug reports to Christian Cernuschi - xchris@lifegate.it') printstderr('') forced_quit() ########################## def no_col(string): """ function used by report (-s option) when in no-color """ if string:return '('+string+')' else:return "" ########################## def read_system_use(): """ reads system global use and sets systemuse list """ global systemuse output_emerge = commands.getoutput('emerge info 2>/dev/null | grep USE') systemuse=output_emerge.split('\"')[1].split(' ') if not systemuse: printstderr(red('!!! Error: Could not fetch useflag settings!\n')) forced_quit() ########################## def read_package_use(): """ reads /etc/portage/package.use and sets package_use_dict dictionary """ global package_use_dict errormessage='' package_use_dict={} try: fd_pkguse=open(packageuse_file,'r') except: return False for x in fd_pkguse: row=x.replace('\n','').strip(' ') row_list=clean_void(row.split(' ')) if not row_list:continue pkt=row_list[0] row_list.remove(pkt) plus=[] minus=[] if pkt[0]=='>':errormessage=yellow('\n*** WARNING: >= syntax in /etc/portage/package.use not yet supported!\n*** Entries with >= syntax will not be checked!') for y in row_list: if y[0]=='-': minus.append(y[1:]) else: plus.append(y) if package_use_dict.has_key(pkt): for z in plus:package_use_dict[pkt][0].append(z) for z in minus:package_use_dict[pkt][1].append(z) else: package_use_dict[pkt]=[plus,minus] if errormessage: printstderr(errormessage) fd_pkguse.close() return True ########################## def in_package_use(pktname,useflag,mode): """ tells if a packaet has a specific use flag set 0 mode for positive flag,1 for negative """ # mode plus,minus pkt=pkgsplit(pktname)[0] if mode=='plus':selector=0 else:selector=1 if package_use_dict.has_key('='+pktname): if useflag in package_use_dict['='+pktname][selector]: return True elif package_use_dict.has_key(pkt): if useflag in package_use_dict[pkt][selector]: return True return False ########################## def get_use (dir): """ returns used and unused flags for a pkt """ supported=[] enviroment=[] used=[] unused=[] supported=read_use_file(dir,'IUSE') enviroment=read_use_file(dir,'USE') for x in supported: if x != "": if x in enviroment:used.append(x) else:unused.append(x) return used,unused ########################## def read_use_file(dir,use): """ returns a list containing (I)USE content """ USE='' filename=dir+"/"+use try: usefile=open (filename,'r') USE=usefile.read() USE=USE.replace('\n','') ## elimino \n finale usefile.close() except: pass list=[] list=USE.split(' ') return uniq(list) ########################## def lookup_use(pattern_list,mode): """ returns pkts that use or not use a specified list of useflags """ results={} for use in pattern_list: for row in database: if mode != "n": if use in database[row][0]: results[row]="+" if mode !="u": if use in database[row][1]: results[row]="-" return results ########################## def print_use_desc(uselist,shown_packages): """ prints out descriptions for useflags list passed """ uselist=uniq(uselist) try: fd_use_desc=open ("/usr/portage/profiles/use.desc",'r') usefile_content=fd_use_desc.read().split('\n') fd_use_desc.close() except: usefile_content=[""] try: fd_local_usedesc=open("/usr/portage/profiles/use.local.desc",'r') localusefile_content=fd_local_usedesc.read().split('\n') fd_local_usedesc.close() except: localusefile_content=[""] for x in uselist: found=False back=len(x).__str__() for y in usefile_content: pattern=re.compile("^"+x+" - ") result=pattern.match(y) if result: found=True start_pos=y.find('-')+2 print green(x)+":\033["+back+"D\033[19C",y[start_pos:] if not found: for y in localusefile_content: pattern=re.compile(".*:"+x+" - ") result=pattern.match(y) if result: package_name=y[:y.find(':')] if package_name in shown_packages: found=True print green(x)+":\033["+back+"D\033[19C",yellow("LOCAL USEFLAG:"),y if not found:print green(x)+":\033["+back+"D\033[19C","Missing description." ########################## def uniq(list): """ returns a uniq and sorted list from a given one """ results=[] for x in list: if x not in results and x:results.append(x) results.sort() return results ######################### def clean_void(list): """ cleans void elements of list""" results=[] for x in list: if x:results.append(x) return results ########################## def pkgsplit(string): """ returns a list with pkgname,version from a given string """ version_matcher=re.compile("(-[0-9])+.*") version_search=version_matcher.search(string) if version_search: pos=string.find(version_search.group(0)) return [string[:pos],string[pos+1:]] else: printstderr(red('!!! Critical Error: pkgslit:')+":"+string+":") forced_quit() ########################## def printstderr(string): """ prints out to stderr """ print >> sys.stderr,string ########################## def serialize(list,separator): """ return a serial string from a given list with a given separator """ string="" for x in list:string=string+" "+separator+x return string.strip(' ') ########################## def wantedname(string): """ return cat/pkg-ver or cat/pkg if no-version specified """ if options['no-version']:return pkgsplit(string)[0] else:return string ########################## def difference(pattern): """ --difference search method """ TOTAL_EXTRA=[] TOTAL_MISS=[] shown_packages=[] headline=True keys=database.keys() keys.sort() for row in keys: if requested_stop:forced_quit() if row.find(pattern[0])!=-1: EXTRA=[] MISSING=[] for x in database[row][1]: if (x in systemuse and not in_package_use(row,x,'minus')) or (x not in systemuse and in_package_use(row,x,'plus')): TOTAL_MISS.append(x) MISSING.append(x) shown_packages.append(pkgsplit(row)[0]) for x in database[row][0]: if (x not in systemuse and not in_package_use(row,x,'plus')) or (x in systemuse and in_package_use(row,x,'minus')): TOTAL_EXTRA.append(x) EXTRA.append(x) shown_packages.append(pkgsplit(row)[0]) if EXTRA or MISSING: if headline and not (not sys.stdout.isatty() and (options['quiet'] or options['packageuse-output'])): print headline=False if not EXTRA:space='' else:space=' ' if options['packageuse-output']:plus_separator='' else:plus_separator='+' totaluse=red(serialize(EXTRA,plus_separator))+space+blue(serialize(MISSING,'-')) if options['quiet']: print darkgreen('='+wantedname(row)) elif options['packageuse-output']: print darkgreen(wantedname(row))+" "+totaluse.strip(' ') else: print darkgreen('='+wantedname(row))+": "+totaluse.strip(' ') TOTAL_EXTRA=uniq(TOTAL_EXTRA) TOTAL_MISS=uniq(TOTAL_MISS) if (TOTAL_EXTRA or TOTAL_MISS) and sys.stdout.isatty():print if (TOTAL_EXTRA or TOTAL_MISS) and not options['quiet'] and not options['packageuse-output']: print print yellow("Referring to your system USE flag settings") if TOTAL_EXTRA:print yellow("Found packages with these extra use flags: ")+red(serialize(TOTAL_EXTRA,'')) if TOTAL_MISS:print yellow("Found packages with these missing use flags: ")+blue(serialize(TOTAL_MISS,'')) print "\n" if not options["quiet"]: print_use_desc(uniq(TOTAL_EXTRA+TOTAL_MISS),shown_packages) print if not (TOTAL_EXTRA or TOTAL_MISS): if pattern[0]: printstderr(yellow('\n!!! No discordant installed package found for specified Pattern: ')+pattern[0]+'\n') else: printstderr(green('\n*** Your system is compiled according to your settings.Well done!\n')) ########################## def make_multi_report(args,mode): """ -a,-u.nu search method ,results passed by lookup_use,args are useflags""" all_use_dict={} nocorrespondency=False args_for_description=[] for use in args: if requested_stop:forced_quit() all_use_dict[use]=lookup_use([use],mode) if not all_use_dict[use]: if not nocorrespondency:printstderr('') printstderr(yellow('>>> No installed package found for useflag: ')+use) nocorrespondency=True else: args_for_description.append(use) if nocorrespondency:printstderr('') ## we build a package list packages_list=[] for use in all_use_dict: if requested_stop:forced_quit() for packages in all_use_dict[use]: packages_list.append(packages) packages_list=uniq(packages_list) results={} for package in packages_list: positive=[] negative=[] for use in all_use_dict: if requested_stop:forced_quit() if all_use_dict[use].has_key(package): if all_use_dict[use][package]=="+": positive.append(use) else: negative.append(use) results[package]=[uniq(positive),uniq(negative)] keys=results.keys() keys.sort() if keys: if not (not sys.stdout.isatty() and options['quiet']):print for package in keys: name=wantedname(package) positive=results[package][0] negative=results[package][1] if not positive:space='' else:space=' ' totaluse=red(serialize(positive,'+'))+space+blue(serialize(negative,'-')) if options['quiet']: print darkgreen("="+name) else: print darkgreen("="+name)+":",totaluse.strip(' ') if not (not sys.stdout.isatty() and options['quiet']):print if not options["quiet"]: shown_packages=[] for package in packages_list: shown_packages.append(pkgsplit(package)[0]) print_use_desc(args_for_description,shown_packages) print ########################## def make_single_report(args): """ --single search method,results passed by lookup_use,args are useflags""" results=lookup_use (args,"s") using=[] notusing=[] for x in results: if results[x]=="+": using.append("="+x) else: notusing.append("="+x) if results: using.sort() notusing.sort() if not (not sys.stdout.isatty() and options['quiet']):print for x in using: print darkgreen(wantedname(x)) for x in notusing: print notcompiled(wantedname(x)) if not (not sys.stdout.isatty() and options['quiet']):print if not options['quiet']: if not options['no-color']: print yellow("Entries in"),darkgreen("GREEN"),yellow("have been compiled with specific useflag:"),args[0] print yellow("Entries in"),darkred("RED"),yellow("have not been compiled with specific useflag:"),args[0] else: print "Entries not in parenthesis have been compiled with specific useflag:",args[0] print "Entries in parenthesis have not been compiled with specific useflag:",args[0] print total_packages=using+notusing shown_packages=[] for package in total_packages: shown_packages.append(pkgsplit(package.strip('='))[0]) print_use_desc(args,shown_packages) print else: printstderr(yellow('\n>>> No installed package found for useflag: ')+args[0]) printstderr('') ########################## def db_action(mode): """ saves or restore db upon mode """ global database,shelf if mode=='save': try: printstderr('>>> Saving database in '+database_file) shelf=shelve.open(database_file) shelf['database']=database shelf.close() except: printstderr(red('!!! Error Saving Database\n')) try: printstderr('>>> Saving settings for AutoSync mode') try: pre_seek=db_dump('read_dump_point')[1] except: pre_seek=0 db_dump('save_dump_point',pre_seek) except: printstderr(red('!!! Error Saving settings for AutoSync mode\n')) elif mode=='restore': try: shelf=shelve.open(database_file) database=shelf['database'] shelf.close() except: printstderr(red('!!! Critical Error: Loading DB')) forced_quit() ########################## def db_build(arg,dirname,files): """ builds entire db """ for row in files: if requested_stop:forced_quit() if row.find('IUSE')!=-1: if os.path.getsize(dirname+"/"+row)>1: row=dirname+'/'+row medium_slash_point=dirname.rfind('/') start_point=dirname[:medium_slash_point].rfind('/')+1 package=dirname[start_point:] database[package]=get_use(dirname) #else: # printstderr(red("Skipping:"+dirname+" "+ os.path.getsize(dirname+"/"+row).__str__())) ######################### def db_manager(mode): """ database manager,for choosing when to update db """ if mode=='build': printstderr (">>> Building database..") os.path.walk(pkgdir,db_build,'') db_action('save') elif mode=='choose': if options['database-sync']: db_manager('build') else: if db_dump('has_changed'): printstderr('>>> Database resync needed!') db_manager('build') else: db_action('restore') if not database: printstderr(red('!!! Critical Error: Cannot read your /var/db/pkg')) forced_quit() ########################## def db_dump(action,pre_seek=0): """ core function for auto-sync database detect""" __candidatematcher__ = re.compile("(^[0-9]+: +::: completed emerge|^[0-9]+: + >>> unmerge success:|^[0-9]+: +=== inject:)") if action=='calculate_dump': last=False try: fd_emerge=open(emergelog_file,'r') if pre_seek!=0: if os.path.getsize(emergelog_file)>pre_seek: fd_emerge.seek(pre_seek) else: #printstderr('*** Need to rescan /var/log/emerge.log') db_dump('save_dump_point',0) last_seek=pre_seek x="--" while x: x=fd_emerge.readline() if requested_stop:forced_quit() if __candidatematcher__.match(x): pre_seek=last_seek last_seek=fd_emerge.tell() last=x fd_emerge.close() except: printstderr (red('\n*** Auto-Sync Disabled as you are not part of portage group')) return "---------","disabled" return last,pre_seek elif action=='save_dump_point': last,pre_seek=db_dump('calculate_dump',pre_seek) if last: try: shelf=shelve.open(database_file) shelf['dump']=last shelf['pre_seek']=pre_seek shelf.close() return True except: printstderr(red('!!! Error saving dump point for Auto-sync mode\n')) return False return last elif action=='read_dump_point': try: shelf=shelve.open(database_file) last=shelf['dump'] pre_seek=shelf['pre_seek'] shelf.close() return last,pre_seek except: return False,0 elif action=='has_changed': database_dump_point,pre_seek=db_dump('read_dump_point') if database_dump_point: actual_dump_point=db_dump('calculate_dump',pre_seek)[0] if actual_dump_point: if actual_dump_point==database_dump_point: return False else:return True else:return True else:return True ########################## MAIN ########################## def main(): """ Main function """ xtermTitle('Usetool -'+version) global notcompiled get_input() check_input() if not sys.stdout.isatty() and not options['color']: options['no-color']=True if options["no-color"]: nocolor() notcompiled=no_col else: notcompiled=darkred if options["difference"]: read_system_use() if not options['ignore-packageuse']: read_package_use() db_manager('choose') difference(args or ['']) elif options["find"]: db_manager('choose') make_multi_report(args,'a') elif options["using"]: db_manager('choose') make_multi_report(args,'u') elif options["not-using"]: db_manager('choose') make_multi_report(args,'n') elif options["single"]: db_manager('choose') make_single_report(args) elif options['database-sync']: db_manager('build') else: usage() xtermTitleReset() if __name__ == '__main__': signal.signal(signal.SIGINT,SIG_handler) signal.signal(signal.SIGTERM,SIG_handler) signal.signal(signal.SIGABRT,SIG_handler) main()