mirror of
https://github.com/nchevsky/systemrescue-zfs.git
synced 2026-01-06 16:50:18 +01:00
300 lines
12 KiB
Python
Executable file
300 lines
12 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 main():
|
|
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)
|
|
config = fullcfg['autorun']
|
|
#print(json.dumps(config, indent=4))
|
|
|
|
# ---- 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=','')
|
|
|
|
# ---- show the effective configuration
|
|
logging.info(f"Showing the effective autorun configuration ...")
|
|
for key, val in config.items():
|
|
logging.info(f"config['{key}']={val}")
|
|
|
|
# ---- determine the effective script files suffixes
|
|
if config['ar_suffixes'] in (None, 'no', ''):
|
|
suffixes=['']
|
|
else:
|
|
suffixes=[''] + str(config['ar_suffixes']).split(',')
|
|
logging.info(f"suffixes={suffixes}")
|
|
|
|
# ---- exit here is there is nothing to do
|
|
if config['ar_disable'] == True:
|
|
writemsg(f"Autorun has been disabled using ar_disable, exiting now")
|
|
sys.exit(0)
|
|
|
|
# ---- parse autorun sources ----
|
|
if re.match('^https?://', config['ar_source']):
|
|
while config['ar_attempts'] > 0 and not autorunfiles:
|
|
time.sleep(1)
|
|
config['ar_attempts'] -= 1
|
|
search_autoruns(config['ar_source'], suffixes, copyfilefct_http)
|
|
elif re.match('^/dev/', config['ar_source']): # mount a partition/device
|
|
mnt1=('mount',config['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://', config['ar_source']): # mount an nfs share
|
|
source=config['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://', config['ar_source']): # mount a samba share
|
|
source=config['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 config['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 config['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'):
|
|
config['ar_nowait'] = True
|
|
if (config['ar_nowait'] == False) 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)
|