summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <lars.wirzenius@codethink.co.uk>2014-05-20 14:35:59 +0100
committerLars Wirzenius <lars.wirzenius@codethink.co.uk>2014-05-20 14:35:59 +0100
commitaa7ad950590dffef2a7dc37135c763e9890a5b9e (patch)
tree3cf5156554d3bd89f86cf4142af0f7a944e110c9
downloadxfer-hole-aa7ad950590dffef2a7dc37135c763e9890a5b9e.tar.gz
Initial commit
-rwxr-xr-xmk-test-data32
-rwxr-xr-xrecv-hole41
-rwxr-xr-xtest.sh12
-rwxr-xr-xxfer-hole.py107
4 files changed, 192 insertions, 0 deletions
diff --git a/mk-test-data b/mk-test-data
new file mode 100755
index 0000000..57a3213
--- /dev/null
+++ b/mk-test-data
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+data()
+{
+ echo -n "foobar" >> "$1"
+}
+
+hole()
+{
+ truncate --size +10240 "$1"
+}
+
+
+touch test.empty
+
+data test.data
+
+hole test.hole
+
+data test.data-hole
+hole test.data-hole
+
+hole test.hole-data
+data test.hole-data
+
+data test.data-hole-data
+hole test.data-hole-data
+data test.data-hole-data
+
+hole test.hole-data-hole
+data test.hole-data-hole
+hole test.hole-data-hole
diff --git a/recv-hole b/recv-hole
new file mode 100755
index 0000000..cd54233
--- /dev/null
+++ b/recv-hole
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+set -eu
+
+
+die()
+{
+ echo "$@" 1>&2
+ exit 1
+}
+
+
+recv_hole()
+{
+ local n
+
+ read n
+ truncate --size "+$n" "$1"
+}
+
+
+recv_data()
+{
+ local n
+
+ read n
+ dd of="$1" conv=notrunc oflag=append bs=1 count="$n" status=noxfer 2>&1 | grep -Ev ' records (in|out)$' || true
+}
+
+
+output="$1"
+
+touch "$output"
+while read what
+do
+ case "$what" in
+ DATA) recv_data "$output" ;;
+ HOLE) recv_hole "$output" ;;
+ *) die "Unknown instruction: $what" ;;
+ esac
+done
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..04f2d3f
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+set -eu
+
+rm -rf result
+mkdir result
+for x in test.*
+do
+ echo "============== $x ================="
+ ./xfer-hole.py "$x" | ./recv-hole "result/$x"
+ cmp "$x" "result/$x"
+done
diff --git a/xfer-hole.py b/xfer-hole.py
new file mode 100755
index 0000000..0822232
--- /dev/null
+++ b/xfer-hole.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+
+
+import errno
+import os
+import sys
+
+
+SEEK_DATA = 3
+SEEK_HOLE = 4
+
+
+filename = sys.argv[1]
+fd = os.open(filename, os.O_RDONLY)
+pos = 0
+
+
+DATA = 'data'
+HOLE = 'hole'
+EOF = 'eof'
+
+
+def safe_lseek(fd, pos, whence):
+ try:
+ return os.lseek(fd, pos, whence)
+ except OSError as e:
+ if e.errno == errno.ENXIO:
+ return -1
+ raise
+
+
+def current_data_or_pos(fd, pos):
+ length = safe_lseek(fd, 0, os.SEEK_END)
+ next_data = safe_lseek(fd, pos, SEEK_DATA)
+ next_hole = safe_lseek(fd, pos, SEEK_HOLE)
+
+ if pos == length:
+ return EOF, pos
+ elif pos == next_data:
+ return DATA, pos
+ elif pos == next_hole:
+ return HOLE, pos
+ else:
+ assert False, \
+ ("Do not understand: pos=%d next_data=%d next_hole=%d" %
+ (pos, next_data, next_hole))
+
+
+def next_data_or_hole(fd, pos):
+ length = safe_lseek(fd, 0, os.SEEK_END)
+ next_data = safe_lseek(fd, pos, SEEK_DATA)
+ next_hole = safe_lseek(fd, pos, SEEK_HOLE)
+
+ if pos == length:
+ return EOF, pos
+ elif pos == next_data:
+ # We are at data.
+ if next_hole == -1 or next_hole == length:
+ return EOF, length
+ else:
+ return HOLE, next_hole
+ elif pos == next_hole:
+ # We are at a hole.
+ if next_data == -1 or next_data == length:
+ return EOF, length
+ else:
+ return DATA, next_data
+ else:
+ assert False, \
+ ("Do not understand: pos=%d next_data=%d next_hole=%d" %
+ (pos, next_data, next_hole))
+
+
+def find_data_and_holes(fd):
+ pos = safe_lseek(fd, 0, os.SEEK_CUR)
+
+ kind, pos = current_data_or_pos(fd, pos)
+ while kind != EOF:
+ yield kind, pos
+ kind, pos = next_data_or_hole(fd, pos)
+ yield kind, pos
+
+
+def make_xfer_instructions(fd):
+ prev_kind = None
+ prev_pos = None
+ for kind, pos in find_data_and_holes(fd):
+ if prev_kind == DATA:
+ yield (DATA, prev_pos, pos)
+ elif prev_kind == HOLE:
+ yield (HOLE, prev_pos, pos)
+ prev_kind = kind
+ prev_pos = pos
+
+
+def copy_slice_from_file(to, fd, start, end):
+ safe_lseek(fd, start, os.SEEK_SET)
+ data = os.read(fd, end - start)
+ to.write(data)
+
+
+for kind, start, end in make_xfer_instructions(fd):
+ if kind == HOLE:
+ sys.stdout.write('HOLE\n%d\n' % (end - start))
+ elif kind == DATA:
+ sys.stdout.write('DATA\n%d\n' % (end - start))
+ copy_slice_from_file(sys.stdout, fd, start, end)