tahoe add-alias/create-alias: don't corrupt non-newline-terminated alias

file. Closes #741.
This commit is contained in:
Brian Warner 2010-01-14 13:02:46 -08:00
parent d3d1293d2f
commit 874a979a8e
2 changed files with 84 additions and 8 deletions

View File

@ -3,6 +3,7 @@ import os.path
from allmydata import uri
from allmydata.scripts.common_http import do_http, check_http_error
from allmydata.scripts.common import get_aliases
from allmydata.util.fileutil import move_into_place
def add_alias(options):
nodedir = options['node-directory']
@ -19,9 +20,24 @@ def add_alias(options):
return 1
aliasfile = os.path.join(nodedir, "private", "aliases")
cap = uri.from_string_dirnode(cap).to_string()
f = open(aliasfile, "a")
f.write("%s: %s\n" % (alias, cap))
# we use os.path.exists, rather than catching EnvironmentError, to avoid
# clobbering the valuable alias file in case of spurious or transient
# filesystem errors.
if os.path.exists(aliasfile):
f = open(aliasfile, "r")
aliases = f.read()
f.close()
if not aliases.endswith("\n"):
aliases += "\n"
else:
aliases = ""
aliases += "%s: %s\n" % (alias, cap)
f = open(aliasfile+".tmp", "w")
f.write(aliases)
f.close()
move_into_place(aliasfile+".tmp", aliasfile)
print >>stdout, "Alias '%s' added" % (alias,)
return 0
@ -52,16 +68,28 @@ def create_alias(options):
new_uri = resp.read().strip()
# probably check for others..
f = open(aliasfile, "a")
f.write("%s: %s\n" % (alias, new_uri))
# see above about EnvironmentError
if os.path.exists(aliasfile):
f = open(aliasfile, "r")
aliases = f.read()
f.close()
if not aliases.endswith("\n"):
aliases += "\n"
else:
aliases = ""
aliases += "%s: %s\n" % (alias, new_uri)
f = open(aliasfile+".tmp", "w")
f.write(aliases)
f.close()
move_into_place(aliasfile+".tmp", aliasfile)
print >>stdout, "Alias '%s' created" % (alias,)
return 0
def list_aliases(options):
nodedir = options['node-directory']
stdout = options.stdout
stderr = options.stderr
aliases = get_aliases(nodedir)
alias_names = sorted(aliases.keys())
max_width = max([len(name) for name in alias_names] + [0])

View File

@ -98,7 +98,6 @@ class CLI(unittest.TestCase):
def test_dump_cap_chk(self):
key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
storage_index = hashutil.storage_index_hash(key)
uri_extension_hash = hashutil.uri_extension_hash("stuff")
needed_shares = 25
total_shares = 100
@ -460,6 +459,7 @@ class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase):
def test_create(self):
self.basedir = "cli/CreateAlias/create"
self.set_up_grid()
aliasfile = os.path.join(self.get_clientdir(), "private", "aliases")
d = self.do_cli("create-alias", "tahoe")
def _done((rc,stdout,stderr)):
@ -522,6 +522,56 @@ class CreateAlias(GridTestMixin, CLITestMixin, unittest.TestCase):
self._test_webopen(["two:"], self.two_url)
d.addCallback(_test_urls)
def _remove_trailing_newline_and_create_alias(ign):
f = open(aliasfile, "r")
old = f.read()
f.close()
# ticket #741 is about a manually-edited alias file (which
# doesn't end in a newline) being corrupted by a subsequent
# "tahoe create-alias"
f = open(aliasfile, "w")
f.write(old.rstrip())
f.close()
return self.do_cli("create-alias", "un-corrupted1")
d.addCallback(_remove_trailing_newline_and_create_alias)
def _check_not_corrupted1((rc,stdout,stderr)):
self.failUnless("Alias 'un-corrupted1' created" in stdout, stdout)
self.failIf(stderr)
# the old behavior was to simply append the new record, causing a
# line that looked like "NAME1: CAP1NAME2: CAP2". This won't look
# like a valid dircap, so get_aliases() will raise an exception.
aliases = get_aliases(self.get_clientdir())
self.failUnless("added" in aliases)
self.failUnless(aliases["added"].startswith("URI:DIR2:"))
# to be safe, let's confirm that we don't see "NAME2:" in CAP1.
# No chance of a false-negative, because the hyphen in
# "un-corrupted1" is not a valid base32 character.
self.failIfIn("un-corrupted1:", aliases["added"])
self.failUnless("un-corrupted1" in aliases)
self.failUnless(aliases["un-corrupted1"].startswith("URI:DIR2:"))
d.addCallback(_check_not_corrupted1)
def _remove_trailing_newline_and_add_alias(ign):
# same thing, but for "tahoe add-alias"
f = open(aliasfile, "r")
old = f.read()
f.close()
f = open(aliasfile, "w")
f.write(old.rstrip())
f.close()
return self.do_cli("add-alias", "un-corrupted2", self.two_uri)
d.addCallback(_remove_trailing_newline_and_add_alias)
def _check_not_corrupted((rc,stdout,stderr)):
self.failUnless("Alias 'un-corrupted2' added" in stdout, stdout)
self.failIf(stderr)
aliases = get_aliases(self.get_clientdir())
self.failUnless("un-corrupted1" in aliases)
self.failUnless(aliases["un-corrupted1"].startswith("URI:DIR2:"))
self.failIfIn("un-corrupted2:", aliases["un-corrupted1"])
self.failUnless("un-corrupted2" in aliases)
self.failUnless(aliases["un-corrupted2"].startswith("URI:DIR2:"))
d.addCallback(_check_not_corrupted)
return d
class Put(GridTestMixin, CLITestMixin, unittest.TestCase):
@ -592,7 +642,6 @@ class Put(GridTestMixin, CLITestMixin, unittest.TestCase):
self.set_up_grid()
rel_fn = os.path.join(self.basedir, "DATAFILE")
abs_fn = os.path.abspath(rel_fn)
# we make the file small enough to fit in a LIT file, for speed
DATA = "short file"
DATA2 = "short file two"
@ -676,7 +725,6 @@ class Put(GridTestMixin, CLITestMixin, unittest.TestCase):
DATA = "data" * 100
DATA2 = "two" * 100
rel_fn = os.path.join(self.basedir, "DATAFILE")
abs_fn = os.path.abspath(rel_fn)
DATA3 = "three" * 100
f = open(rel_fn, "w")
f.write(DATA3)