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 s" boot_noncluster" unsetenv 253 start \ load config, kernel and modules 254 ." Current boot device: " s" currdev" getenv type cr 255; 256 257\ beadm list [device] 258\ beadm activate BE [device] | device 259\ 260\ lists BE's from current or specified device /boot/menu.lst file 261\ activates specified BE by unloading modules, setting currdev and 262\ running start to load configuration. 263: beadm ( -- ) ( throws: abort ) 264 0= if ( interpreted ) get_arguments then 265 266 dup 0= if 267 ." Usage:" cr 268 ." beadm activate {beName [device] | device}" cr 269 ." beadm list [device]" cr 270 ." Use lsdev to get device names." cr 271 drop exit 272 then 273 \ First argument is 0 when we're interprated. See support.4th 274 \ for get_arguments reading the rest of the line and parsing it 275 \ stack: argN lenN ... arg1 len1 N 276 \ rotate arg1 len1, dont use argv[] as we want to get arg1 out of stack 277 -rot 2dup 278 279 s" list" compare-insensitive 0= if ( list ) 280 2drop 281 argc 1 = if ( list currdev ) 282 \ add dev to list of args and switch to case 2 283 current-dev rot 1 + 284 then 285 2 = if ( list device ) list-dev exit then 286 ." too many arguments" cr abort 287 then 288 s" activate" compare-insensitive 0= if ( activate ) 289 argc 1 = if ( missing be ) 290 drop ." missing bName" cr abort 291 then 292 argc 2 = if ( activate be ) 293 \ need to set arg list into proper order 294 1+ >R \ save argc+1 to return stack 295 296 \ if the prefix is fd, cd, net or disk and we have : 297 \ in the name, it is device and inject empty be name 298 over 2 s" fd" compare 0= >R 299 over 2 s" cd" compare 0= R> or >R 300 over 3 s" net" compare 0= R> or >R 301 over 4 s" disk" compare 0= R> or 302 if ( prefix is fd or cd or net or disk ) 303 2dup [char] : strchr nip 304 if ( its : in name ) 305 true 306 else 307 false 308 then 309 else 310 false 311 then 312 313 if ( it is device name ) 314 0 0 R> 315 else 316 \ add device, swap with be and receive argc 317 current-dev 2swap R> 318 then 319 then 320 3 = if ( activate be device ) activate-dev exit then 321 ." too many arguments" cr abort 322 then 323 ." Unknown argument" cr abort 324; 325 326also forth definitions also builtins 327 328\ make beadm available as user command. 329builtin: beadm 330 331\ count the pages of BE list 332\ leave FALSE in stack in case of error 333: be-pages ( -- flag ) 334 1 local flag 335 0 0 2local currdev 336 0 0 2local title 337 end-locals 338 339 current-dev menu.lst 2dup 2>R 340 0 to end_of_file? 341 O_RDONLY fopen fd ! 342 2R> drop free-memory 343 reset_line_reading 344 fd @ -1 = if FALSE else 345 s" currdev" getenv 346 over ( addr len addr ) 347 4 s" zfs:" compare 0= if 348 5 - \ len -= 5 349 swap 4 + \ addr += 4 350 swap to currdev 351 then 352 353 0 354 begin 355 end_of_file? 0= 356 while 357 read_line 358 get_name_value 359 s" title" name_buffer strget compare 360 0= if 1+ then 361 362 flag if \ check for title 363 value_buffer strget strdup to title free_buffers 364 read_line \ get bootfs 365 get_name_value 366 value_buffer strget currdev compare 0= if 367 title s" zfs_be_active" setenv 368 0 to flag 369 then 370 title drop free-memory 0 0 to title 371 free_buffers 372 else 373 free_buffers 374 read_line \ get bootfs 375 then 376 repeat 377 fd @ fclose 378 line_buffer strfree 379 read_buffer strfree 380 5 /mod swap dup page_remainder ! \ save remainder 381 if 1+ then 382 dup page_count ! \ save count 383 n2s s" zfs_be_pages" setenv 384 TRUE 385 then 386; 387 388: be-set-page { | entry count n device -- } 389 page_count @ 0= if 390 be-pages 391 page_count @ 0= if exit then 392 then 393 394 0 to device 395 1 s" zfs_be_currpage" getenvn 396 5 * 397 page_count @ 5 * 398 page_remainder @ if 399 5 page_remainder @ - - 400 then 401 swap - 402 dup to entry 403 0 < if 404 entry 5 + to count 405 0 to entry 406 else 407 5 to count 408 then 409 current-dev menu.lst 2dup 2>R 410 0 to end_of_file? 411 O_RDONLY fopen fd ! 412 2R> drop free-memory 413 reset_line_reading 414 fd @ -1 = if EOPEN throw then 415 0 to n 416 begin 417 end_of_file? 0= 418 while 419 n entry < if 420 read_line \ skip title 421 read_line \ skip bootfs 422 n 1+ to n 423 else 424 \ Use reverse loop to display descending order 425 \ for BE list. 426 0 count 1- do 427 read_line \ read title line 428 get_name_value 429 value_buffer strget 430 52 i + \ ascii 4 + i 431 s" bootenvmenu_caption[4]" 20 +c! setenv 432 value_buffer strget 433 52 i + \ ascii 4 + i 434 s" bootenvansi_caption[4]" 20 +c! setenv 435 436 free_buffers 437 read_line \ read value line 438 get_name_value 439 440 \ set menu entry command 441 name_buffer strget s" chain" compare 442 0= if 443 s" set_be_chain" 444 else 445 s" set_bootenv" 446 then 447 52 i + \ ascii 4 + i 448 s" bootenvmenu_command[4]" 20 +c! setenv 449 450 \ set device name 451 name_buffer strget s" chain" compare 452 0= if 453 \ for chain, use the value as is 454 value_buffer strget 455 else 456 \ check last char in the name 457 value_buffer strget 2dup + c@ 458 [char] : <> if 459 \ make zfs device name 460 swap drop 461 5 + allocate if 462 ENOMEM throw 463 then 464 s" zfs:" ( addr addr' len' ) 465 2 pick swap move ( addr ) 466 dup to device 467 4 value_buffer strget 468 strcat ( addr len ) 469 s" :" strcat 470 then 471 then 472 473 52 i + \ ascii 4 + i 474 s" bootenv_root[4]" 13 +c! setenv 475 device free-memory 0 to device 476 free_buffers 477 -1 478 +loop 479 480 5 count do \ unset unused entries 481 52 i + \ ascii 4 + i 482 dup s" bootenvmenu_caption[4]" 20 +c! unsetenv 483 dup s" bootenvansi_caption[4]" 20 +c! unsetenv 484 dup s" bootenvmenu_command[4]" 20 +c! unsetenv 485 s" bootenv_root[4]" 13 +c! unsetenv 486 loop 487 488 1 to end_of_file? \ we are done 489 then 490 repeat 491 fd @ fclose 492 line_buffer strfree 493 read_buffer strfree 494; 495