Blame SOURCES/mailman-migrate-fhs

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