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 ;