1\ $OpenBSD: bootblk.fth,v 1.1.1.1 2001/08/18 04:16:37 jason Exp $ 2\ $NetBSD: bootblk.fth,v 1.3 2001/08/15 20:10:24 eeh Exp $ 3\ 4\ IEEE 1275 Open Firmware Boot Block 5\ 6\ Parses disklabel and UFS and loads the file called `ofwboot' 7\ 8\ 9\ Copyright (c) 1998 Eduardo Horvath. 10\ All rights reserved. 11\ 12\ Redistribution and use in source and binary forms, with or without 13\ modification, are permitted provided that the following conditions 14\ are met: 15\ 1. Redistributions of source code must retain the above copyright 16\ notice, this list of conditions and the following disclaimer. 17\ 2. Redistributions in binary form must reproduce the above copyright 18\ notice, this list of conditions and the following disclaimer in the 19\ documentation and/or other materials provided with the distribution. 20\ 3. All advertising materials mentioning features or use of this software 21\ must display the following acknowledgement: 22\ This product includes software developed by Eduardo Horvath. 23\ 4. The name of the author may not be used to endorse or promote products 24\ derived from this software without specific prior written permission 25\ 26\ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 27\ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 28\ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 29\ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 30\ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 31\ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32\ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33\ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34\ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 35\ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36\ 37 38offset16 39hex 40headers 41 42false value boot-debug? 43 44\ 45\ First some housekeeping: Open /chosen and set up vectors into 46\ client-services 47 48" /chosen" find-package 0= if ." Cannot find /chosen" 0 then 49constant chosen-phandle 50 51" /openprom/client-services" find-package 0= if 52 ." Cannot find client-services" cr abort 53then constant cif-phandle 54 55defer cif-claim ( align size virt -- base ) 56defer cif-release ( size virt -- ) 57defer cif-open ( cstr -- ihandle|0 ) 58defer cif-close ( ihandle -- ) 59defer cif-read ( len adr ihandle -- #read ) 60defer cif-seek ( low high ihandle -- -1|0|1 ) 61\ defer cif-peer ( phandle -- phandle ) 62\ defer cif-getprop ( len adr cstr phandle -- ) 63 64: find-cif-method ( method,len -- xf ) 65 cif-phandle find-method drop 66; 67 68" claim" find-cif-method to cif-claim 69" open" find-cif-method to cif-open 70" close" find-cif-method to cif-close 71" read" find-cif-method to cif-read 72" seek" find-cif-method to cif-seek 73 74: twiddle ( -- ) ." ." ; \ Need to do this right. Just spit out periods for now. 75 76\ 77\ Support routines 78\ 79 80: strcmp ( s1 l1 s2 l2 -- true:false ) 81 rot tuck <> if 3drop false exit then 82 comp 0= 83; 84 85\ Move string into buffer 86 87: strmov ( s1 l1 d -- d l1 ) 88 dup 2over swap -rot ( s1 l1 d s1 d l1 ) 89 move ( s1 l1 d ) 90 rot drop swap 91; 92 93\ Move s1 on the end of s2 and return the result 94 95: strcat ( s1 l1 s2 l2 -- d tot ) 96 2over swap ( s1 l1 s2 l2 l1 s1 ) 97 2over + rot ( s1 l1 s2 l2 s1 d l1 ) 98 move rot + ( s1 s2 len ) 99 rot drop ( s2 len ) 100; 101 102: strchr ( s1 l1 c -- s2 l2 ) 103 begin 104 dup 2over 0= if ( s1 l1 c c s1 ) 105 2drop drop exit then 106 c@ = if ( s1 l1 c ) 107 drop exit then 108 -rot /c - swap ca1+ ( c l2 s2 ) 109 swap rot 110 again 111; 112 113 114: cstr ( ptr -- str len ) 115 dup 116 begin dup c@ 0<> while + repeat 117 over - 118; 119 120\ 121\ BSD FFS parameters 122\ 123 124fload assym.fth.h 125 126sbsize buffer: sb-buf 127-1 value boot-ihandle 128dev_bsize value bsize 1290 value raid-offset \ Offset if it's a raid-frame partition 130 131: strategy ( addr size start -- nread ) 132 raid-offset + bsize * 0 " seek" boot-ihandle $call-method 133 -1 = if 134 ." strategy: Seek failed" cr 135 abort 136 then 137 " read" boot-ihandle $call-method 138; 139 140\ 141\ Cylinder group macros 142\ 143 144: cgbase ( cg fs -- cgbase ) fs_fpg l@ * ; 145: cgstart ( cg fs -- cgstart ) 146 2dup fs_cgmask l@ not and ( cg fs stuff -- ) 147 over fs_cgoffset l@ * -rot ( stuffcg fs -- ) 148 cgbase + 149; 150: cgdmin ( cg fs -- 1st-data-block ) dup fs_dblkno l@ -rot cgstart + ; 151: cgimin ( cg fs -- inode-block ) dup fs_iblkno l@ -rot cgstart + ; 152: cgsblock ( cg fs -- super-block ) dup fs_sblkno l@ -rot cgstart + ; 153: cgstod ( cg fs -- cg-block ) dup fs_cblkno l@ -rot cgstart + ; 154 155\ 156\ Block and frag position macros 157\ 158 159: blkoff ( pos fs -- off ) fs_qbmask x@ and ; 160: fragoff ( pos fs -- off ) fs_qfmask x@ and ; 161: lblktosize ( blk fs -- off ) fs_bshift l@ << ; 162: lblkno ( pos fs -- off ) fs_bshift l@ >> ; 163: numfrags ( pos fs -- off ) fs_fshift l@ >> ; 164: blkroundup ( pos fs -- off ) dup fs_bmask l@ -rot fs_qbmask x@ + and ; 165: fragroundup ( pos fs -- off ) dup fs_fmask l@ -rot fs_qfmask x@ + and ; 166\ : fragroundup ( pos fs -- off ) tuck fs_qfmask x@ + swap fs_fmask l@ and ; 167: fragstoblks ( pos fs -- off ) fs_fragshift l@ >> ; 168: blkstofrags ( blk fs -- frag ) fs_fragshift l@ << ; 169: fragnum ( fsb fs -- off ) fs_frag l@ 1- and ; 170: blknum ( fsb fs -- off ) fs_frag l@ 1- not and ; 171: dblksize ( lbn dino fs -- size ) 172 -rot ( fs lbn dino ) 173 di_size x@ ( fs lbn di_size ) 174 -rot dup 1+ ( di_size fs lbn lbn+1 ) 175 2over fs_bshift l@ ( di_size fs lbn lbn+1 di_size b_shift ) 176 rot swap << >= ( di_size fs lbn res1 ) 177 swap ndaddr >= or if ( di_size fs ) 178 swap drop fs_bsize l@ exit ( size ) 179 then tuck blkoff swap fragroundup ( size ) 180; 181 182 183: ino-to-cg ( ino fs -- cg ) fs_ipg l@ / ; 184: ino-to-fsbo ( ino fs -- fsb0 ) fs_inopb l@ mod ; 185: ino-to-fsba ( ino fs -- ba ) \ Need to remove the stupid stack diags someday 186 2dup ( ino fs ino fs ) 187 ino-to-cg ( ino fs cg ) 188 over ( ino fs cg fs ) 189 cgimin ( ino fs inode-blk ) 190 -rot ( inode-blk ino fs ) 191 tuck ( inode-blk fs ino fs ) 192 fs_ipg l@ ( inode-blk fs ino ipg ) 193 mod ( inode-blk fs mod ) 194 swap ( inode-blk mod fs ) 195 dup ( inode-blk mod fs fs ) 196 fs_inopb l@ ( inode-blk mod fs inopb ) 197 rot ( inode-blk fs inopb mod ) 198 swap ( inode-blk fs mod inopb ) 199 / ( inode-blk fs div ) 200 swap ( inode-blk div fs ) 201 blkstofrags ( inode-blk frag ) 202 + 203; 204: fsbtodb ( fsb fs -- db ) fs_fsbtodb l@ << ; 205 206\ 207\ File stuff 208\ 209 210niaddr /w* constant narraysize 211 212struct 213 8 field >f_ihandle \ device handle 214 8 field >f_seekp \ seek pointer 215 8 field >f_fs \ pointer to super block 216 dinode_SIZEOF field >f_di \ copy of on-disk inode 217 8 field >f_buf \ buffer for data block 218 4 field >f_buf_size \ size of data block 219 4 field >f_buf_blkno \ block number of data block 220constant file_SIZEOF 221 222file_SIZEOF buffer: the-file 223sb-buf the-file >f_fs x! 224 225dinode_SIZEOF buffer: cur-inode 226h# 2000 buffer: indir-block 227-1 value indir-addr 228 229\ 230\ Translate a fileblock to a disk block 231\ 232\ We only allow single indirection 233\ 234 235: block-map ( fileblock -- diskblock ) 236 \ Direct block? 237 dup ndaddr < if ( fileblock ) 238 cur-inode di_db ( arr-indx arr-start ) 239 swap la+ l@ exit ( diskblock ) 240 then ( fileblock ) 241 ndaddr - ( fileblock' ) 242 \ Now we need to check the indirect block 243 dup sb-buf fs_nindir l@ < if ( fileblock' ) 244 cur-inode di_ib l@ dup ( fileblock' indir-block indir-block ) 245 indir-addr <> if ( fileblock' indir-block ) 246 to indir-addr ( fileblock' ) 247 indir-block ( fileblock' indir-block ) 248 sb-buf dup fs_bsize l@ ( fileblock' indir-block fs fs_bsize ) 249 swap indir-addr swap ( fileblock' indir-block fs_bsize indiraddr fs ) 250 fsbtodb ( fileblock' indir-block fs_bsize db ) 251 strategy ( fileblock' nread ) 252 then ( fileblock' nread|indir-block ) 253 drop \ Really should check return value 254 indir-block swap la+ l@ exit 255 then 256 dup sb-buf fs_nindir - ( fileblock'' ) 257 \ Now try 2nd level indirect block -- just read twice 258 dup sb-buf fs_nindir l@ dup * < if ( fileblock'' ) 259 cur-inode di_ib 1 la+ l@ ( fileblock'' indir2-block ) 260 to indir-addr ( fileblock'' ) 261 \ load 1st level indir block 262 indir-block ( fileblock'' indir-block ) 263 sb-buf dup fs_bsize l@ ( fileblock'' indir-block fs fs_bsize ) 264 swap indir-addr swap ( fileblock'' indir-block fs_bsize indiraddr fs ) 265 fsbtodb ( fileblock'' indir-block fs_bsize db ) 266 strategy ( fileblock'' nread ) 267 drop ( fileblock'' ) 268 dup sb-buf fs_nindir / ( fileblock'' indir-offset ) 269 indir-block swap la+ l@ ( fileblock'' indirblock ) 270 to indir-addr ( fileblock'' ) 271 \ load 2nd level indir block 272 indir-block ( fileblock'' indir-block ) 273 sb-buf dup fs_bsize l@ ( fileblock'' indir-block fs fs_bsize ) 274 swap indir-addr swap ( fileblock'' indir-block fs_bsize indiraddr fs ) 275 fsbtodb ( fileblock'' indir-block fs_bsize db ) 276 strategy ( fileblock'' nread ) 277 drop ( fileblock'' ) 278 sb-buf fs_nindir l@ mod indir-block swap la+ l@ exit 279 then 280 ." block-map: exceeded max file size" cr 281 abort 282; 283 284\ 285\ Read file into internal buffer and return pointer and len 286\ 287 2882000 buffer: cur-block \ Why do dynamic allocation? 289-1 value cur-blockno 2900 value cur-offset 291 292: buf-read-file ( fs -- len buf ) 293 cur-offset swap ( seekp fs ) 294 2dup blkoff ( seekp fs off ) 295 -rot 2dup lblkno ( off seekp fs block ) 296 swap 2dup cur-inode ( off seekp block fs block fs inop ) 297 swap dblksize ( off seekp block fs size ) 298 rot dup cur-blockno ( off seekp fs size block block cur ) 299 <> if ( off seekp fs size block ) 300 block-map ( off seekp fs size diskblock ) 301 dup 0= if ( off seekp fs size diskblock ) 302 over cur-block swap 0 fill ( off seekp fs size diskblock ) 303 boot-debug? if ." buf-read-file fell off end of file" cr then 304 else 305 2dup sb-buf fsbtodb cur-block -rot strategy ( off seekp fs size diskblock nread ) 306 rot 2dup <> if " buf-read-file: short read." cr abort then 307 then ( off seekp fs diskblock nread size ) 308 nip nip ( off seekp fs size ) 309 else ( off seekp fs size block block cur ) 310 2drop ( off seekp fs size ) 311 then 312\ dup cur-offset + to cur-offset \ Set up next xfer -- not done 313 nip nip swap - ( len ) 314 cur-block 315; 316 317\ 318\ Read inode into cur-inode -- uses cur-block 319\ 320 321: read-inode ( inode fs -- ) 322 twiddle ( inode fs -- inode fs ) 323 324 cur-block ( inode fs -- inode fs buffer ) 325 326 over ( inode fs buffer -- inode fs buffer fs ) 327 fs_bsize l@ ( inode fs buffer -- inode fs buffer size ) 328 329 2over ( inode fs buffer size -- inode fs buffer size inode fs ) 330 2over ( inode fs buffer size inode fs -- inode fs buffer size inode fs buffer size ) 331 2swap tuck ( inode fs buffer size inode fs buffer size -- inode fs buffer size buffer size fs inode fs ) 332 333 ino-to-fsba ( inode fs buffer size buffer size fs inode fs -- inode fs buffer size buffer size fs fsba ) 334 swap ( inode fs buffer size buffer size fs fsba -- inode fs buffer size buffer size fsba fs ) 335 fsbtodb ( inode fs buffer size buffer size fsba fs -- inode fs buffer size buffer size db ) 336 337 dup to cur-blockno ( inode fs buffer size buffer size dstart -- inode fs buffer size buffer size dstart ) 338 strategy ( inode fs buffer size buffer size dstart -- inode fs buffer size nread ) 339 <> if ." read-inode - residual" cr abort then 340 dup 2over ( inode fs buffer -- inode fs buffer buffer inode fs ) 341 ino-to-fsbo ( inode fs buffer -- inode fs buffer buffer fsbo ) 342 dinode_SIZEOF * + ( inode fs buffer buffer fsbo -- inode fs buffer dinop ) 343 cur-inode dinode_SIZEOF move ( inode fs buffer dinop -- inode fs buffer ) 344 \ clear out the old buffers 345 drop ( inode fs buffer -- inode fs ) 346 2drop 347; 348 349\ Identify inode type 350 351: is-dir? ( dinode -- true:false ) di_mode w@ ifmt and ifdir = ; 352: is-symlink? ( dinode -- true:false ) di_mode w@ ifmt and iflnk = ; 353 354 355 356\ 357\ Hunt for directory entry: 358\ 359\ repeat 360\ load a buffer 361\ while entries do 362\ if entry == name return 363\ next entry 364\ until no buffers 365\ 366 367: search-directory ( str len -- ino|0 ) 368 0 to cur-offset 369 begin cur-offset cur-inode di_size x@ < while ( str len ) 370 sb-buf buf-read-file ( str len len buf ) 371 over 0= if ." search-directory: buf-read-file zero len" cr abort then 372 swap dup cur-offset + to cur-offset ( str len buf len ) 373 2dup + nip ( str len buf bufend ) 374 swap 2swap rot ( bufend str len buf ) 375 begin dup 4 pick < while ( bufend str len buf ) 376 dup d_ino l@ 0<> if ( bufend str len buf ) 377 boot-debug? if dup dup d_name swap d_namlen c@ type cr then 378 2dup d_namlen c@ = if ( bufend str len buf ) 379 dup d_name 2over ( bufend str len buf dname str len ) 380 comp 0= if ( bufend str len buf ) 381 \ Found it -- return inode 382 d_ino l@ nip nip nip ( dino ) 383 boot-debug? if ." Found it" cr then 384 exit ( dino ) 385 then 386 then ( bufend str len buf ) 387 then ( bufend str len buf ) 388 dup d_reclen w@ + ( bufend str len nextbuf ) 389 repeat 390 drop rot drop ( str len ) 391 repeat 392 2drop 2drop 0 ( 0 ) 393; 394 395: ffs_oldcompat ( -- ) 396\ Make sure old ffs values in sb-buf are sane 397 sb-buf fs_npsect dup l@ sb-buf fs_nsect l@ max swap l! 398 sb-buf fs_interleave dup l@ 1 max swap l! 399 sb-buf fs_postblformat l@ fs_42postblfmt = if 400 8 sb-buf fs_nrpos l! 401 then 402 sb-buf fs_inodefmt l@ fs_44inodefmt < if 403 sb-buf fs_bsize l@ 404 dup ndaddr * 1- sb-buf fs_maxfilesize x! 405 niaddr 0 ?do 406 sb-buf fs_nindir l@ * dup ( sizebp sizebp -- ) 407 sb-buf fs_maxfilesize dup x@ ( sizebp sizebp *fs_maxfilesize fs_maxfilesize -- ) 408 rot ( sizebp *fs_maxfilesize fs_maxfilesize sizebp -- ) 409 + ( sizebp *fs_maxfilesize new_fs_maxfilesize -- ) swap x! ( sizebp -- ) 410 loop drop ( -- ) 411 sb-buf dup fs_bmask l@ not swap fs_qbmask x! 412 sb-buf dup fs_fmask l@ not swap fs_qfmask x! 413 then 414; 415 416: read-super ( sector -- ) 4170 " seek" boot-ihandle $call-method 418 -1 = if 419 ." Seek failed" cr 420 abort 421 then 422 sb-buf sbsize " read" boot-ihandle $call-method 423 dup sbsize <> if 424 ." Read of superblock failed" cr 425 ." requested" space sbsize . 426 ." actual" space . cr 427 abort 428 else 429 drop 430 then 431; 432 433: ufs-open ( bootpath,len -- ) 434 boot-ihandle -1 = if 435 over cif-open dup 0= if ( boot-path len ihandle? ) 436 ." Could not open device" space type cr 437 abort 438 then ( boot-path len ihandle ) 439 to boot-ihandle \ Save ihandle to boot device 440 then 2drop 441 sboff read-super 442 sb-buf fs_magic l@ fs_magic_value <> if 443 64 dup to raid-offset 444 dev_bsize * sboff + read-super 445 sb-buf fs_magic l@ fs_magic_value <> if 446 ." Invalid superblock magic" cr 447 abort 448 then 449 then 450 sb-buf fs_bsize l@ dup maxbsize > if 451 ." Superblock bsize" space . ." too large" cr 452 abort 453 then 454 fs_SIZEOF < if 455 ." Superblock bsize < size of superblock" cr 456 abort 457 then 458 ffs_oldcompat 459 boot-debug? if ." ufs-open complete" cr then 460; 461 462: ufs-close ( -- ) 463 boot-ihandle dup -1 <> if 464 cif-close -1 to boot-ihandle 465 then 466; 467 468: boot-path ( -- boot-path ) 469 " bootpath" chosen-phandle get-package-property if 470 ." Could not find bootpath in /chosen" cr 471 abort 472 else 473 decode-string 2swap 2drop 474 then 475; 476 477: boot-args ( -- boot-args ) 478 " bootargs" chosen-phandle get-package-property if 479 ." Could not find bootargs in /chosen" cr 480 abort 481 else 482 decode-string 2swap 2drop 483 then 484; 485 4862000 buffer: boot-path-str 4872000 buffer: boot-path-tmp 488 489: split-path ( path len -- right len left len ) 490\ Split a string at the `/' 491 begin 492 dup -rot ( oldlen right len left ) 493 ascii / left-parse-string ( oldlen right len left len ) 494 dup 0<> if 4 roll drop exit then 495 2drop ( oldlen right len ) 496 rot over = ( right len diff ) 497 until 498; 499 500: find-file ( load-file len -- ) 501 rootino dup sb-buf read-inode ( load-file len -- load-file len ino ) 502 -rot ( load-file len ino -- pino load-file len ) 503 \ 504 \ For each path component 505 \ 506 begin split-path dup 0<> while ( pino right len left len -- ) 507 cur-inode is-dir? not if ." Inode not directory" cr abort then 508 boot-debug? if ." Looking for" space 2dup type space ." in directory..." cr then 509 search-directory ( pino right len left len -- pino right len ino|false ) 510 dup 0= if ." Bad path" cr abort then ( pino right len cino ) 511 sb-buf read-inode ( pino right len ) 512 cur-inode is-symlink? if \ Symlink -- follow the damn thing 513 \ Save path in boot-path-tmp 514 boot-path-tmp strmov ( pino new-right len ) 515 516 \ Now deal with symlink 517 cur-inode di_size x@ ( pino right len linklen ) 518 dup sb-buf fs_maxsymlinklen l@ ( pino right len linklen linklen maxlinklen ) 519 < if \ Now join the link to the path 520 cur-inode di_shortlink l@ ( pino right len linklen linkp ) 521 swap boot-path-str strmov ( pino right len new-linkp linklen ) 522 else \ Read file for symlink -- Ugh 523 \ Read link into boot-path-str 524 boot-path-str dup sb-buf fs_bsize l@ 525 0 block-map ( pino right len linklen boot-path-str bsize blockno ) 526 strategy drop swap ( pino right len boot-path-str linklen ) 527 then ( pino right len linkp linklen ) 528 \ Concatenate the two paths 529 strcat ( pino new-right newlen ) 530 swap dup c@ ascii / = if \ go to root inode? 531 rot drop rootino -rot ( rino len right ) 532 then 533 rot dup sb-buf read-inode ( len right pino ) 534 -rot swap ( pino right len ) 535 then ( pino right len ) 536 repeat 537 2drop drop 538; 539 540: read-file ( size addr -- ) 541 \ Read x bytes from a file to buffer 542 begin over 0> while 543 cur-offset cur-inode di_size x@ > if ." read-file EOF exceeded" cr abort then 544 sb-buf buf-read-file ( size addr len buf ) 545 over 2over drop swap ( size addr len buf addr len ) 546 move ( size addr len ) 547 dup cur-offset + to cur-offset ( size len newaddr ) 548 tuck + ( size len newaddr ) 549 -rot - swap ( newaddr newsize ) 550 repeat 551 2drop 552; 553 554h# 5000 constant loader-base 555 556\ 557\ Elf support -- find the load addr 558\ 559 560: is-elf? ( hdr -- res? ) h# 7f454c46 = ; 561 562\ 563\ Finally we finish it all off 564\ 565 566: load-file-signon ( load-file len boot-path len -- load-file len boot-path len ) 567 ." Loading file" space 2over type cr ." from device" space 2dup type cr 568; 569 570: load-file-print-size ( size -- size ) 571 ." Loading" space dup . space ." bytes of file..." cr 572; 573 574: load-file ( load-file len boot-path len -- load-base ) 575 boot-debug? if load-file-signon then 576 the-file file_SIZEOF 0 fill \ Clear out file structure 577 ufs-open ( load-file len ) 578 find-file ( ) 579 580 \ 581 \ Now we've found the file we should read it in in one big hunk 582 \ 583 584 cur-inode di_size x@ ( file-len ) 585 dup " to file-size" evaluate ( file-len ) 586 boot-debug? if load-file-print-size then 587 0 to cur-offset 588 loader-base ( buf-len addr ) 589 2dup read-file ( buf-len addr ) 590 ufs-close ( buf-len addr ) 591 dup is-elf? if ." load-file: not an elf executable" cr abort then 592 593 \ Luckily the prom should be able to handle ELF executables by itself 594 595 nip ( addr ) 596; 597 598: do-boot ( bootfile -- ) 599 ." OpenBSD IEEE 1275 Bootblock" cr 600 boot-path load-file ( -- load-base ) 601 dup 0<> if " to load-base init-program" evaluate then 602; 603 604 605boot-args ascii V strchr 0<> swap drop if 606 true to boot-debug? 607then 608 609boot-args ascii D strchr 0= swap drop if 610 " /ofwboot" do-boot 611then exit 612 613 614