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