1\ 2\ This file and its contents are supplied under the terms of the 3\ Common Development and Distribution License ("CDDL"), version 1.0. 4\ You may only use this file in accordance with the terms of version 5\ 1.0 of the CDDL. 6\ 7\ A full copy of the text of the CDDL should have accompanied this 8\ source. A copy of the CDDL is also available via the Internet at 9\ http://www.illumos.org/license/CDDL. 10 11\ Copyright 2017 Toomas Soome <tsoome@me.com> 12\ Copyright 2019 OmniOS Community Edition (OmniOSce) Association. 13\ Copyright 2019 Joyent, Inc. 14 15\ This module is implementing the beadm user command to support listing 16\ and switching Boot Environments (BE) from command line and 17\ support words to provide data for BE menu in loader menu system. 18\ Note: this module needs an update to provide proper BE vocabulary. 19 20only forth also support-functions also file-processing 21also file-processing definitions also parser 22also line-reading definitions also builtins definitions 23 24variable page_count 25variable page_remainder 260 page_count ! 270 page_remainder ! 28 29\ from menu.4th 30: +c! ( N C-ADDR/U K -- C-ADDR/U ) 31 3 pick 3 pick ( n c-addr/u k -- n c-addr/u k n c-addr ) 32 rot + c! ( n c-addr/u k n c-addr -- n c-addr/u ) 33 rot drop ( n c-addr/u -- c-addr/u ) 34; 35 36: get_value ( -- ) 37 eat_space 38 line_pointer 39 skip_to_end_of_line 40 line_pointer over - 41 strdup value_buffer strset 42 ['] exit to parsing_function 43; 44 45: get_name ( -- ) 46 read_name 47 ['] get_value to parsing_function 48; 49 50: get_name_value 51 line_buffer strget + to end_of_line 52 line_buffer .addr @ to line_pointer 53 ['] get_name to parsing_function 54 begin 55 end_of_line? 0= 56 while 57 parsing_function execute 58 repeat 59; 60 61\ beadm support 62: beadm_longest_title ( addr len -- width ) 63 0 to end_of_file? 64 O_RDONLY fopen fd ! 65 reset_line_reading 66 fd @ -1 = if EOPEN throw then 67 0 >r \ length into return stack 68 begin 69 end_of_file? 0= 70 while 71 free_buffers 72 read_line 73 get_name_value 74 value_buffer .len @ r@ > if r> drop value_buffer .len @ >r then 75 free_buffers 76 read_line 77 repeat 78 fd @ fclose 79 r> 1 + \ space between columns 80; 81 82\ Pretty print BE list 83: beadm_list ( width addr len -- ) 84 0 to end_of_file? 85 O_RDONLY fopen fd ! 86 reset_line_reading 87 fd @ -1 = if EOPEN throw then 88 ." BE" dup 2 - spaces ." Type Device" cr 89 begin 90 end_of_file? 0= 91 while 92 free_buffers 93 read_line 94 get_name_value 95 value_buffer strget type 96 dup value_buffer .len @ - spaces 97 free_buffers 98 read_line 99 get_name_value 100 name_buffer strget type 101 name_buffer strget s" bootfs" compare 0= if 2 spaces then 102 name_buffer strget s" chain" compare 0= if 3 spaces then 103 value_buffer strget type cr 104 free_buffers 105 repeat 106 fd @ fclose 107 drop 108; 109 110\ we are called with strings be_name menu_file, to simplify the stack 111\ management, we open the menu and free the menu_file. 112: beadm_bootfs ( be_addr be_len maddr mlen -- addr len taddr tlen flag | flag ) 113 0 to end_of_file? 114 2dup O_RDONLY fopen fd ! 115 drop free-memory 116 fd @ -1 = if EOPEN throw then 117 reset_line_reading 118 begin 119 end_of_file? 0= 120 while 121 free_buffers 122 read_line 123 get_name_value 124 2dup value_buffer strget compare 125 0= if ( title == be ) 126 2drop \ drop be_name 127 free_buffers 128 read_line 129 get_name_value 130 value_buffer strget strdup 131 name_buffer strget strdup -1 132 free_buffers 133 1 to end_of_file? \ mark end of file to skip the rest 134 else 135 read_line \ skip over next line 136 then 137 repeat 138 fd @ fclose 139 line_buffer strfree 140 read_buffer strfree 141 dup -1 > if ( be_addr be_len ) 142 2drop 143 0 144 then 145; 146 147: current-dev ( -- addr len ) \ return current dev 148 s" currdev" getenv 149 2dup [char] / strchr nip 150 dup 0> if ( strchr '/' != NULL ) - else drop then 151 \ we have now zfs:pool or diskname: 152; 153 154\ chop trailing ':' 155: colon- ( addr len -- addr len - 1 | addr len ) 156 2dup 1 - + C@ [char] : = if ( string[len-1] == ':' ) 1 - then 157; 158 159\ add trailing ':' 160: colon+ ( addr len -- addr len+1 ) 161 2dup + \ addr len -- addr+len 162 [char] : swap c! \ save ':' at the end of the string 163 1+ \ addr len -- addr len+1 164; 165 166\ make menu.lst path 167: menu.lst ( addr len -- addr' len' ) 168 colon- 169 \ need to allocate space for len + 16 170 dup 16 + allocate if ENOMEM throw then 171 swap 2dup 2>R \ copy of new addr len to return stack 172 move 2R> 173 s" :/boot/menu.lst" strcat 174; 175 176\ list be's on device 177: list-dev ( addr len -- ) 178 menu.lst 2dup 2>R 179 beadm_longest_title 180 line_buffer strfree 181 read_buffer strfree 182 R@ swap 2R> \ addr width addr len 183 beadm_list free-memory 184 ." Current boot device: " s" currdev" getenv type cr 185 line_buffer strfree 186 read_buffer strfree 187; 188 189\ activate be on device. 190\ if be name was not given, set currdev 191\ otherwize, we query device:/boot/menu.lst for bootfs and 192\ if found, and bootfs type is chain, attempt chainload. 193\ set currdev to bootfs. 194\ if we were able to set currdev, reload the config 195 196: activate-dev ( dev.addr dev.len be.addr be.len -- ) 197 198 dup 0= if 199 2drop 200 colon- \ remove : at the end of the dev name 201 dup 1+ allocate if ENOMEM throw then 202 dup 2swap 0 -rot strcat 203 colon+ 204 s" currdev" setenv \ setenv currdev = device 205 free-memory 206 else 207 2swap menu.lst 208 beadm_bootfs if ( addr len taddr tlen ) 209 2dup s" chain" compare 0= if 210 drop free-memory \ free type 211 2dup 212 dup 6 + allocate if ENOMEM throw then 213 dup >R 214 0 s" chain " strcat 215 2swap strcat ['] evaluate catch drop 216 \ We are still there? 217 R> free-memory \ free chain command 218 drop free-memory \ free addr 219 exit 220 then 221 drop free-memory \ free type 222 \ check last char in the name 223 2dup + c@ [char] : <> if 224 \ have dataset and need to get zfs:pool/ROOT/be: 225 dup 5 + allocate if ENOMEM throw then 226 0 s" zfs:" strcat 227 2swap strcat 228 colon+ 229 then 230 2dup s" currdev" setenv 231 drop free-memory 232 else 233 ." No such BE in menu.lst or menu.lst is missing." cr 234 exit 235 then 236 then 237 238 \ reset BE menu 239 0 page_count ! 240 \ need to do: 241 0 unload drop 242 free-module-options 243 \ unset the env variables with kernel arguments 244 s" acpi-user-options" unsetenv 245 s" boot-args" unsetenv 246 s" boot_ask" unsetenv 247 s" boot_single" unsetenv 248 s" boot_verbose" unsetenv 249 s" boot_kmdb" unsetenv 250 s" boot_drop_into_kmdb" unsetenv 251 s" boot_reconfigure" unsetenv 252 start \ load config, kernel and modules 253 ." Current boot device: " s" currdev" getenv type cr 254; 255 256\ beadm list [device] 257\ beadm activate BE [device] | device 258\ 259\ lists BE's from current or specified device /boot/menu.lst file 260\ activates specified BE by unloading modules, setting currdev and 261\ running start to load configuration. 262: beadm ( -- ) ( throws: abort ) 263 0= if ( interpreted ) get_arguments then 264 265 dup 0= if 266 ." Usage:" cr 267 ." beadm activate {beName [device] | device}" cr 268 ." beadm list [device]" cr 269 ." Use lsdev to get device names." cr 270 drop exit 271 then 272 \ First argument is 0 when we're interprated. See support.4th 273 \ for get_arguments reading the rest of the line and parsing it 274 \ stack: argN lenN ... arg1 len1 N 275 \ rotate arg1 len1, dont use argv[] as we want to get arg1 out of stack 276 -rot 2dup 277 278 s" list" compare-insensitive 0= if ( list ) 279 2drop 280 argc 1 = if ( list currdev ) 281 \ add dev to list of args and switch to case 2 282 current-dev rot 1 + 283 then 284 2 = if ( list device ) list-dev exit then 285 ." too many arguments" cr abort 286 then 287 s" activate" compare-insensitive 0= if ( activate ) 288 argc 1 = if ( missing be ) 289 drop ." missing bName" cr abort 290 then 291 argc 2 = if ( activate be ) 292 \ need to set arg list into proper order 293 1+ >R \ save argc+1 to return stack 294 295 \ if the prefix is fd, cd, net or disk and we have : 296 \ in the name, it is device and inject empty be name 297 over 2 s" fd" compare 0= >R 298 over 2 s" cd" compare 0= R> or >R 299 over 3 s" net" compare 0= R> or >R 300 over 4 s" disk" compare 0= R> or 301 if ( prefix is fd or cd or net or disk ) 302 2dup [char] : strchr nip 303 if ( its : in name ) 304 true 305 else 306 false 307 then 308 else 309 false 310 then 311 312 if ( it is device name ) 313 0 0 R> 314 else 315 \ add device, swap with be and receive argc 316 current-dev 2swap R> 317 then 318 then 319 3 = if ( activate be device ) activate-dev exit then 320 ." too many arguments" cr abort 321 then 322 ." Unknown argument" cr abort 323; 324 325also forth definitions also builtins 326 327\ make beadm available as user command. 328builtin: beadm 329 330\ count the pages of BE list 331\ leave FALSE in stack in case of error 332: be-pages ( -- flag ) 333 1 local flag 334 0 0 2local currdev 335 0 0 2local title 336 end-locals 337 338 current-dev menu.lst 2dup 2>R 339 0 to end_of_file? 340 O_RDONLY fopen fd ! 341 2R> drop free-memory 342 reset_line_reading 343 fd @ -1 = if FALSE else 344 s" currdev" getenv 345 over ( addr len addr ) 346 4 s" zfs:" compare 0= if 347 5 - \ len -= 5 348 swap 4 + \ addr += 4 349 swap to currdev 350 then 351 352 0 353 begin 354 end_of_file? 0= 355 while 356 read_line 357 get_name_value 358 s" title" name_buffer strget compare 359 0= if 1+ then 360 361 flag if \ check for title 362 value_buffer strget strdup to title free_buffers 363 read_line \ get bootfs 364 get_name_value 365 value_buffer strget currdev compare 0= if 366 title s" zfs_be_active" setenv 367 0 to flag 368 then 369 title drop free-memory 0 0 to title 370 free_buffers 371 else 372 free_buffers 373 read_line \ get bootfs 374 then 375 repeat 376 fd @ fclose 377 line_buffer strfree 378 read_buffer strfree 379 5 /mod swap dup page_remainder ! \ save remainder 380 if 1+ then 381 dup page_count ! \ save count 382 n2s s" zfs_be_pages" setenv 383 TRUE 384 then 385; 386 387: be-set-page { | entry count n device -- } 388 page_count @ 0= if 389 be-pages 390 page_count @ 0= if exit then 391 then 392 393 0 to device 394 1 s" zfs_be_currpage" getenvn 395 5 * 396 page_count @ 5 * 397 page_remainder @ if 398 5 page_remainder @ - - 399 then 400 swap - 401 dup to entry 402 0 < if 403 entry 5 + to count 404 0 to entry 405 else 406 5 to count 407 then 408 current-dev menu.lst 2dup 2>R 409 0 to end_of_file? 410 O_RDONLY fopen fd ! 411 2R> drop free-memory 412 reset_line_reading 413 fd @ -1 = if EOPEN throw then 414 0 to n 415 begin 416 end_of_file? 0= 417 while 418 n entry < if 419 read_line \ skip title 420 read_line \ skip bootfs 421 n 1+ to n 422 else 423 \ Use reverse loop to display descending order 424 \ for BE list. 425 0 count 1- do 426 read_line \ read title line 427 get_name_value 428 value_buffer strget 429 52 i + \ ascii 4 + i 430 s" bootenvmenu_caption[4]" 20 +c! setenv 431 value_buffer strget 432 52 i + \ ascii 4 + i 433 s" bootenvansi_caption[4]" 20 +c! setenv 434 435 free_buffers 436 read_line \ read value line 437 get_name_value 438 439 \ set menu entry command 440 name_buffer strget s" chain" compare 441 0= if 442 s" set_be_chain" 443 else 444 s" set_bootenv" 445 then 446 52 i + \ ascii 4 + i 447 s" bootenvmenu_command[4]" 20 +c! setenv 448 449 \ set device name 450 name_buffer strget s" chain" compare 451 0= if 452 \ for chain, use the value as is 453 value_buffer strget 454 else 455 \ check last char in the name 456 value_buffer strget 2dup + c@ 457 [char] : <> if 458 \ make zfs device name 459 swap drop 460 5 + allocate if 461 ENOMEM throw 462 then 463 s" zfs:" ( addr addr' len' ) 464 2 pick swap move ( addr ) 465 dup to device 466 4 value_buffer strget 467 strcat ( addr len ) 468 s" :" strcat 469 then 470 then 471 472 52 i + \ ascii 4 + i 473 s" bootenv_root[4]" 13 +c! setenv 474 device free-memory 0 to device 475 free_buffers 476 -1 477 +loop 478 479 5 count do \ unset unused entries 480 52 i + \ ascii 4 + i 481 dup s" bootenvmenu_caption[4]" 20 +c! unsetenv 482 dup s" bootenvansi_caption[4]" 20 +c! unsetenv 483 dup s" bootenvmenu_command[4]" 20 +c! unsetenv 484 s" bootenv_root[4]" 13 +c! unsetenv 485 loop 486 487 1 to end_of_file? \ we are done 488 then 489 repeat 490 fd @ fclose 491 line_buffer strfree 492 read_buffer strfree 493; 494