[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#198602: ITP: debbackup -- Backup and restore Debian specifics (package status, conffiles)



On Tue, Jun 24, 2003 at 08:47:53PM +1000, Daniel Stone wrote:

> On Tue, Jun 24, 2003 at 08:29:05PM +1000, Martijn van Oosterhout wrote:
> > Hey, how far along is this? I've been thinking that this would be very
> > possible and very useful. Including having the result be an ISO image and
> > combine with one of those autoinstall tools to automatically recreate a
> > machine without intervention. Fill up any remaining space with a copy of
> > base and any other large packages.
> > 
> > Just dreaming, ok? :)
> 
> It's still in the initial stages right now, but that could certainly be
> very handy - maybe an external tool to automatically create the ISO?
> Right now, it just spits out a tar file for debrestore to deal with -
> containing one file with the package information, a directory for
> conffiles, and stuff. I'm sure that could be burnt along with the ISO,
> and then debrestore be automatically run ...
> 
> Mmm, good ideas you have. :)

Here's a small script that I started to write a while back and never used
for much.  It reads a file list on stdin and filters out files which are
unchanged from the packages available from apt.  The idea would be to
generate a list of files to be backed up, and pipe it through this program
to reduce the size of the backup without losing local modifications.  Maybe
it will have some useful ideas for you.

-- 
 - mdz
#!/usr/bin/python

import sys
import os
import md5
import apt_pkg
import StringIO

def log(msg):
    pass
    #print >>sys.stderr, msg

def xargs_pipe(command, args):
    max = 1024
    argswork = args[:]
    ret = ''
    while argswork:
        argstr = ''
        while len(argstr) < (len(command) + max) and argswork:
            argstr += ' ' + argswork.pop(0)

        cmd = os.popen(command + argstr)
        ret += cmd.read()
    return ret

def set_intersect(one, two):
    one_hash = {}
    ret = []
    for item in one: one_hash[one] = 1
    for item in two:
        if item in one:
            ret.append(item)
    return ret

def modified_conffile(conffiles, f):
    # maybe should remember the deletion of a conffile?  dpkg cares...
    if f not in conffiles or not os.path.exists(f):
        return None
    
    dbhash = conffiles[f]
    hashcalc = md5.new()
    hashcalc.update(open(f).read())
    return hashcalc.hexdigest() == dbhash

def conffiles(packages):
    log("Reading conffile data...")
    conffiles = {}
    pkghash = {}
    for package in packages: pkghash[package] = 1
    
    # XXX, dpkg internal interface
    parser = apt_pkg.ParseTagFile(open('/var/lib/dpkg/status'))
    
    while parser.Step() == 1:
        # This is potentially broken with respect to filenames with spaces, but
        # it emulates current dpkg (1.10.9)
        package, cfstanza = (parser.Section.get('Package'),
                             parser.Section.get('Conffiles'))

        if package not in pkghash:
            continue

        if not cfstanza:
            continue
        
        cfentries = cfstanza.split()
        while cfentries:
            cfname = cfentries.pop(0)
            if not cfentries:
                # XXX broken
                continue
            cfhash = cfentries.pop(0)
            conffiles[cfname] = cfhash

    return conffiles

def contents(packages):
    log("Reading package contents...")
    return xargs_pipe(
        "dpkg-query --listfiles ",
        packages
        ).split('\n')

def installed_packages():
    log("Reading installed package list...")
    installed = []
    parser = apt_pkg.ParseTagFile(os.popen(
        "dpkg-query --show --showformat='Package: ${Package}\nVersion: ${Version}\nStatus: ${Status}\n\n'"
        ))
    while parser.Step() == 1:
        package, version = (parser.Section.get('Package'),
                            parser.Section.get('Version'))
        desired, error, status = parser.Section.get('Status').split()
        if status == 'installed':
            installed.append((package,version))

    return installed

def available_packages():
    log("Reading available package list...")
    available = []
    parser = apt_pkg.ParseTagFile(os.popen("apt-cache dumpavail"))
    while parser.Step() == 1:
        available.append((parser.Section.get('Package'),
                          parser.Section.get('Version')))
    return available

def alternatives():
    log("Reading alternatives...")
    alternatives = []
    
    # XXX, dpkg internal interface
    alternatives_dir = '/var/lib/dpkg/alternatives'
    for alt in os.listdir(alternatives_dir):
        lines = open(os.path.join(alternatives_dir,alt)).readlines()
        alternatives.append(lines[1][:-1])

    return alternatives

def diversions():
    log("Reading diversions...")
    diversions = []

    for line in os.popen("dpkg-divert --list '*'").readlines():
        # diversion of /usr/bin/autoconf to /usr/bin/autoconf2.50 by autoconf2.13
        diversions.append(line.split()[4])

    return diversions

def main():
    action = sys.argv[1]
    
    if action == 'filter':
        available = {}
        for package in available_packages():
            available[package] = 1

        packages = []
        for package in installed_packages():
            if package in available:
                packages.append(package[0])

        exclude_list = {}

        for f in alternatives():
            exclude_list[f] = 1

        for f in diversions():
            exclude_list[f] = 1

        cf = conffiles(packages)
        lastfile = None
        for f in contents(packages):
            if lastfile and f.startswith('locally diverted to: '):
                exclude_list[lastfile] -= 1
                
            if modified_conffile(cf, f):
                continue
            
            exclude_list[f] = exclude_list.setdefault(f,0) + 1
            lastfile = f

        for f in exclude_list.keys():
            if exclude_list[f] < 1:
                del exclude_list[f]

        packages = None

        log("Ready to filter")
        while 1:
            line = sys.stdin.readline()
            if not line:
                break
            filename = line[:-1]
            if filename not in exclude_list:
                print filename
    else:
        sys.stderr.write('No action specified\n')
        sys.exit(1)

if __name__ == '__main__':
    main()

Attachment: pgpyLZW5nYqsk.pgp
Description: PGP signature


Reply to: