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