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 20 only forth also support-functions also file-processing 21 also file-processing definitions also parser 22 also line-reading definitions also builtins definitions 23 24 variable page_count 25 variable page_remainder 26 0 page_count ! 27 0 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 325 also forth definitions also builtins 326 327 \ make beadm available as user command. 328 builtin: 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 ;