49da8b
#!/usr/bin/python
49da8b
49da8b
import sys
49da8b
import os
49da8b
import re
49da8b
import shutil
49da8b
import getopt
49da8b
from stat import *
49da8b
49da8b
#------------------------------------------------------------------------------
49da8b
49da8b
# Command Line Args
49da8b
doit = True
49da8b
verbose = False
49da8b
quiet = False
49da8b
warn = False
49da8b
force = False
49da8b
print_mapping = False
49da8b
remove_files = False
49da8b
remove_installation = False
49da8b
49da8b
# Scan Results
49da8b
existing_files = {}
49da8b
non_existing_files = {}
49da8b
49da8b
# Directory and File mappings
49da8b
49da8b
# This is the complete directory map, it includes both data files
49da8b
# and run-time files
49da8b
dir_map = {
49da8b
    '/var/mailman'				: '/var/lib/mailman',
49da8b
    '/var/mailman/Mailman'			: '/usr/lib/mailman/Mailman',
49da8b
    '/var/mailman/archives'			: '/var/lib/mailman/archives',
49da8b
    '/var/mailman/bin'				: '/usr/lib/mailman/bin',
49da8b
    '/var/mailman/cgi-bin'			: '/usr/lib/mailman/cgi-bin',
49da8b
    '/var/mailman/cron'				: '/usr/lib/mailman/cron',
49da8b
    '/var/mailman/data'				: '/var/lib/mailman/data',
49da8b
    '/var/mailman/lists'			: '/var/lib/mailman/lists',
49da8b
    '/var/mailman/locks'			: '/var/lock/mailman',
49da8b
    '/var/mailman/logs'				: '/var/log/mailman',
49da8b
    '/var/mailman/mail'				: '/usr/lib/mailman/mail',
49da8b
    '/var/mailman/messages'			: '/usr/lib/mailman/messages',
49da8b
    '/var/mailman/pythonlib'			: '/usr/lib/mailman/pythonlib',
49da8b
    '/var/mailman/qfiles'			: '/var/spool/mailman',
49da8b
    '/var/spool/mailman/qfiles'			: '/var/spool/mailman',
49da8b
    '/var/mailman/scripts'			: '/usr/lib/mailman/scripts',
49da8b
    '/var/mailman/spam'				: '/var/lib/mailman/spam',
49da8b
    '/var/mailman/templates'			: '/usr/lib/mailman/templates',
49da8b
    '/var/mailman/tests'			: '/usr/lib/mailman/tests'
49da8b
}
49da8b
49da8b
# These are directories that contain data files the user may
49da8b
# want to preserve from an old installation and should be copied
49da8b
# into the new directory location.
49da8b
data_dir_map = {
49da8b
    '/var/mailman/archives'			: '/var/lib/mailman/archives',
49da8b
    '/var/mailman/data'				: '/var/lib/mailman/data',
49da8b
    '/var/mailman/lists'			: '/var/lib/mailman/lists',
49da8b
    '/var/mailman/logs'				: '/var/log/mailman',
49da8b
    '/var/mailman/qfiles'			: '/var/spool/mailman',
49da8b
    '/var/spool/mailman/qfiles'			: '/var/spool/mailman',
49da8b
    '/var/mailman/spam'				: '/var/lib/mailman/spam',
49da8b
}
49da8b
49da8b
# These are mappings for individual files. They represent files that
49da8b
# cannot be mapped via their parent dirctories, they must be treated
49da8b
# individually.
49da8b
file_map = {
49da8b
    '/var/mailman/data/adm.pw'			: '/etc/mailman/adm.pw',
49da8b
    '/var/mailman/data/creator.pw'		: '/etc/mailman/creator.pw',
49da8b
    '/var/mailman/data/aliases'			: '/etc/mailman/aliases',
49da8b
    '/var/mailman/data/virtual-mailman'		: '/etc/mailman/virtual-mailman',
49da8b
    '/var/mailman/data/sitelist.cfg'		: '/etc/mailman/sitelist.cfg',
49da8b
    '/var/mailman/data/master-qrunner.pid'	: '/var/run/mailman/master-qrunner.pid'
49da8b
}
49da8b
49da8b
#------------------------------------------------------------------------------
49da8b
49da8b
def DumpMapping():
49da8b
    '''Print out the directory and file mappings'''
