s390-tools/zpxe.rexx

529 lines
17 KiB
Rexx

/* zPXE: REXX PXE Client for System z
zPXE is a PXE client used with Cobbler or a just a plain TFTP server.
It must be run under z/VM. zPXE uses TFTP to first download a
user-specific profile (if one exists), or a list of available profiles.
From the profile a specific kernel, initial RAMdisk, and PARM file are
then downloaded and these files are then punched to start the install
process.
zPXE does not require a writeable 191 A disk. Files are downloaded to a
temporary disk (VDISK).
zPXE can also IPL from a DASD volume by default. You can specify the
default DASD device in ZPXE CONF, as well as the hostname or IP address
of the Cobbler or TFTP server.
---
Copyright 2006-2009, Red Hat, Inc
Brad Hinson <bhinson@redhat.com>
Copyright 2012, 2017, SUSE Linux,
Mark Post <mpost@suse.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/* Set the default environment for "safety" reasons. */
ADDRESS COMMAND
/* Save the value of the trace state */
tvar_o=trace()
tvar_c=tvar_o
/* Was this script invoked with "debug" as one of the parameters? */
debug=0
if arg() then
do
parse upper arg uparg
do sub=1 to words(uparg)
if word(uparg,sub) = "DEBUG" then
do
debug=1;
trace i;
tvar_c=trace()
end;
else do /* This is a do/end in case we want to add to it later */
trace e
tvar_c=trace()
end
end
end
/* Set some defaults */
/* These values are intended to be modified by the site using this */
/* script to match their environment. */
userid=''
server=''
iplDisk=''
server_def = 'internal.tftp.server' /* define default TFTP server */
iplDisk_def = '150' /* define default IPL DASD */
FM='T' /* Default file mode is T */
profilelist = 'PROFILE LIST' FM /* Disk will be accessed as FM later */
profiledetail = 'PROFILE DETAIL' FM
zpxeparm = 'ZPXE PARM' FM
zpxeconf = 'ZPXE CONF' FM
config = 'ZPXE CONF A'
seconds=10 /* The default amount of time to wait for console input */
workDiskType='VFB-512'
workDiskSize=200000 /* This is approximately 97MB of space. */
/* For TDISK instead of VDISK, comment out the previous two lines and */
/* uncomment the following two lines.*/
/* workDisk='T3390' */
/* workDiskSize=138 */
/* Make it possible to interrupt zPXE and to enter CMS no matter how
the guest was started, if there is a system-specific profile
or not, etc.
*/
if debug then say 'Debugging, so we will skip the wait and just run.'
else do
say
say 'Enter a non-blank character and ENTER (or two ENTERs)',
'within' seconds 'seconds to interrupt zPXE.'
ADDRESS CMS 'WAKEUP +00:'seconds '(CONS'
/* Check for the interrupt code */
if rc = 6 then do
say 'Interrupt received: exiting to CMS...'
ADDRESS CMS 'DESBUF' /* Clear the stack */
exit
end
end
/* For translating strings to lowercase */
lower = xrange('a','i')xrange('j','r')xrange('s','z')
upper = xrange('A','I')xrange('J','R')xrange('S','Z')
/* Query user ID. This is used to determine:
1. Whether a user-specific PXE profile exists.
2. Whether user is disconnected.
The logic that gets followed will vary based on the results.
*/
ADDRESS CMS 'QUERY USER' userid() '(STACK'
parse pull userid_def dash dsc
if dsc = 'DSC' then disconnected=1 /* user is disconnected */
else disconnected=0
/* Yeah, this call to translate looks backward, but it's not. Sorry. */
userid_def = translate(userid_def, lower, upper)
/* Useful settings normally found in PROFILE EXEC */
'CP SET RUN ON'
'CP SET PF11 RETRIEVE FORWARD'
'CP SET PF12 RETRIEVE'
/* Useful setting for a script that may run unattended */
'CP TERM HOLD OFF'
/* We want to have a way to figure out what went wrong if something
isn't working. */
'CP SPOOL CONSOLE STOP CLOSE' /* Close any existing spooled console. */
'CP SPOOL CONSOLE START' /* Start spooling the console for this run. */
if \ debug then ADDRESS CMS 'VMFCLEAR' /* clear screen */
/* The following two commands that were in the original script are */
/* almost certainly not going to work for anyone that only has CP */
/* privilege class G */
/* 'set vdisk syslim infinite' */
/* 'set vdisk userlim infinite' */
/* Define a temporary disk to store files and CMS FORMAT it */
/* If your site doesn't allow this, but does allow TDISKs, change the */
/* DEFINE command to T3390 instead */
'CP SET EMSG OFF'
if \ debug then trace off
'CP DETACH FFFF' /* detach ffff if present */
trace value tvar_c
'CP SET EMSG ON'
'CP DEFINE' workDiskType' AS FFFF' workDiskSize
queue '1'
queue 'tmpdsk'
if \ debug then /* If debug was not specified, then */
ADDRESS CMS 'set cmstype ht' /* suppress format output */
ADDRESS CMS 'format ffff' FM /* format VDISK as file mode FM */
ADDRESS CMS 'set cmstype rt' /* Resume seeing command output */
say 'DASD FFFF has been CMS formatted'
/* Check for the ZPXE CONF A config file and use whatever is there in
preference over the defaults in this script */
call GetZPXECONF
/* For any values not found in ZPXE CONF A, or if it doesn't exist, use
the default values specified in this script. */
if server = '' then do
say 'Setting TFTP server to 'server_def
server = server_def
end
if iplDisk = '' then do
say 'Setting IPL disk to default of 'iplDisk_def
iplDisk = iplDisk_def
end
if userid = '' then do
say 'Setting userid to default of 'userid_def
userid = userid_def
end
/* Link to TCPMAINT's 592 disk for access to the TFTP command */
say
ADDRESS CMS 'exec vmlink tcpmaint 592'
say
say 'Connecting to server 'server /* print server name */
/* Check whether a user-specific PXE profile exists. */
call GetTFTP '/s390x/s_'userid 'profile.detail.'FM
if lines(profiledetail) > 0 then call ProcessUserProfile
else do /* no user-specific profile was found */
say 'No profile found for' userid
if disconnected then do /* user is disconnected */
ADDRESS CMS 'release' FM '(detach'
ADDRESS CMS 'exec vmlink tcpmaint 592 <detach>'
say 'User is disconnected. Booting from DASD 'iplDisk'...'
'CP IPL' iplDisk
end
else call ProcessGenericProfiles /* user is interactive -> prompt */
end /* no user-specific profile was found */
trace value tvar_o
exit
/* */
/* Subroutines called from the main script */
/* */
/* Procedure GetZPXECONF
*/
GetZPXECONF:
if lines(config) > 0 then do
say config "was found"
do while lines(config) > 0
inputline = linein(config)
parse upper var inputline keyword value .
select
when (keyword = 'HOST') then do /* line is server hostname/IP */
server = value
if server = '' then say config "didn't have an IP address for",
"the TFTP server."
else say ' Setting TFTP server to 'server
end
when (keyword = 'IPLDISK') then do /* line is default IPL disk */
iplDisk = value
if iplDisk = '' then say config "didn't have an IPL Disk parm."
else say ' Setting IPL disk to 'iplDisk
end
otherwise do /* line is userid to use instead of the default */
userid = translate(keyword,lower,upper) /* Still not backward */
say ' Setting userid to 'userid
end
end /* select */
end /* do while lines(config) > 0 */
end /* if lines(config) > 0 */
return /* GetZPXECONF */
/* Procedure ProcessUserProfile
*/
ProcessUserProfile:
say 'Profile for 'userid' found'
say
bootRc = ParseSystemRecord() /* parse file for boot action */
if bootRc = 0 then do
say 'The profile said we should boot from local disk.'
ADDRESS CMS 'release' FM '(detach'
ADDRESS CMS 'exec vmlink tcpmaint 592 <detach>'
say 'IPLing from' iplDisk
'CP IPL' iplDisk /* boot from default DASD */
end /* if bootRc = 0 */
else do /* The profile should contain pointers to kernel, etc.*/
abort=0
/* Get the user PARM file that contains network info */
say 'Downloading parameter file [/s390x/s_'userid'_parm]...'
call GetTFTP '/s390x/s_'userid'_parm' 'zpxe.parm.'FM
if CheckDownload('s_'userid'_parm' zpxeparm) <> 0 then
abort=1
/* Get the user CONF file that currently isn't used for anything */
say 'Downloading conf file [/s390x/s_'userid'_conf]...'
call GetTFTP '/s390x/s_'userid'_conf' 'zpxe.conf.'FM
if CheckDownload('s_'userid'_conf' zpxeconf) <> 0 then
abort=1
if abort then do
say 'Aborting PXE boot.'
exit 99
end
call DownloadBinaries /* download kernel and initrd */
say 'Starting install...'
say
call PunchFiles /* punch files to begin install */
exit
end /* he profile should contain pointers to kernel, etc */
/* ProcessGenericProfiles
*/
ProcessGenericProfiles:
/* Download the list of generic profiles available */
say 'Downloading the profile list [/s390x/profile_list]...'
call GetTFTP '/s390x/profile_list' 'profile.list.'FM
if CheckDownload('profile_list' profilelist) <> 0 then do
say '** **'
say '** No profile list found **'
say '** Possible error connecting to server? **'
say '** **'
exit 99
end
/* Display a menu of the generic profiles */
say
say 'zPXE MENU'
say '---------'
/* Display one profile per line */
do count = 1 by 1 while lines(profilelist) > 0
inputline = linein(profilelist)
parse var inputline profile.count
say count'. 'profile.count
end
/* Add two non-profile selections to the menu */
say count'. Don''t continue, exit to CMS'
say
say
say 'Enter Choice -->'
say 'or press <Enter> to boot from disk [DASD 'iplDisk']'
parse pull answer .
select
when answer = count then do /* Exit to CMS was selected */
say
say 'Exiting to CMS...'
exit
end
when answer = '' then do /* IPL from default disk */
ADDRESS CMS 'release' FM '(detach'
ADDRESS CMS 'exec vmlink tcpmaint 592 <detach>'
say 'Booting from DASD 'iplDisk'...'
'CP IPL' iplDisk
end
when (answer > 0) & (answer < count) then do /* valid response */
abort=0
say 'Downloading generic profile [/s390x/p_'profile.answer']...'
call GetTFTP '/s390x/p_'profile.answer 'profile.detail.'FM
if CheckDownload('p_'profile.answer profiledetail) <> 0 then
abort=1
say 'Downloading generic parameter file',
'[/s390x/p_'profile.answer'_parm]...'
call GetTFTP '/s390x/p_'profile.answer'_parm' 'zpxe.parm.'FM
if CheckDownload('p_'profile.answer'_parm' zpxeparm) <> 0 then
abort=1
say 'Downloading generic conf file',
'[/s390x/p_'profile.answer'_conf]...'
call GetTFTP '/s390x/p_'profile.answer'_conf' 'zpxe.conf.'FM
if CheckDownload('p_'profile.answer'_conf' zpxeconf) <> 0 then
abort=1
if abort then do
say 'Aborting PXE boot.'
exit 99
end
/* We have to add the HostIP parameter to the parm file, since that is
going to vary for each guest, so we can't hard-code it in the generic
profiles. We use the numeric part of the guest name, which starts in
column 6, after "LINUX". But we have to watch out for leading zeros,
since that will appear as an octal number to Linux. So, we use the
fact that Rexx/Regina doesn't care about leading zeros, but will
remove them when used in an arithmetic statement, such as follows. */
lastoctet=substr(userid,6)
lastoctet=lastoctet+0 /* Adding a zero won't change the value */
hostipparm=' HostIP=10.121.157.'lastoctet
call lineout zpxeparm, hostipparm
call lineout zpxeparm /* close the output file */
if \ debug then ADDRESS CMS 'VMFCLEAR' /* clear screen */
say
say 'Using profile 'answer' ['profile.answer']'
say
call DownloadBinaries /* download kernel and initrd */
say 'Starting install...'
say
call PunchFiles
end /* valid response */
otherwise do /* The user entered something that wasn't in the list */
say 'Invalid choice, exiting to CMS...'
exit
end
end /* Select */
/* Procedure GetTFTP
Use CMS TFTP client to download files
path: remote file location
filename: local file name
transfermode [optional]: 'ascii' or 'octet'
*/
GetTFTP:
parse arg path filename transfermode
if transfermode <> '' then
queue 'mode' transfermode
queue 'get 'path filename
queue 'quit'
if \ debug then
ADDRESS CMS 'set cmstype ht' /* suppress TFTP output */
ADDRESS CMS 'tftp' server
ADDRESS CMS 'set cmstype rt'
return /* GetTFTP */
/* Procedure CheckDownload
TFTP is dumb, so you can't ever tell if a file was actually retrieved
or not from the return code.
path: The filename (including path) that was to be retrieved
via TFTP
filename: The local CMS filename that should have received it.
*/
CheckDownload:
parse arg path filename
if lines(filename) = 0 then do
say 'The' path 'file was not successfully retrieved'
return 99
end
else return 0
/* End CheckDownload */
/* Procedure DownloadBinaries
Download kernel and initial RAMdisk. Convert both
to fixed record length 80.
*/
DownloadBinaries:
inputline = linein(profiledetail) /* first line is kernel */
parse var inputline kernelpath
if kernelpath = '' then do
say 'The path to the kernel is null. Aborting...'
exit 99
end
say 'Downloading kernel ['kernelpath']...'
call GetTFTP kernelpath 'kernel.img.'FM octet
if CheckDownload(kernelpath kernel img FM) <> 0 then do
say 'Aborting PXE boot.'
exit 99
end
inputline = linein(profiledetail) /* second line is initrd */
parse var inputline initrdpath
if initrdpath = '' then do
say 'The path to the initrd is null. Aborting...'
exit 99
end
say 'Downloading initrd ['initrdpath']...'
call GetTFTP initrdpath 'initrd.img.'FM octet
if CheckDownload(initrdpath initrd img FM) <> 0 then do
say 'Aborting PXE boot.'
exit 99
end
inputline = linein(profiledetail) /* third line is kernel parms */
parse var inputline kparms
if kparms <> '' then do /* If there are parms, add them to the end */
call lineout zpxeparm, kparms /* add ks line to end of parm */
call lineout zpxeparm /* close file */
end
/* Convert to fixed record length since they're going to be run
through the virtual card reader. */
ADDRESS CMS 'pipe < KERNEL IMG 'FM' | fblock 80 00 | > KERNEL IMG' FM
ADDRESS CMS 'pipe < INITRD IMG 'FM' | fblock 80 00 | > INITRD IMG' FM
ADDRESS CMS 'pipe < ' zpxeparm ' | fblock 80 SPACE | > ' zpxeparm
return /* DownloadBinaries */
/* Procedure PunchFiles
Punch the kernel, initial RAMdisk, and PARM file.
Then IPL to start the install process.
*/
PunchFiles:
'CP SPOOL PUNCH *'
'CP CLOSE READER'
'CP PURGE READER ALL' /* clear reader contents */
ADDRESS CMS 'punch kernel img' FM '( noheader' /* punch kernel */
ADDRESS CMS 'punch zpxe parm' FM '( noheader' /* punch PARM file */
ADDRESS CMS 'punch initrd img' FM '( noheader' /* punch initrd */
ADDRESS CMS 'release' FM '(detach' /* release and detach the VDISK */
ADDRESS CMS 'exec vmlink tcpmaint 592 <detach>' /* and this disk */
'CP CHANGE READER ALL KEEP NOHOLD' /* keep files in reader */
'CP IPL 00C CLEAR' /* IPL the reader */
return /* PunchFiles */
/* Procedure ParseSystemRecord
Open system record file to look for local boot flag.
Return 0 if local flag found (guest will IPL default DASD).
Return 1 otherwise (guest will download kernel/initrd and install).
*/
ParseSystemRecord:
inputline = linein(profiledetail) /* get first line */
parse var inputline systemaction .
/* Close the file to reset the read pointer to the beginning. Yes I
know that calling lineout to close a file seems weird, but it's
how Rexx/Regina works. */
call lineout profiledetail
if systemaction = 'local' then
return 0
else
return 1
/* End ParseSystemRecord */