mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-27 00:21:07 +00:00
Merge pull request #492 from exarkun/2879.magic-folder-create-and-umask.0
Create the magic-folder local directory if necessary
This commit is contained in:
commit
c219102afc
@ -1,6 +1,7 @@
|
||||
|
||||
import sys, os
|
||||
import os.path
|
||||
from errno import EEXIST
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
import time
|
||||
@ -40,6 +41,13 @@ _DEFAULT_DOWNLOAD_UMASK = 0o077
|
||||
|
||||
IN_EXCL_UNLINK = 0x04000000L
|
||||
|
||||
|
||||
class ConfigurationError(Exception):
|
||||
"""
|
||||
There was something wrong with some magic-folder configuration.
|
||||
"""
|
||||
|
||||
|
||||
def get_inotify_module():
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
@ -148,22 +156,6 @@ def load_magic_folders(node_directory):
|
||||
interval = int(config.get("magic_folder", "poll_interval"))
|
||||
except ConfigParser.NoOptionError:
|
||||
interval = 60
|
||||
dir_fp = to_filepath(directory)
|
||||
|
||||
if not dir_fp.exists():
|
||||
raise Exception(
|
||||
"The '[magic_folder] local.directory' parameter is {} "
|
||||
"but there is no directory at that location.".format(
|
||||
quote_local_unicode_path(directory),
|
||||
)
|
||||
)
|
||||
if not dir_fp.isdir():
|
||||
raise Exception(
|
||||
"The '[magic_folder] local.directory' parameter is {} "
|
||||
"but the thing at that location is not a directory.".format(
|
||||
quote_local_unicode_path(directory)
|
||||
)
|
||||
)
|
||||
|
||||
if config.has_option("magic_folder", "download.umask"):
|
||||
umask = int(config.get("magic_folder", "download.umask"), 8)
|
||||
@ -211,31 +203,87 @@ def load_magic_folders(node_directory):
|
||||
)
|
||||
|
||||
# check configuration
|
||||
for (name, mf_config) in folders.items():
|
||||
if not isinstance(mf_config, dict):
|
||||
raise Exception(
|
||||
"Each item in '{}' must itself be a dict".format(yaml_fname)
|
||||
)
|
||||
for k in ['collective_dircap', 'upload_dircap', 'directory', 'poll_interval']:
|
||||
if k not in mf_config:
|
||||
raise Exception(
|
||||
"Config for magic folder '{}' is missing '{}'".format(
|
||||
name, k
|
||||
)
|
||||
)
|
||||
for k in ['collective_dircap', 'upload_dircap']:
|
||||
if isinstance(mf_config[k], unicode):
|
||||
mf_config[k] = mf_config[k].encode('ascii')
|
||||
|
||||
if not isinstance(
|
||||
mf_config.setdefault(u"umask", _DEFAULT_DOWNLOAD_UMASK),
|
||||
int,
|
||||
):
|
||||
raise Exception("magic-folder download umask must be an integer")
|
||||
|
||||
folders = dict(
|
||||
(name, fix_magic_folder_config(yaml_fname, name, config))
|
||||
for (name, config)
|
||||
in folders.items()
|
||||
)
|
||||
return folders
|
||||
|
||||
|
||||
def fix_magic_folder_config(yaml_fname, name, config):
|
||||
"""
|
||||
Check the given folder configuration for validity.
|
||||
|
||||
If it refers to a local directory which does not exist, create that
|
||||
directory with the configured permissions.
|
||||
|
||||
:param unicode yaml_fname: The configuration file from which the
|
||||
configuration was read.
|
||||
|
||||
:param unicode name: The name of the magic-folder this particular
|
||||
configuration blob is associated with.
|
||||
|
||||
:param config: The configuration for a single magic-folder. This is
|
||||
expected to be a ``dict`` with certain keys and values of certain
|
||||
types but these properties will be checked.
|
||||
|
||||
:raise ConfigurationError: If the given configuration object does not
|
||||
conform to some magic-folder configuration requirement.
|
||||
"""
|
||||
if not isinstance(config, dict):
|
||||
raise ConfigurationError(
|
||||
"Each item in '{}' must itself be a dict".format(yaml_fname)
|
||||
)
|
||||
|
||||
for k in ['collective_dircap', 'upload_dircap', 'directory', 'poll_interval']:
|
||||
if k not in config:
|
||||
raise ConfigurationError(
|
||||
"Config for magic folder '{}' is missing '{}'".format(
|
||||
name, k
|
||||
)
|
||||
)
|
||||
|
||||
if not isinstance(
|
||||
config.setdefault(u"umask", _DEFAULT_DOWNLOAD_UMASK),
|
||||
int,
|
||||
):
|
||||
raise Exception("magic-folder download umask must be an integer")
|
||||
|
||||
# make sure directory for magic folder exists
|
||||
dir_fp = to_filepath(config['directory'])
|
||||
umask = config.setdefault('umask', 0077)
|
||||
|
||||
try:
|
||||
os.mkdir(dir_fp.path, 0777 & (~ umask))
|
||||
except OSError as e:
|
||||
if EEXIST != e.errno:
|
||||
# Report some unknown problem.
|
||||
raise ConfigurationError(
|
||||
"magic-folder {} configured path {} could not be created: "
|
||||
"{}".format(
|
||||
name,
|
||||
dir_fp.path,
|
||||
str(e),
|
||||
),
|
||||
)
|
||||
elif not dir_fp.isdir():
|
||||
# Tell the user there's a collision.
|
||||
raise ConfigurationError(
|
||||
"magic-folder {} configured path {} exists and is not a "
|
||||
"directory".format(
|
||||
name, dir_fp.path,
|
||||
),
|
||||
)
|
||||
|
||||
result_config = config.copy()
|
||||
for k in ['collective_dircap', 'upload_dircap']:
|
||||
if isinstance(config[k], unicode):
|
||||
result_config[k] = config[k].encode('ascii')
|
||||
return result_config
|
||||
|
||||
|
||||
|
||||
def save_magic_folders(node_directory, folders):
|
||||
fileutil.write_atomically(
|
||||
os.path.join(node_directory, u"private", u"magic_folders.yaml"),
|
||||
|
@ -1,8 +1,8 @@
|
||||
|
||||
import os, sys, time
|
||||
import shutil, json
|
||||
import stat, shutil, json
|
||||
import mock
|
||||
from os.path import join, exists
|
||||
from os.path import join, exists, isdir
|
||||
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet import defer, task, reactor
|
||||
@ -19,7 +19,10 @@ from allmydata.test.common import ShouldFailMixin
|
||||
from .cli.test_magic_folder import MagicFolderCLITestMixin
|
||||
|
||||
from allmydata.frontends import magic_folder
|
||||
from allmydata.frontends.magic_folder import MagicFolder, WriteFileMixin
|
||||
from allmydata.frontends.magic_folder import (
|
||||
MagicFolder, WriteFileMixin,
|
||||
ConfigurationError,
|
||||
)
|
||||
from allmydata import magicfolderdb, magicpath
|
||||
from allmydata.util.fileutil import get_pathinfo
|
||||
from allmydata.util.fileutil import abspath_expanduser_unicode
|
||||
@ -31,6 +34,9 @@ _debug = False
|
||||
class NewConfigUtilTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# some tests look at the umask of created directories or files
|
||||
# so we set an explicit one
|
||||
self._old_umask = os.umask(0o022)
|
||||
self.basedir = abspath_expanduser_unicode(unicode(self.mktemp()))
|
||||
os.mkdir(self.basedir)
|
||||
self.local_dir = abspath_expanduser_unicode(unicode(self.mktemp()))
|
||||
@ -52,21 +58,96 @@ class NewConfigUtilTests(unittest.TestCase):
|
||||
}
|
||||
|
||||
# we need a bit of tahoe.cfg
|
||||
with open(join(self.basedir, u"tahoe.cfg"), "w") as f:
|
||||
f.write(
|
||||
u"[magic_folder]\n"
|
||||
u"enabled = True\n"
|
||||
)
|
||||
self.write_tahoe_config(
|
||||
self.basedir,
|
||||
u"[magic_folder]\n"
|
||||
u"enabled = True\n",
|
||||
)
|
||||
# ..and the yaml
|
||||
yaml_fname = join(self.basedir, u"private", u"magic_folders.yaml")
|
||||
self.write_magic_folder_config(self.basedir, self.folders)
|
||||
|
||||
def tearDown(self):
|
||||
os.umask(self._old_umask)
|
||||
|
||||
def write_tahoe_config(self, basedir, tahoe_config):
|
||||
with open(join(basedir, u"tahoe.cfg"), "w") as f:
|
||||
f.write(tahoe_config)
|
||||
|
||||
def write_magic_folder_config(self, basedir, folder_configuration):
|
||||
yaml_fname = join(basedir, u"private", u"magic_folders.yaml")
|
||||
with open(yaml_fname, "w") as f:
|
||||
f.write(yamlutil.safe_dump({u"magic-folders": self.folders}))
|
||||
f.write(yamlutil.safe_dump({u"magic-folders": folder_configuration}))
|
||||
|
||||
def test_load(self):
|
||||
folders = magic_folder.load_magic_folders(self.basedir)
|
||||
self.assertEqual(['default'], list(folders.keys()))
|
||||
self.assertEqual(folders['default'][u'umask'], 0o077)
|
||||
|
||||
def test_load_makes_directory(self):
|
||||
"""
|
||||
If the *directory* does not exist then it is created by
|
||||
``load_magic_folders``.
|
||||
"""
|
||||
os.rmdir(self.local_dir)
|
||||
# Just pick some arbitrary bits.
|
||||
# rwxr-xr--
|
||||
perm = stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH
|
||||
self.folders[u"default"][u"umask"] = (0o777 & ~perm)
|
||||
self.write_magic_folder_config(self.basedir, self.folders)
|
||||
|
||||
magic_folder.load_magic_folders(self.basedir)
|
||||
|
||||
# It is created.
|
||||
self.assertTrue(
|
||||
isdir(self.local_dir),
|
||||
"magic-folder local directory {} was not created".format(
|
||||
self.local_dir,
|
||||
),
|
||||
)
|
||||
# It has permissions determined by the configured umask.
|
||||
if sys.platform != "win32":
|
||||
self.assertEqual(
|
||||
perm,
|
||||
stat.S_IMODE(os.stat(self.local_dir).st_mode),
|
||||
)
|
||||
else:
|
||||
# Do directories even have permissions on Windows?
|
||||
print("Not asserting directory-creation mode on windows")
|
||||
|
||||
def test_directory_collision(self):
|
||||
"""
|
||||
If a non-directory already exists at the magic folder's configured local
|
||||
directory path, ``load_magic_folders`` raises an exception.
|
||||
"""
|
||||
os.rmdir(self.local_dir)
|
||||
open(self.local_dir, "w").close()
|
||||
|
||||
with self.assertRaises(ConfigurationError) as ctx:
|
||||
magic_folder.load_magic_folders(self.basedir)
|
||||
self.assertIn(
|
||||
"exists and is not a directory",
|
||||
str(ctx.exception),
|
||||
)
|
||||
|
||||
def test_directory_creation_error(self):
|
||||
"""
|
||||
If a directory at the magic folder's configured local directory path
|
||||
cannot be created for some other reason, ``load_magic_folders`` raises
|
||||
an exception.
|
||||
"""
|
||||
os.rmdir(self.local_dir)
|
||||
open(self.local_dir, "w").close()
|
||||
self.folders[u"default"][u"directory"] = self.local_dir + "/foo"
|
||||
self.write_magic_folder_config(self.basedir, self.folders)
|
||||
|
||||
with self.assertRaises(ConfigurationError) as ctx:
|
||||
magic_folder.load_magic_folders(self.basedir)
|
||||
self.assertIn(
|
||||
"could not be created",
|
||||
str(ctx.exception),
|
||||
)
|
||||
|
||||
|
||||
def test_both_styles_of_config(self):
|
||||
os.unlink(join(self.basedir, u"private", u"magic_folders.yaml"))
|
||||
with self.assertRaises(Exception) as ctx:
|
||||
@ -194,22 +275,25 @@ class LegacyConfigUtilTests(unittest.TestCase):
|
||||
pass
|
||||
|
||||
def test_load_legacy_no_dir(self):
|
||||
expected = self.local_dir + 'foo'
|
||||
with open(join(self.basedir, u"tahoe.cfg"), "w") as f:
|
||||
f.write(
|
||||
u"[magic_folder]\n"
|
||||
u"enabled = True\n"
|
||||
u"local.directory = {}\n"
|
||||
u"poll_interval = {}\n".format(
|
||||
self.local_dir + 'foo',
|
||||
expected,
|
||||
self.poll_interval,
|
||||
)
|
||||
)
|
||||
|
||||
with self.assertRaises(Exception) as ctx:
|
||||
magic_folder.load_magic_folders(self.basedir)
|
||||
self.assertIn(
|
||||
"there is no directory at that location",
|
||||
str(ctx.exception)
|
||||
magic_folder.load_magic_folders(self.basedir)
|
||||
|
||||
self.assertTrue(
|
||||
isdir(expected),
|
||||
"magic-folder local directory {} was not created".format(
|
||||
expected,
|
||||
),
|
||||
)
|
||||
|
||||
def test_load_legacy_not_a_dir(self):
|
||||
@ -226,10 +310,10 @@ class LegacyConfigUtilTests(unittest.TestCase):
|
||||
with open(self.local_dir + "foo", "w") as f:
|
||||
f.write("not a directory")
|
||||
|
||||
with self.assertRaises(Exception) as ctx:
|
||||
with self.assertRaises(ConfigurationError) as ctx:
|
||||
magic_folder.load_magic_folders(self.basedir)
|
||||
self.assertIn(
|
||||
"location is not a directory",
|
||||
"is not a directory",
|
||||
str(ctx.exception)
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user