Commit 1eb5f2e5 authored by segfault's avatar segfault

Use pathlib

parent 27a69261
......@@ -72,13 +72,13 @@ class Services(object):
filenames = os.listdir(SERVICES_DIR)
filenames.sort()
for filename in filenames:
root = os.path.splitext(filename)[0]
name = os.path.basename(root)
root = path.splitext(filename)[0]
name = path.basename(root)
if name.startswith("__"):
continue
if name in module_paths:
raise DuplicateServiceError("Multiple files for service %r" % root)
module_paths[name] = os.path.join(SERVICES_DIR, filename)
module_paths[name] = path.join(SERVICES_DIR, filename)
return module_paths
def get_service(self, name: str) -> "OnionService":
......
from logging import getLogger
from os import path
import os
from pathlib import Path
import sh
from pwd import getpwnam
from grp import getgrnam
......@@ -13,9 +13,9 @@ logger = getLogger(__name__)
class BindMount(object):
# Path to the source of the bind mount. Relative to service's state directory if not an absolute path.
source: str
source: Path
# Absolute path to the target of the bind mount
target: str
target: Path
owner = "root"
group = "root"
......@@ -23,8 +23,8 @@ class BindMount(object):
is_dir = False
def __init__(self,
source: str = None,
target: str = None,
source: Path = None,
target: Path = None,
owner: str = None,
group: str = None,
mode: int = None,
......@@ -48,49 +48,49 @@ class BindMount(object):
self.mode = 0o750 if self.is_dir else 0o640
def create(self):
logger.debug("Creating bind-mount %r", self.source)
logger.debug("Creating bind-mount %s", self.source)
if self.is_dir:
os.mkdir(self.source, mode=self.mode)
self.source.mkdir(mode=self.mode)
else:
os.close(os.open(self.source, os.O_CREAT, mode=self.mode))
os.chown(self.source, uid=getpwnam(self.owner).pw_uid, gid=getgrnam(self.group).gr_gid)
self.source.touch(mode=self.mode)
os.chown(str(self.source), uid=getpwnam(self.owner).pw_uid, gid=getgrnam(self.group).gr_gid)
def is_mounted(self):
try:
sh.findmnt("--mountpoint", self.target)
sh.findmnt("--mountpoint", str(self.target))
except sh.ErrorReturnCode_1:
return False
return True
def mount(self):
logger.debug("Bind-mounting %r to %r", self.source, self.target)
logger.debug("Bind-mounting %s to %s", self.source, self.target)
self.ensure_target_exists()
sh.mount("--bind", self.source, self.target)
sh.mount("--bind", str(self.source), str(self.target))
def ensure_target_exists(self):
if path.exists(self.target):
if self.target.exists():
return
if self.is_dir:
logger.debug("Creating target directory %r", self.target)
sh.mkdir(self.target)
logger.debug("Creating target directory %s", self.target)
self.target.mkdir(mode=0o700)
else:
logger.debug("Creating target file %r", self.target)
sh.touch(self.target)
logger.debug("Creating target file %s", self.target)
self.target.touch(mode=0o700)
self.created_empty_target = True
def unmount(self):
logger.debug("Unmounting %r", self.target)
sh.umount(self.target)
logger.debug("Unmounting %s", self.target)
sh.umount(str(self.target))
if self.created_empty_target:
self.remove_empty_target()
def remove_empty_target(self):
if path.isdir(self.source):
logger.debug("Removing target directory %r", self.target)
sh.rmdir(self.target)
if self.source.is_dir():
logger.debug("Removing target directory %s", self.target)
self.target.rmdir()
else:
logger.debug("Removing target file %r", self.target)
logger.debug("Removing target file %s", self.target)
with open_locked(self.target) as f:
if f.read():
raise FileNotEmptyError("Refusing to remove target file %r: File not empty", self.target)
sh.rm(self.target)
raise FileNotEmptyError("Refusing to remove target file %s: File not empty", self.target)
self.target.unlink()
from logging import getLogger
import os
from pathlib import Path
from pwd import getpwnam
from grp import getgrnam
from abc import ABCMeta
......@@ -13,7 +14,7 @@ logger = getLogger(__name__)
class DataFile(object, metaclass=ABCMeta):
source: str
source: Path
default_content: str
owner = "root"
......@@ -21,7 +22,7 @@ class DataFile(object, metaclass=ABCMeta):
mode = 0o640
def create(self):
logger.debug("Creating file %r", self.source)
logger.debug("Creating file %s", self.source)
with open_locked(self.source, 'w') as f:
os.fchown(f.fileno(), uid=getpwnam(self.owner).pw_uid, gid=getgrnam(self.group).gr_gid)
os.fchmod(f.fileno(), mode=self.mode)
......@@ -72,7 +73,7 @@ class IniLikeConfigFile(DataFile, metaclass=ABCMeta):
self.config.read_string(f.read())
return self.config.get(section, key)
except (configparser.NoSectionError, configparser.NoOptionError):
raise OptionNotFoundError("Property %r not found in config file %r" % (key, self.source))
raise OptionNotFoundError("Property %r not found in config file %s" % (key, self.source))
def set(self, section: str, key: str, value: str):
try:
......@@ -85,7 +86,7 @@ class IniLikeConfigFile(DataFile, metaclass=ABCMeta):
f.truncate()
self.config.write(f)
except (configparser.NoSectionError, configparser.NoOptionError):
raise OptionNotFoundError("Property %r not found in config file %r" % (key, self.source))
raise OptionNotFoundError("Property %r not found in config file %s" % (key, self.source))
def remove(self, section: str, key: str):
try:
......@@ -96,4 +97,4 @@ class IniLikeConfigFile(DataFile, metaclass=ABCMeta):
f.truncate()
self.config.write(f)
except (configparser.NoSectionError, configparser.NoOptionError):
raise OptionNotFoundError("Property %r not found in config file %r" % (key, self.source))
raise OptionNotFoundError("Property %r not found in config file %s" % (key, self.source))
from pathlib import Path
from logging import getLogger
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
......@@ -18,7 +19,7 @@ KEY_SIZE = 1024
class HSPrivateKey(DataFile):
source = "private_key"
source = Path("private_key")
default_content = str()
def create(self):
......
from pathlib import Path
import yaml
from onionkit.data_file import DataFile
......@@ -6,7 +7,7 @@ from onionkit.util import open_locked
class OptionsFile(DataFile):
source = "options"
source = Path("options")
default_content = '{}'
def get(self, key: str):
......@@ -14,7 +15,7 @@ class OptionsFile(DataFile):
try:
return options[key]
except KeyError:
raise OptionNotFoundError("Could not find option %r in %r" % (key, self.source))
raise OptionNotFoundError("Could not find option %r in %s" % (key, self.source))
def set(self, key: str, value):
with open_locked(self.source, 'r+') as f:
......
......@@ -2,7 +2,6 @@ from logging import getLogger
from typing import Union
from threading import Thread
from abc import abstractmethod
import os
from gi.repository import Gio, GLib
......@@ -24,12 +23,12 @@ class UnregistrationFailedError(Exception):
class DBusObject(object):
@property
@abstractmethod
def dbus_info(self):
def dbus_info(self) -> str:
pass
@property
@abstractmethod
def dbus_path(self):
def dbus_path(self) -> str:
pass
def __init__(self):
......
import abc
import os
from logging import getLogger
from os import path
from onionkit.dbus.object import DBusObject
from onionkit.exceptions import OptionNotFoundError
......@@ -40,7 +40,7 @@ class OnionServiceOption(DBusObject, metaclass=abc.ABCMeta):
@property
def dbus_path(self):
return path.join(self.service.dbus_path, self.Name)
return os.path.join(self.service.dbus_path, self.Name)
def __init__(self, service: "OnionService"):
def set_default_value():
......
from os import path
import sh
import shutil
from pathlib import Path
from logging import getLogger
from onionkit import _
......@@ -36,26 +36,25 @@ class Persistence(OnionServiceOption):
def make_persistent(self):
logger.info("Making service %r persistent", self.service.Name)
state_dir = path.join(STATE_DIR, self.service.Name)
state_dir = Path(STATE_DIR, self.service.Name)
self.move(self.service.state_dir, state_dir)
self.service.state_dir = state_dir
self.service.expand_data_file_paths()
def remove_persistence(self):
logger.info("Removing persistence of service %r", self.service.Name)
tmp_state_dir = path.join(TEMP_STATE_DIR, self.service.Name)
tmp_state_dir = Path(TEMP_STATE_DIR, self.service.Name)
self.move(self.service.state_dir, tmp_state_dir)
self.service.state_dir = tmp_state_dir
self.service.expand_data_file_paths()
def is_persistent(self):
return self.service.state_dir.startswith(path.join(STATE_DIR))
return Path(STATE_DIR) in self.service.state_dir.parents
@staticmethod
def move(src, dest):
def move(src: Path, dest: Path):
logger.debug("Moving %r to %r", src, dest)
if path.exists(dest):
if dest.exists():
raise FileExistsError("Couldn't move %r to %r, destination %r already exists" %
(src, dest, dest))
sh.mv(src, dest)
shutil.move(str(src), str(dest))
import os
from os import path
from pathlib import Path
import shutil
import abc
from colorlog import getLogger
......@@ -85,16 +85,16 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
@property
def dbus_path(self):
return path.join(SERVICES_PATH, self.Name)
return os.path.join(SERVICES_PATH, self.Name)
def __init__(self):
super().__init__()
logger.debug("Initializing service %r", self.Name)
if path.exists(path.join(STATE_DIR, self.Name)):
self.state_dir = path.join(STATE_DIR, self.Name)
if Path(STATE_DIR, self.Name).exists():
self.state_dir = Path(STATE_DIR, self.Name)
else:
self.state_dir = path.join(TEMP_STATE_DIR, self.Name)
self.state_dir = Path(TEMP_STATE_DIR, self.Name)
self.options_dict: Dict[str, "OnionServiceOption"] = dict()
self.options_file = OptionsFile()
self.private_key = HSPrivateKey()
......@@ -105,7 +105,7 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
self._status = Status.INITIALIZING
self._transaction_status = str()
self._transaction_progress = 101 # 101 means the progress cannot be calculated
self._is_installed = os.path.isdir(self.state_dir)
self._is_installed = self.state_dir.exists()
self._address = self.private_key.derive_onion_address() if self.IsInstalled else str()
# ----- Exported functions ----- #
......@@ -452,15 +452,12 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
self.options_dict[option].apply()
def create_state_dir(self):
try:
os.mkdir(self.state_dir)
logger.debug("Created state directory %r", self.state_dir)
except FileExistsError:
pass
logger.debug("Creating state directory %s", self.state_dir)
self.state_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
def remove_state_dir(self):
logger.debug("Removing state directory %r", self.state_dir)
shutil.rmtree(self.state_dir)
logger.debug("Removing state directory %s", self.state_dir)
shutil.rmtree(str(self.state_dir))
def create_data_files(self):
logger.debug("Creating data files for %r", self.Name)
......@@ -470,12 +467,16 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
def expand_data_file_paths(self):
"""Expand the data file paths, which are relative to the service's state directory"""
logger.debug("Expanding data file paths")
state_dir = Path(STATE_DIR, self.Name)
temp_state_dir = Path(TEMP_STATE_DIR, self.Name)
for f in self.data_files:
for p in (path.join(STATE_DIR, self.Name), path.join(TEMP_STATE_DIR, self.Name)):
if f.source.startswith(p):
f.source = f.source[len(p)+1:]
if not path.isabs(f.source):
f.source = path.join(self.state_dir, f.source)
# Remove existing parent state dir (if any)
if state_dir in f.source.parents:
f.source = f.source.relative_to(state_dir)
elif temp_state_dir in f.source.parents:
f.source = f.source.relative_to(temp_state_dir)
# Add current state dir as parent
f.source = Path(self.state_dir, f.source)
def bind_mount(self):
"""Bind mount config and data files and directories"""
......
......@@ -3,6 +3,7 @@ import threading
import fcntl
from contextlib import contextmanager
from typing import TextIO
from pathlib import Path
from gi.repository import GLib
......@@ -22,15 +23,15 @@ def process_mainloop_events():
@contextmanager
def open_locked(path, *args, **kwargs) -> TextIO:
with open(path, *args, **kwargs) as f:
def open_locked(path: Path, *args, **kwargs) -> TextIO:
with path.open(*args, **kwargs) as f:
try:
logger.log(5, "Acquiring file lock on %r", path)
logger.log(5, "Acquiring file lock on %s", path)
fcntl.flock(f, fcntl.LOCK_EX)
logger.log(5, "Acquired file lock on %r", path)
logger.log(5, "Acquired file lock on %s", path)
yield f
finally:
logger.log(5, "Releasing file lock on %r", path)
logger.log(5, "Releasing file lock on %s", path)
fcntl.flock(f, fcntl.LOCK_UN)
#!/usr/bin/env python3
import argparse
import os
from colorlog import getLogger
from xml.etree import ElementTree
from os import path
from pathlib import Path
import atexit
import shutil
......@@ -64,7 +64,7 @@ class OnionKit(DBusObject):
None)
logger.log(5, "GetServices() introspection data: %s", ret.unpack()[0])
nodes = ElementTree.fromstring(ret.unpack()[0])
service_paths = [path.join(SERVICES_PATH, node.attrib['name']) for node in nodes
service_paths = [os.path.join(SERVICES_PATH, node.attrib['name']) for node in nodes
if node.tag == "node"]
logger.debug("Service paths: %s", service_paths)
return service_paths
......
import os
from os import path
from pathlib import Path
import random
import string
......@@ -17,15 +16,15 @@ from onionkit.systemd import SystemdManager
from onionkit.config import STATE_DIR
SERVICE_STATE_DIR = path.join(STATE_DIR, "gobby")
DATA_DIR = "/var/lib/infinoted"
DOCS_DIR = path.join(DATA_DIR, "docs")
LOG_FILE = path.join(DATA_DIR, "infinoted.log")
SERVICE_STATE_DIR = Path(STATE_DIR, "gobby")
DATA_DIR = Path("/var/lib/infinoted")
DOCS_DIR = Path(DATA_DIR, "docs")
LOG_FILE = Path(DATA_DIR, "infinoted.log")
class InfinotedConfigFile(IniLikeConfigFile, BindMount):
source = "infinoted.conf"
target = "/etc/xdg/infinoted.conf"
source = Path("infinoted.conf")
target = Path("/etc/xdg/infinoted.conf")
owner = "root"
group = "infinoted"
default_content = f"""# onionkit gobby-service configuration
......@@ -37,8 +36,8 @@ security-policy=no-tls"""
# XXX: Remove once infinoted Debian package ships its own systemd unit file (see Debian bug #810865)
class SystemdUnit(IniLikeConfigFile, BindMount):
source = "infinoted.service"
target = "/etc/systemd/system/infinoted.service"
source = Path("infinoted.service")
target = Path("/etc/systemd/system/infinoted.service")
mode = 0o644
default_content = """[Unit]
Description=collaborative text editor service
......@@ -121,7 +120,7 @@ class GobbyServer(OnionService):
data_files = [
config_file,
systemd_unit_file,
BindMount(source="data", target=DATA_DIR, owner="infinoted", group="infinoted", mode=0o700, is_dir=True)
BindMount(source=Path("data"), target=DATA_DIR, owner="infinoted", group="infinoted", mode=0o700, is_dir=True)
]
def post_option_initialization_configuration(self):
......
from pathlib import Path
from distutils.dir_util import copy_tree
from onionkit import _
......@@ -11,8 +12,8 @@ from onionkit.bind_mount import BindMount
class ConfigDir(BindMount):
source = "config"
target = "/etc/lighttpd"
source = Path("config")
target = Path("/etc/lighttpd")
owner = "root"
group = "www-data"
is_dir = True
......@@ -47,7 +48,7 @@ class LighttpdServer(OnionService):
data_files = [
config_dir,
BindMount(source="html", target="/var/www/html", owner="root", group="www-data", is_dir=True)
BindMount(source=Path("html"), target=Path("/var/www/html"), owner="root", group="www-data", is_dir=True)
]
......
from logging import getLogger
from os import path
from pathlib import Path
import string
import random
import sqlite3
......@@ -20,13 +20,13 @@ from onionkit.bind_mount import BindMount
logger = getLogger(__name__)
DATA_DIR = "/var/lib/mumble-server/"
DB_FILE = path.join(DATA_DIR, "mumble-server.sqlite")
DATA_DIR = Path("/var/lib/mumble-server/")
DB_FILE = Path(DATA_DIR, "mumble-server.sqlite")
FINGERPRINT_LOADING_TIMEOUT = 2
class MumbleConfigFile(DataFile, BindMount):
source = "mumble-server.ini"
target = "/etc/mumble-server.ini"
source = Path("mumble-server.ini")
target = Path("/etc/mumble-server.ini")
owner = "root"
group = "mumble-server"
......@@ -103,11 +103,11 @@ class TLSFingerprint(option.OnionServiceOption):
raise ReadOnlyOptionError("Option %r can't be modified" % self.Name)
def load(self) -> str:
if not path.isfile(DB_FILE):
logger.warning("Could not load TLS certificate of service %r: No such file: %r", self.service.Name, DB_FILE)
if not DB_FILE.exists():
logger.warning("Could not load TLS certificate of service %r: No such file: %s", self.service.Name, DB_FILE)
return str()
connection = sqlite3.connect(DB_FILE)
connection = sqlite3.connect(str(DB_FILE))
c = connection.cursor()
cert_row = None
start_time = loop_time = time.perf_counter()
......@@ -151,7 +151,7 @@ class MumbleServer(OnionService):
data_files = [
config_file,
BindMount(source="data", target=DATA_DIR, owner="mumble-server", group="mumble-server", is_dir=True)
BindMount(source=Path("data"), target=DATA_DIR, owner="mumble-server", group="mumble-server", is_dir=True)
]
def post_option_initialization_configuration(self):
......
import re
from pathlib import Path
from logging import getLogger
from onionkit import _
......@@ -10,15 +12,12 @@ from onionkit.option import OnionServiceOption
from onionkit.data_file import DataFile
from onionkit.bind_mount import BindMount
import re
from os import path
logger = getLogger(__name__)
class ConfigDir(BindMount):
source = "config"
target = "/etc/prosody"
source = Path("config")
target = Path("/etc/prosody")
owner = "root"
group = "prosody"
is_dir = True
......@@ -28,7 +27,7 @@ config_dir = ConfigDir()
class ProsodyConfigFile(DataFile):
source = path.join(config_dir.source, "prosody.cfg.lua")
source = Path(config_dir.source, "prosody.cfg.lua")
owner = "root"
group = "prosody"
......@@ -123,7 +122,7 @@ class ProsodyServer(OnionService):
data_files = [
config_dir,
config_file,
BindMount(source="data", target="/var/lib/prosody", owner="prosody", group="prosody", is_dir=True)
BindMount(source=Path("data"), target=Path("/var/lib/prosody"), owner="prosody", group="prosody", is_dir=True)
]
@property
......@@ -154,8 +153,8 @@ class ProsodyServer(OnionService):
)
self.update_config(replacements)
def create_onion_address(self):
super().create_onion_address()
def RegenerateAddress(self):
super().RegenerateAddress()
self.set_virtual_host(self.Address)
......
import sh
from os import path
from pathlib import Path
import random
import string
import logging
......@@ -18,8 +18,8 @@ from onionkit.bind_mount import BindMount
class DataDir(BindMount):
source = "data"
target = "/var/lib/sftp"
source = Path("data")
target = Path("/var/lib/sftp")
is_dir = True
......@@ -27,7 +27,7 @@ data_dir = DataDir()
class SSHDConfigFile(DataFile):
source = path.join(data_dir.source, "sshd_config")