mirror of
https://github.com/nchevsky/systemrescue-zfs.git
synced 2026-04-21 06:03:41 +00:00
The recently implemented type casting code doesn't work intuitively for booleans: for example it treats a string "0" as False and doesn't show an error message for it. So the user might be unaware of this. So add a dedicated conversion function for booleans. Also add checks for conversions from dicts and lists, forbid to cast them to strings because that is most probably not what the user wants.
345 lines
14 KiB
Python
Executable file
345 lines
14 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Distributed under the terms of the GNU General Public License v2
|
|
# 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
|
|
#
|
|
# ----------------------- changeslog: -----------------------------------------
|
|
# 2003-10-01: Pierre Dorgueil --> original bash version of autorun for sysrescue
|
|
# 2008-01-26: Francois Dupoux --> rewrote autorun in python to support http
|
|
# 2008-01-27: Francois Dupoux --> added 'ar_ignorefail', 'ar_nodel', 'ar_disable'
|
|
# 2017-05-30: Gernot Fink --> ported the script from python2 to python3
|
|
# 2021-07-07: Alexander Mahr --> added 'ar_attempts'
|
|
# 2022-01-09: Francois Dupoux --> added support for yaml configuration
|
|
# 2022-01-09: Francois Dupoux --> option 'autoruns=' renamed 'ar_suffixes='
|
|
# 2022-01-23: Francois Dupoux --> use the generated effective configuration file
|
|
#
|
|
# ----------------------- autorun exec rules: ---------------------------------
|
|
# - pass 'ar_source=/dev/fd#' to request floppy device test
|
|
# - CD is tested if no floppy requested or no autorun found on floppy
|
|
# - if a file named 'autorun' is found on any media, it is always run, except if
|
|
# option 'ar_disable' is used
|
|
# - if a file named 'autorun[0-9A-F]' is found on any media, it is run if either
|
|
# - 'ar_suffixes=...' arg did specify its suffix (ex. ar_suffixes=1,3,5), or
|
|
# - no 'ar_suffixes=...' arg was passed
|
|
# - pass ar_suffixes=no to prevent running any 'autorun[0-9A-F]' file
|
|
# - defaults to allow all 'autorun[0-9A-F]' files
|
|
# - if many autorun files are to be run,
|
|
# - always in alphab order: autorun, then autorun0, then autorun1 etc...
|
|
# - first non-zero exit code stops all (except if ar_ignorefail is used)
|
|
# - if option 'ar_nodel' is used, the temp copy of the script will not be deleted
|
|
# - if option 'ar_ignorefail' is used, do not stop autorun if a script failed
|
|
# - if option 'ar_disable' is used, absolutely no autorun script will be run
|
|
|
|
import sys, os, re, subprocess, logging, time, glob, json
|
|
|
|
# ------------------------ 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','/run/archiso/copytoram/autorun','/run/archiso/copytoram','/var/autorun/cdrom','/root','/usr/share/sys.autorun']
|
|
effectivecfg="/etc/sysrescue/sysrescue-effective-config.json"
|
|
autorunfiles=[]
|
|
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] == '\x7f' and content[1] == 'E' and \
|
|
content[2] == 'L' and content[3] == '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)
|
|
remain=totallen-startpos-len(title)
|
|
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, 755)
|
|
return 0
|
|
else:
|
|
return -1
|
|
|
|
def copyfilefct_http(src, dest):
|
|
logging.debug(f"Attempting to download {src} ...")
|
|
cmd=('wget','-q',src,'-O',dest)
|
|
p = subprocess.Popen(cmd)
|
|
p.wait()
|
|
if p.returncode == 0:
|
|
logging.info(f"Successfully downloaded {src}")
|
|
os.chmod(dest, 755)
|
|
return 0
|
|
else:
|
|
logging.warning(f"Failed to download {src}")
|
|
os.unlink(dest)
|
|
return -1
|
|
|
|
def search_autoruns(dirname, suffixes, 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.append(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', and '1'; false values
|
|
are 'n', 'no', 'f', 'false', 'off', and '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'):
|
|
return True
|
|
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
|
|
return False
|
|
else:
|
|
raise ValueError("invalid truth value %r" % (val,))
|
|
|
|
def read_cfg_value(name, defaultval):
|
|
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
|
|
print(f"config['{name}']={val}")
|
|
return val
|
|
|
|
def main():
|
|
global config
|
|
|
|
errcnt=0 # in case no autorun executed
|
|
logging.basicConfig(filename='/var/log/sysrescue-autorun.log', format='%(asctime)s %(message)s', level=logging.DEBUG)
|
|
writemsg('Initializing autorun ...')
|
|
|
|
# ---- read the effective configuration file
|
|
if os.path.exists(effectivecfg) == False:
|
|
print (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 = { }
|
|
|
|
# ---- 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'
|
|
config['ar_suffixes'] = curopt.replace('autoruns=','')
|
|
|
|
# ---- load the config into local variables, allow the keys not to exist
|
|
# ---- also print the effective configuration
|
|
logging.info(f"Showing the effective autorun configuration ...")
|
|
ar_suffixes = read_cfg_value('ar_suffixes', "")
|
|
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', False)
|
|
|
|
# ---- determine the effective script files suffixes
|
|
if ar_suffixes in (None, 'no', ''):
|
|
suffixes=['']
|
|
else:
|
|
suffixes=[''] + str(ar_suffixes).split(',')
|
|
logging.info(f"suffixes={suffixes}")
|
|
|
|
# ---- exit here is there is nothing to do
|
|
if ar_disable == True:
|
|
writemsg(f"Autorun has been disabled using ar_disable, exiting now")
|
|
sys.exit(0)
|
|
|
|
# ---- parse autorun sources ----
|
|
if re.match('^https?://', ar_source):
|
|
while ar_attempts > 0 and not autorunfiles:
|
|
time.sleep(1)
|
|
ar_attempts -= 1
|
|
search_autoruns(ar_source, suffixes, 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('fatal error: cannot mount', mnt1)
|
|
sys.exit(1)
|
|
search_autoruns(autorunmnt, suffixes, copyfilefct_basic)
|
|
subprocess.Popen(mnt2)
|
|
elif re.match('^nfs://', ar_source): # mount an nfs share
|
|
source=ar_source.replace('nfs://','')
|
|
mnt1=('mount','-t','nfs','-o','nolock',source,autorunmnt)
|
|
mnt2=('umount',autorunmnt)
|
|
p = subprocess.Popen(mnt1)
|
|
p.wait()
|
|
if p.returncode != 0:
|
|
writemsg ('fatal error: cannot mount', mnt1)
|
|
sys.exit(1)
|
|
search_autoruns(autorunmnt, suffixes, copyfilefct_basic)
|
|
subprocess.Popen(mnt2)
|
|
elif re.match('^smb://', ar_source): # mount a samba share
|
|
source=ar_source.replace('smb://','')
|
|
mnt1=('mount','-t','cifs','//%s'%source,autorunmnt)
|
|
mnt2=('umount',autorunmnt)
|
|
p = subprocess.Popen(mnt1)
|
|
p.wait()
|
|
if p.returncode != 0:
|
|
writemsg ('fatal error: cannot mount',mnt1)
|
|
sys.exit(1)
|
|
search_autoruns(autorunmnt, suffixes, copyfilefct_basic)
|
|
subprocess.Popen(mnt2)
|
|
else: # search in all default directories
|
|
writemsg ('Cannot find a valid ar_source, searching scripts in the default directories')
|
|
found=0
|
|
for curdir in defaultsrc:
|
|
if found == 0:
|
|
found += search_autoruns(curdir, suffixes, copyfilefct_basic)
|
|
|
|
# ---- 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 ----
|
|
for curfile in autorunfiles:
|
|
try:
|
|
if not is_elf_binary(curfile):
|
|
processdostextfiles(curfile)
|
|
# compatibility with old autorun: add #!/bin/sh if no shebang
|
|
ensure_shebang(curfile)
|
|
except:
|
|
pass
|
|
filebase=os.path.basename(curfile)
|
|
writemsg("\n")
|
|
writemsg(format_title(f'executing {filebase}', '='))
|
|
redir=os.path.join(autorunlog, filebase)
|
|
|
|
logoutput=open(redir,'wt')
|
|
try:
|
|
# directly (=without extra shell) execute the script
|
|
# 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(curfile, stdin=None, stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT, shell=False,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)
|
|
|
|
# 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()
|
|
|
|
returncode = proc.returncode
|
|
except OSError as e:
|
|
# for example the program wasn't found or is not executable
|
|
writemsg (f'Execution of {filebase} failed: {e.strerror}')
|
|
returncode = e.errno
|
|
|
|
fileres=open(redir+'.return','wt')
|
|
fileres.write(str(returncode)+'\n')
|
|
fileres.close()
|
|
writemsg('='*80)
|
|
writemsg (f'Execution of {filebase} returned {returncode}')
|
|
if returncode != 0:
|
|
errcnt += 1
|
|
if ar_ignorefail == False:
|
|
writemsg (f'Now aborting autorun as {filebase} has failed')
|
|
break; # Stop on the first script failure
|
|
|
|
# ---- delete the copies of the scripts ----
|
|
if ar_nodel == False:
|
|
for curfile in autorunfiles:
|
|
writemsg (f'removing {curfile}')
|
|
os.unlink(curfile)
|
|
|
|
# ---- wait a keypress feature -----
|
|
if os.path.isfile('/etc/ar_nowait'):
|
|
ar_nowait = True
|
|
if (ar_nowait != True) and (len(autorunfiles) > 0):
|
|
writemsg(f'Autorun scripts completed with {errcnt} errors, press <Enter> to continue')
|
|
sys.stdin.read(1)
|
|
|
|
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)
|