|
|
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 |
|