reject transfers when there isn't enough disk space available

closes #91

Also tweaks an error message: don't say "refusing to clobber pre-existing
file FOO" when we don't check that it's actually a file. Just say "..
pre-existing 'FOO'".
This commit is contained in:
Brian Warner 2016-12-10 15:30:51 -08:00
parent b57928431a
commit f3e1aab3a1
2 changed files with 120 additions and 45 deletions

View File

@ -8,7 +8,8 @@ from twisted.python import log
from ..wormhole import wormhole from ..wormhole import wormhole
from ..transit import TransitReceiver from ..transit import TransitReceiver
from ..errors import TransferError, WormholeClosedError from ..errors import TransferError, WormholeClosedError
from ..util import dict_to_bytes, bytes_to_dict, bytes_to_hexstr from ..util import (dict_to_bytes, bytes_to_dict, bytes_to_hexstr,
estimate_free_space)
APPID = u"lothar.com/wormhole/text-or-file-xfer" APPID = u"lothar.com/wormhole/text-or-file-xfer"
@ -198,6 +199,11 @@ class TwistedReceiver:
self.abs_destname = self._decide_destname("file", self.abs_destname = self._decide_destname("file",
file_data["filename"]) file_data["filename"])
self.xfersize = file_data["filesize"] self.xfersize = file_data["filesize"]
free = estimate_free_space(self.abs_destname)
if free is not None and free < self.xfersize:
self._msg(u"Error: insufficient free space (%sB) for file (%sB)"
% (free, self.xfersize))
raise TransferRejectedError()
self._msg(u"Receiving file (%s) into: %s" % self._msg(u"Receiving file (%s) into: %s" %
(naturalsize(self.xfersize), os.path.basename(self.abs_destname))) (naturalsize(self.xfersize), os.path.basename(self.abs_destname)))
@ -214,6 +220,11 @@ class TwistedReceiver:
self.abs_destname = self._decide_destname("directory", self.abs_destname = self._decide_destname("directory",
file_data["dirname"]) file_data["dirname"])
self.xfersize = file_data["zipsize"] self.xfersize = file_data["zipsize"]
free = estimate_free_space(self.abs_destname)
if free is not None and free < file_data["numbytes"]:
self._msg(u"Error: insufficient free space (%sB) for directory (%sB)"
% (free, file_data["numbytes"]))
raise TransferRejectedError()
self._msg(u"Receiving directory (%s) into: %s/" % self._msg(u"Receiving directory (%s) into: %s/" %
(naturalsize(self.xfersize), os.path.basename(self.abs_destname))) (naturalsize(self.xfersize), os.path.basename(self.abs_destname)))
@ -232,8 +243,7 @@ class TwistedReceiver:
# get confirmation from the user before writing to the local directory # get confirmation from the user before writing to the local directory
if os.path.exists(abs_destname): if os.path.exists(abs_destname):
self._msg(u"Error: refusing to overwrite existing %s %s" % self._msg(u"Error: refusing to overwrite existing '%s'" % destname)
(mode, destname))
raise TransferRejectedError() raise TransferRejectedError()
return abs_destname return abs_destname

View File

