#!/usr/bin/env python """ The PyBitmessage startup script """ # Copyright (c) 2012-2016 Jonathan Warren # Copyright (c) 2012-2020 The Bitmessage developers # Distributed under the MIT/X11 software license. See the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # Right now, PyBitmessage only support connecting to stream 1. It doesn't # yet contain logic to expand into further streams. import os import sys try: import pathmagic except ImportError: from pybitmessage import pathmagic app_dir = pathmagic.setup() import depends depends.check_dependencies() import getopt import multiprocessing # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. import signal import threading import time import traceback import defaults import shared import shutdown import state from bmconfigparser import BMConfigParser from debug import logger # this should go before any threads from helper_startup import ( adjustHalfOpenConnectionsLimit, fixSocket, start_proxyconfig) from inventory import Inventory # Network objects and threads from network import ( BMConnectionPool, Dandelion, AddrThread, AnnounceThread, BMNetworkThread, InvThread, ReceiveQueueThread, DownloadThread, UploadThread ) from network.knownnodes import readKnownNodes from singleinstance import singleinstance # Synchronous threads from threads import ( set_thread_name, printLock, addressGenerator, objectProcessor, singleCleaner, singleWorker, sqlThread) def signal_handler(signum, frame): """Single handler for any signal sent to pybitmessage""" process = multiprocessing.current_process() thread = threading.current_thread() logger.error( 'Got signal %i in %s/%s', signum, process.name, thread.name ) if process.name == "RegExParser": # on Windows this isn't triggered, but it's fine, # it has its own process termination thing raise SystemExit if "PoolWorker" in process.name: raise SystemExit if thread.name not in ("PyBitmessage", "MainThread"): return logger.error("Got signal %i", signum) # there are possible non-UI variants to run bitmessage # which should shutdown especially test-mode if state.thisapp.daemon or not state.enableGUI: shutdown.doCleanShutdown() else: print('# Thread: %s(%d)' % (thread.name, thread.ident)) for filename, lineno, name, line in traceback.extract_stack(frame): print('File: "%s", line %d, in %s' % (filename, lineno, name)) if line: print(' %s' % line.strip()) print('Unfortunately you cannot use Ctrl+C when running the UI' ' because the UI captures the signal.') class Main(object): """Main PyBitmessage class""" def start(self): """Start main application""" # pylint: disable=too-many-statements,too-many-branches,too-many-locals fixSocket() adjustHalfOpenConnectionsLimit() config = BMConfigParser() daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') try: opts, _ = getopt.getopt( sys.argv[1:], "hcdt", ["help", "curses", "daemon", "test"]) except getopt.GetoptError: self.usage() sys.exit(2) for opt, _ in opts: if opt in ("-h", "--help"): self.usage() sys.exit() elif opt in ("-d", "--daemon"): daemon = True elif opt in ("-c", "--curses"): state.curses = True elif opt in ("-t", "--test"): state.testmode = True if os.path.isfile(os.path.join( state.appdata, 'unittest.lock')): daemon = True state.enableGUI = False # run without a UI # Fallback: in case when no api command was issued state.last_api_response = time.time() # Apply special settings config.set( 'bitmessagesettings', 'apienabled', 'true') config.set( 'bitmessagesettings', 'apiusername', 'username') config.set( 'bitmessagesettings', 'apipassword', 'password') config.set( 'bitmessagesettings', 'apivariant', 'legacy') config.set( 'bitmessagesettings', 'apinotifypath', os.path.join(app_dir, 'tests', 'apinotify_handler.py') ) if daemon: state.enableGUI = False # run without a UI if state.enableGUI and not state.curses and not depends.check_pyqt(): sys.exit( 'PyBitmessage requires PyQt unless you want' ' to run it as a daemon and interact with it' ' using the API. You can download PyQt from ' 'http://www.riverbankcomputing.com/software/pyqt/download' ' or by searching Google for \'PyQt Download\'.' ' If you want to run in daemon mode, see ' 'https://bitmessage.org/wiki/Daemon\n' 'You can also run PyBitmessage with' ' the new curses interface by providing' ' \'-c\' as a commandline argument.' ) # is the application already running? If yes then exit. state.thisapp = singleinstance("", daemon) if daemon: with printLock: print('Running as a daemon. Send TERM signal to end.') self.daemonize() self.setSignalHandler() set_thread_name("PyBitmessage") state.dandelion = config.safeGetInt('network', 'dandelion') # dandelion requires outbound connections, without them, # stem objects will get stuck forever if state.dandelion and not config.safeGetBoolean( 'bitmessagesettings', 'sendoutgoingconnections'): state.dandelion = 0 if state.testmode or config.safeGetBoolean( 'bitmessagesettings', 'extralowdifficulty'): defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100) defaults.networkDefaultPayloadLengthExtraBytes = int( defaults.networkDefaultPayloadLengthExtraBytes / 100) readKnownNodes() # Not needed if objproc is disabled if state.enableObjProc: # Start the address generation thread addressGeneratorThread = addressGenerator() # close the main program even if there are threads left addressGeneratorThread.daemon = True addressGeneratorThread.start() # Start the thread that calculates POWs singleWorkerThread = singleWorker() # close the main program even if there are threads left singleWorkerThread.daemon = True singleWorkerThread.start() # Start the SQL thread sqlLookup = sqlThread() # DON'T close the main program even if there are threads left. # The closeEvent should command this thread to exit gracefully. sqlLookup.daemon = False sqlLookup.start() Inventory() # init # init, needs to be early because other thread may access it early Dandelion() # Enable object processor and SMTP only if objproc enabled if state.enableObjProc: # SMTP delivery thread if daemon and config.safeGet( 'bitmessagesettings', 'smtpdeliver', '') != '': from class_smtpDeliver import smtpDeliver smtpDeliveryThread = smtpDeliver() smtpDeliveryThread.start() # SMTP daemon thread if daemon and config.safeGetBoolean( 'bitmessagesettings', 'smtpd'): from class_smtpServer import smtpServer smtpServerThread = smtpServer() smtpServerThread.start() # Start the thread that calculates POWs objectProcessorThread = objectProcessor() # DON'T close the main program even the thread remains. # This thread checks the shutdown variable after processing # each object. objectProcessorThread.daemon = False objectProcessorThread.start() # Start the cleanerThread singleCleanerThread = singleCleaner() # close the main program even if there are threads left singleCleanerThread.daemon = True singleCleanerThread.start() # Not needed if objproc disabled if state.enableObjProc: shared.reloadMyAddressHashes() shared.reloadBroadcastSendersForWhichImWatching() # API is also objproc dependent if config.safeGetBoolean('bitmessagesettings', 'apienabled'): import api # pylint: disable=relative-import singleAPIThread = api.singleAPI() # close the main program even if there are threads left singleAPIThread.daemon = True singleAPIThread.start() # start network components if networking is enabled if state.enableNetwork: start_proxyconfig() BMConnectionPool().connectToStream(1) asyncoreThread = BMNetworkThread() asyncoreThread.daemon = True asyncoreThread.start() for i in range(config.getint('threads', 'receive')): receiveQueueThread = ReceiveQueueThread(i) receiveQueueThread.daemon = True receiveQueueThread.start() if config.safeGetBoolean('bitmessagesettings', 'udp'): state.announceThread = AnnounceThread() state.announceThread.daemon = True state.announceThread.start() state.invThread = InvThread() state.invThread.daemon = True state.invThread.start() state.addrThread = AddrThread() state.addrThread.daemon = True state.addrThread.start() state.downloadThread = DownloadThread() state.downloadThread.daemon = True state.downloadThread.start() state.uploadThread = UploadThread() state.uploadThread.daemon = True state.uploadThread.start() if config.safeGetBoolean('bitmessagesettings', 'upnp'): import upnp upnpThread = upnp.uPnPThread() upnpThread.start() else: # Populate with hardcoded value (same as connectToStream above) state.streamsInWhichIAmParticipating.append(1) if not daemon and state.enableGUI: if state.curses: if not depends.check_curses(): sys.exit() print('Running with curses') import bitmessagecurses bitmessagecurses.runwrapper() else: import bitmessageqt bitmessageqt.run() else: config.remove_option('bitmessagesettings', 'dontconnect') if daemon: while state.shutdown == 0: time.sleep(1) if ( state.testmode and time.time() - state.last_api_response >= 30 ): self.stop() elif not state.enableGUI: state.enableGUI = True try: # pylint: disable=relative-import from tests import core as test_core except ImportError: self.stop() return test_core_result = test_core.run() self.stop() test_core.cleanup() sys.exit(not test_core_result.wasSuccessful()) @staticmethod def daemonize(): """Running as a daemon. Send signal in end.""" grandfatherPid = os.getpid() parentPid = None try: if os.fork(): # unlock state.thisapp.cleanup() # wait until grandchild ready while True: time.sleep(1) os._exit(0) # pylint: disable=protected-access except AttributeError: # fork not implemented pass else: parentPid = os.getpid() state.thisapp.lock() # relock os.umask(0) try: os.setsid() except AttributeError: # setsid not implemented pass try: if os.fork(): # unlock state.thisapp.cleanup() # wait until child ready while True: time.sleep(1) os._exit(0) # pylint: disable=protected-access except AttributeError: # fork not implemented pass else: state.thisapp.lock() # relock state.thisapp.lockPid = None # indicate we're the final child sys.stdout.flush() sys.stderr.flush() if not sys.platform.startswith('win'): si = open(os.devnull, 'r') so = open(os.devnull, 'a+') se = open(os.devnull, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) if parentPid: # signal ready os.kill(parentPid, signal.SIGTERM) os.kill(grandfatherPid, signal.SIGTERM) @staticmethod def setSignalHandler(): """Setting the Signal Handler""" signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # signal.signal(signal.SIGINT, signal.SIG_DFL) @staticmethod def usage(): """Displaying the usages""" print('Usage: ' + sys.argv[0] + ' [OPTIONS]') print(''' Options: -h, --help show this help message and exit -c, --curses use curses (text mode) interface -d, --daemon run in daemon (background) mode -t, --test dryrun, make testing All parameters are optional. ''') @staticmethod def stop(): """Stop main application""" with printLock: print('Stopping Bitmessage Deamon.') shutdown.doCleanShutdown() # .. todo:: nice function but no one is using this @staticmethod def getApiAddress(): """This function returns API address and port""" if not BMConfigParser().safeGetBoolean( 'bitmessagesettings', 'apienabled'): return None address = BMConfigParser().get('bitmessagesettings', 'apiinterface') port = BMConfigParser().getint('bitmessagesettings', 'apiport') return {'address': address, 'port': port} def main(): """Triggers main module""" mainprogram = Main() mainprogram.start() if __name__ == "__main__": main() # So far, the creation of and management of the Bitmessage protocol and this # client is a one-man operation. Bitcoin tips are quite appreciated. # 1H5XaDA6fYENLbknwZyjiYXYPQaFjjLX2u