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,27 +460,40 @@ 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":
message = "test message\n"
send_cfg.what = receive_name = send_filename = "testfile"
with open(os.path.join(send_dir, send_filename), "w") as f: with open(os.path.join(send_dir, send_filename), "w") as f:
f.write(message) f.write(message)
send_cfg.what = receive_filename = send_filename
recv_cfg.what = receive_filename
elif mode == "directory":
# $send_dir/
# $send_dir/$dirname/
# $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" PRESERVE = "don't clobber me\n"
clobberable = os.path.join(receive_dir, receive_filename) clobberable = os.path.join(receive_dir, receive_name)
with open(clobberable, "w") as f: with open(clobberable, "w") as f:
f.write(PRESERVE) f.write(PRESERVE)
@ -488,11 +503,15 @@ 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":
free_space = 10000000
else:
free_space = 0
with mock.patch("wormhole.cli.cmd_receive.estimate_free_space",
return_value=free_space):
f = yield self.assertFailure(send_d, TransferError) f = yield self.assertFailure(send_d, TransferError)
self.assertEqual(str(f), "remote error, transfer abandoned: transfer rejected") self.assertEqual(str(f), "remote error, transfer abandoned: transfer rejected")
f = yield self.assertFailure(receive_d, TransferError) f = yield self.assertFailure(receive_d, TransferError)
self.assertEqual(str(f), "transfer rejected") self.assertEqual(str(f), "transfer rejected")
@ -513,6 +532,7 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
(receive_stdout, receive_stderr)) (receive_stdout, receive_stderr))
# check sender # check sender
if mode == "file":
self.failUnlessIn("Sending {size:s} file named '{name}'{NL}" self.failUnlessIn("Sending {size:s} file named '{name}'{NL}"
.format(size=naturalsize(len(message)), .format(size=naturalsize(len(message)),
name=send_filename, name=send_filename,
@ -520,22 +540,67 @@ class PregeneratedCode(ServerBase, ScriptsBase, unittest.TestCase):
self.failUnlessIn("On the other computer, please run: " self.failUnlessIn("On the other computer, please run: "
"wormhole receive{NL}" "wormhole receive{NL}"
"Wormhole code is: {code}{NL}{NL}" "Wormhole code is: {code}{NL}{NL}"
.format(code=code, NL=NL), .format(code=send_cfg.code, NL=NL),
send_stdout) send_stdout)
self.failIfIn("File sent.. waiting for confirmation{NL}" self.failIfIn("File sent.. waiting for confirmation{NL}"
"Confirmation received. Transfer complete.{NL}" "Confirmation received. Transfer complete.{NL}"
.format(NL=NL), send_stdout) .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}"
.format(NL=NL), receive_stdout)
self.failIfIn("Received file written to ", receive_stdout) self.failIfIn("Received file written to ", receive_stdout)
fn = os.path.join(receive_dir, receive_filename) if failmode == "noclobber":
self.failUnlessIn("Error: "
"refusing to overwrite existing 'testfile'{NL}"
.format(NL=NL), receive_stdout)
else:
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)) self.failUnless(os.path.exists(fn))
with open(fn, "r") as f: with open(fn, "r") as f:
self.failUnlessEqual(f.read(), PRESERVE) 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):
self._setup_relay(error="please upgrade XYZ") self._setup_relay(error="please upgrade XYZ")