@ -449,7 +449,9 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
return self._do_test(mode="directory", override_filename=True) return self._do_test(mode="directory", override_filename=True)
@inlineCallbacks @inlineCallbacks
def test_file_noclobber(self): def _do_test_fail(self, mode, failmode):
assert mode in ("file", "directory")
assert failmode in ("noclobber", "toobig")
send_cfg = config("send") send_cfg = config("send")
recv_cfg = config("receive") recv_cfg = config("receive")
@ -458,29 +460,42 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
cfg.relay_url = self.relayurl cfg.relay_url = self.relayurl
cfg.transit_helper = "" cfg.transit_helper = ""
cfg.listen = False cfg.listen = False
cfg.code = code = "1-abc" cfg.code = "1-abc"
cfg.stdout = io.StringIO() cfg.stdout = io.StringIO()
cfg.stderr = io.StringIO() cfg.stderr = io.StringIO()
message = "test message"
recv_cfg.accept_file = True
send_dir = self.mktemp() send_dir = self.mktemp()
os.mkdir(send_dir) os.mkdir(send_dir)
receive_dir = self.mktemp() receive_dir = self.mktemp()
os.mkdir(receive_dir) os.mkdir(receive_dir)
recv_cfg.accept_file = True # don't ask for permission
send_filename = "testfile" if mode == "file":
with open(os.path.join(send_dir, send_filename), "w") as f: message = "test message\n"
f.write(message) send_cfg.what = receive_name = send_filename = "testfile"
send_cfg.what = receive_filename = send_filename with open(os.path.join(send_dir, send_filename), "w") as f:
recv_cfg.what = receive_filename f.write(message)
PRESERVE = "don't clobber me\n" elif mode == "directory":
clobberable = os.path.join(receive_dir, receive_filename) # $send_dir/
with open(clobberable, "w") as f: # $send_dir/$dirname/
f.write(PRESERVE) # $send_dir/$dirname/[12345]
# cd $send_dir && wormhole send $dirname
# cd $receive_dir && wormhole receive
# expect: $receive_dir/$dirname/[12345]
send_cfg.what = receive_name = send_dirname = "testdir"
os.mkdir(os.path.join(send_dir, send_dirname))
for i in range(5):
path = os.path.join(send_dir, send_dirname, str(i))
with open(path, "w") as f:
f.write("test message %d\n" % i)
if failmode == "noclobber":
PRESERVE = "don't clobber me\n"
clobberable = os.path.join(receive_dir, receive_name)
with open(clobberable, "w") as f:
f.write(PRESERVE)
send_cfg.cwd = send_dir send_cfg.cwd = send_dir
send_d = cmd_send.send(send_cfg) send_d = cmd_send.send(send_cfg)
@ -488,13 +503,17 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
recv_cfg.cwd = receive_dir recv_cfg.cwd = receive_dir
receive_d = cmd_receive.receive(recv_cfg) receive_d = cmd_receive.receive(recv_cfg)
# both sides will fail because of the pre-existing file # both sides will fail
if failmode == "noclobber":
f = yield self.assertFailure(send_d, TransferError) free_space = 10000000
self.assertEqual(str(f), "remote error, transfer abandoned: transfer rejected") else:
free_space = 0
f = yield self.assertFailure(receive_d, TransferError) with mock.patch("wormhole.cli.cmd_receive.estimate_free_space",
self.assertEqual(str(f), "transfer rejected") return_value=free_space):
f = yield self.assertFailure(send_d, TransferError)
self.assertEqual(str(f), "remote error, transfer abandoned: transfer rejected")
f = yield self.assertFailure(receive_d, TransferError)
self.assertEqual(str(f), "transfer rejected")
send_stdout = send_cfg.stdout.getvalue() send_stdout = send_cfg.stdout.getvalue()
send_stderr = send_cfg.stderr.getvalue() send_stderr = send_cfg.stderr.getvalue()
@ -513,28 +532,74 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
(receive_stdout, receive_stderr)) (receive_stdout, receive_stderr))
# check sender # check sender
self.failUnlessIn("Sending {size:s} file named '{name}'{NL}" if mode == "file":
.format(size=naturalsize(len(message)), self.failUnlessIn("Sending {size:s} file named '{name}'{NL}"
name=send_filename, .format(size=naturalsize(len(message)),
NL=NL), send_stdout) name=send_filename,
self.failUnlessIn("On the other computer, please run: " NL=NL), send_stdout)
"wormhole receive{NL}" self.failUnlessIn("On the other computer, please run: "
"Wormhole code is: {code}{NL}{NL}" "wormhole receive{NL}"
.format(code=code, NL=NL), "Wormhole code is: {code}{NL}{NL}"
send_stdout) .format(code=send_cfg.code, NL=NL),
self.failIfIn("File sent.. waiting for confirmation{NL}" send_stdout)
"Confirmation received. Transfer complete.{NL}" self.failIfIn("File sent.. waiting for confirmation{NL}"
.format(NL=NL), send_stdout) "Confirmation received. Transfer complete.{NL}"
.format(NL=NL), send_stdout)
elif mode == "directory":
self.failUnlessIn("Sending directory", send_stdout)
self.failUnlessIn("named 'testdir'", send_stdout)
self.failUnlessIn("On the other computer, please run: "
"wormhole receive{NL}"
"Wormhole code is: {code}{NL}{NL}"
.format(code=send_cfg.code, NL=NL), send_stdout)
self.failIfIn("File sent.. waiting for confirmation{NL}"
"Confirmation received. Transfer complete.{NL}"
.format(NL=NL), send_stdout)
# check receiver # check receiver
self.failUnlessIn("Error: " if mode == "file":
"refusing to overwrite existing file testfile{NL}" self.failIfIn("Received file written to ", receive_stdout)
.format(NL=NL), receive_stdout) if failmode == "noclobber":
self.failIfIn("Received file written to ", receive_stdout) self.failUnlessIn("Error: "
fn = os.path.join(receive_dir, receive_filename) "refusing to overwrite existing 'testfile'{NL}"
self.failUnless(os.path.exists(fn)) .format(NL=NL), receive_stdout)
with open(fn, "r") as f: else:
self.failUnlessEqual(f.read(), PRESERVE) self.failUnlessIn("Error: "
"insufficient free space (0B) for file (13B){NL}"
.format(NL=NL), receive_stdout)
elif mode == "directory":
self.failIfIn("Received files written to {name}"
.format(name=receive_name), receive_stdout)
#want = (r"Receiving directory \(\d+ \w+\) into: {name}/"
# .format(name=receive_name))
#self.failUnless(re.search(want, receive_stdout),
# (want, receive_stdout))
if failmode == "noclobber":
self.failUnlessIn("Error: "
"refusing to overwrite existing 'testdir'{NL}"
.format(NL=NL), receive_stdout)
else:
self.failUnlessIn("Error: "
"insufficient free space (0B) for directory (75B){NL}"
.format(NL=NL), receive_stdout)
if failmode == "noclobber":
fn = os.path.join(receive_dir, receive_name)
self.failUnless(os.path.exists(fn))
with open(fn, "r") as f:
self.failUnlessEqual(f.read(), PRESERVE)
# check server stats
self._rendezvous.get_stats()
def test_fail_file_noclobber(self):
return self._do_test_fail("file", "noclobber")
def test_fail_directory_noclobber(self):
return self._do_test_fail("directory", "noclobber")
def test_fail_file_toobig(self):
return self._do_test_fail("file", "toobig")
def test_fail_directory_toobig(self):
return self._do_test_fail("directory", "toobig")
class NotWelcome(ServerBase, unittest.TestCase): class NotWelcome(ServerBase, unittest.TestCase):
def setUp(self): def setUp(self):