mirror of
https://github.com/nchevsky/systemrescue-zfs.git
synced 2026-03-19 02:14:39 +01:00
This has been deprecated for some time and conflicts with the (preferred) "autorun" dir in there
718 lines
26 KiB
Python
Executable file
718 lines
26 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','/root','/usr/share/sys.autorun']
|
|
effectivecfg="/etc/sysrescue/sysrescue-effective-config.json"
|
|
config = {}
|
|
|
|
# ----------------------- functions definitions --------------------------------
|
|
def writemsg(message):
|
|
print(message)
|
|
logging.info(message)
|
|
|
|
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 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"] = { }
|
|
|
|
# ---- 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
|
|
|
|
# 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)
|