1 |
Author: zmedico |
2 |
Date: 2008-03-28 10:21:22 +0000 (Fri, 28 Mar 2008) |
3 |
New Revision: 9539 |
4 |
|
5 |
Modified: |
6 |
main/branches/2.1.2/bin/emerge-webrsync |
7 |
Log: |
8 |
Merge emerge-webrsync from trunk for bugs #210945 and #130039. |
9 |
|
10 |
|
11 |
Modified: main/branches/2.1.2/bin/emerge-webrsync |
12 |
=================================================================== |
13 |
--- main/branches/2.1.2/bin/emerge-webrsync 2008-03-28 10:18:08 UTC (rev 9538) |
14 |
+++ main/branches/2.1.2/bin/emerge-webrsync 2008-03-28 10:21:22 UTC (rev 9539) |
15 |
@@ -1,14 +1,41 @@ |
16 |
#!/bin/bash |
17 |
-# Copyright 1999-2006 Gentoo Foundation |
18 |
+# Copyright 1999-2008 Gentoo Foundation |
19 |
# Distributed under the terms of the GNU General Public License v2 |
20 |
# $Id$ |
21 |
# Author: Karl Trygve Kalleberg <karltk@g.o> |
22 |
# Rewritten from the old, Perl-based emerge-webrsync script |
23 |
+# Author: Alon Bar-Lev <alon.barlev@×××××.com> |
24 |
+# Major rewrite from Karl's scripts. |
25 |
|
26 |
-type portageq > /dev/null || exit $? |
27 |
+# TODO: |
28 |
+# - all output should prob be converted to e* funcs |
29 |
+# - add support for ROOT |
30 |
+ |
31 |
+# |
32 |
+# gpg key import |
33 |
+# KEY_ID=0x7DDAD20D |
34 |
+# gpg --homedir /etc/portage/gnupg --keyserver subkeys.pgp.net --recv-keys $KEY_ID |
35 |
+# gpg --homedir /etc/portage/gnupg --edit-key $KEY_ID trust |
36 |
+# |
37 |
+ |
38 |
+# Only echo if in verbose mode |
39 |
+vvecho() { [[ ${do_verbose} -eq 1 ]] && echo "$@" ; } |
40 |
+# Only echo if not in verbose mode |
41 |
+nvecho() { [[ ${do_verbose} -eq 0 ]] && echo "$@" ; } |
42 |
+# warning echos |
43 |
+wecho() { echo "${argv0}: warning: $*" 1>&2 ; } |
44 |
+# error echos |
45 |
+eecho() { echo "${argv0}: error: $*" 1>&2 ; } |
46 |
+ |
47 |
+argv0=$0 |
48 |
+if ! type portageq > /dev/null ; then |
49 |
+ eecho "could not find 'portageq'; aborting" |
50 |
+ exit 1 |
51 |
+fi |
52 |
eval $(portageq envvar -v FEATURES FETCHCOMMAND GENTOO_MIRRORS \ |
53 |
- PORTAGE_BIN_PATH PORTAGE_INST_UID PORTAGE_INST_GID PORTAGE_NICENESS \ |
54 |
- PORTAGE_TMPDIR PORTDIR PORTAGE_RSYNC_EXTRA_OPTS http_proxy ftp_proxy) |
55 |
+ PORTAGE_BIN_PATH PORTAGE_GPG_DIR \ |
56 |
+ PORTAGE_NICENESS PORTAGE_RSYNC_EXTRA_OPTS PORTAGE_TMPDIR PORTDIR \ |
57 |
+ http_proxy ftp_proxy) |
58 |
DISTDIR="${PORTAGE_TMPDIR}/emerge-webrsync" |
59 |
export http_proxy ftp_proxy |
60 |
|
61 |
@@ -20,147 +47,346 @@ |
62 |
|
63 |
source "${PORTAGE_BIN_PATH}"/isolated-functions.sh || exit 1 |
64 |
|
65 |
-if [ ! -d $DISTDIR ] ; then |
66 |
- mkdir -p $DISTDIR |
67 |
+do_verbose=0 |
68 |
+do_debug=0 |
69 |
+ |
70 |
+if hasq webrsync-gpg ${FEATURES} ; then |
71 |
+ WEBSYNC_VERIFY_SIGNATURE=1 |
72 |
+else |
73 |
+ WEBSYNC_VERIFY_SIGNATURE=0 |
74 |
fi |
75 |
+if [ ${WEBSYNC_VERIFY_SIGNATURE} != 0 -a -z "${PORTAGE_GPG_DIR}" ]; then |
76 |
+ eecho "please set PORTAGE_GPG_DIR in make.conf" |
77 |
+ exit 1 |
78 |
+fi |
79 |
|
80 |
-cd "$DISTDIR" |
81 |
+do_tar() { |
82 |
+ local file=$1; shift |
83 |
+ local decompressor |
84 |
+ case ${file} in |
85 |
+ *.lzma) decompressor="lzcat" ;; |
86 |
+ *.bz2) decompressor="bzcat" ;; |
87 |
+ *.gz) decompressor="zcat" ;; |
88 |
+ *) decompressor="cat" ;; |
89 |
+ esac |
90 |
+ ${decompressor} "${file}" | tar "$@" |
91 |
+ _pipestatus=${PIPESTATUS[*]} |
92 |
+ [[ ${_pipestatus// /} -eq 0 ]] |
93 |
+} |
94 |
|
95 |
-found=0 |
96 |
-if [ "$1" == "-v" ] ; then |
97 |
- wgetops= |
98 |
-else |
99 |
- #this sucks. probably better to do 1> /dev/null |
100 |
- #that said, waiting on the refactoring. |
101 |
- if [ "${FETCHCOMMAND/wget}" != "${FETCHCOMMAND}" ]; then |
102 |
- wgetops="-q" |
103 |
- elif [ "${FETCHCOMMAND/curl}" != "${FETCHCOMMAND}" ]; then |
104 |
- wgetops="-s -f" |
105 |
+get_utc_date_in_seconds() { |
106 |
+ date -u +"%s" |
107 |
+} |
108 |
+ |
109 |
+get_date_part() { |
110 |
+ local utc_time_in_secs="$1" |
111 |
+ local part="$2" |
112 |
+ |
113 |
+ if [[ ${USERLAND} == BSD ]] ; then |
114 |
+ date -r ${utc_time_in_secs} -u +"${part}" |
115 |
+ else |
116 |
+ date -d @${utc_time_in_secs} -u +"${part}" |
117 |
fi |
118 |
-fi |
119 |
+} |
120 |
|
121 |
-if type -P md5sum > /dev/null; then |
122 |
- md5_com='md5sum -c "${FILE}.md5sum"' |
123 |
-elif type -P md5 > /dev/null; then |
124 |
- md5_com='[ "$(md5 -q ${FILE})" == "$(cut -d \ -f 1 ${FILE}.md5sum)" ]' |
125 |
-else |
126 |
- echo "warning, unable to do md5 verification of the snapshot!" |
127 |
- echo "no suitable md5/md5sum binary was found!" |
128 |
- md5_com='true' |
129 |
-fi |
130 |
+get_utc_second_from_string() { |
131 |
+ local s="$1" |
132 |
+ date -d "${s:0:4}-${s:4:2}-${s:6:2}" -u +"%s" |
133 |
+} |
134 |
|
135 |
+get_portage_timestamp() { |
136 |
+ local portage_current_timestamp=0 |
137 |
+ |
138 |
+ if [ -f "${PORTDIR}/metadata/timestamp.x" ]; then |
139 |
+ portage_current_timestamp=$(cut -f 1 -d " " "${PORTDIR}/metadata/timestamp.x" ) |
140 |
+ fi |
141 |
+ |
142 |
+ echo "${portage_current_timestamp}" |
143 |
+} |
144 |
+ |
145 |
+fetch_file() { |
146 |
+ local URI="$1" |
147 |
+ local FILE="$2" |
148 |
+ local opts |
149 |
+ |
150 |
+ if [ "${FETCHCOMMAND/wget/}" != "${FETCHCOMMAND}" ]; then |
151 |
+ opts="--continue $(nvecho -q)" |
152 |
+ elif [ "${FETCHCOMMAND/curl/}" != "${FETCHCOMMAND}" ]; then |
153 |
+ opts="--continue-at - $(nvecho -s -f)" |
154 |
+ else |
155 |
+ rm -f "${FILE}" |
156 |
+ fi |
157 |
+ |
158 |
+ vecho "Fetching file ${FILE} ..." |
159 |
+ # already set DISTDIR= |
160 |
+ eval "${FETCHCOMMAND}" ${opts} |
161 |
+ [ -s "${FILE}" ] |
162 |
+} |
163 |
+ |
164 |
+check_file_digest() { |
165 |
+ local digest="$1" |
166 |
+ local file="$2" |
167 |
+ local r=1 |
168 |
+ |
169 |
+ vecho "Checking digest ..." |
170 |
+ |
171 |
+ if type -P md5sum > /dev/null; then |
172 |
+ md5sum -c $digest && r=0 |
173 |
+ elif type -P md5 > /dev/null; then |
174 |
+ [ "$(md5 -q $file)" == "$(cut -d \ -f 1 \"$digest\")" ] && r=0 |
175 |
+ else |
176 |
+ eecho "cannot check digest: no suitable md5/md5sum binaries found" |
177 |
+ fi |
178 |
+ |
179 |
+ return "${r}" |
180 |
+} |
181 |
+ |
182 |
+check_file_signature() { |
183 |
+ local signature="$1" |
184 |
+ local file="$2" |
185 |
+ local r=1 |
186 |
+ |
187 |
+ if [ ${WEBSYNC_VERIFY_SIGNATURE} != 0 ]; then |
188 |
+ |
189 |
+ vecho "Checking signature ..." |
190 |
+ |
191 |
+ if type -p gpg > /dev/null; then |
192 |
+ gpg --homedir "${PORTAGE_GPG_DIR}" --verify "$signature" "$file" && r=0 |
193 |
+ else |
194 |
+ eecho "cannot check signature: gpg binary not found" |
195 |
+ fi |
196 |
+ else |
197 |
+ r=0 |
198 |
+ fi |
199 |
+ |
200 |
+ return "${r}" |
201 |
+} |
202 |
+ |
203 |
+get_snapshot_timestamp() { |
204 |
+ local file="$1" |
205 |
+ |
206 |
+ do_tar "${file}" --to-stdout -xf - portage/metadata/timestamp.x | cut -f 1 -d " " |
207 |
+} |
208 |
+ |
209 |
sync_local() { |
210 |
- echo Syncing local tree... |
211 |
- if type -P tarsync &> /dev/null; then |
212 |
- # tarsync doesn't take numeric uid/gid so we need to convert them. |
213 |
- local inst_user="$(python -c "import pwd; print pwd.getpwuid(int('${PORTAGE_INST_UID:-0}'))[0]")" |
214 |
- local inst_group="$(python -c "import grp; print grp.getgrgid(int('${PORTAGE_INST_GID:-0}'))[0]")" |
215 |
- if ! tarsync "${FILE}" "${PORTDIR}" -v -s 1 -o ${inst_user} -g ${inst_group} -e /distfiles -e /packages -e /local; then |
216 |
- echo "tarsync failed; tarball is corrupt?" |
217 |
- exit 1; |
218 |
+ local file="$1" |
219 |
+ |
220 |
+ vecho "Syncing local tree ..." |
221 |
+ |
222 |
+ # tarsync-0.2.1 doesn't seem to support lzma compression. |
223 |
+ if [ "${file##*.}" != "lzma" ] && type -P tarsync &> /dev/null; then |
224 |
+ if ! tarsync $(vvecho -v) -s 1 -o portage -g portage -e /distfiles -e /packages -e /local "${file}" "${PORTDIR}"; then |
225 |
+ eecho "tarsync failed; tarball is corrupt? (${file})" |
226 |
+ return 1 |
227 |
fi |
228 |
- rm "${FILE}" |
229 |
else |
230 |
- if ! tar jxf $FILE; then |
231 |
- echo "Tar failed to extract the image. Please review the output." |
232 |
- echo "Executed command: tar jxf $FILE" |
233 |
- exit 1 |
234 |
+ if ! do_tar "${file}" xf -; then |
235 |
+ eecho "tar failed to extract the image. tarball is corrupt? (${file})" |
236 |
+ rm -fr portage |
237 |
+ return 1 |
238 |
fi |
239 |
- rm -f $FILE |
240 |
- # Make sure user and group file ownership is ${PORTAGE_INST_UID}:${PORTAGE_INST_GID} |
241 |
- chown -R ${PORTAGE_INST_UID:-0}:${PORTAGE_INST_GID:-0} portage |
242 |
+ |
243 |
+ # Free disk space |
244 |
+ rm -f "${file}" |
245 |
+ |
246 |
+ chown portage:portage portage &> /dev/null && \ |
247 |
+ chown -R portage:portage portage |
248 |
cd portage |
249 |
rsync -av --progress --stats --delete --delete-after \ |
250 |
- --exclude='/distfiles' --exclude='/packages' \ |
251 |
- --exclude='/local' ${PORTAGE_RSYNC_EXTRA_OPTS} . "${PORTDIR%%/}" |
252 |
+ --exclude='/distfiles' --exclude='/packages' \ |
253 |
+ --exclude='/local' ${PORTAGE_RSYNC_EXTRA_OPTS} . "${PORTDIR%%/}" |
254 |
cd .. |
255 |
- echo "cleaning up" |
256 |
- rm -rf portage |
257 |
+ |
258 |
+ vecho "Cleaning up ..." |
259 |
+ rm -fr portage |
260 |
fi |
261 |
+ |
262 |
if hasq metadata-transfer ${FEATURES} ; then |
263 |
- echo "transferring metadata/cache" |
264 |
+ vecho "Updating cache ..." |
265 |
emerge --metadata |
266 |
fi |
267 |
[ -x /etc/portage/bin/post_sync ] && /etc/portage/bin/post_sync |
268 |
+ return 0 |
269 |
} |
270 |
|
271 |
-echo "Fetching most recent snapshot" |
272 |
+do_snapshot() { |
273 |
+ local ignore_timestamp="$1" |
274 |
+ local date="$2" |
275 |
|
276 |
-declare -i attempts=0 |
277 |
-while (( $attempts < 40 )) ; do |
278 |
- attempts=$(( attempts + 1 )) |
279 |
+ local r=1 |
280 |
|
281 |
- # The snapshot for a given day is generated at 01:45 UTC on the following |
282 |
- # day, so the current day's snapshot (going by UTC time) hasn't been |
283 |
- # generated yet. Therefore, always start by looking for the previous day's |
284 |
- # snapshot (for attempts=1, subtract 1 day from the current UTC time). |
285 |
- daysbefore=$(expr $(date -u +"%s") - 86400 \* ${attempts}) |
286 |
- if [ "${USERLAND}" = "BSD" ]; then |
287 |
- DATE_ARGS="-r ${daysbefore}" |
288 |
- else |
289 |
- DATE_ARGS="-d @${daysbefore}" |
290 |
+ local base_file="portage-${date}.tar" |
291 |
+ |
292 |
+ local have_files=0 |
293 |
+ local mirror |
294 |
+ |
295 |
+ local compressions="" |
296 |
+ type lzcat > /dev/null && compressions="${compressions} lzma" |
297 |
+ type bzcat > /dev/null && compressions="${compressions} bz2" |
298 |
+ type zcat > /dev/null && compressions="${compressions} gz" |
299 |
+ if [[ -z ${compressions} ]] ; then |
300 |
+ eecho "unable to locate any decompressors (lzcat or bzcat or zcat)" |
301 |
+ exit 1 |
302 |
fi |
303 |
- day=$(date ${DATE_ARGS} -u +"%d") |
304 |
- month=$(date ${DATE_ARGS} -u +"%m") |
305 |
- year=$(date ${DATE_ARGS} -u +"%Y") |
306 |
|
307 |
- FILE_ORIG="portage-${year}${month}${day}.tar.bz2" |
308 |
+ for mirror in ${GENTOO_MIRRORS} ; do |
309 |
|
310 |
- echo "Attempting to fetch file dated: ${year}${month}${day}" |
311 |
- |
312 |
- got_md5=0 |
313 |
+ vecho "Trying to retrieve ${date} snapshot from ${mirror} ..." |
314 |
|
315 |
- if [ ! -e "${FILE_ORIG}.md5sum" ]; then |
316 |
- FILE="${FILE_ORIG}.md5sum" |
317 |
- for i in $GENTOO_MIRRORS ; do |
318 |
- URI="${i}/snapshots/${FILE}" |
319 |
- if (eval "$FETCHCOMMAND $wgetops") && [ -s "${FILE}" ]; then |
320 |
- got_md5=1 |
321 |
+ for compression in ${compressions} ; do |
322 |
+ local file="portage-${date}.tar.${compression}" |
323 |
+ local digest="${file}.md5sum" |
324 |
+ local signature="${file}.gpgsig" |
325 |
+ |
326 |
+ if [ -s "${file}" -a -s "${digest}" -a -s "${signature}" ] ; then |
327 |
+ check_file_digest "${digest}" "${file}" && \ |
328 |
+ check_file_signature "${signature}" "${file}" && \ |
329 |
+ have_files=1 |
330 |
+ fi |
331 |
+ |
332 |
+ if [ ${have_files} -eq 0 ] ; then |
333 |
+ fetch_file "${mirror}/snapshots/${digest}" "${digest}" && \ |
334 |
+ fetch_file "${mirror}/snapshots/${signature}" "${signature}" && \ |
335 |
+ fetch_file "${mirror}/snapshots/${file}" "${file}" && \ |
336 |
+ check_file_digest "${digest}" "${file}" && \ |
337 |
+ check_file_signature "${signature}" "${file}" && \ |
338 |
+ have_files=1 |
339 |
+ fi |
340 |
+ |
341 |
+ # |
342 |
+ # If timestamp is invalid |
343 |
+ # we want to try and retrieve |
344 |
+ # from a different mirror |
345 |
+ # |
346 |
+ if [ ${have_files} -eq 1 ]; then |
347 |
+ |
348 |
+ vecho "Getting snapshot timetasmp ..." |
349 |
+ local snapshot_timestamp=$(get_snapshot_timestamp "${file}") |
350 |
+ |
351 |
+ if [ ${ignore_timestamp} == 0 ]; then |
352 |
+ if [ ${snapshot_timestamp} -lt $(get_portage_timestamp) ]; then |
353 |
+ wecho "portage is newer than snapshot" |
354 |
+ have_files=0 |
355 |
+ fi |
356 |
+ else |
357 |
+ local utc_seconds=$(get_utc_second_from_string "${date}") |
358 |
+ |
359 |
+ # |
360 |
+ # Check that this snapshot |
361 |
+ # is what it claims to be ... |
362 |
+ # |
363 |
+ if [ ${snapshot_timestamp} -lt ${utc_seconds} ] || \ |
364 |
+ [ ${snapshot_timestamp} -gt $((${utc_seconds}+ 2*86400)) ]; then |
365 |
+ |
366 |
+ wecho "snapshot timestamp is not in acceptable period" |
367 |
+ have_files=0 |
368 |
+ fi |
369 |
+ fi |
370 |
+ fi |
371 |
+ |
372 |
+ if [ ${have_files} -eq 1 ]; then |
373 |
break |
374 |
+ else |
375 |
+ # |
376 |
+ # Remove files and use a different mirror |
377 |
+ # |
378 |
+ rm -f "${file}" "${digest}" "${signature}" |
379 |
fi |
380 |
done |
381 |
+ |
382 |
+ [ ${have_files} -eq 1 ] && break |
383 |
+ done |
384 |
+ |
385 |
+ if [ ${have_files} -eq 1 ]; then |
386 |
+ sync_local "${file}" && r=0 |
387 |
else |
388 |
- got_md5=1 |
389 |
+ vecho "${date} snapshot was not found" |
390 |
fi |
391 |
- FILE="${FILE_ORIG}" |
392 |
+ |
393 |
+ rm -f "${file}" "${digest}" "${signature}" |
394 |
+ return "${r}" |
395 |
+} |
396 |
|
397 |
- if (($got_md5 == 0 )); then |
398 |
- echo " --- No md5sum present on the mirror. (Not yet available.)" |
399 |
- continue |
400 |
- elif [ -s "${FILE}" ]; then |
401 |
- if eval "$md5_com"; then |
402 |
- echo " === snapshot $FILE is correct, using it" |
403 |
- sync_local |
404 |
- echo |
405 |
- echo " === Snapshot has been sync'd" |
406 |
- echo |
407 |
- exit 0 |
408 |
- else |
409 |
- rm $FILE |
410 |
+do_latest_snapshot() { |
411 |
+ local attempts=-1 |
412 |
+ local r=1 |
413 |
+ |
414 |
+ vecho "Fetching most recent snapshot ..." |
415 |
+ |
416 |
+ while (( ${attempts} < 40 )) ; do |
417 |
+ local day |
418 |
+ local month |
419 |
+ local year |
420 |
+ local seconds |
421 |
+ |
422 |
+ attempts=$(( ${attempts} + 1 )) |
423 |
+ |
424 |
+ utc_attempt=$(expr $(get_utc_date_in_seconds) - 86400 \* ${attempts}) |
425 |
+ |
426 |
+ day=$(get_date_part ${utc_attempt} "%d") |
427 |
+ month=$(get_date_part ${utc_attempt} "%m") |
428 |
+ year=$(get_date_part ${utc_attempt} "%Y") |
429 |
+ utc_midnight=$(get_date_part $(expr ${utc_attempt} - ${utc_attempt} % 86400) "%s") |
430 |
+ |
431 |
+ if [ ${utc_midnight} -lt $(($(get_portage_timestamp)-86400)) ]; then |
432 |
+ wecho "portage content is newer than available snapshots (use --revert option to overide)" |
433 |
+ r=0 |
434 |
+ break |
435 |
fi |
436 |
+ |
437 |
+ if do_snapshot 0 "${year}${month}${day}"; then |
438 |
+ r=0 |
439 |
+ break; |
440 |
+ fi |
441 |
+ done |
442 |
+ |
443 |
+ return "${r}" |
444 |
+} |
445 |
+ |
446 |
+usage() { |
447 |
+ cat <<-EOF |
448 |
+ Usage: $0 [options] |
449 |
+ |
450 |
+ Options: |
451 |
+ --revert=yyyymmdd Revert to snapshot |
452 |
+ -q, --quiet Only output errors |
453 |
+ -v, --verbose Enable verbose output |
454 |
+ -x, --debug Enable debug output |
455 |
+ -h, --help This help screen (duh!) |
456 |
+ EOF |
457 |
+ if [[ -n $* ]] ; then |
458 |
+ printf "\nError: %s\n" "$*" 1>&2 |
459 |
+ exit 1 |
460 |
+ else |
461 |
+ exit 0 |
462 |
fi |
463 |
+} |
464 |
+ |
465 |
+main() { |
466 |
+ local arg |
467 |
+ local revert_date |
468 |
|
469 |
- for i in $GENTOO_MIRRORS ; do |
470 |
- URI="${i}/snapshots/$FILE" |
471 |
- rm -f "$FILE" |
472 |
- if (eval "$FETCHCOMMAND $wgetops") && [ -s "$FILE" ]; then |
473 |
- if ! eval "$md5_com"; then |
474 |
- echo "md5 failed on $FILE" |
475 |
- rm ${FILE} |
476 |
- continue |
477 |
- else |
478 |
- sync_local |
479 |
- echo |
480 |
- echo " *** Completed websync, please now perform a normal rsync if possible." |
481 |
- echo " Update is current as of the of YYYYMMDD: ${year}${month}${day}" |
482 |
- echo |
483 |
- exit 0 |
484 |
- fi |
485 |
- fi |
486 |
+ [ ! -d "${DISTDIR}" ] && mkdir -p "${DISTDIR}" |
487 |
+ cd "${DISTDIR}" |
488 |
|
489 |
+ for arg in "$@" ; do |
490 |
+ local v=${arg#*=} |
491 |
+ case ${arg} in |
492 |
+ -h|--help) usage ;; |
493 |
+ -q|--quiet) PORTAGE_QUIET=1 ;; |
494 |
+ -v|--verbose) do_verbose=1 ;; |
495 |
+ -x|--debug) do_debug=1 ;; |
496 |
+ --revert=*) revert_date=${v} ;; |
497 |
+ *) usage "Invalid option '${arg}'" ;; |
498 |
+ esac |
499 |
done |
500 |
-done |
501 |
+ [[ ${do_debug} -eq 1 ]] && set -x |
502 |
|
503 |
-rm -rf portage |
504 |
+ if [[ -n ${revert_date} ]] ; then |
505 |
+ do_snapshot 1 "${revert_date}" |
506 |
+ else |
507 |
+ do_latest_snapshot |
508 |
+ fi |
509 |
+} |
510 |
|
511 |
-exit 1 |
512 |
+main "$@" |
513 |
|
514 |
-- |
515 |
gentoo-commits@l.g.o mailing list |