49da8b
    print "Directory Mapping:"
49da8b
    for key in dir_map.keys():
49da8b
        print "%s --> %s" %(key, dir_map[key])
49da8b
49da8b
    print "\nFile Mapping:"
49da8b
    for key in file_map.keys():
49da8b
        print "%s --> %s" %(key, file_map[key])
49da8b
49da8b
def RecordFile(src, dst):
49da8b
    '''If the src files (old) exists record this as a potential
49da8b
    file operation. File operations are grouped into two sets,
49da8b
    those where the dst (new) files exists and those where it does not
49da8b
    exist. This is done to prevent overwriting files'''
49da8b
    
49da8b
    global existing_files, non_existing_files
49da8b
49da8b
    if not os.path.exists(src):
49da8b
        return
49da8b
49da8b
    if existing_files.has_key(src):
49da8b
        if warn:
49da8b
            print "WARNING: src file already seen (%s) and has dst match: (%s)" % (src, dst)
49da8b
        return
49da8b
49da8b
    if non_existing_files.has_key(src):
49da8b
        if warn:
49da8b
            print "WARNING: src file already seen (%s) does not have dst match" % (src)
49da8b
        return
49da8b
49da8b
    if os.path.exists(dst):
49da8b
        existing_files[src] = dst
49da8b
    else:
49da8b
        non_existing_files[src] = dst
49da8b
49da8b
def GetCopyFiles(old_root, new_root):
49da8b
    '''Recursively generate a list of src files (old) in the old_root
49da8b
    and pair each of them with their new dst path name'''
49da8b
    
49da8b
    prefix_re = re.compile("^(%s)/*(.*)" % re.escape(old_root))
49da8b
    dst_files_existing = []
49da8b
    dst_files_non_existing = []
49da8b
    for root, dirs, files in os.walk(old_root):
49da8b
        match = prefix_re.match(root)
49da8b
        subdir = match.group(2)
49da8b
        for name in files:
49da8b
            oldpath = os.path.join(root, name)
49da8b
            newpath = os.path.join(new_root, subdir, name)
49da8b
            RecordFile(oldpath, newpath)
49da8b
49da8b
def CopyFile(src_path, dst_path):
49da8b
    '''Copy file, preserve its mode and ownership. If the dst directory
49da8b
    does not exist, create it preserving the mode and ownership of the
49da8b
    src direcotry'''
49da8b
    
49da8b
    if not doit:
49da8b
        print "cp %s %s" % (src_path, dst_path)
49da8b
        return
49da8b
    
49da8b
    src_dir = os.path.dirname(src_path)
49da8b
    dst_dir = os.path.dirname(dst_path)
49da8b
49da8b
    if not os.path.isdir(dst_dir):
49da8b
        if os.path.exists(dst_dir):
49da8b
            print "ERROR: dst dir exists, but is not directory (%s)" % dst_dir
49da8b
            return
49da8b
        st = os.stat(src_dir)
49da8b
        os.makedirs(dst_dir, st[ST_MODE])
49da8b
        os.chown(dst_dir, st[ST_UID], st[ST_GID])
49da8b
    
49da8b
    shutil.copy2(src_path, dst_path)
49da8b
    st = os.stat(src_path)
49da8b
    os.chown(dst_path, st[ST_UID], st[ST_GID])
49da8b
49da8b
def RemoveFile(path):
49da8b
    '''Remove the file'''
49da8b
    
49da8b
    if not os.path.exists(path):
49da8b
        if warn:
49da8b
            print "WARNING: attempt to remove non-existent file (%s)" % path
49da8b
        return
49da8b
49da8b
    if not os.path.isfile(path):
49da8b
        if warn:
49da8b
            print "WARNING: attempt to remove non-plain file (%s)" % path
49da8b
        return
49da8b
49da8b
    if not doit:
49da8b
        print "rm %s" % (path)
49da8b
        return
49da8b
49da8b
    os.unlink(path)
49da8b
    
49da8b
def RemoveDirs(top):
49da8b
    '''Delete everything reachable from the directory named in 'top',
49da8b
    assuming there are no symbolic links.
49da8b
    CAUTION:  This is dangerous!  For example, if top == '/', it
49da8b
    could delete all your disk files.'''
