mirror of
https://github.com/nchevsky/systemrescue-zfs.git
synced 2025-12-31 21:59:58 +01:00
Until now sysrescue-configuration.lua always ensured the default values were in the effective JSON config because there was no way to remove them. Now the lua script is improved to allow full config merging, including a delete function. This could lead to the user accidently removing a value. The scripts did not expect this and accessed non existing keys, leading to an exception. This is fixed with this commit.
318 lines
13 KiB
Python
Executable file
318 lines
13 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 read_cfg_value(name, defaultval=None):
|
|
if name in config:
|
|
val = config[name]
|
|
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)
|