]> git.decadent.org.uk Git - dak.git/blob - config/debian/dinstall.functions
No longer update a tracefile hourly. Instead update our tracefile the second before...
[dak.git] / config / debian / dinstall.functions
1 # -*- mode:sh -*-
2 # Timestamp. Used for dinstall stat graphs
3 function ts() {
4         echo "Archive maintenance timestamp ($1): $(date +%H:%M:%S)"
5 }
6
7 # Cleanup actions
8 function cleanup() {
9         rm -f ${LOCK_DAILY}
10         rm -f ${LOCK_ACCEPTED}
11 }
12
13 # If we error out this one is called, *FOLLOWED* by cleanup above
14 function onerror() {
15     ERRDATE=$(date "+%Y.%m.%d-%H:%M:%S")
16
17     subject="ATTENTION ATTENTION!"
18     if [ "${error}" = "false" ]; then
19         subject="${subject} (continued)"
20     else
21         subject="${subject} (interrupted)"
22     fi
23     subject="${subject} dinstall error at ${ERRDATE} in ${STAGEFILE} - (Be quiet, Brain, or I'll stab you with a Q-tip)"
24
25     cat "${STAGEFILE}.log" | mail -s "${subject}" -a "X-Debian: DAK" cron@ftp-master.debian.org
26 }
27
28 ########################################################################
29 # the actual dinstall functions follow                                 #
30 ########################################################################
31
32 # pushing merkels QA user, part one
33 function merkel1() {
34     log "Telling merkels QA user that we start dinstall"
35     ssh -2 -i ~dak/.ssh/push_merkel_qa  -o BatchMode=yes -o SetupTimeOut=90 -o ConnectTimeout=90 qa@merkel.debian.org sleep 1
36 }
37
38 # Create the postgres dump files
39 function pgdump_pre() {
40     log "Creating pre-daily-cron-job backup of projectb database..."
41     pg_dump projectb > $base/backup/dump_pre_$(date +%Y.%m.%d-%H:%M:%S)
42 }
43
44 function pgdump_post() {
45     log "Creating post-daily-cron-job backup of projectb database..."
46     cd $base/backup
47     POSTDUMP=$(date +%Y.%m.%d-%H:%M:%S)
48     pg_dump projectb > $base/backup/dump_$POSTDUMP
49     pg_dumpall --globals-only > $base/backup/dumpall_$POSTDUMP
50     ln -sf $base/backup/dump_$POSTDUMP current
51     ln -sf $base/backup/dumpall_$POSTDUMP currentall
52 }
53
54 # Load the dak-dev projectb
55 function pgdakdev() {
56     cd $base/backup
57     echo "drop database projectb" | psql -p 5433 template1
58         cat currentall | psql -p 5433 template1
59     createdb -p 5433 -T template0 projectb
60     fgrep -v '\connect' current | psql -p 5433 projectb
61 }
62
63 # Updating various files
64 function updates() {
65     log "Updating Bugs docu, Mirror list and mailing-lists.txt"
66     cd $configdir
67     $scriptsdir/update-bugdoctxt
68     $scriptsdir/update-mirrorlists
69     $scriptsdir/update-mailingliststxt
70     $scriptsdir/update-pseudopackages.sh
71 }
72
73 # Process (oldstable)-proposed-updates "NEW" queue
74 function punew_do() {
75     date -u -R >> REPORT
76     dak process-policy $1 | tee -a REPORT | mail -e -s "NEW changes in $1" debian-release@lists.debian.org
77     echo >> REPORT
78 }
79 function punew() {
80     log "Doing automated p-u-new processing"
81     cd "${queuedir}/p-u-new"
82     punew_do "$1"
83 }
84 function opunew() {
85     log "Doing automated o-p-u-new processing"
86     cd "${queuedir}/o-p-u-new"
87     punew_do "$1"
88 }
89
90 # The first i18n one, syncing new descriptions
91 function i18n1() {
92     log "Synchronizing i18n package descriptions"
93     # First sync their newest data
94     cd ${scriptdir}/i18nsync
95     rsync -aq --delete --delete-after ddtp-sync:/does/not/matter . || true
96
97     # Now check if we still know about the packages for which they created the files
98     # is the timestamp signed by us?
99     if $(gpgv --keyring /srv/ftp.debian.org/s3kr1t/dot-gnupg/pubring.gpg timestamp.gpg timestamp); then
100         # now read it. As its signed by us we are sure the content is what we expect, no need
101         # to do more here. And we only test -d a directory on it anyway.
102         TSTAMP=$(cat timestamp)
103         # do we have the dir still?
104         if [ -d ${scriptdir}/i18n/${TSTAMP} ]; then
105             # Lets check!
106             if ${scriptsdir}/ddtp-i18n-check.sh . ${scriptdir}/i18n/${TSTAMP}; then
107                 # Yay, worked, lets copy around
108                 for dir in squeeze sid; do
109                     if [ -d dists/${dir}/ ]; then
110                         cd dists/${dir}/main/i18n
111                         rsync -aq --delete --delete-after  . ${ftpdir}/dists/${dir}/main/i18n/.
112                     fi
113                     cd ${scriptdir}/i18nsync
114                 done
115             else
116                 echo "ARRRR, bad guys, wrong files, ARRR"
117                 echo "Arf, Arf, Arf, bad guys, wrong files, arf, arf, arf" | mail -s "Don't you kids take anything. I'm watching you. I've got eye implants in the back of my head." debian-l10n-devel@lists.alioth.debian.org
118             fi
119         else
120             echo "ARRRR, missing the timestamp ${TSTAMP} directory, not updating i18n, ARRR"
121             echo "Arf, Arf, Arf, missing the timestamp ${TSTAMP} directory, not updating i18n, arf, arf, arf" | mail -s "Lisa, if you don't like your job you don't strike. You just go in every day and do it really half-assed. That's the American way." debian-l10n-devel@lists.alioth.debian.org
122         fi
123     else
124         echo "ARRRRRRR, could not verify our timestamp signature, ARRR. Don't mess with our files, i18n guys, ARRRRR."
125         echo "Arf, Arf, Arf, could not verify our timestamp signature, arf. Don't mess with our files, i18n guys, arf, arf, arf" | mail -s "You can't keep blaming yourself. Just blame yourself once, and move on." debian-l10n-devel@lists.alioth.debian.org
126     fi
127 }
128
129 function cruft() {
130     log "Checking for cruft in overrides"
131     dak check-overrides
132 }
133
134 function dominate() {
135     log "Removing obsolete source and binary associations"
136     dak dominate
137 }
138
139 function filelist() {
140     log "Generating file lists for apt-ftparchive"
141     dak generate-filelist
142 }
143
144 function fingerprints() {
145     log "Not updating fingerprints - scripts needs checking"
146
147     log "Updating fingerprints"
148     dak import-keyring -L /srv/keyring.debian.org/keyrings/debian-keyring.gpg
149
150     OUTFILE=$(mktemp)
151     dak import-keyring --generate-users "%s" /srv/keyring.debian.org/keyrings/debian-maintainers.gpg >"${OUTFILE}"
152
153     if [ -s "${OUTFILE}" ]; then
154         /usr/sbin/sendmail -odq -oi -t -f envelope@ftp-master.debian.org <<EOF
155 From: Debian FTP Masters <ftpmaster@ftp-master.debian.org>
156 To: <debian-project@lists.debian.org>
157 Subject: Debian Maintainers Keyring changes
158 Content-Type: text/plain; charset=utf-8
159 MIME-Version: 1.0
160
161 The following changes to the debian-maintainers keyring have just been activated:
162
163 $(cat $OUTFILE)
164
165 Debian distribution maintenance software,
166 on behalf of the Keyring maintainers
167
168 EOF
169     fi
170     rm -f "$OUTFILE"
171 }
172
173 function overrides() {
174     log "Writing overrides into text files"
175     cd $overridedir
176     dak make-overrides
177
178     # FIXME
179     rm -f override.sid.all3
180     for i in main contrib non-free main.debian-installer; do cat override.sid.$i >> override.sid.all3; done
181 }
182
183 function mpfm() {
184     log "Generating package / file mapping"
185     dak make-pkg-file-mapping | bzip2 -9 > $base/ftp/indices/package-file.map.bz2
186 }
187
188 function packages() {
189     log "Generating Packages and Sources files"
190     cd $configdir
191     GZIP='--rsyncable' ; export GZIP
192     apt-ftparchive generate apt.conf
193 }
194
195 function pdiff() {
196     log "Generating pdiff files"
197     dak generate-index-diffs
198 }
199
200 function release() {
201     log "Generating Release files"
202     dak generate-releases
203 }
204
205 function dakcleanup() {
206     log "Cleanup old packages/files"
207     dak clean-suites -m 10000
208     dak clean-queues
209 }
210
211 function buildd_dir() {
212     # Rebuilt the buildd dir to avoid long times of 403
213     log "Regenerating the buildd incoming dir"
214     STAMP=$(date "+%Y%m%d%H%M")
215     make_buildd_dir
216 }
217
218 function mklslar() {
219     cd $ftpdir
220
221     FILENAME=ls-lR
222
223     log "Removing any core files ..."
224     find -type f -name core -print0 | xargs -0r rm -v
225
226     log "Checking permissions on files in the FTP tree ..."
227     find -type f \( \! -perm -444 -o -perm +002 \) -ls
228     find -type d \( \! -perm -555 -o -perm +002 \) -ls
229
230     log "Checking symlinks ..."
231     symlinks -rd .
232
233     log "Creating recursive directory listing ... "
234     rm -f .${FILENAME}.new
235     TZ=UTC ls -lR > .${FILENAME}.new
236
237     if [ -r ${FILENAME}.gz ] ; then
238         mv -f ${FILENAME}.gz ${FILENAME}.old.gz
239         mv -f .${FILENAME}.new ${FILENAME}
240         rm -f ${FILENAME}.patch.gz
241         zcat ${FILENAME}.old.gz | diff -u - ${FILENAME} | gzip --rsyncable -9cfn - >${FILENAME}.patch.gz
242         rm -f ${FILENAME}.old.gz
243     else
244         mv -f .${FILENAME}.new ${FILENAME}
245     fi
246
247     gzip --rsyncable -9cfN ${FILENAME} >${FILENAME}.gz
248     rm -f ${FILENAME}
249 }
250
251 function mkmaintainers() {
252     log 'Creating Maintainers index ... '
253
254     cd $indices
255     dak make-maintainers ${scriptdir}/masterfiles/pseudo-packages.maintainers | \
256         sed -e "s/~[^  ]*\([   ]\)/\1/"  | \
257         awk '{printf "%-20s ", $1; for (i=2; i<=NF; i++) printf "%s ", $i; printf "\n";}' > .new-maintainers
258
259     if ! cmp -s .new-maintainers Maintainers || [ ! -f Maintainers ]; then
260             log "installing Maintainers ... "
261             mv -f .new-maintainers Maintainers
262             gzip --rsyncable -9v <Maintainers >.new-maintainers.gz
263             mv -f .new-maintainers.gz Maintainers.gz
264     else
265         rm -f .new-maintainers
266     fi
267 }
268
269 function copyoverrides() {
270     log 'Copying override files into public view ...'
271
272     for ofile in $copyoverrides ; do
273             cd $overridedir
274             chmod g+w override.$ofile
275
276             cd $indices
277
278             newofile=override.$ofile.gz
279             rm -f .newover-$ofile.gz
280             pc="`gzip 2>&1 -9nv <$overridedir/override.$ofile >.newover-$ofile.gz`"
281         if ! cmp -s .newover-$ofile.gz $newofile || [ ! -f $newofile ]; then
282                     log "   installing new $newofile $pc"
283                     mv -f .newover-$ofile.gz $newofile
284                     chmod g+w $newofile
285         else
286                     rm -f .newover-$ofile.gz
287             fi
288     done
289 }
290
291 function mkfilesindices() {
292     umask 002
293     cd $base/ftp/indices/files/components
294
295     ARCHLIST=$(tempfile)
296
297     log "Querying projectb..."
298     echo 'SELECT l.path, f.filename, a.arch_string FROM location l JOIN files f ON (f.location = l.id) LEFT OUTER JOIN (binaries b JOIN architecture a ON (b.architecture = a.id)) ON (f.id = b.file)' | psql projectb -At | sed 's/|//;s,^/srv/ftp.debian.org/ftp,.,' | sort >$ARCHLIST
299
300     includedirs () {
301         perl -ne 'print; while (m,/[^/]+$,) { $_=$`; print $_ . "\n" unless $d{$_}++; }'
302     }
303     poolfirst () {
304         perl -e '@nonpool=(); while (<>) { if (m,^\./pool/,) { print; } else { push @nonpool, $_; } } print for (@nonpool);'
305     }
306
307     log "Generating sources list"
308     (
309         sed -n 's/|$//p' $ARCHLIST
310         cd $base/ftp
311         find ./dists -maxdepth 1 \! -type d
312         find ./dists \! -type d | grep "/source/"
313     ) | sort -u | gzip --rsyncable -9 > source.list.gz
314
315     log "Generating arch lists"
316
317     ARCHES=$( (<$ARCHLIST sed -n 's/^.*|//p'; echo amd64) | grep . | grep -v all | sort -u)
318     for a in $ARCHES; do
319         (sed -n "s/|$a$//p" $ARCHLIST
320             sed -n 's/|all$//p' $ARCHLIST
321
322             cd $base/ftp
323             find ./dists -maxdepth 1 \! -type d
324             find ./dists \! -type d | grep -E "(proposed-updates.*_$a.changes$|/main/disks-$a/|/main/installer-$a/|/Contents-$a|/binary-$a/)"
325         ) | sort -u | gzip --rsyncable -9 > arch-$a.list.gz
326     done
327
328     log "Generating suite lists"
329
330     suite_list () {
331         printf 'SELECT DISTINCT l.path, f.filename FROM (SELECT sa.source AS source FROM src_associations sa WHERE sa.suite = %d UNION SELECT b.source AS source FROM bin_associations ba JOIN binaries b ON (ba.bin = b.id) WHERE ba.suite = %d) s JOIN dsc_files df ON (s.source = df.source) JOIN files f ON (df.file = f.id) JOIN location l ON (f.location = l.id)\n' $1 $1 | psql -F' ' -A -t projectb
332
333         printf 'SELECT l.path, f.filename FROM bin_associations ba JOIN binaries b ON (ba.bin = b.id) JOIN files f ON (b.file = f.id) JOIN location l ON (f.location = l.id) WHERE ba.suite = %d\n' $1 | psql -F' ' -A -t projectb
334     }
335
336     printf 'SELECT id, suite_name FROM suite\n' | psql -F' ' -At projectb |
337     while read id suite; do
338         [ -e $base/ftp/dists/$suite ] || continue
339         (
340             (cd $base/ftp
341                 distname=$(cd dists; readlink $suite || echo $suite)
342                 find ./dists/$distname \! -type d
343                 for distdir in ./dists/*; do
344                     [ "$(readlink $distdir)" != "$distname" ] || echo $distdir
345                 done
346             )
347             suite_list $id | tr -d ' ' | sed 's,^/srv/ftp.debian.org/ftp,.,'
348         ) | sort -u | gzip --rsyncable -9 > suite-${suite}.list.gz
349     done
350
351     log "Finding everything on the ftp site to generate sundries"
352     (cd $base/ftp; find . \! -type d \! -name 'Archive_Maintenance_In_Progress' | sort) >$ARCHLIST
353
354     rm -f sundries.list
355     zcat *.list.gz | cat - *.list | sort -u |
356     diff - $ARCHLIST | sed -n 's/^> //p' > sundries.list
357
358     log "Generating files list"
359
360     for a in $ARCHES; do
361         (echo ./project/trace; zcat arch-$a.list.gz source.list.gz) |
362         cat - sundries.list dists.list project.list docs.list indices.list |
363         sort -u | poolfirst > ../arch-$a.files
364     done
365
366     (cd $base/ftp/
367             for dist in sid squeeze; do
368                     find ./dists/$dist/main/i18n/ \! -type d | sort -u | gzip --rsyncable -9 > $base/ftp/indices/files/components/translation-$dist.list.gz
369             done
370     )
371
372     (cat ../arch-i386.files ../arch-amd64.files; zcat suite-oldstable.list.gz suite-proposed-updates.list.gz ; zcat translation-sid.list.gz ; zcat translation-squeeze.list.gz) |
373     sort -u | poolfirst > ../typical.files
374
375     rm -f $ARCHLIST
376     log "Done!"
377 }
378
379 function mkchecksums() {
380     dsynclist=$dbdir/dsync.list
381     md5list=$indices/md5sums
382
383     log -n "Creating md5 / dsync index file ... "
384
385     cd "$ftpdir"
386     ${bindir}/dsync-flist -q generate $dsynclist --exclude $dsynclist --md5
387     ${bindir}/dsync-flist -q md5sums $dsynclist | gzip -9n --rsyncable > ${md5list}.gz
388     ${bindir}/dsync-flist -q link-dups $dsynclist || true
389 }
390
391 function mirror() {
392     log "Regenerating \"public\" mirror/ hardlink fun"
393     date -u > ${ftpdir}/project/trace/ftp-master.debian.org
394     echo "Using dak v1" >> ${ftpdir}/project/trace/ftp-master.debian.org
395     echo "Running on host: $(hostname -f)" >> ${ftpdir}/project/trace/ftp-master.debian.org
396     cd ${mirrordir}
397     rsync -aH --link-dest ${ftpdir} --delete --delete-after --ignore-errors ${ftpdir}/. .
398 }
399
400 function expire() {
401     log "Expiring old database dumps..."
402     cd $base/backup
403     $scriptsdir/expire_dumps -d . -p -f "dump_*"
404 }
405
406 function transitionsclean() {
407     log "Removing out of date transitions..."
408     cd $base
409     dak transitions -c -a
410 }
411
412 function reports() {
413     # Send a report on NEW/BYHAND packages
414     log "Nagging ftpteam about NEW/BYHAND packages"
415     dak queue-report | mail -e -s "NEW and BYHAND on $(date +%D)" ftpmaster@ftp-master.debian.org
416     # and one on crufty packages
417     log "Sending information about crufty packages"
418     dak cruft-report > $webdir/cruft-report-daily.txt
419     dak cruft-report -s experimental >> $webdir/cruft-report-daily.txt
420     cat $webdir/cruft-report-daily.txt | mail -e -s "Debian archive cruft report for $(date +%D)" ftpmaster@ftp-master.debian.org
421 }
422
423 function dm() {
424     log "Updating DM html page"
425     $scriptsdir/dm-monitor >$webdir/dm-uploaders.html
426 }
427
428 function bts() {
429     log "Categorizing uncategorized bugs filed against ftp.debian.org"
430     dak bts-categorize
431 }
432
433 function merkel2() {
434     # Push dak@merkel so it syncs the projectb there. Returns immediately, the sync runs detached
435     log "Trigger merkel/flotows projectb sync"
436     ssh -2 -o BatchMode=yes -o SetupTimeOut=30 -o ConnectTimeout=30 -i ~/.ssh/push_merkel_projectb dak@merkel.debian.org sleep 1
437     # Also trigger flotow, the ftpmaster test box
438     ssh -2 -o BatchMode=yes -o SetupTimeOut=30 -o ConnectTimeout=30 -i ~/.ssh/push_flotow_projectb dak@flotow.debconf.org sleep 1
439 }
440
441 function merkel3() {
442     # Push dak@merkel to tell it to sync the dd accessible parts. Returns immediately, the sync runs detached
443     log "Trigger merkels dd accessible parts sync"
444     ssh -2 -o BatchMode=yes -o SetupTimeOut=30 -o ConnectTimeout=30 -i ~/.ssh/push_merkel_ddaccess dak@merkel.debian.org sleep 1
445 }
446
447 function mirrorpush() {
448     log "Starting the mirrorpush"
449     date -u > /srv/ftp.debian.org/web/mirrorstart
450     echo "Using dak v1" >> /srv/ftp.debian.org/web/mirrorstart
451     echo "Running on host $(hostname -f)" >> /srv/ftp.debian.org/web/mirrorstart
452     sudo -H -u archvsync /home/archvsync/runmirrors > ~dak/runmirrors.log 2>&1 &
453 }
454
455 function i18n2() {
456     log "Exporting package data foo for i18n project"
457     STAMP=$(date "+%Y%m%d%H%M")
458     mkdir -p ${scriptdir}/i18n/${STAMP}
459     cd ${scriptdir}/i18n/${STAMP}
460     dak control-suite -l stable > lenny
461     dak control-suite -l testing > squeeze
462     dak control-suite -l unstable > sid
463     echo "${STAMP}" > timestamp
464     gpg --secret-keyring /srv/ftp.debian.org/s3kr1t/dot-gnupg/secring.gpg --keyring /srv/ftp.debian.org/s3kr1t/dot-gnupg/pubring.gpg --no-options --batch --no-tty --armour --default-key 55BE302B --detach-sign -o timestamp.gpg timestamp
465     rm -f md5sum
466     md5sum * > md5sum
467     cd ${webdir}/
468     ln -sfT ${scriptdir}/i18n/${STAMP} i18n
469
470     cd ${scriptdir}
471     find ./i18n -mindepth 1 -maxdepth 1 -mtime +2 -not -name "${STAMP}" -type d -print0 | xargs --no-run-if-empty -0 rm -rf
472 }
473
474 function stats() {
475     log "Updating stats data"
476     cd $configdir
477     $scriptsdir/update-ftpstats $base/log/* > $base/misc/ftpstats.data
478     R --slave --vanilla < $base/misc/ftpstats.R
479     dak stats arch-space > $webdir/arch-space
480     dak stats pkg-nums > $webdir/pkg-nums
481 }
482
483 function aptftpcleanup() {
484     log "Clean up apt-ftparchive's databases"
485     cd $configdir
486     apt-ftparchive -q clean apt.conf
487 }
488
489 function compress() {
490     log "Compress old psql backups"
491     cd $base/backup/
492     find -maxdepth 1 -mindepth 1 -type f -name 'dump_pre_*' -mtime +2 -print0 | xargs -0 --no-run-if-empty rm
493
494     find -maxdepth 1 -mindepth 1 -type f -name 'dump_*' \! -name '*.bz2' \! -name '*.gz' -mmin +720 |
495     while read dumpname; do
496         echo "Compressing $dumpname"
497         bzip2 -9fv "$dumpname"
498     done
499     find -maxdepth 1 -mindepth 1 -type f -name "dumpall_*" \! -name '*.bz2' \! -name '*.gz' -mmin +720 |
500     while read dumpname; do
501         echo "Compressing $dumpname"
502         bzip2 -9fv "$dumpname"
503     done
504     finddup -l -d $base/backup
505 }
506
507 function logstats() {
508     $masterdir/tools/logs.py "$1"
509 }
510
511 # save timestamp when we start
512 function savetimestamp() {
513         NOW=`date "+%Y.%m.%d-%H:%M:%S"`
514         echo ${NOW} > "${dbdir}/dinstallstart"
515 }
516
517 function maillogfile() {
518     cat "$LOGFILE" | mail -s "Log for dinstall run of ${NOW}" cron@ftp-master.debian.org
519 }
520
521 function renamelogfile() {
522     if [ -f "${dbdir}/dinstallstart" ]; then
523         NOW=$(cat "${dbdir}/dinstallstart")
524 #        maillogfile
525         mv "$LOGFILE" "$logdir/dinstall_${NOW}.log"
526         logstats "$logdir/dinstall_${NOW}.log"
527         bzip2 -9 "$logdir/dinstall_${NOW}.log"
528     else
529         error "Problem, I don't know when dinstall started, unable to do log statistics."
530         NOW=`date "+%Y.%m.%d-%H:%M:%S"`
531 #        maillogfile
532         mv "$LOGFILE" "$logdir/dinstall_${NOW}.log"
533         bzip2 -9 "$logdir/dinstall_${NOW}.log"
534     fi
535 }
536
537 function testingsourcelist() {
538     dak ls -s testing -f heidi -r .| egrep 'source$' > ${webdir}/testing.list
539 }
540
541 # do a last run of process-unchecked before dinstall is on.
542 function process_unchecked() {
543     log "Processing the unchecked queue"
544     UNCHECKED_WITHOUT_LOCK="-p"
545     do_unchecked
546     sync_debbugs
547 }
548
549 # do a run of newstage only before dinstall is on.
550 function newstage() {
551     log "Processing the newstage queue"
552     UNCHECKED_WITHOUT_LOCK="-p"
553     do_newstage
554 }
555
556 # Function to update a "statefile" telling people what we are doing
557 # (more or less).
558 #
559 # This should be called with the argument(s)
560 #  - Status name we want to show.
561 #
562 function state() {
563     RIGHTNOW="$(date -u +"%a %b %d %T %Z %Y (%s)")"
564     cat >"${DINSTALLSTATE}" <<EOF
565 Dinstall start: ${DINSTALLBEGIN}
566 Current action: ${1}
567 Action start: ${RIGHTNOW}
568 EOF
569 }