diff --git a/management/backup.py b/management/backup.py index 1cd2e13a..92f8a740 100755 --- a/management/backup.py +++ b/management/backup.py @@ -10,8 +10,9 @@ import os, os.path, shutil, glob, re, datetime, sys import dateutil.parser, dateutil.relativedelta, dateutil.tz import rtyaml +from exclusiveprocess import Lock -from utils import exclusive_process, load_environment, shell, wait_for_service, fix_boto +from utils import load_environment, shell, wait_for_service, fix_boto rsync_ssh_options = [ "--ssh-options='-i /root/.ssh/id_rsa_miab'", @@ -204,7 +205,10 @@ def get_target_type(config): def perform_backup(full_backup): env = load_environment() - exclusive_process("backup") + # Create an global exclusive lock so that the backup script + # cannot be run more than one. + Lock(die=True).forever() + config = get_backup_config(env) backup_root = os.path.join(env["STORAGE_ROOT"], 'backup') backup_cache_dir = os.path.join(backup_root, 'cache') diff --git a/management/ssl_certificates.py b/management/ssl_certificates.py index b0355cfc..0d282d0c 100755 --- a/management/ssl_certificates.py +++ b/management/ssl_certificates.py @@ -411,9 +411,11 @@ def provision_certificates(env, agree_to_tos_url=None, logger=None, show_extende def provision_certificates_cmdline(): import sys - from utils import load_environment, exclusive_process + from exclusiveprocess import Lock - exclusive_process("update_tls_certificates") + from utils import load_environment + + Lock(die=True).forever() env = load_environment() verbose = False diff --git a/management/utils.py b/management/utils.py index d590abb5..652b48f6 100644 --- a/management/utils.py +++ b/management/utils.py @@ -106,76 +106,6 @@ def sort_email_addresses(email_addresses, env): ret.extend(sorted(email_addresses)) # whatever is left return ret -def exclusive_process(name): - # Ensure that a process named `name` does not execute multiple - # times concurrently. - import os, sys, atexit - pidfile = '/var/run/mailinabox-%s.pid' % name - mypid = os.getpid() - - # Attempt to get a lock on ourself so that the concurrency check - # itself is not executed in parallel. - with open(__file__, 'r+') as flock: - # Try to get a lock. This blocks until a lock is acquired. The - # lock is held until the flock file is closed at the end of the - # with block. - os.lockf(flock.fileno(), os.F_LOCK, 0) - - # While we have a lock, look at the pid file. First attempt - # to write our pid to a pidfile if no file already exists there. - try: - with open(pidfile, 'x') as f: - # Successfully opened a new file. Since the file is new - # there is no concurrent process. Write our pid. - f.write(str(mypid)) - atexit.register(clear_my_pid, pidfile) - return - except FileExistsError: - # The pid file already exixts, but it may contain a stale - # pid of a terminated process. - with open(pidfile, 'r+') as f: - # Read the pid in the file. - existing_pid = None - try: - existing_pid = int(f.read().strip()) - except ValueError: - pass # No valid integer in the file. - - # Check if the pid in it is valid. - if existing_pid: - if is_pid_valid(existing_pid): - print("Another %s is already running (pid %d)." % (name, existing_pid), file=sys.stderr) - sys.exit(1) - - # Write our pid. - f.seek(0) - f.write(str(mypid)) - f.truncate() - atexit.register(clear_my_pid, pidfile) - - -def clear_my_pid(pidfile): - import os - os.unlink(pidfile) - - -def is_pid_valid(pid): - """Checks whether a pid is a valid process ID of a currently running process.""" - # adapted from http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid - import os, errno - if pid <= 0: raise ValueError('Invalid PID.') - try: - os.kill(pid, 0) - except OSError as err: - if err.errno == errno.ESRCH: # No such process - return False - elif err.errno == errno.EPERM: # Not permitted to send signal - return True - else: # EINVAL - raise - else: - return True - def shell(method, cmd_args, env={}, capture_stderr=False, return_bytes=False, trap=False, input=None): # A safe way to execute processes. # Some processes like apt-get require being given a sane PATH. diff --git a/setup/management.sh b/setup/management.sh index cfc6aad4..7299ec50 100755 --- a/setup/management.sh +++ b/setup/management.sh @@ -16,7 +16,7 @@ apt_install build-essential libssl-dev libffi-dev python3-dev # The first line is the packages that Josh maintains himself! # NOTE: email_validator is repeated in setup/questions.sh, so please keep the versions synced. hide_output pip3 install --upgrade \ - rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" \ + rtyaml "email_validator>=1.0.0" "free_tls_certificates>=0.1.3" "exclusiveprocess" \ "idna>=2.0.0" "cryptography>=1.0.2" boto psutil # duplicity uses python 2 so we need to get the python 2 package of boto to have backups to S3.