Source code for darc.proxy.freenet

# -*- coding: utf-8 -*-
# pylint: disable=ungrouped-imports
"""Freenet Proxy
===================

The :mod:`darc.proxy.freenet` module contains the auxiliary functions
around managing and processing the Freenet proxy.

"""

import contextlib
import getpass
import os
import platform
import shlex
import signal
import subprocess  # nosec: B404
import time
from typing import TYPE_CHECKING, cast

import psutil

from darc.const import DARC_USER, DEBUG, getpid
from darc.error import FreenetBootstrapFailed, UnsupportedPlatform
from darc.logging import DEBUG as LOG_DEBUG
from darc.logging import ERROR as LOG_ERROR
from darc.logging import INFO as LOG_INFO
from darc.logging import VERBOSE as LOG_VERBOSE
from darc.logging import WARNING as LOG_WARNING
from darc.logging import logger

if TYPE_CHECKING:
    from io import IO  # type: ignore[attr-defined] # pylint: disable=no-name-in-module
    from signal import Signals  # pylint: disable=no-name-in-module
    from subprocess import Popen  # nosec: B404
    from types import FrameType
    from typing import NoReturn, Optional, Union

# ZeroNet args
FREENET_ARGS = shlex.split(os.getenv('FREENET_ARGS', ''))

# bootstrap wait
BS_WAIT = float(os.getenv('FREENET_WAIT', '90'))

# Freenet port
FREENET_PORT = os.getenv('FREENET_PORT', '8888')

# Freenet bootstrap retry
FREENET_RETRY = int(os.getenv('FREENET_RETRY', '3'))

# Freenet project path
FREENET_PATH = os.getenv('FREENET_PATH', '/usr/local/src/freenet')

# manage Freenet through darc?
_MNG_FREENET = bool(int(os.getenv('DARC_FREENET', '1')))

# Freenet bootstrapped flag
_FREENET_BS_FLAG = not _MNG_FREENET
# Freenet daemon process
_FREENET_PROC = None
# Freenet bootstrap args
_unsupported = False
if getpass.getuser() == 'root':
    _system = platform.system()
    if _system in ['Linux', 'Darwin']:
        _FREENET_ARGS = ['su', '-', DARC_USER, os.path.join(FREENET_PATH, 'run.sh'), 'start']
    else:
        _unsupported = True
        _FREENET_ARGS = []
else:
    _FREENET_ARGS = [os.path.join(FREENET_PATH, 'run.sh'), 'start']
_FREENET_ARGS.extend(FREENET_ARGS)

if _unsupported:
    if DEBUG:
        logger.debug('-*- FREENET PROXY -*-')
        logger.pline(LOG_ERROR, 'unsupported system: %s', platform.system())
        logger.pline(LOG_DEBUG, logger.horizon)
else:
    logger.plog(LOG_DEBUG, '-*- FREENET PROXY -*-', object=_FREENET_ARGS)


[docs]def launch_freenet() -> 'Popen[bytes]': """Launch Freenet process. See Also: This function mocks the behaviour of :func:`stem.process.launch_tor`. """ pidfile = os.path.join(FREENET_PATH, 'Freenet.pid') with contextlib.suppress(OSError): os.remove(pidfile) zeronet_process = None try: zeronet_process = subprocess.Popen( # pylint: disable=consider-using-with # nosec _FREENET_ARGS, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) def timeout_handlet(signum: 'Optional[Union[int, Signals]]' = None, frame: 'Optional[FrameType]' = None) -> 'NoReturn': raise OSError('reached a %i second timeout without success' % BS_WAIT) signal.signal(signal.SIGALRM, timeout_handlet) signal.setitimer(signal.ITIMER_REAL, BS_WAIT) while True: init_line = cast( 'IO[bytes]', zeronet_process.stdout ).readline().decode('utf-8', 'replace').strip() logger.pline(LOG_VERBOSE, init_line) if not init_line: if (code := zeronet_process.returncode) is not None and code == 0: return zeronet_process raise OSError(f'Process terminated: Timed out [{code}]') if os.path.exists(pidfile): pid = getpid(pidfile) time.sleep(1) # wait a little bit if psutil.pid_exists(pid): return zeronet_process except BaseException: if zeronet_process is not None: zeronet_process.kill() # don't leave a lingering process zeronet_process.wait() raise finally: signal.alarm(0) # stop alarm
[docs]def _freenet_bootstrap() -> None: """Freenet bootstrap. The bootstrap arguments are defined as :data:`~darc.proxy.freenet._FREENET_ARGS`. Raises: subprocess.CalledProcessError: If the return code of :data:`~darc.proxy.freenet._FREENET_PROC` is non-zero. See Also: * :func:`darc.proxy.freenet.freenet_bootstrap` * :func:`darc.proxy.freenet.launch_freenet` * :data:`darc.proxy.freenet.BS_WAIT` * :data:`darc.proxy.freenet._FREENET_BS_FLAG` * :data:`darc.proxy.freenet._FREENET_PROC` """ global _FREENET_BS_FLAG, _FREENET_PROC # pylint: disable=global-statement # launch Freenet process _FREENET_PROC = launch_freenet() # update flag _FREENET_BS_FLAG = True
[docs]def freenet_bootstrap() -> None: """Bootstrap wrapper for Freenet. The function will bootstrap the Freenet proxy. It will retry for :data:`~darc.proxy.freenet.FREENET_RETRY` times in case of failure. Also, it will **NOT** re-bootstrap the proxy as is guaranteed by :data:`~darc.proxy.freenet._FREENET_BS_FLAG`. Warns: FreenetBootstrapFailed: If failed to bootstrap Freenet proxy. Raises: :exc:`UnsupportedPlatform`: If the system is not supported, i.e. not macOS or Linux. See Also: * :func:`darc.proxy.freenet._freenet_bootstrap` * :data:`darc.proxy.freenet.FREENET_RETRY` * :data:`darc.proxy.freenet._FREENET_BS_FLAG` """ if _unsupported: raise UnsupportedPlatform(f'unsupported system: {platform.system()}') # don't re-bootstrap if _FREENET_BS_FLAG: return logger.info('-*- Freenet Bootstrap -*-') for _ in range(FREENET_RETRY+1): try: _freenet_bootstrap() break except Exception: if DEBUG: logger.ptb('[Error bootstraping Freenet proxy]') logger.pexc(LOG_WARNING, category=FreenetBootstrapFailed, line='freenet_bootstrap()') logger.pline(LOG_INFO, logger.horizon)