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