diff --git a/ChangeLog b/ChangeLog index bd59eb998794f51a0e782a45af08a4ea9a8b5f80..ab1282165c12a35b290f68ac5f3f40aa994a18f1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,54 @@ +2007-04-03 Per Cederqvist <ceder@sedesopim.lysator.liu.se> + + LYSrdiff partitions are now named after both the disk and the + partition, such as "2/0". Support cloning. Support watching the + backup activity remotely. Various other fixes and improvements. + + * lysrdiff-status: Adapted to the new disk/part scheme, and the + new double "--" in state files. Have a per-user state file, so + that many users can use this script. + + * lysrdiff-set-status.py: New. Talk to lysrdiff-monitord.py. + + * lysrdiff-move-obsolete: New option: --auto-confirm. Adapted to + the new disk/part scheme, and the new double "--" in state files. + + * lysrdiff-move-job: New. Move a job from one disk to another. + (Actually, the old backup copy is left in place. Use + lysrdiff-move-obsolete to deal with it.) + + * lysrdiff-monitord.py: New. Keep track of what each disk is + doing, and provide a vt100-over-telnet interface to watch it + remotely. + + * lysrdiff-label-disk: New. Create the lysrdiff.id file and the + basic directory structure. Create space filler files that can be + removed in an emergency. + + * lysrdiff-df: New. Run df on all mounted lysrdiff partitions. + + * fetch-backup-work: Fetch all of lenin:/home. + + * distribute-tasks: The lysrdiff argument is now on the form + disk/part. + + * Makefile (install): Added lysrdiff-label-disk and lysrdiff-df. + (install-one-task): Added lysrdiff-set-status.py and + lysrdiff-monitord.py. + + * backup-one-task: Replaced the lysrdiffpart argument with disk + and part arguments. Handle --only-prune. Log status using + lysrdiff-set-status.py. Removed special-case remoterdiff for + sedesopim. The parts of state file names are now separated by + "--" intead of "-", to avoid confusion with categories and + subcategories that contain a "-". Remove increments older than + 120 days. Log the disk usage after a backup completes. Clone the + backup to all mounted copies. + + * backup-all: Added --only-prune. The part argument is now a + disk/part string, and the format of the lysrdiff.id file has + changed. + 2007-01-25 Per Cederqvist <ceder@sedesopim.lysator.liu.se> Removed special cases. diff --git a/Makefile b/Makefile index 94f3d5f3003602f43ba757508fe8ca943bfce351..eddadbb7374ab22b0bf02caae97611742697b9ce 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,11 @@ install: install-one-task cp distribute-tasks $(BINDIR)/ cp fetch-backup-work $(BINDIR)/ cp lysrdiff-status $(BINDIR)/ + cp lysrdiff-df $(BINDIR)/ cp lysrdiff-move-obsolete $(BINDIR)/ + cp lysrdiff-label-disk $(BINDIR)/ install-one-task: cp backup-one-task $(BINDIR)/ + cp lysrdiff-set-status.py $(BINDIR)/ + cp lysrdiff-monitord.py $(BINDIR)/ diff --git a/backup-all b/backup-all index e8174873f239423fbc54a604ba7a8d06d2cf35ec..32d04cc35e565932f4906931f68b5aaa315064aa 100755 --- a/backup-all +++ b/backup-all @@ -1,7 +1,7 @@ #!/bin/sh usage () { - echo $0: usage: $0 '[ options ] partno...' >&2 + echo $0: usage: $0 '[ options ] disk/part [ disk/part ... ]' >&2 echo ' --failed Only run backup jobs that failed.' >&2 echo ' --retry Only run backup jobs that produced warnings.' >&2 echo ' --new Only run backup jobs that have never completed.' >&2 @@ -10,6 +10,7 @@ usage () { failed= retry= new= +only_prune= while true do @@ -23,6 +24,9 @@ do x--new) shift new=--new;; + x--only-prune) + shift + only_prune=--only-prune;; x--*) usage exit 1;; @@ -32,6 +36,7 @@ done PARTS="$@" + if [ "$PARTS" = "" ] then echo $0: you forgot to specify what parts to back up >&2 @@ -39,10 +44,12 @@ then fi rundf () { - df -h /lysrdiff/0/perm | sed -n 1p + df -h /lysrdiff/1/perm/0 | sed -n 1p for lysrdiffpart in $PARTS do - df -h /lysrdiff/$lysrdiffpart/perm | sed 1d + disk=`echo $lysrdiffpart|sed sx/.*xx` + part=`echo $lysrdiffpart|sed sx.*/xx` + df -h /lysrdiff/$disk/perm/$part | sed 1d done } @@ -50,7 +57,9 @@ rundf for lysrdiffpart in $PARTS do - if [ "`cat /lysrdiff/$lysrdiffpart/perm/lysrdiff.id`" != "$lysrdiffpart perm" ] + disk=`echo $lysrdiffpart|sed sx/.*xx` + part=`echo $lysrdiffpart|sed sx.*/xx` + if [ "`cat /lysrdiff/$disk/perm/$part/lysrdiff.id`" != "$disk perm $part" ] then echo lysrdiffpart $lysrdiffpart not found >&2 exit 1 @@ -62,12 +71,16 @@ done total=0 for lysrdiffpart in $PARTS do - total=`expr $total + \`wc -l </lysrdiff/$lysrdiffpart/perm/lysrdiff/tasks\`` + disk=`echo $lysrdiffpart|sed sx/.*xx` + part=`echo $lysrdiffpart|sed sx.*/xx` + total=`expr $total + \`wc -l </lysrdiff/$disk/perm/$part/lysrdiff/tasks\`` done ctr=1 for lysrdiffpart in $PARTS do + disk=`echo $lysrdiffpart|sed sx/.*xx` + part=`echo $lysrdiffpart|sed sx.*/xx` while read category subcategory server serverpath do @@ -88,11 +101,11 @@ do fi /nobackup/backup.lysator/bin/backup-one-task \ - $failed $retry $new \ - $lysrdiffpart "$category" "$subcategory" "$server" "$serverpath" \ - "$ctr/$total $category $subcategory" + $failed $retry $new $only_prune \ + $disk $part "$category" "$subcategory" "$server" "$serverpath" \ + "$ctr/$total $disk/$part $category $subcategory" ctr=`expr $ctr + 1` - done < /lysrdiff/$lysrdiffpart/perm/lysrdiff/tasks + done < /lysrdiff/$disk/perm/$part/lysrdiff/tasks done rundf diff --git a/backup-one-task b/backup-one-task index 5370a8f4a4bcc25ada2abf458c418f7c9bad7a1b..8e6f9adb60e80517251556b5d42ace84d35d12d0 100755 --- a/backup-one-task +++ b/backup-one-task @@ -1,17 +1,23 @@ #!/bin/sh usage () { - echo $0: usage: $0 [ options ] lysrdiffpart category subcategory server serverpath msg >&2 + echo $0: usage: $0 [ options ] disk part category subcategory server serverpath msg >&2 echo Example: $0 0 home ceder inservitus /export/home/ceder "1/123" >&2 echo Recognized options: >&2 echo ' --failed Only run failed backups' >&2 echo ' --retry Only run backups with output from rdiff-backup' >&2 echo ' --new Only run backups that never completed' >&2 + echo ' --only-prune Only prune old increments' >&2 + echo 'Backups are always performed to the "perm" disk.' >&2 + echo 'Once the backup is finished, it is cloned to all mounted copies.' >&2 } failed=0 retry=0 new=0 +only_prune=0 + +ss=/nobackup/backup.lysator/bin/lysrdiff-set-status.py while [ $# -gt 1 ] do @@ -25,6 +31,9 @@ do x--new) shift new=1;; + x--only-prune) + shift + only_prune=1;; x--*) usage exit 1;; @@ -32,27 +41,28 @@ do esac done -if [ $# != 6 ] +if [ $# != 7 ] then usage exit 1 fi -lysrdiffpart="$1" -category="$2" -subcategory="$3" -server="$4" -serverpath="$5" -msg="$6" +disk="$1" +part="$2" +lysrdiffpart="$1/$2" +category="$3" +subcategory="$4" +server="$5" +serverpath="$6" +msg="$7" case "$server" in wrath) remoterdiff=/usr/bin/rdiff-backup;; manhattan) remoterdiff=/usr/bin/rdiff-backup;; - sedesopim) remoterdiff=/usr/bin/rdiff-backup;; *) remoterdiff=/opt/LYSrdiff/bin/rdiff-backup;; esac -lysrdiff="/lysrdiff/$lysrdiffpart/perm/lysrdiff" +lysrdiff="/lysrdiff/$disk/perm/$part/lysrdiff" base="$lysrdiff/backups/$category/$subcategory" exclude="$base"/exclude excl_abs="$base"/exclude.abs @@ -62,19 +72,21 @@ summaryfile="$base"/backup-summary.txt lockdir="$base"/lock state="$lysrdiff"/state origin="$base"/origin +rdiffdir="$files/rdiff-backup-data" +incrementsdir="$rdiffdir/increments" mkdir -p "$files" mkdir "$lockdir" || exit 1 -statebase="$state"/"$category"-"$subcategory" +statebase="$state"/"$category"--"$subcategory" -if [ $failed = 1 ] && [ ! -f "$statebase"-fail ] +if [ $failed = 1 ] && [ ! -f "$statebase"--fail ] then rmdir "$lockdir" exit 0 fi -if [ $new = 1 ] && [ -f "$statebase"-end ] +if [ $new = 1 ] && [ -f "$statebase"--end ] then rmdir "$lockdir" exit 0 @@ -101,7 +113,7 @@ then # Leave the origin.new file. Flag this as a failure. touch "$base"/last-failure - touch "$statebase"-fail + touch "$statebase"--fail rmdir "$lockdir" exit 0 @@ -113,7 +125,27 @@ unset SSH_AUTH_SOCK CLR='\e[2K\r' -echo -ne "${msg}: fetching exclude file" +AGE=120D + +echo -ne "${CLR}${msg}: removing increments older than $AGE" +$ss $disk $part "${msg}: removing increments older than $AGE" + +/opt/LYSrdiff/bin/rdiff-backup \ + --remove-older-than $AGE \ + --null-separator \ + --force \ + -v \ + "$files" > "$rdifflogfile" 2>&1 + +if [ $only_prune = 1 ] +then + rmdir "$lockdir" + $ss $disk $part "" + exit 0 +fi + +echo -ne "${CLR}${msg}: fetching exclude file" +$ss $disk $part "${msg}: fetching exclude file" # Fetch an up-to-date exclude file. rm -f "$exclude" @@ -129,10 +161,10 @@ sed < "$exclude" \ # See how long time the previous backup took. -if [ -f "$statebase"-start ] && [ -f "$statebase"-end ] +if [ -f "$statebase"--start ] && [ -f "$statebase"--end ] then - startdec="`stat --format '%Y' \"$statebase\"-start`" - enddec="`stat --format '%Y' \"$statebase\"-end`" + startdec="`stat --format '%Y' \"$statebase\"--start`" + enddec="`stat --format '%Y' \"$statebase\"--end`" seconds=`expr $enddec - $startdec` ETA=`date +' (ETA: %H:%M:%S)' -d "$seconds seconds"` else @@ -140,9 +172,10 @@ else fi touch "$base"/backup-attempt-start -touch "$statebase"-attempt +touch "$statebase"--attempt echo -ne "${CLR}${msg}: running rdiff-backup$ETA" +$ss $disk $part "${msg}: rdiff-backup$ETA" schema="ssh -o BatchMode=yes -o ServerAliveInterval=120" schema="$schema -a -k -x -i /root/.ssh/backupkey" @@ -157,48 +190,81 @@ schema="$schema %s $remoterdiff --server" exit=$? echo -ne "${CLR}" +$ss $disk $part "${msg}: updating status files" if [ $exit = 0 ] then touch "$base"/last-good-backup mv "$base"/backup-attempt-start "$base"/last-good-start - touch "$statebase"-end - mv "$statebase"-attempt "$statebase"-start + touch "$statebase"--end + mv "$statebase"--attempt "$statebase"--start + + starttime="`stat --format '%y' \"$statebase\"--start|sed 's/\..*//'`" + endtime="`stat --format '%y' \"$statebase\"--end|sed 's/\..*//'`" + startdec="`stat --format '%Y' \"$statebase\"--start`" + enddec="`stat --format '%Y' \"$statebase\"--end`" + + echo -ne "${CLR}${msg}: running du" + $ss $disk $part "${msg}: running du" - starttime="`stat --format '%y' \"$statebase\"-start|sed 's/\..*//'`" - endtime="`stat --format '%y' \"$statebase\"-end|sed 's/\..*//'`" - startdec="`stat --format '%Y' \"$statebase\"-start`" - enddec="`stat --format '%Y' \"$statebase\"-end`" + sizes="" - echo $starttime $endtime time=`expr $enddec - $startdec` >> "$summaryfile" + if [ -d "$files" ] + then + totalsize=`du -ks "$files" | awk '{print $1}'` + sizes="$sizes totalsize=$totalsize" + echo $totalsize > "$base"/totalsize + fi + + if [ -d "$rdiffdir" ] + then + metasize=`du -ks "$rdiffdir" --exclude increments | awk '{print $1}'` + sizes="$sizes metasize=$metasize" + echo $metasize > "$base"/metasize + fi + + if [ -d "$incrementsdir" ] + then + incrementsize=`du -ks "$incrementsdir" | awk '{print $1}'` + sizes="$sizes incrementsize=$incrementsize" + echo $incrementsize > "$base"/incrementsize + fi + + echo -ne "${CLR}" + $ss $disk $part "${msg}: updating log file" + + time=`expr $enddec - $startdec` + echo $time > "$base"/time + echo $starttime $endtime time=$time"$sizes" \ + >> "$summaryfile" rm -f "$base"/last-failure - rm -f "$statebase"-fail + rm -f "$statebase"--fail if [ `wc -c < "$rdifflogfile"` = 0 ] then - touch "$statebase"-nowarn + touch "$statebase"--nowarn touch "$base"/last-nowarn-backup - rm -f "$statebase"-neverwarnfree - rm -f "$statebase"-warn + rm -f "$statebase"--neverwarnfree + rm -f "$statebase"--warn logger -p local5.info -t LYSrdiff "$lysrdiffpart $category $subcategory: OK" else echo ${msg}: WARNING: output from rdiff-backup: echo sed 's/^/ /' "$rdifflogfile" echo - if [ ! -f "$statebase"-warn ] + if [ ! -f "$statebase"--warn ] then - touch "$statebase"-warn + touch "$statebase"--warn fi - if [ ! -f "$statebase"-nowarn ] && [ ! -f "$statebase"-neverwarnfree ] + if [ ! -f "$statebase"--nowarn ] && [ ! -f "$statebase"--neverwarnfree ] then - touch "$statebase"-neverwarnfree + touch "$statebase"--neverwarnfree fi logger -p local5.info -t LYSrdiff "$lysrdiffpart $category $subcategory: OK with warnings" fi else mv "$base"/backup-attempt-start "$base"/last-failure - mv "$statebase"-attempt "$statebase"-fail + mv "$statebase"--attempt "$statebase"--fail echo ${msg}: FAIL: echo sed 's/^/ /' "$rdifflogfile" @@ -206,4 +272,42 @@ else logger -p local5.info -t LYSrdiff "$lysrdiffpart $category $subcategory: FAIL" fi +for copy in A B +do + id="/lysrdiff/$disk/$copy/$part/lysrdiff.id" + if [ -f "$id" ] + then + if [ "`cat $id`" != "$disk $copy $part" ] + then + echo wrong disk mounted at /lysrdiff/$disk/$copy/$part >&2 + else + copylysrdiff="/lysrdiff/$disk/$copy/$part/lysrdiff" + copybase="$copylysrdiff/backups/$category/$subcategory" + copystate="$copylysrdiff"/state + copystatebase="$copystate"/"$category"--"$subcategory" + cp /lysrdiff/$disk/perm/$part/lysrdiff/tasks \ + /lysrdiff/$disk/$copy/$part/lysrdiff/tasks.tmp \ + && mv -f /lysrdiff/$disk/$copy/$part/lysrdiff/tasks.tmp \ + /lysrdiff/$disk/$copy/$part/lysrdiff/tasks + + mkdir -p "$copybase" + if mkdir "$copybase"/lock + then + echo -ne "${CLR}${msg}: copying to $copy" + $ss $disk $part "${msg}: copying to $copy" + + rsync -a --delete "$base"/ "$copybase" + rm -f "$copystatebase"--* || true + cp -a "$statebase"--* "$copystate" + rmdir "$copybase"/lock + echo -ne "${CLR}" + else + echo copy $disk $copy $part already locked >&2 + fi + fi + fi +done + +$ss $disk $part "" + rmdir "$lockdir" diff --git a/distribute-tasks b/distribute-tasks index 8bd4b6f6230e732ba4bd34b5cf21b17e8c185c9c..10badd27bccb90b2e72b2221724600472ac19703 100755 --- a/distribute-tasks +++ b/distribute-tasks @@ -10,12 +10,13 @@ ROOT = "/lysrdiff" class JobInfo(object): def __init__(self, category, subcategory, host, directory, - lysrdiffpart=None): + disk=None, part=None): self.__category = category self.__subcategory = subcategory self.__host = host self.__directory = directory - self.__lysrdiffpart = lysrdiffpart + self.__disk = disk + self.__part = part self.__active = False def set_active(self): @@ -25,7 +26,7 @@ class JobInfo(object): return self.__active def set_lysrdiffpart(self, part): - self.__lysrdiffpart = part + self.__disk, self.__part = part def category(self): return self.__category @@ -40,7 +41,10 @@ class JobInfo(object): return self.__directory def lysrdiffpart(self): - return self.__lysrdiffpart + if self.__disk is None: + return None + else: + return (self.__disk, self.__part) def source(self): return (self.host(), self.directory()) @@ -49,8 +53,8 @@ class JobInfo(object): return (self.category(), self.subcategory()) def rdiff_dest(self): - return "/lysrdiff/%d/perm/lysrdiff/backups/%s/%s" % ( - self.lysrdiffpart(), self.category(), self.subcategory()) + return "/lysrdiff/%d/perm/%d/lysrdiff/backups/%s/%s" % ( + self.__disk, self.__part, self.category(), self.subcategory()) def task_desc(self): return "%s %s %s %s" % ( @@ -58,7 +62,9 @@ class JobInfo(object): self.host(), self.directory()) def newtasks(): - return int(open("/nobackup/backup.lysator/var/newtasks").read()) + where = open("/nobackup/backup.lysator/var/newtasks").read() + disk, part = where.split("/") + return (int(disk), int(part)) # key: (host, directory) # value: JobInfo @@ -74,19 +80,19 @@ ordered_tasks = [] fatal = False interactive = False -def tasklist_file(lysrdiffpart): - return "/lysrdiff/%d/perm/lysrdiff/tasks" % lysrdiffpart +def tasklist_file(disk, part): + return "/lysrdiff/%d/perm/%d/lysrdiff/tasks" % (disk, part) -def parse_line(line, lysrdiffpart=None): +def parse_line(line, disk=None, part=None): [category, subcategory, host, directory] = line.split() - info = JobInfo(category, subcategory, host, directory, lysrdiffpart) + info = JobInfo(category, subcategory, host, directory, disk, part) return info -def read_tasks(lysrdiffpart): +def read_tasks(disk, part): global fatal - for line in file(tasklist_file(lysrdiffpart), "r"): - info = parse_line(line, lysrdiffpart) + for line in file(tasklist_file(disk, part), "r"): + info = parse_line(line, disk, part) if (info.host(), info.directory()) in tasks_per_source: sys.stderr.write("Duplicate backup detected!\n %s\n %s\n" % ( @@ -172,14 +178,15 @@ def write_task_lists(partitions): part = job.lysrdiffpart() if part in partitions: if part not in files: - fn = tasklist_file(job.lysrdiffpart()) + fn = tasklist_file(job.lysrdiffpart()[0], + job.lysrdiffpart()[1]) os.mkdir(fn + ".lock") files[part] = file(fn + ".new", "w") files[part].write(job.task_desc() + "\n") for lysrdiffpart, fp in files.items(): fp.close() - fn = tasklist_file(lysrdiffpart) + fn = tasklist_file(lysrdiffpart[0], lysrdiffpart[1]) if skipped: os.system("diff -u %s %s" % (fn, fn + ".new")) raw_input("[confirm]") @@ -196,10 +203,14 @@ def main(): if opt in ("-i", "--interactive"): interactive = True - partitions = sets.Set([int(x) for x in args]) + partitions = sets.Set() + for spec in args: + disk, part = spec.split("/") + partitions.add((int(disk), int(part))) - for lysrdiffpart in range(2): - read_tasks(lysrdiffpart) + for disk in [1, 2]: + for part in [0, 1]: + read_tasks(disk, part) if interactive: read_new_tasks() if not fatal: diff --git a/fetch-backup-work b/fetch-backup-work index fe9658cd90a7ac215586ed55453f17f9b4ee382a..f515bc565dce42a5fa7f8e421fcfcf43bee748f0 100755 --- a/fetch-backup-work +++ b/fetch-backup-work @@ -87,9 +87,7 @@ echo lenin var-log lenin /var/log >> $NT echo lenin var-spool-postfix lenin /var/spool/postfix >> $NT echo lenin var-lib-mailman lenin /var/lib/mailman >> $NT -# FIXME: Not yet backed up: lenin:/home (where all the user mail exists) ssh lenin 'cd /home && ls -1 | while read d ; do [ -d "$d" ] && [ ! -L "$d" ] && echo "$d" ; done' \ -| grep '^[tvamqpojk]' \ | awk '{ print "mail", $1, "lenin", "/home/" $1 }' \ | sort >> $NT diff --git a/lysrdiff-df b/lysrdiff-df new file mode 100755 index 0000000000000000000000000000000000000000..88308ab3c30fceec3828e670e5b97ffb3211f15a --- /dev/null +++ b/lysrdiff-df @@ -0,0 +1,18 @@ +#!/bin/sh + +parts="" +for d in 1 2 +do + for p in 0 1 + do + for copy in perm A B + do + if [ -f /lysrdiff/$d/$copy/$p/lysrdiff.id ] + then + parts=" $parts /lysrdiff/$d/$copy/$p" + fi + done + done +done + +df -h $parts diff --git a/lysrdiff-label-disk b/lysrdiff-label-disk new file mode 100755 index 0000000000000000000000000000000000000000..e5fc686a057e14d8f1e0a978301840fb288cc373 --- /dev/null +++ b/lysrdiff-label-disk @@ -0,0 +1,54 @@ +#!/bin/sh + +disk=$1 +copy=$2 +part=$3 + +cd /lysrdiff/$disk/$copy/$part || exit 1 +if [ -d lost+found ] +then : +else + echo lost+found missing >&2 + exit 1 +fi + +if [ -f lysrdiff.id ] +then + if [ "`cat lysrdiff.id`" = "$disk $copy $part" ] + then + echo Good lysrdiff.id already exited >&2 + else + echo Disk mismatch error. >&2 + echo Mounted: `cat lysrdiff.id` >&2 + echo Expected: $disk $copy $part >&2 + exit 1 + fi +else + echo $disk $copy $part > lysrdiff.id +fi + +cat > README << EOF +This disc belongs to Lysator. It contains backups. Please contact +root@lysator.liu.se if you find this disc. Please don't read the +files on it, as it would violate the personal integrity of the members +of Lysator. + +Your cooperation will be appreciated. +EOF + +mkdir -p spacefiller +for i in 0 1 2 3 4 5 6 7 8 9 +do + fn=spacefiller/filler-$i + if [ -f $fn ] || dd if=/dev/zero of=$fn bs=1024 count=10240 + then : + else + rm $fn + break + fi +done + +mkdir -p lysrdiff/backups +mkdir -p lysrdiff/state + +[ -f lysrdiff/tasks ] || touch lysrdiff/tasks diff --git a/lysrdiff-monitord.py b/lysrdiff-monitord.py new file mode 100755 index 0000000000000000000000000000000000000000..34207d2ec9cdd99dedd402f688001482c07f95da --- /dev/null +++ b/lysrdiff-monitord.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python + +import socket +import select +import sys +import time + +class Client(object): + def __init__(self, accept_retval, dispatcher): + self.__s, peer = accept_retval + self.__peer_ip = peer[0] + self.__peer_port = peer[1] + dispatcher.register(self) + self.__writebuf = "" + self.__readbuf = "" + self.__got_eof = False + self.__dispatcher = dispatcher + + def fileno(self): + return self.__s.fileno() + + def want_read(self): + if self.__got_eof: + return [] + else: + return [self] + + def want_write(self): + if self.__writebuf: + return [self] + else: + return [] + + def read_event(self): + try: + bytes = self.__s.recv(100) + except socket.error, e: + if e[0] == errno.ECONNTIMEOUT: + self.got_eof(True) + return + raise + if bytes == "": + self.got_eof() + return + + self.__readbuf += bytes + self.parse() + + def got_eof(self, error=False): + self.__got_eof = True + if not error: + self.parse() + if len(self.__readbuf) > 0: + sys.stderr.write("EOF with %d bytes pending from client.\n" % ( + len(self.__readbuf))) + if len(self.__writebuf) == 0 or error: + self.__dispatcher.unregister(self) + self.__s.close() + del self + + def write_event(self): + rv = self.__s.send(self.__writebuf) + if rv > 0: + self.__writebuf = self.__writebuf[rv:] + + def parse(self): + ix = self.__readbuf.find("\n") + if ix < 0: + return + cmd = self.__readbuf[:ix] + self.__readbuf = self.__readbuf[ix+1:] + self.handle_cmd(cmd) + + def handle_cmd(self, cmd): + if cmd == "": + pass + elif cmd.startswith("set-status "): + split = cmd.split(None, 3) + if len(split) == 3: + cmd, disk, part = split + status = "" + else: + cmd, disk, part, status = split + dispatcher.set_status(int(disk), int(part), status) + + else: + sys.stderr.write("Unknown command received from client.\n") + + def write(self, buf): + self.__writebuf += buf + self.write_event() + +class Server(object): + IP = '127.0.0.1' + PORT = 9933 + CLIENT = Client + + def __init__(self, dispatcher): + self.__s = socket.socket() + self.__s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) + self.__s.bind((self.IP, self.PORT)) + self.__s.listen(3) + self.__s.setblocking(False) + dispatcher.register(self) + self.__dispatcher = dispatcher + + def want_read(self): + return [self] + + def want_write(self): + return [] + + def fileno(self): + return self.__s.fileno() + + def read_event(self): + self.CLIENT(self.__s.accept(), self.__dispatcher) + +class Vt100Client(Client): + + def handle_cmd(self, cmd): + pass + + def __init__(self, accept_retval, dispatcher): + Client.__init__(self, accept_retval, dispatcher) + dispatcher.register_vt100(self) + +class Vt100Server(Server): + IP = '' + PORT = 9934 + CLIENT = Vt100Client + +class Dispatcher(object): + def __init__(self): + self.__clients = set() + self.__vt100 = set() + self.__break = False + self.__status = {} + + def register(self, client): + self.__clients.add(client) + + def unregister(self, client): + self.__clients.remove(client) + self.__vt100.discard(client) + self.__break = True + self.write_monitor_status() + + def toploop(self): + while True: + r = [] + w = [] + self.__break = False + + for c in self.__clients: + r += c.want_read() + w += c.want_write() + + r, w, e = select.select(r, w, [], None) + + for c in w: + if self.__break: + break + c.write_event() + + for c in r: + if self.__break: + break + c.read_event() + + def register_vt100(self, client): + self.__vt100.add(client) + + msg = "\033[2J\033[HLYSrdiff status\r\n===============" + for disk in self.__status.keys(): + for part in self.__status[disk].keys(): + msg += self.format_status_vt100(disk, part) + client.write(msg) + self.write_monitor_status() + + def write_monitor_status(self): + msg = self.format_monitor_status() + for c in self.__vt100: + c.write(msg) + + def format_monitor_status(self): + msg = "\033[9;1H" + msg += "%d clients" % len(self.__vt100) + msg += "\033[K" + msg += "\033[H" + return msg + + def set_status(self, disk, part, status): + if disk not in self.__status: + self.__status[disk] = {} + + timestamp = time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(time.time())) + self.__status[disk][part] = (timestamp, status) + + msg = self.format_status_vt100(disk, part) + for c in self.__vt100: + c.write(msg) + + print timestamp, "%d/%d" % (disk, part), status + + def format_status_vt100(self, disk, part): + timestamp, status = self.__status[disk][part] + + msg = "\033[%d;1H" % (2 + 2 * disk + part) + msg += timestamp + msg += " " + msg += status + msg += "\033[K" + msg += "\033[H" + + return msg + +if __name__ == '__main__': + dispatcher = Dispatcher() + Server(dispatcher) + Vt100Server(dispatcher) + dispatcher.toploop() diff --git a/lysrdiff-move-job b/lysrdiff-move-job new file mode 100755 index 0000000000000000000000000000000000000000..9b3c2f557c7cf2d160fd272f282d372505bfb18a --- /dev/null +++ b/lysrdiff-move-job @@ -0,0 +1,100 @@ +#!/bin/sh + +uncloned=0 + +if [ x"$1" = x"--uncloned" ] +then + uncloned=1 + shift +fi + +if [ $# != 2 ] +then + echo usage: "$0 [ --uncloned ] from to" >&2 + exit 2 +fi + +srcdisk=`echo $1|sed sx/.*xx` +srcpart=`echo $1|sed sx.*/xx` +dstdisk=`echo $2|sed sx/.*xx` +dstpart=`echo $2|sed sx.*/xx` + +srctask=/lysrdiff/$srcdisk/perm/$srcpart/lysrdiff/tasks +dsttask=/lysrdiff/$dstdisk/perm/$dstpart/lysrdiff/tasks + +mkdir $srctask.lock || exit 1 +mkdir $dsttask.lock || { + rmdir $srctask.lock + exit 1 +} + +while true +do + MOVEDTASK=/tmp/movedtask.$$ + head -1 $srctask > $MOVEDTASK || exit 1 + sed 1d $srctask > $srctask.new || exit 1 + cat $dsttask $MOVEDTASK > $dsttask.new || exit 1 + + read category subcategory rest < $MOVEDTASK + + if [ $uncloned = 0 ] + then + break + fi + + if [ -z "`ls /lysrdiff/$srcdisk/?/$srcpart/lysrdiff/backups/$category/$subcategory 2>/dev/null`" ] + then + break + fi + + echo "Already cloned: $category $subcategory" + cat $MOVEDTASK >> $srctask.new || exit 1 + mv $srctask.new $srctask + rm $dsttask.new + rm $MOVEDTASK +done + +rm $MOVEDTASK + +echo "Moving $category $subcategory" +ss=/nobackup/backup.lysator/bin/lysrdiff-set-status.py +$ss $srcdisk $srcpart "Moving $category $subcategory to $dstdisk $dstpart" +$ss $dstdisk $dstpart "Moving $category $subcategory from $srcdisk $srcpart" + +srcroot="/lysrdiff/$srcdisk/perm/$srcpart/lysrdiff" +dstroot="/lysrdiff/$dstdisk/perm/$dstpart/lysrdiff" +srcbase="$srcroot/backups/$category/$subcategory" +dstbase="$dstroot/backups/$category/$subcategory" +srcstate="$srcroot/state" +dststate="$dstroot/state" + +if [ -d "$dstbase" ] +then + echo "$dstbase": already exists >&2 + rmdir $srctask.lock + rmdir $dsttask.lock + exit 1 +fi + +if [ ! -d "$srcbase" ] +then + echo "$srcbase": no such dir >&2 + rmdir $srctask.lock + rmdir $dsttask.lock + exit 1 +fi + +mkdir "$srcbase"/lock +mkdir -p "$dstbase"/lock +rsync -a "$srcbase/" "$dstbase" || exit 1 +cp -a "$srcstate/$category--$subcategory--"* "$dststate" || exit 1 +rmdir "$srcbase"/lock +rmdir "$dstbase"/lock + +$ss $srcdisk $srcpart "" +$ss $dstdisk $dstpart "" + +mv $srctask.new $srctask +mv $dsttask.new $dsttask +rmdir $srctask.lock +rmdir $dsttask.lock diff --git a/lysrdiff-move-obsolete b/lysrdiff-move-obsolete index 364684cee15a18fb99e0a1fb1db4135585f105b0..4455b7311c7b36eadd0d2ce1bfd2e8a00b3ae77f 100755 --- a/lysrdiff-move-obsolete +++ b/lysrdiff-move-obsolete @@ -2,21 +2,31 @@ shopt -s extglob -for base in /lysrdiff/*/perm/lysrdiff +auto_confirm=0 + +if [ "$1" = --auto-confirm ] +then + auto_confirm=1 +fi + +for base in /lysrdiff/*/*/*/lysrdiff do cd $base/backups || continue for cat in * do + [ "$cat" = '*' ] && continue ( cd "$cat" || continue for subcat in * do + [ "$subcat" = '*' ] && continue + fgrep "$cat $subcat " $base/tasks >/dev/null if [ $? -eq 1 ] then echo $base $cat $subcat seems obsolete - ls -l $base/state/"$cat"-"$subcat"-+([a-z]) | sed 's/^/ /' - for otherbase in /lysrdiff/*/perm/lysrdiff + ls -l $base/state/"$cat"--"$subcat"--+([a-z]) | sed 's/^/ /' + for otherbase in /lysrdiff/*/perm/*/lysrdiff do if [ $base != $otherbase ] then @@ -25,7 +35,7 @@ do then echo ' found in '$otherbase: echo -n ' ' - ls -l $otherbase/state/"$cat-$subcat-end" + ls -l $otherbase/state/"$cat--$subcat--end" fi fi done @@ -34,13 +44,16 @@ do echo ' ALREADY OBSOLETED!' continue fi - echo -n '[CONFIRM]' - read line + if [ $auto_confirm = 0 ] + then + echo -n '[CONFIRM]' + read line + fi mkdir -p $base/obsolete/backups/"$cat" mkdir -p $base/obsolete/state mv $base/backups/"$cat"/"$subcat" \ $base/obsolete/backups/"$cat"/"$subcat" - mv $base/state/"$cat"-"$subcat"-+([a-z]) $base/obsolete/state + mv $base/state/"$cat"--"$subcat"--+([a-z]) $base/obsolete/state echo fi done diff --git a/lysrdiff-set-status.py b/lysrdiff-set-status.py new file mode 100755 index 0000000000000000000000000000000000000000..c88e9d1b37fd983175f48996eb78da4bdd521fa7 --- /dev/null +++ b/lysrdiff-set-status.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +import sys +import errno +import socket + +def sendmsg(disk, part, msg): + s = socket.socket() + try: + s.connect(("127.0.0.1", 9933)) + except socket.error, e: + if e[0] == errno.ECONNREFUSED: + return + raise + s.send("set-status %d %d %s\n" % (disk, part, msg)) + s.close() + +if __name__ == '__main__': + sendmsg(int(sys.argv[1]), int(sys.argv[2]), sys.argv[3]) diff --git a/lysrdiff-status b/lysrdiff-status index 82d57d14048d372539a45d338a2e93d2da46052f..048037ee2f29249bd29c97032b38f5e1fe452aa6 100755 --- a/lysrdiff-status +++ b/lysrdiff-status @@ -1,53 +1,55 @@ #!/bin/sh -df -hl /lysrdiff/*/perm/lysrdiff +df -hl /lysrdiff/*/perm/*/lysrdiff echo -statecache=/tmp/cached-state +statecache=/tmp/cached-state-$USER -for part in /lysrdiff/*/perm/lysrdiff +for base in /lysrdiff/*/perm/*/lysrdiff do - partno=`echo $part|sed 's%/lysrdiff/\([0-9]*\)/perm/lysrdiff%\1%'` - find $part/state -type f -printf " %TY-%Tm-%Td %TH:%TM:%TS $partno %P\n" \ - | sort > $statecache.$partno.$$ - mv -f $statecache.$partno.$$ $statecache-$partno - echo -n $partno':' - echo -n ' 'Tasks: `wc -l < $part/tasks` - echo -n ' 'Fresh: `find $part/state -name \*-start -ctime 0 -print|wc -l` - echo -n ' '1day: `find $part/state -name \*-start -ctime 1 -print|wc -l` - echo -n ' 'Stale: `find $part/state -name \*-start -ctime +1 -print|wc -l` - echo -n ' 'Tot: `ls $part/state/*-end|wc -l` - echo -n ' 'Warn: `find $part/state -name \*-warn -print|wc -l` - echo ' 'Err: `ls $part/state/*-fail 2>/dev/null|wc -l` + disk=`echo $base|sed 's%/lysrdiff/\([0-9]*\)/perm/\([0-9]*\)/lysrdiff%\1%'` + part=`echo $base|sed 's%/lysrdiff/\([0-9]*\)/perm/\([0-9]*\)/lysrdiff%\2%'` + find $base/state -type f \ + -printf " %TY-%Tm-%Td %TH:%TM:%TS $disk/$part %P\n" \ + | sort > $statecache.$disk.$part.$$ + mv -f $statecache.$disk.$part.$$ $statecache-$disk.$part + echo -n $disk/$part':' + echo -n ' 'Tasks: `wc -l < $base/tasks` + echo -n ' 'Fresh: `find $base/state -name \*--start -ctime 0 -print|wc -l` + echo -n ' '1day: `find $base/state -name \*--start -ctime 1 -print|wc -l` + echo -n ' 'Stale: `find $base/state -name \*--start -ctime +1 -print|wc -l` + echo -n ' 'Tot: `ls $base/state/*--end|wc -l` + echo -n ' 'Warn: `find $base/state -name \*--warn -print|wc -l` + echo ' 'Err: `ls $base/state/*--fail 2>/dev/null|wc -l` done echo echo Warnings: echo -grep -h -- '-warn$' $statecache-* | sort +grep -h -- '--warn$' $statecache-* | sort echo echo Failures: echo -grep -h -- '-fail$' $statecache-* | sort +grep -h -- '--fail$' $statecache-* | sort echo echo Top 5 most stale backups: echo -grep -h -- '-start$' $statecache-* | sort | head -5 +grep -h -- '--start$' $statecache-* | sort | head -5 echo echo Most stale per part: echo for i in $statecache-* do - grep -h -- '-start$' $i | sort | head -1 + grep -h -- '--start$' $i | sort | head -1 done echo echo Freshest per part: echo for i in $statecache-* do - grep -h -- '-start$' $i | sort | tail -1 + grep -h -- '--start$' $i | sort | tail -1 done echo echo All unfinished backup attempts: echo -grep -h -- '-attempt$' $statecache-* | sort | head -5 +grep -h -- '--attempt$' $statecache-* | sort | head -5