49da8b
    for root, dirs, files in os.walk(top, topdown=False):
49da8b
        for name in files:
49da8b
            path = os.path.join(root, name)
49da8b
            if not doit:
49da8b
                print "rm %s" % (path)
49da8b
            else:
49da8b
                os.remove(path)
49da8b
        for name in dirs:
49da8b
            path = os.path.join(root, name)
49da8b
            if not doit:
49da8b
                print "rmdir %s" % (path)
49da8b
            else:
49da8b
                os.rmdir(path)
49da8b
49da8b
def Usage():
49da8b
    print """
49da8b
This script will help you copy mailman data files from the old
49da8b
directory structure to the new FHS directory structure.
49da8b
49da8b
Mailman should not be running when you perform this!
49da8b
/sbin/service mailman stop
49da8b
49da8b
This script is conservative, by default it will not overwrite
49da8b
any file in the new directory on the assumption it is most recent
49da8b
and most correct. If you want to force overwrites use -f.
49da8b
49da8b
Files are copied to the new directories, if you want to remove the
49da8b
old data files use -r. Hint: copy first and test, once everything is
49da8b
working remove the old files with -r. If you want to remove the entire
49da8b
old installation use -R
49da8b
49da8b
migrate [-f] [-n] [-q] [-v] [-w] [-m] [-r] [-R]
49da8b
-n don't execute, but show what would be done
49da8b
-f force destination overwrites
49da8b
-m print mapping
49da8b
-r remove old data files
49da8b
-R remove entire old installation
49da8b
-q be quiet
49da8b
-v be verbose
49da8b
-w print warnings
49da8b
-h help
49da8b
"""
49da8b
49da8b
#------------------------------------------------------------------------------
49da8b
49da8b
try:
49da8b
    opts, args = getopt.getopt(sys.argv[1:], "nfvmqwhrR")
49da8b
    for o, a in opts:
49da8b
        if o == "-n":
49da8b
            doit = False
49da8b
        elif o == "-f":
49da8b
            force = True
49da8b
        elif o == "-v":
49da8b
            verbose = True
49da8b
        elif o == "-m":
49da8b
            print_mapping = True
49da8b
        elif o == "-q":
49da8b
            quiet = True
49da8b
        elif o == "-w":
49da8b
            warn = True
49da8b
        elif o == "-r":
49da8b
            remove_files = True
49da8b
        elif o == "-R":
49da8b
            remove_installation = True
49da8b
        elif o == "-h":
49da8b
            Usage()
49da8b
            sys.exit(1)
49da8b
except getopt.GetoptError, err:
49da8b
    print err
49da8b
    Usage()
49da8b
    sys.exit(1)
49da8b
49da8b
49da8b
if print_mapping:
49da8b
    DumpMapping()
49da8b
    sys.exit(0)
49da8b
49da8b
# Generate file list
49da8b
for src_dir in data_dir_map.keys():
49da8b
    GetCopyFiles(src_dir, dir_map[src_dir])
49da8b
49da8b
for src_file in file_map.keys():
49da8b
    RecordFile(src_file, file_map[src_file])
49da8b
49da8b
49da8b
# Copy files
49da8b
for src in non_existing_files:
49da8b
    dst = non_existing_files[src]
49da8b
    CopyFile(src, dst)
49da8b
49da8b
if force:
49da8b
    for src in existing_files:
49da8b
        dst = existing_files[src]
49da8b
        CopyFile(src, dst)
49da8b
else:
49da8b
    if len(existing_files) > 0 and not quiet:
49da8b
        print "\nThe following files already exist in the destination, they will NOT be copied"
49da8b
        print "To force overwriting invoke with -f\n"
49da8b
        for src in existing_files:
49da8b
            dst = existing_files[src]
49da8b
            print "# cp %s %s" %(src, dst)
49da8b
49da8b
# Remove old files
49da8b
if remove_files:
49da8b
    for src in existing_files:
49da8b
        RemoveFile(src)
49da8b
    for src in non_existing_files:
49da8b
        RemoveFile(src)
49da8b
49da8b
if remove_installation:
49da8b
    for old_dir in dir_map.keys():
49da8b
        RemoveDirs(old_dir)
49da8b
49da8b
49da8b
sys.exit(0)    
49da8b