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