1 |
#!/usr/bin/python |
2 |
|
3 |
import sys |
4 |
import os |
5 |
import re |
6 |
import shutil |
7 |
import getopt |
8 |
from stat import * |
9 |
|
10 |
#------------------------------------------------------------------------------ |
11 |
|
12 |
# Command Line Args |
13 |
doit = True |
14 |
verbose = False |
15 |
quiet = False |
16 |
warn = False |
17 |
force = False |
18 |
print_mapping = False |
19 |
remove_files = False |
20 |
remove_installation = False |
21 |
|
22 |
# Scan Results |
23 |
existing_files = {} |
24 |
non_existing_files = {} |
25 |
|
26 |
# Directory and File mappings |
27 |
|
28 |
# This is the complete directory map, it includes both data files |
29 |
# and run-time files |
30 |
dir_map = { |
31 |
'/var/mailman' : '/var/lib/mailman', |
32 |
'/var/mailman/Mailman' : '/usr/lib/mailman/Mailman', |
33 |
'/var/mailman/archives' : '/var/lib/mailman/archives', |
34 |
'/var/mailman/bin' : '/usr/lib/mailman/bin', |
35 |
'/var/mailman/cgi-bin' : '/usr/lib/mailman/cgi-bin', |
36 |
'/var/mailman/cron' : '/usr/lib/mailman/cron', |
37 |
'/var/mailman/data' : '/var/lib/mailman/data', |
38 |
'/var/mailman/lists' : '/var/lib/mailman/lists', |
39 |
'/var/mailman/locks' : '/var/lock/mailman', |
40 |
'/var/mailman/logs' : '/var/log/mailman', |
41 |
'/var/mailman/mail' : '/usr/lib/mailman/mail', |
42 |
'/var/mailman/messages' : '/usr/lib/mailman/messages', |
43 |
'/var/mailman/pythonlib' : '/usr/lib/mailman/pythonlib', |
44 |
'/var/mailman/qfiles' : '/var/spool/mailman', |
45 |
'/var/spool/mailman/qfiles' : '/var/spool/mailman', |
46 |
'/var/mailman/scripts' : '/usr/lib/mailman/scripts', |
47 |
'/var/mailman/spam' : '/var/lib/mailman/spam', |
48 |
'/var/mailman/templates' : '/usr/lib/mailman/templates', |
49 |
'/var/mailman/tests' : '/usr/lib/mailman/tests' |
50 |
} |
51 |
|
52 |
# These are directories that contain data files the user may |
53 |
# want to preserve from an old installation and should be copied |
54 |
# into the new directory location. |
55 |
data_dir_map = { |
56 |
'/var/mailman/archives' : '/var/lib/mailman/archives', |
57 |
'/var/mailman/data' : '/var/lib/mailman/data', |
58 |
'/var/mailman/lists' : '/var/lib/mailman/lists', |
59 |
'/var/mailman/logs' : '/var/log/mailman', |
60 |
'/var/mailman/qfiles' : '/var/spool/mailman', |
61 |
'/var/spool/mailman/qfiles' : '/var/spool/mailman', |
62 |
'/var/mailman/spam' : '/var/lib/mailman/spam', |
63 |
} |
64 |
|
65 |
# These are mappings for individual files. They represent files that |
66 |
# cannot be mapped via their parent dirctories, they must be treated |
67 |
# individually. |
68 |
file_map = { |
69 |
'/var/mailman/data/adm.pw' : '/etc/mailman/adm.pw', |
70 |
'/var/mailman/data/creator.pw' : '/etc/mailman/creator.pw', |
71 |
'/var/mailman/data/aliases' : '/etc/mailman/aliases', |
72 |
'/var/mailman/data/virtual-mailman' : '/etc/mailman/virtual-mailman', |
73 |
'/var/mailman/data/sitelist.cfg' : '/etc/mailman/sitelist.cfg', |
74 |
'/var/mailman/data/master-qrunner.pid' : '/var/run/mailman/master-qrunner.pid' |
75 |
} |
76 |
|
77 |
#------------------------------------------------------------------------------ |
78 |
|
79 |
def DumpMapping(): |
80 |
'''Print out the directory and file mappings''' |
81 |
print "Directory Mapping:" |
82 |
for key in dir_map.keys(): |
83 |
print "%s --> %s" %(key, dir_map[key]) |
84 |
|
85 |
print "\nFile Mapping:" |
86 |
for key in file_map.keys(): |
87 |
print "%s --> %s" %(key, file_map[key]) |
88 |
|
89 |
def RecordFile(src, dst): |
90 |
'''If the src files (old) exists record this as a potential |
91 |
file operation. File operations are grouped into two sets, |
92 |
those where the dst (new) files exists and those where it does not |
93 |
exist. This is done to prevent overwriting files''' |
94 |
|
95 |
global existing_files, non_existing_files |
96 |
|
97 |
if not os.path.exists(src): |
98 |
return |
99 |
|
100 |
if existing_files.has_key(src): |
101 |
if warn: |
102 |
print "WARNING: src file already seen (%s) and has dst match: (%s)" % (src, dst) |
103 |
return |
104 |
|
105 |
if non_existing_files.has_key(src): |
106 |
if warn: |
107 |
print "WARNING: src file already seen (%s) does not have dst match" % (src) |
108 |
return |
109 |
|
110 |
if os.path.exists(dst): |
111 |
existing_files[src] = dst |
112 |
else: |
113 |
non_existing_files[src] = dst |
114 |
|
115 |
def GetCopyFiles(old_root, new_root): |
116 |
'''Recursively generate a list of src files (old) in the old_root |
117 |
and pair each of them with their new dst path name''' |
118 |
|
119 |
prefix_re = re.compile("^(%s)/*(.*)" % re.escape(old_root)) |
120 |
dst_files_existing = [] |
121 |
dst_files_non_existing = [] |
122 |
for root, dirs, files in os.walk(old_root): |
123 |
match = prefix_re.match(root) |
124 |
subdir = match.group(2) |
125 |
for name in files: |
126 |
oldpath = os.path.join(root, name) |
127 |
newpath = os.path.join(new_root, subdir, name) |
128 |
RecordFile(oldpath, newpath) |
129 |
|
130 |
def CopyFile(src_path, dst_path): |
131 |
'''Copy file, preserve its mode and ownership. If the dst directory |
132 |
does not exist, create it preserving the mode and ownership of the |
133 |
src direcotry''' |
134 |
|
135 |
if not doit: |
136 |
print "cp %s %s" % (src_path, dst_path) |
137 |
return |
138 |
|
139 |
src_dir = os.path.dirname(src_path) |
140 |
dst_dir = os.path.dirname(dst_path) |
141 |
|
142 |
if not os.path.isdir(dst_dir): |
143 |
if os.path.exists(dst_dir): |
144 |
print "ERROR: dst dir exists, but is not directory (%s)" % dst_dir |
145 |
return |
146 |
st = os.stat(src_dir) |
147 |
os.makedirs(dst_dir, st[ST_MODE]) |
148 |
os.chown(dst_dir, st[ST_UID], st[ST_GID]) |
149 |
|
150 |
shutil.copy2(src_path, dst_path) |
151 |
st = os.stat(src_path) |
152 |
os.chown(dst_path, st[ST_UID], st[ST_GID]) |
153 |
|
154 |
def RemoveFile(path): |
155 |
'''Remove the file''' |
156 |
|
157 |
if not os.path.exists(path): |
158 |
if warn: |
159 |
print "WARNING: attempt to remove non-existent file (%s)" % path |
160 |
return |
161 |
|
162 |
if not os.path.isfile(path): |
163 |
if warn: |
164 |
print "WARNING: attempt to remove non-plain file (%s)" % path |
165 |
return |
166 |
|
167 |
if not doit: |
168 |
print "rm %s" % (path) |
169 |
return |
170 |
|
171 |
os.unlink(path) |
172 |
|
173 |
def RemoveDirs(top): |
174 |
'''Delete everything reachable from the directory named in 'top', |
175 |
assuming there are no symbolic links. |
176 |
CAUTION: This is dangerous! For example, if top == '/', it |
177 |
could delete all your disk files.''' |
178 |
for root, dirs, files in os.walk(top, topdown=False): |
179 |
for name in files: |
180 |
path = os.path.join(root, name) |
181 |
if not doit: |
182 |
print "rm %s" % (path) |
183 |
else: |
184 |
os.remove(path) |
185 |
for name in dirs: |
186 |
path = os.path.join(root, name) |
187 |
if not doit: |
188 |
print "rmdir %s" % (path) |
189 |
else: |
190 |
os.rmdir(path) |
191 |
|
192 |
def Usage(): |
193 |
print """ |
194 |
This script will help you copy mailman data files from the old |
195 |
directory structure to the new FHS directory structure. |
196 |
|
197 |
Mailman should not be running when you perform this! |
198 |
/sbin/service mailman stop |
199 |
|
200 |
This script is conservative, by default it will not overwrite |
201 |
any file in the new directory on the assumption it is most recent |
202 |
and most correct. If you want to force overwrites use -f. |
203 |
|
204 |
Files are copied to the new directories, if you want to remove the |
205 |
old data files use -r. Hint: copy first and test, once everything is |
206 |
working remove the old files with -r. If you want to remove the entire |
207 |
old installation use -R |
208 |
|
209 |
migrate [-f] [-n] [-q] [-v] [-w] [-m] [-r] [-R] |
210 |
-n don't execute, but show what would be done |
211 |
-f force destination overwrites |
212 |
-m print mapping |
213 |
-r remove old data files |
214 |
-R remove entire old installation |
215 |
-q be quiet |
216 |
-v be verbose |
217 |
-w print warnings |
218 |
-h help |
219 |
""" |
220 |
|
221 |
#------------------------------------------------------------------------------ |
222 |
|
223 |
try: |
224 |
opts, args = getopt.getopt(sys.argv[1:], "nfvmqwhrR") |
225 |
for o, a in opts: |
226 |
if o == "-n": |
227 |
doit = False |
228 |
elif o == "-f": |
229 |
force = True |
230 |
elif o == "-v": |
231 |
verbose = True |
232 |
elif o == "-m": |
233 |
print_mapping = True |
234 |
elif o == "-q": |
235 |
quiet = True |
236 |
elif o == "-w": |
237 |
warn = True |
238 |
elif o == "-r": |
239 |
remove_files = True |
240 |
elif o == "-R": |
241 |
remove_installation = True |
242 |
elif o == "-h": |
243 |
Usage() |
244 |
sys.exit(1) |
245 |
except getopt.GetoptError, err: |
246 |
print err |
247 |
Usage() |
248 |
sys.exit(1) |
249 |
|
250 |
|
251 |
if print_mapping: |
252 |
DumpMapping() |
253 |
sys.exit(0) |
254 |
|
255 |
# Generate file list |
256 |
for src_dir in data_dir_map.keys(): |
257 |
GetCopyFiles(src_dir, dir_map[src_dir]) |
258 |
|
259 |
for src_file in file_map.keys(): |
260 |
RecordFile(src_file, file_map[src_file]) |
261 |
|
262 |
|
263 |
# Copy files |
264 |
for src in non_existing_files: |
265 |
dst = non_existing_files[src] |
266 |
CopyFile(src, dst) |
267 |
|
268 |
if force: |
269 |
for src in existing_files: |
270 |
dst = existing_files[src] |
271 |
CopyFile(src, dst) |
272 |
else: |
273 |
if len(existing_files) > 0 and not quiet: |
274 |
print "\nThe following files already exist in the destination, they will NOT be copied" |
275 |
print "To force overwriting invoke with -f\n" |
276 |
for src in existing_files: |
277 |
dst = existing_files[src] |
278 |
print "# cp %s %s" %(src, dst) |
279 |
|
280 |
# Remove old files |
281 |
if remove_files: |
282 |
for src in existing_files: |
283 |
RemoveFile(src) |
284 |
for src in non_existing_files: |
285 |
RemoveFile(src) |
286 |
|
287 |
if remove_installation: |
288 |
for old_dir in dir_map.keys(): |
289 |
RemoveDirs(old_dir) |
290 |
|
291 |
|
292 |
sys.exit(0) |
293 |
|