1 |
brianr |
1.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 |
|
|
|