#!/usr/bin/env python # -*- coding: iso-8859-1 -*- """ Saves shared libraries and configuration files prior to updating ports. This step can prove itself very valuable in case something goes wrong while updating ports. """ # Author: Cyrille Szymanski # Thanks to: Michel Talon # Licence: BSD (revised) date = 'March 2007' version = 'save_pkg v0.7' import os, os.path, sys, re, copy import tarfile import getopt options = 'afhvxX' # option switches parameters = 'o:' # parameters default_switches = { 'all' : False, 'regex' : False, 'extended' : False, 'file_switch' : False, 'outdir' : '.', } def usage(): spaces = ' '*(len(sys.argv[0])+7) print 'Usage: %s' % sys.argv[0], print '[-fhvxX] [-o outdir] -a | pkg-name ...' print print ' pkg-name ...' print ' The named packages are saved.' print print ' -a Save all currently installed packages.' print print ' -f Force a call to file(1) on every package file' print ' installed. Only do this if you think shared libraries' print ' might be omitted because they have a non-standard' print ' extension. Note that this option is for the paranoïd.' print print ' -h Print this help text.' print print ' -o outdir' print ' Place saved gzipped files in outdir.' print print ' -v Print the program version.' print print ' -x Treat the pkg-name as a regular expression and save' print ' files for packages whose names match that regular' print ' expression.' print print ' -X Like -x, but treats the pkg-name as an extended regular' print ' expression.' # This section defines rules to exclude/include files in the backup archive. # Rules are as follows: # 1. Files which match an accept rule are saved. # 2. Unless the -f option is used, files which match # an ignore rule are discarded. # 3. Files are tested against file(1), those matching # "shared library" are saved. # Files ending with these extensions are discarded. ignore_list = [ '.gz', '.tgz', '.bz2', '.htm', '.h', '.html', '.xml', '.css', '.c', '.cc', '.cxx', '.gif', '.jpg', '.jpeg', '.png', '.svg', '.pdf', '.ps', '.mp3', '.ogg', '.wav', '.txt', '.pl', '.py', '.pyc', '.pyo', '.rb', '.sh', '.csh', '.tex', '.info', '.docbook', '.a', '.la', '.desktop', ] # Files ending with these extensions are automatically saved. save_list = [ '.conf', # save configuration files '.cf', # save configuration files '.cfg', # save configuration files ] # Files whose path contains these expressions are discarded. path_ignore_list = [ '/man/', # exclude man pages '/share/examples/', # exclude samples '/bin/', # exclude executables '/include', # exclude include files ] # Files whose path contains these expressions are saved. path_save_list = [ '/etc/', # save configuration files ] def list_ports( args, all, regex, extended ): """ get the list of ports installed on the machine """ args_str = ' '.join(args) if regex: if extended: opts = '-X ' else: opts = '-x ' elif all: opts = '-a' else: opts = '' pipe = os.popen( 'pkg_info -E %s %s' % (opts, args_str), 'r' ) ports_str = pipe.read().strip() pipe.close() if len(ports_str)==0: print 'Warning: pkg_info -E %s %s returned no result' % (opts, args_str) ports = [] else: ports = ports_str.split() return ports def list_port_files( portname, regex, extended ): """ get the list of files which have been installed by a port """ print '... fetching the file list' opts = '' if regex: if extended: opts += '-X ' else: opts += '-x ' pipe = os.popen( 'pkg_info -q -L %s %s' % (opts, portname), 'r' ) portfiles_str = pipe.read().strip() pipe.close() if len(portfiles_str)==0: print 'Warning: pkg_info -q -L %s %s returned no result' % (opts, portname) portfiles = [] else: portfiles = portfiles_str.split() return portfiles def tag_file_to_save( portfile, try_file ): # always save files whose path contains these expressions for sav in path_save_list: if portfile.find(sav)>=0: return True # always save files with these extensions for sav in save_list: if portfile.endswith(sav): return True # exclude some well known files if not try_file: # always exclude files whose path contains these expressions for ign in path_ignore_list: if portfile.find(ign)>=0: return False # always exclude files with these extensions for ign in ignore_list: if portfile.endswith(ign): return False return None def filter_files_to_save( portfiles, try_file ): """ sort files in three bins: do_save, do_not_save, do_not_know """ do_save, do_not_save, do_not_know = [], [], [] for portfile in portfiles: # sort in three bins status = tag_file_to_save( portfile, try_file ) if status is None: do_not_know.append( portfile ) elif status is True: do_save.append( portfile ) else: do_not_save.append( portfile ) return do_save, do_not_save, do_not_know def list_files_to_save( portfiles, try_file ): """ select the files to save """ print '... filtering important files (%d)' % len(portfiles) # filter files we already know want to keep or throw away for sure do_save, do_not_save, do_not_know = filter_files_to_save( portfiles, try_file ) # analyse unknown files files_to_check = do_not_know #[] # DEBUG (pipe_w, pipe_r) = os.popen2( 'file -nbf -', 't' ) for portfile in files_to_check: # call file(1) pipe_w.write( '%s\n' % portfile ) pipe_w.flush() filetype_str = pipe_r.readline() # save shared libraries if filetype_str.find( 'shared object' )>=0: do_save.append( portfile ) pipe_w.close() pipe_r.close() return do_save def zip_files( infiles, outfile, prefix ): """ place the selected files in a tar.gz archive """ # Don't create an empty file which would clutter the backup diretory. if len(infiles)==0: return print '... saving files to %s' % outfile tar = tarfile.open( outfile, 'w:gz' ) for name in infiles: print '...... archiving %s' % name try: tar.add( name, '%s%s' % (prefix, name) ) except IOError, e: print "Sorry,", name, "is not readable." print "Error: %s" % e except OSError, e: print "Sorry,", name, "is not readable." print "Error: %s" % e tar.close() def save_port( portname, outdir, switches ): """ save important files which have been installed by a port """ print 'Saving port %s' % portname portfiles = list_port_files( portname, switches['regex'], switches['extended'] ) files_to_save = list_files_to_save( portfiles, switches['file_switch'] ) # Check that outdir exists, or we will crash if not os.path.isdir(outdir): try: os.mkdir(outdir) except OSError: # outdir exists but is not a directory print outdir, "is not a directory!" sys.exit() outfile = '%s/%s.saved.tar.gz' % (outdir, portname) prefix = '%s/' % portname zip_files( files_to_save, outfile, prefix ) def save_ports( portnames, outdir, switches ): """ save the selected ports """ for portname in portnames: save_port( portname, outdir, switches ) def main(): # parse arguments switches = copy.deepcopy(default_switches) try: opts, args = getopt.getopt( sys.argv[1:], options+parameters ) except getopt.GetoptError, e: print 'Error: %s' % e usage() sys.exit(1) for o, a in opts: if o == '-a': switches['all'] = True if o == '-f': switches['file_switch'] = True if o == '-h': usage() sys.exit() if o == '-v': print version, date sys.exit() if o == '-x': switches['regex'] = True switches['extended'] = False if o == '-X': switches['regex'] = True switches['extended'] = True if o == '-o': switches['outdir'] = a # decode what the user wants if switches['all'] and (switches['regex'] or switches['extended']): print 'Error: -a cannot be used with -x or -X' usage() sys.exit(1) if switches['all'] and len(args)>0: print 'Error: No port names are needed when -a is used' usage() sys.exit(1) if not switches['all'] and len(args)==0: print 'Error: You must specify at least one package name' usage() sys.exit(1) # do the magic! portlist = list_ports( args, switches['all'], switches['regex'], switches['extended'] ) if portlist is None or len(portlist)==0: print 'Error: No matching ports were found' sys.exit(1) else: print 'I will be saving the following port(s):' for portname in portlist: print '\t%s' % portname save_ports( portlist, switches['outdir'], switches ) if __name__ == '__main__': main()