systemrescue-zfs/airootfs/etc/systemd/scripts/sysrescue-autorun
Gerd v. Egidy f4f582edb4 Implement new autorun configuration ( #287)
- Implement a new style configuring autorun scripts ("autorun.exec"), more suited to a YAML config file than the old style (#287)
- The old style autorun scripts are still fully supported, they are loaded into keys from "1000-autorun" to "1026-autorunF"
- change the default for ar_nowait to true
- add "shell" option for new autorun exec scripts: let bash interpret the command instead of directly forking it from python
- allow to wait for keypress with a countdown timer, all keys are accepted now (instead of just enter as in the past)
- fix is_elf_binary
- improve output and logging (#253)
- use curl instead of wget for downloading scripts from URLs
- bind-mount /run/archios/bootmnt in case of copytoram to create a stable path for the new-style scripts
- deprecate storing autorun scripts in the root of the boot disk (#252)
- don't check /var/autorun/cdrom for autorun scripts anymore, it was not documented and there are more than enough better alternatives
2022-10-11 19:29:13 +00:00

758 lines
28 KiB
Python
Executable file

#!/usr/bin/env python3
#
# Distributed under the terms of the GNU General Public License v2
# SPDX-License-Identifier: GPL-2.0-only
#
# The original bash version of autorun was developed by Pierre Dorgueil in 2003
# The current python implementation has been developed by Francois Dupoux in 2008
# For more detailed history and contributors see git log
#
# For documentation see
# https://www.system-rescue.org/manual/Run_your_own_scripts_with_autorun/
#
import sys, os, re, subprocess, logging, time, json, traceback, tty, termios, select, tempfile, stat
# ------------------------ initialize internal variables -----------------------
pidfile='/run/autorun.pid'
basedir='/var/autorun'
autorunlog=basedir+'/log'
autorunmnt=basedir+'/mnt'
autoruntmp=basedir+'/tmp'
defaultsrc=['/run/archiso/bootmnt/autorun','/run/archiso/bootmnt','/root','/usr/share/sys.autorun']
effectivecfg="/etc/sysrescue/sysrescue-effective-config.json"
config = {}
# ----------------------- functions definitions --------------------------------
def writemsg(message):
print(message)
logging.info(message)
# remove all '\r' in that file
def processdostextfiles(curfile):
txt=open(curfile,'rb').read()
origlen=len(txt)
txt=txt.replace(b'\r',b'')
if len(txt) != origlen:
writemsg(f'WARNING: \\r line endings removed from {curfile}.')
writemsg('Relying on automatic line ending sanitizing is deprecated and it will be removed from a future release.')
txtfile=open(curfile, 'wb')
txtfile.write(txt)
txtfile.close()
def is_elf_binary(filename):
with open(filename,'rb') as f:
content = f.read(4)
if len(content) == 4 and \
content[0] == 0x7f and content[1] == ord('E') and \
content[2] == ord('L') and content[3] == ord('F'):
return True
else:
return False
def ensure_shebang(filename):
# does the file have a shebang?
with open(filename,'r+') as f:
content = f.read()
if len(content) > 2 and content[0] == '#' and content[1] == '!':
# we have a shebang, nothing to do
return
# no shebang, we have to add one
writemsg(f'WARNING: no shebang in {filename}.')
writemsg('This is deprecated and a shebang will be required in future releases.')
f.seek(0, 0)
f.write("#!/bin/sh\n" + content)
def format_title(title, padding):
totallen=80
startpos=int(totallen/2)-int(len(title)/2)-1
remain=totallen-startpos-len(title)-2
text=(padding*startpos)+" "+title+" "+(padding*remain)
return text
def copyfilefct_basic(src, dest):
if os.path.isfile(src):
dstfile=open(dest, 'wb')
dstfile.write(open(src,'rb').read())
dstfile.close()
os.chmod(dest, 0o755)
return 0
else:
return -1
def copyfilefct_http(src, dest):
writemsg(f"Attempting to download {src} ...")
cmd=("curl","--connect-timeout","30","--silent","--show-error","--fail","--output",dest,src)
p = subprocess.Popen(cmd)
p.wait()
if p.returncode == 0:
writemsg(f"Successfully downloaded {src}")
os.chmod(dest, 0o755)
return 0
else:
writemsg(f"Failed to download {src}")
# delete anything we retrieved, but don't care if there is no file
try:
os.unlink(dest)
except:
pass
return -1
def search_autoruns(dirname, suffixes, autorunfiles, copyfilefct):
found=0
for ext in suffixes:
curpath=os.path.join(dirname, f'autorun{ext}')
newpath=os.path.join(autoruntmp, f'autorun{ext}')
if copyfilefct(curpath, newpath)==0:
autorunfiles[ext]=newpath
found+=1
return found
def strtobool (val):
"""Convert a string representation of truth to true (1) or false (0).
True values are 'y', 'yes', 't', 'true', 'on', '1', '1.0'; false values
are 'n', 'no', 'f', 'false', 'off', '0', '0.0'. Raises ValueError if
'val' is anything else.
Function adapted from Pythons distutils.util.py because it will be deprecated soon
Copyright (c) Python Software Foundation; All Rights Reserved
"""
val = str(val).lower()
if val in ('y', 'yes', 't', 'true', 'on', '1', '1.0'):
return True
elif val in ('n', 'no', 'f', 'false', 'off', '0', '0.0'):
return False
else:
raise ValueError("invalid truth value %r" % (val,))
def is_float(str_to_test):
try:
float(str_to_test)
return True
except ValueError:
return False
def read_cfg_value(name, defaultval, printit=True):
if name in config:
chkval = config[name]
try:
if isinstance(chkval, list) or isinstance(chkval, dict):
raise TypeError(f"must be a {type(defaultval)}, not a {type(chkval)}")
elif isinstance(defaultval, bool) and not isinstance(chkval, bool):
val = strtobool(chkval)
else:
val = type(defaultval)(chkval)
except (TypeError, ValueError) as e:
writemsg(f"config['{name}'] with {chkval} is not the same type as defaultval: {e}")
val = defaultval
else:
val = defaultval
if printit:
writemsg(f"config['{name}']={val}")
return val
def wait_for_keypress(timeout=None):
try:
# disable line buffering for stdin
old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
pollObj = select.poll()
pollObj.register(sys.stdin.fileno(), select.POLLIN | select.POLLHUP | select.POLLERR)
# is there input data available? clear it. we just want *new* keypresses, not old input
while len(pollObj.poll(0)) > 0:
os.read(sys.stdin.fileno(), 4096)
if timeout is None:
print('Press any key to continue')
else:
# timeout in seconds
countdown = int(timeout)
while timeout is None or countdown > 0:
if timeout is not None:
# ESC M is reverse linefeed
# If it can't be interpreted (for example on a dumb serial console) we just get the regular newlines
print(f'\n\033MWait {countdown} seconds or press any key to continue ', end="")
# 1 second wait for event
if len(pollObj.poll(1000)) > 0:
# we got some input, read as much as we can to not leave the next script with half-processed input
os.read(sys.stdin.fileno(), 4096)
# we don't really care for the data, any key is ok to abort
break
if timeout is not None:
countdown -= 1
# key pressed or countdown reached 0
print()
return
except KeyboardInterrupt:
# ctrl+c counts as just any other key
print()
return
except termios.error:
# probably some strange stdin pipe, so no keypress to wait for -> just sleep
if timeout is None:
timeout = 30
print(f'Wait for {timeout} seconds')
try:
time.sleep(float(timeout))
except KeyboardInterrupt:
# clean abort with ctrl+C
print()
return
pass
finally:
# always reset the stdin state
try:
# restore stdio terminal settings
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
except:
# ignore termios errors, probably some strange stdin, so no keypress anyway
pass
def build_config():
"""Build the effective configuration for autorun.
Searches for autorun-scripts in several paths and uses the old-style ar_suffixes= option to
merge the scripts found into the new-style "exec" structure which is then executed.
The scripts are copied to autoruntmp, regardless where they were found.
"""
global config
global ar_ignorefail, ar_nodel, ar_nowait, ar_attempts
# ---- read the effective configuration file, build basic config structure
if not os.path.exists(effectivecfg):
writemsg(f"Failed to find effective configuration file in {effectivecfg}")
sys.exit(1)
with open(effectivecfg) as file:
fullcfg = json.load(file)
if 'autorun' in fullcfg:
config = fullcfg['autorun']
else:
config = { }
if not 'exec' in config or not isinstance(config['exec'], dict):
config["exec"] = { }
# ---- parse legacy options passed on the boot command line
for curopt in open("/proc/cmdline","r").read().split():
if re.match('^autoruns=', curopt): # "autoruns" is the legacy name for 'ar_suffixes'
writemsg('WARNING: deprecated option `autoruns=` used, it will be removed soon. Use `ar_suffixes` instead')
config['ar_suffixes'] = curopt.replace('autoruns=','')
# ---- load the config into local variables, allow the keys not to exist
# ---- also print the effective configuration
writemsg("Showing the effective autorun configuration ...")
ar_suffixes = read_cfg_value('ar_suffixes', "", printit=False)
ar_disable = read_cfg_value('ar_disable', False)
ar_attempts = read_cfg_value('ar_attempts', 1)
ar_source = read_cfg_value('ar_source', "")
ar_ignorefail = read_cfg_value('ar_ignorefail', False)
ar_nodel = read_cfg_value('ar_nodel', False)
ar_nowait = read_cfg_value('ar_nowait', True)
# ---- determine the effective script files suffixes
if ar_suffixes in (None, 'no', ''):
suffixes=['']
else:
suffixes=[''] + str(ar_suffixes).split(',')
# validate suffixes, remove everything that is not on the whitelist
to_remove = []
for suf in suffixes:
if suf not in ( "", "0", "1", "2", "3", "4", "5", "6" ,"7", "8" ,"9", "A" ,"B" ,"C" ,"D", "E" , "F" ):
writemsg(f"Illegal autorun suffix {suf}: removed")
to_remove.append(suf)
for suf in to_remove:
suffixes.remove(suf)
writemsg(f"ar_suffixes={suffixes}")
# print+log at least the root exec entry names for debugging
if 'exec' in config and isinstance(config['exec'], dict):
exectext = str(list(config['exec'].keys()))
else:
exectext = "[]"
writemsg(f"exec={exectext}")
# ---- exit here is there is nothing to do
if ar_disable:
writemsg("Autorun has been disabled using ar_disable, exiting now")
sys.exit(0)
# ---- parse autorun sources ----
autorunfiles= {}
if re.match('^https?://', ar_source):
retries = int(ar_attempts)
while retries > 0 and not autorunfiles:
time.sleep(1)
retries -= 1
search_autoruns(ar_source, suffixes, autorunfiles, copyfilefct_http)
elif re.match('^/dev/', ar_source): # mount a partition/device
mnt1=('mount',ar_source,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
p.wait()
if p.returncode != 0:
writemsg(f'error: cannot mount {ar_source}')
return
search_autoruns(autorunmnt, suffixes, autorunfiles, copyfilefct_basic)
subprocess.Popen(mnt2)
elif re.match('^nfs://', ar_source): # mount an nfs share
source=ar_source.replace('nfs://','')
# retry=1 means retry it for 1 minute, but there also is a 3 minute tcp timeout -> 3 minutes timeout
mnt1=('mount','-t','nfs','-o','nolock,retry=1',source,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
p.wait()
if p.returncode != 0:
writemsg (f'error: cannot mount {source}')
return
search_autoruns(autorunmnt, suffixes, autorunfiles, copyfilefct_basic)
subprocess.Popen(mnt2)
elif re.match('^smb://', ar_source): # mount a samba share
source=ar_source.replace('smb://','')
# use -o guest to prevent mount from asking for a password
mnt1=('mount','-t','cifs','-o','guest','//%s'%source,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
p.wait()
if p.returncode != 0:
writemsg (f'error: cannot mount {source}')
return
search_autoruns(autorunmnt, suffixes, autorunfiles, copyfilefct_basic)
subprocess.Popen(mnt2)
else: # search in all default directories
writemsg ('Cannot find a valid ar_source, searching scripts in the default directories')
for curdir in defaultsrc:
if search_autoruns(curdir, suffixes, autorunfiles, copyfilefct_basic) > 0:
writemsg (f'Using autorun scripts from {curdir}')
if curdir == '/run/archiso/bootmnt':
writemsg('WARNING: Storing autorun scripts in the root of the boot device is deprecated!')
writemsg('These scripts will be ignored in a future release. Use the autorun directory instead.')
writemsg("")
break
# build exec structure
for ext, curfile in sorted(autorunfiles.items()):
# build entry name for the exec dict
if ext == '':
entryno = 1000
elif ext[0].isdigit():
entryno = 1010 + int(ext[0])
elif ord(ext[0]) >= ord('A') and ord(ext[0]) <= ord('F'):
entryno = 1020 + ord(ext[0]) - ord('A')
else:
writemsg (f'fatal error: illegal autorun extension {ext}')
sys.exit(1)
filebase=os.path.basename(curfile)
entryname = f'{entryno}-{filebase}'
# overwrite entry if it already exists
config["exec"][entryname] = { }
config["exec"][entryname]['path'] = curfile
# leave everything else at default values
def download_script(url, execname):
global ar_attempts
retries = int(ar_attempts)
while retries > 0:
writemsg(f'Downloading {execname} from {url} ...')
if retries != int(ar_attempts):
time.sleep(3)
retries -= 1
try:
success = False
targetfile=tempfile.NamedTemporaryFile(dir=autoruntmp, delete=False)
targetfilename=targetfile.name
targetfile.close()
if re.match('^https?://', url):
if copyfilefct_http(url,targetfilename) == 0:
success = True
return success, targetfilename
elif re.match('^/dev/', url):
# mount a partition/device, something like /dev/disk/by-label/some-disklabel/dir/file
# we have to split the path to the device from the path below first
dircomponents = url.split('/')
path = "/"
for dircomp in dircomponents:
try:
path = os.path.join(path, dircomp)
mode = os.stat(path).st_mode
if stat.S_ISBLK(mode):
# we found our block device to mount
break
elif stat.S_ISDIR(mode):
# continue to descent the path
continue
else:
writemsg(f'Error: unexpected file type at {path}')
return False, ""
except FileNotFoundError:
writemsg(f'Error: can\'t find file {path}')
return False, ""
mountbase = path
srcfile = url.removeprefix(path)
srcfile = srcfile.removeprefix("/")
mnt1=('mount',mountbase,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
p.wait()
if p.returncode != 0:
writemsg(f'Error: cannot mount {mountbase}')
return False, ""
ret = copyfilefct_basic(os.path.join(autorunmnt,srcfile),targetfilename)
subprocess.Popen(mnt2)
if ret == 0:
success = True
return success, targetfilename
else:
writemsg(f'Error: cannot find file {srcfile} in mountpoint {mountbase}')
return False, ""
elif re.match('^nfs://', url):
# mount an nfs share, nfs://servername:/dir/script
source=url.replace('nfs://','')
sourcesplit = source.split(":", maxsplit=1)
if len(sourcesplit) != 2 or len(sourcesplit[1]) == 0:
writemsg(f'Illegal nfs url, not in the form nfs://servername:/dir/script')
return False, ""
srcdir = os.path.dirname(sourcesplit[1])
srcfile = os.path.basename(sourcesplit[1])
if len(srcfile) == 0:
writemsg(f'Illegal nfs url, not in the form nfs://servername:/dir/script')
return False, ""
mountbase=sourcesplit[0]+":"+srcdir
# retry=1 means retry it for 1 minute, but there also is a 3 minute tcp timeout -> 3 minutes timeout
mnt1=('mount','-t','nfs','-o','nolock,retry=1',mountbase,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
p.wait()
if p.returncode != 0:
writemsg(f'Error: cannot mount {mountbase}')
# retry it, it is a network operation after all
continue
ret = copyfilefct_basic(os.path.join(autorunmnt,srcfile),targetfilename)
subprocess.Popen(mnt2)
if ret == 0:
success = True
return success, targetfilename
else:
writemsg(f'Error: cannot find file {srcfile} in mountpoint {mountbase}')
return False, ""
elif re.match('^smb://', url):
# mount a samba share, smb://host/share/dir/script
source=url.replace('smb://','')
srchostdir = os.path.dirname(source)
srcfile = os.path.basename(source)
if len(srchostdir) == 0 or len(srchostdir.split("/")) < 2 or len(srcfile) == 0:
writemsg(f'Illegal smb url, not in the form smb://host/share/dir/script')
return False, ""
mountbase="//"+srchostdir
# use -o guest to prevent mount from asking for a password
mnt1=('mount','-t','cifs','-o','guest',mountbase,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
p.wait()
if p.returncode != 0:
writemsg(f'Error: cannot mount {mountbase}')
# retry it, it is a network operation after all
continue
ret = copyfilefct_basic(os.path.join(autorunmnt,srcfile),targetfilename)
subprocess.Popen(mnt2)
if ret == 0:
success = True
return success, targetfilename
else:
writemsg(f'Error: cannot find file {srcfile} in mountpoint {mountbase}')
return False, ""
finally:
try:
if not success:
os.remove(targetfilename)
except:
# ignore it if the tmpfile is already deleted
pass
return False, ""
def exec_script_errorwrapper(entryname, data):
""" provides errorhandling around exec_script()
does waiting for keypress and similar, depending on result
returns: 0 = ok, 1 = error but continue, 2 = error don't continue
"""
global ar_ignorefail
# determine what to do in case of an error
if "wait" in data and \
( data["wait"] == "always" or data["wait"] == "on_error" or data["wait"] == "never" ):
waitwhen = data["wait"]
else:
waitwhen = "on_error"
if "waitmode" in data and \
( data["waitmode"] == "key" or is_float(data["waitmode"]) ):
waitmode = data["waitmode"]
else:
waitmode = 30
if "on_error" in data and \
( data["on_error"] == "break" or data["on_error"] == "continue" ):
errmode = data["on_error"]
else:
if not ar_ignorefail:
errmode = "break"
else:
errmode = "continue"
# execute the script
try:
errcount = exec_script(entryname, data)
except Exception as e:
# some exception we did not expect, deal with it like any other script error
writemsg('='*80)
writemsg (f'Exception {e.__class__.__name__} occured when executing {entryname}: {e}')
writemsg(traceback.format_exc())
errcount = 1
# waiting for timeout / keypress wanted?
if waitwhen == "always" or \
( waitwhen == "on_error" and errcount > 0 ):
# we need to wait
if waitmode == "key":
wait_for_keypress()
else:
wait_for_keypress(int(waitmode))
if errcount > 0:
if errmode == "break":
# don't continue executing other scripts
return 2
else:
# error, but continue
return 1
else:
# everything ok
return 0
def exec_script(entryname, data):
global config
if 'path' in data and 'url' in data:
writemsg (f'illegal configuration for {entryname}: both "path" and "url" given')
return 1
if not 'path' in data and not 'url' in data:
writemsg (f'illegal configuration for {entryname}: neither "path" nor "url" given')
return 1
if 'url' in data:
# resolve the URL, download to a tmp file, store tmp filename in path
success, path = download_script(str(data["url"]), entryname)
if not success:
return 1
# store temporary filename in config structure so we can delete it later
config["exec"][entryname]["path"] = path
else:
path = str(data["path"])
# we now have something in path, work it
try:
# only modify files in our tmp dir
if os.path.dirname(path) == autoruntmp and not is_elf_binary(path):
processdostextfiles(path)
# compatibility with old autorun: add #!/bin/sh if no shebang
ensure_shebang(path)
except:
pass
# sanitize parameters
if "parameters" in data and isinstance(data['parameters'], (str, int, float, bool)):
# we always want a list, even if we just have one parameter
data['parameters'] = [ data['parameters'] ]
if "parameters" in data and isinstance(data['parameters'], list):
# ensure all parameters are strings
new_param = [ ]
for p in data['parameters']:
new_param.append(str(p))
data['parameters'] = new_param
if "shell" in data and strtobool(data['shell']) is True:
# execution with shell
shell = True
# path_and_param must be just a string that is then interpreted by the shell
# since the user can't always control "path" (think urls), we append all the
# parameters as they are, the user has to ensure quoting etc.
path_and_param = path
if "parameters" in data and isinstance(data['parameters'], list):
for p in data['parameters']:
path_and_param += " " + p
else:
# execution without shell (default, recommended when not needing shell features)
shell = False
path_and_param = [ path ]
if "parameters" in data and isinstance(data['parameters'], list):
path_and_param += data['parameters']
writemsg("")
writemsg(format_title(f'executing {entryname}', '='))
# open logfile, sanitize filename
redir=os.path.join(autorunlog, re.sub('[^-a-zA-Z0-9_.]+', '_', entryname))
logoutput=open(redir,'wt')
exception = False
linelog = ""
try:
# stdin=None means the stdin of sysrescue-autorun will be passed through
# this allows the autorun script to take input from the terminal
proc = subprocess.Popen(path_and_param, stdin=None, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, shell=shell,universal_newlines=True)
# pipe through stdout&stderr live, write it to the autorunlog too
# we do not expect too much data here, so reading byte-by-byte is ok
# but it allows us to show for example progress indicators live on the console
while not proc.stdout.closed and proc.stdout.readable() and proc.poll() is None:
output = proc.stdout.read(1)
sys.stdout.write(output)
sys.stdout.flush()
logoutput.write(output)
# also write to the main autorun log. add manual line buffering because of timestamps added
if output == '\n':
# don't log newlines themselves
logging.info(linelog)
linelog = ""
else:
linelog += output
# the program has ended. read the rest of data that is in the buffer
if not proc.stdout.closed and proc.stdout.readable():
output = proc.stdout.read(-1)
sys.stdout.write(output)
sys.stdout.flush()
logoutput.write(output)
logoutput.close()
# also write to the main autorun log. add manual line buffering because of timestamps added
for char in output:
if char == '\n':
# don't log newlines themselves
logging.info(linelog)
linelog = ""
else:
linelog += char
returncode = proc.returncode
except OSError as e:
# for example the program wasn't found or is not executable
writemsg('='*80)
writemsg (f'Execution of {entryname} failed: {e.strerror}')
returncode = e.errno
exception = True
except KeyboardInterrupt:
print()
writemsg('='*80)
writemsg (f'Execution of {entryname} aborted')
returncode = 1
exception = True
fileres=open(redir+'.return','wt')
fileres.write(str(returncode)+'\n')
fileres.close()
if not exception:
writemsg('='*80)
writemsg (f'Execution of {entryname} returned {returncode}')
if exception or returncode != 0:
# we have an error
return 1
else:
return 0
def main():
global config
global ar_ignorefail, ar_nodel, ar_nowait, ar_attempts
logging.basicConfig(filename='/var/log/sysrescue-autorun.log', format='%(asctime)s %(message)s', level=logging.DEBUG)
writemsg("")
writemsg(format_title('Initializing autorun','#'))
build_config()
if len(config['exec']) == 0:
writemsg('No autorun scripts found, nothing to do.')
return 0
writemsg('Starting autorun execution ...')
# ---- remove user setable ar_nowait flag if set ----
if os.path.isfile('/etc/ar_nowait'):
os.unlink('/etc/ar_nowait')
# ---- execute the autorun scripts found ----
errcnt=0
for entryname, data in sorted(config['exec'].items()):
err = exec_script_errorwrapper(entryname, data)
if err > 0:
errcnt += 1
if err == 2:
# Stop on the first script failure
writemsg (f'Now aborting autorun as {entryname} has failed')
break
writemsg("")
# ---- delete tmp copies of the scripts ----
if not ar_nodel:
for name, data in sorted(config['exec'].items()):
if "path" in data and os.path.dirname(data["path"]) == autoruntmp:
writemsg (f'removing {data["path"]}')
os.unlink(data["path"])
# ---- wait a keypress feature -----
if os.path.isfile('/etc/ar_nowait'):
ar_nowait = True
if not ar_nowait and len(config['exec'].items()) > 0:
writemsg(f'Autorun scripts completed with {errcnt} errors')
wait_for_keypress()
return errcnt
# ----------------------- autorun main ----------------------------------------
for curdir in (basedir, autorunlog, autorunmnt, autoruntmp):
if not os.path.isdir(curdir):
os.mkdir(curdir)
# Exit if already running
if os.path.isfile(pidfile):
sys.exit(0)
# create lockfile
lockfile = open(pidfile, 'wt')
lockfile.write(str(os.getpid()))
try:
res = main()
sys.exit(res)
finally:
os.unlink(pidfile)