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 ;