"""
This script runs the given command under a file lock (similar to the flock
command on some systems).
"""
import fcntl
import os
import sys
import argparse
from sage_bootstrap.compat import quote
class FileType(argparse.FileType):
"""
Version of argparse.FileType with the option to ensure that the full path
to the file exists.
"""
def __init__(self, mode='r', makedirs=False):
super(FileType, self).__init__(mode=mode)
self._makedirs = makedirs
def __call__(self, string):
if self._makedirs and string != '-':
dirname = os.path.dirname(string)
try:
os.makedirs(dirname)
except OSError as exc:
if not os.path.isdir(dirname):
raise argparse.ArgumentTypeError(
"can't create '{0}': {1}".format(dirname, exc))
return super(FileType, self).__call__(string)
class IntOrFileType(FileType):
"""
Like FileType but also accepts an int (e.g. for a file descriptor).
"""
def __call__(self, string):
try:
return int(string)
except ValueError:
return super(IntOrFileType, self).__call__(string)
def run(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
group = parser.add_mutually_exclusive_group()
group.add_argument('-s', '--shared', action='store_true',
help='create a shared lock')
group.add_argument('-x', '--exclusive', action='store_true',
help='create an exclusive lock (the default)')
group.add_argument('-u', '--unlock', action='store_true',
help='remove an existing lock')
parser.add_argument('lock', metavar='LOCK',
type=IntOrFileType('w+', makedirs=True),
help='filename of the lock an integer file descriptor')
parser.add_argument('command', metavar='COMMAND', nargs=argparse.REMAINDER,
help='command to run with the lock including any '
'arguments to that command')
args = parser.parse_args(argv)
if args.shared:
locktype = fcntl.LOCK_SH
elif args.unlock:
locktype = fcntl.LOCK_UN
else:
locktype = fcntl.LOCK_EX
lock = args.lock
command = args.command
if isinstance(lock, int) and command:
parser.error('sage-flock does not accept a command when passed '
'a file descriptor number')
try:
fcntl.flock(lock, locktype | fcntl.LOCK_NB)
except IOError as exc:
if locktype == fcntl.LOCK_SH:
kind = "shared"
elif locktype == fcntl.LOCK_UN:
sys.stderr.write(
"Unexpected error trying to unlock fd: {0}\n".format(exc))
return 1
else:
kind = "exclusive"
sys.stderr.write("Waiting for {0} lock to run {1} ... ".format(
kind, ' '.join(quote(arg) for arg in command)))
fcntl.flock(lock, locktype)
sys.stderr.write("ok\n")
if not (args.unlock or isinstance(lock, int)):
os.execvp(command[0], command)
if __name__ == '__main__':
sys.exit(run())