1 ()                                                                          ()
   2 
   3 
   4 
   5  This file and its contents are supplied under the terms of the  Common
   6 Development and Distribution License ("CDDL"), version 1.0.   You may only use
   7 this file in accordance with the terms of version  1.0 of the CDDL.   A full
   8 copy of the text of the CDDL should have accompanied this  source.  A copy of
   9 the CDDL is also available via the Internet at
  10 http://www.illumos.org/license/CDDL.
  11 
  12  Copyright 2017 Toomas Soome <tsoome@me.com>  Copyright   2019 OmniOS Community
  13 Edition (OmniOSce) Association.   Copyright 2019 Joyent, Inc.
  14 
  15  This module is implementing the beadm user command to support listing  and
  16 switching Boot Environments (BE) from command line and  support words to
  17 provide data for BE menu in loader menu system.   Note: this module needs an
  18 update to provide proper BE vocabulary.
  19 
  20 only forth also support-functions also file-processing also file-processing
  21 definitions also parser also line-reading definitions also builtins
  22 definitions
  23 
  24 variable page_count variable page_remainder 0 page_count !  0 page_remainder !
  25 
  26  from menu.4th : +c! ( N C-ADDR/U K -- C-ADDR/U )      3 pick 3 pick  ( n c-
  27 addr/u k -- n c-addr/u k n c-addr )     rot + c!  ( n c-addr/u k n c-addr -- n
  28 c-addr/u )     rot drop  ( n c-addr/u -- c-addr/u ) ;
  29 
  30 : get_value ( -- )  eat_space      line_pointer   skip_to_end_of_line
  31      line_pointer over -      strdup value_buffer strset    ['] exit to
  32 parsing_function ;
  33 
  34 : get_name ( -- )   read_name      ['] get_value to parsing_function ;
  35 
  36 : get_name_value    line_buffer strget + to end_of_line     line_buffer .addr
  37 @ to line_pointer   ['] get_name to parsing_function   begin
  38           end_of_line? 0=     while          parsing_function execute
  39      repeat ;
  40 
  41  beadm support : beadm_longest_title ( addr len -- width )  0 to end_of_file?
  42      O_RDONLY fopen fd !      reset_line_reading  fd @ -1 = if EOPEN throw
  43 then      0 >r            length into return stack     begin          end_of_file?
  44 0=   while          free_buffers        read_line           get_name_value
  45           value_buffer .len @ r@ > if r> drop value_buffer .len   @ >r then
  46           free_buffers        read_line      repeat    fd @ fclose    r> 1 +
  47            space between columns ;
  48 
  49  Pretty print BE list : beadm_list ( width addr len -- )    0 to end_of_file?
  50      O_RDONLY fopen fd !      reset_line_reading  fd @ -1 = if EOPEN throw
  51 then      ." BE" dup 2 - spaces ." Type    Device" cr  begin
  52           end_of_file? 0=     while          free_buffers        read_line
  53           get_name_value           value_buffer strget type           dup
  54 value_buffer .len @ - spaces       free_buffers        read_line
  55           get_name_value           name_buffer strget type       name_buffer
  56 strget s" bootfs" compare 0= if 2 spaces then          name_buffer strget s"
  57 chain" compare 0= if 3 spaces then           value_buffer strget type cr
  58           free_buffers   repeat    fd @ fclose    drop ;
  59 
  60 \[u00A0]we are called with strings be_name menu_file, to simplify the stack
  61 management, we open the menu and free the menu_file.  : beadm_bootfs ( be_addr
  62 be_len maddr mlen -- addr len taddr tlen flag | flag )      0 to end_of_file?
  63      2dup O_RDONLY fopen fd !      drop free-memory    fd @ -1 = if EOPEN
  64 throw then     reset_line_reading  begin          end_of_file? 0=     while
  65           free_buffers        read_line           get_name_value
  66           2dup value_buffer strget compare        0= if ( title == be )
  67                2drop           drop be_name            free_buffers
  68                read_line                get_name_value
  69                value_buffer strget strdup              name_buffer strget
  70 strdup -1                free_buffers             1 to end_of_file?  mark end
  71 of file to skip the rest           else                read_line  skip over
  72 next line           then      repeat    fd @ fclose    line_buffer strfree
  73      read_buffer strfree      dup -1 > if ( be_addr be_len )       2drop
  74           0    then ;
  75 
  76 : current-dev ( -- addr len )  return current dev      s" currdev" getenv
  77      2dup [char] / strchr nip      dup 0> if ( strchr '/' != NULL ) - else
  78 drop then       we have now zfs:pool or diskname: ;
  79 
  80  chop trailing ':' : colon- ( addr len -- addr len - 1 | addr len )   2dup 1 -
  81 + C@ [char] : = if ( string[len-1] == ':' ) 1 - then ;
  82 
  83  add trailing ':' : colon+ ( addr len -- addr len+1 )  2dup +
  84                 addr len -- addr+len    [char] : swap c!     save ':' at the
  85 end of the string   1+              addr len -- addr len+1 ;
  86 
  87  make menu.lst path : menu.lst ( addr len -- addr' len' )   colon-     need to
  88 allocate space for len + 16   dup 16 + allocate if ENOMEM throw then  swap
  89 2dup 2>R   copy      of new addr len to return stack   move 2R>  s"
  90 :/boot/menu.lst" strcat ;
  91 
  92  list be's on device : list-dev ( addr len -- )   menu.lst 2dup 2>R
  93      beadm_longest_title      line_buffer strfree      read_buffer strfree
  94      R@ swap 2R>     addr width      addr len     beadm_list free-memory   ."
  95 Current boot device: " s" currdev" getenv type cr      line_buffer strfree
  96      read_buffer strfree ;
  97 
  98  activate be on device.   if be name was not given, set currdev  otherwize, we
  99 query device:/boot/menu.lst for bootfs and  if found, and bootfs type is
 100 chain, attempt chainload.   set currdev to bootfs.   if we were able to set
 101 currdev, reload the config
 102 
 103 : activate-dev ( dev.addr dev.len be.addr be.len -- )
 104 
 105      dup 0= if           2drop          colon-               remove : at the
 106 end of the dev name           dup 1+ allocate if ENOMEM throw then         dup
 107 2swap 0 -rot strcat           colon+         s" currdev" setenv   setenv
 108 currdev = device         free-memory    else           2swap menu.lst
 109           beadm_bootfs if ( addr len taddr tlen )                2dup s"
 110 chain" compare 0= if                    drop free-memory     free type
 111                     2dup                     dup 6 + allocate if ENOMEM throw
 112 then                     dup >R                        0 s" chain " strcat
 113                     2swap strcat ['] evaluate catch drop                    We
 114 are still there?                   R> free-memory    free chain command
 115                     drop free-memory     free addr                    exit
 116                then                drop free-memory          free type
 117                 check last char in the name            2dup + c@ [char] : <>
 118 if                   have dataset and need to get zfs:pool/ROOT/be:
 119                     dup 5 + allocate if ENOMEM throw then                  0
 120 s" zfs:" strcat                    2swap strcat                  colon+
 121                then                2dup s" currdev" setenv            drop
 122 free-memory         else                ." No such BE in menu.lst or menu.lst
 123 is missing." cr               exit           then      then
 124 
 125       reset BE menu      0 page_count !       need to do:   0 unload drop
 126      free-module-options       unset the env variables with kernel arguments
 127      s" acpi-user-options" unsetenv     s" boot-args" unsetenv   s" boot_ask"
 128 unsetenv  s" boot_single" unsetenv      s" boot_verbose" unsetenv     s"
 129 boot_kmdb" unsetenv      s" boot_drop_into_kmdb" unsetenv   s"
 130 boot_reconfigure" unsetenv    start                load config, kernel and
 131 modules   ." Current boot device: " s" currdev" getenv type cr ;
 132 
 133  beadm list [device]  beadm activate BE [device] | device  lists BE's from
 134 current or specified device /boot/menu.lst file  activates specified BE by
 135 unloading modules, setting currdev and  running start to load configuration.
 136 : beadm ( -- ) ( throws: abort )   0= if ( interpreted ) get_arguments then
 137 
 138      dup 0= if           ." Usage:" cr       ." beadm activate {beName
 139 [device] | device}" cr        ." beadm list [device]" cr         ." Use lsdev
 140 to get device names." cr           drop exit      then       First argument is
 141 0 when we're interprated.  See support.4th    for get_arguments reading the
 142 rest of the line and parsing it     stack: argN lenN ... arg1 len1 N   rotate
 143 arg1 len1, dont use argv[] as we want to get arg1 out of stack   -rot 2dup
 144 
 145      s" list" compare-insensitive 0= if ( list )       2drop          argc 1 =
 146 if ( list currdev )                 add dev to list of args and switch to case
 147 2              current-dev rot 1 +           then           2 = if ( list
 148 device ) list-dev exit then        ." too many arguments" cr abort    then
 149      s" activate" compare-insensitive 0= if ( activate )         argc 1 = if (
 150 missing be )             drop ." missing bName" cr abort         then
 151           argc 2 = if ( activate be )              need to set arg list into
 152 proper order             1+ >R           save argc+1 to return stack
 153 
 154                 if the prefix is fd, cd, net or disk and we have :
 155                 in the name, it is device and inject empty be name
 156                over 2 s" fd" compare 0= >R          over 2 s" cd" compare
 157 0= R> or >R                over 3 s" net" compare 0= R> or >R
 158                over 4 s" disk" compare 0= R> or                  if ( prefix is fd
 159 or cd or net or disk )                  2dup [char] : strchr nip
 160                     if ( its : in name )                         true
 161                     else                          false
 162                     then                else                     false
 163                then
 164 
 165                if ( it is device name )                     0 0 R>
 166                else                      add device, swap with be and receive
 167 argc                     current-dev 2swap R>                    then
 168           then           3 = if ( activate be device ) activate-dev exit then
 169           ." too many arguments" cr abort    then      ." Unknown argument" cr
 170 abort ;
 171 
 172 also forth definitions also builtins
 173 
 174  make beadm available as user command.  builtin: beadm
 175 
 176  count the pages of BE list  leave FALSE in stack in case of error : be-pages
 177 ( -- flag )    1 local flag   0 0 2local currdev  0 0 2local title    end-
 178 locals
 179 
 180      current-dev menu.lst 2dup 2>R   0 to end_of_file?   O_RDONLY fopen fd
 181 !    2R> drop free-memory     reset_line_reading  fd @ -1 = if FALSE else
 182           s" currdev" getenv       over           ( addr len addr )        4
 183 s" zfs:" compare 0= if             5 -             len -= 5
 184                swap 4 +        addr += 4               swap to currdev
 185           then
 186 
 187           0         begin               end_of_file? 0=          while
 188                read_line                get_name_value                s"
 189 title" name_buffer strget compare            0= if 1+ then
 190 
 191                flag if         check for title                   value_buffer
 192 strget strdup to title free_buffers                    read_line       get
 193 bootfs                   get_name_value                     value_buffer
 194 strget currdev compare 0= if                      title s" zfs_be_active"
 195 setenv                        0 to flag                     then
 196                     title drop free-memory 0 0 to title
 197                     free_buffers             else
 198                     free_buffers                  read_line       get bootfs
 199                then           repeat         fd @ fclose         line_buffer
 200 strfree        read_buffer strfree           5 /mod swap dup page_remainder !
 201            save remainder          if 1+ then          dup page_count !
 202                      save count         n2s s" zfs_be_pages" setenv
 203           TRUE      then ;
 204 
 205 : be-set-page { | entry count n device -- }  page_count @ 0= if       be-pages
 206           page_count @ 0= if exit then  then
 207 
 208      0 to device    1 s" zfs_be_currpage" getenvn      5 *  page_count @ 5 *
 209      page_remainder @ if           5 page_remainder @ - -   then      swap -
 210      dup to entry   0 <      if         entry 5 + to count       0 to entry
 211      else           5 to count     then      current-dev menu.lst 2dup 2>R
 212      0 to end_of_file?   O_RDONLY fopen fd !      2R> drop free-memory
 213      reset_line_reading  fd @ -1 = if EOPEN throw then      0 to n    begin
 214           end_of_file? 0=     while          n entry < if
 215                read_line       skip title              read_line       skip
 216 bootfs              n 1+ to n           else                 Use reverse loop
 217 to display descending order              for BE list.                 0 count
 218 1- do                    read_line       read title line
 219                     get_name_value                     value_buffer strget
 220                     52 i +               ascii 4 + i                  s"
 221 bootenvmenu_caption[4]" 20 +c! setenv                  value_buffer strget
 222                     52 i +               ascii 4 + i                  s"
 223 bootenvansi_caption[4]" 20 +c! setenv
 224 
 225                     free_buffers                  read_line       read value
 226 line                     get_name_value
 227 
 228                      set menu entry command                 name_buffer strget
 229 s" chain" compare                  0= if                         s"
 230 set_be_chain"                 else                          s" set_bootenv"
 231                     then                     52 i +               ascii 4 + i
 232                     s" bootenvmenu_command[4]" 20 +c! setenv
 233 
 234                      set device name                   name_buffer strget s"
 235 chain" compare                     0= if                         \[u00A0]for
 236 chain, use the value as is                        value_buffer strget
 237                     else                           check last char in the name
 238                          value_buffer strget 2dup + c@
 239                          [char] : <> if                                  make
 240 zfs device name                              swap drop
 241                               5 + allocate if
 242                                    ENOMEM throw
 243                               then                               s" zfs:" (
 244 addr addr' len' )                            2 pick swap move ( addr )
 245                               dup to device                           4
 246 value_buffer strget                               strcat    ( addr len )
 247                               s" :" strcat                       then
 248                     then
 249 
 250                     52 i +               ascii 4 + i                  s"
 251 bootenv_root[4]" 13 +c! setenv                    device free-memory 0 to
 252 device                   free_buffers                  -1             +loop
 253 
 254                5 count do           unset unused entries                   52
 255 i +             ascii 4 + i                  dup s" bootenvmenu_caption[4]" 20
 256 +c! unsetenv                  dup s" bootenvansi_caption[4]" 20 +c! unsetenv
 257                     dup s" bootenvmenu_command[4]" 20 +c! unsetenv
 258                     s" bootenv_root[4]" 13 +c! unsetenv               loop
 259 
 260                1 to end_of_file?         we are done        then      repeat
 261      fd @ fclose    line_buffer strfree      read_buffer strfree ;
 262 
 263 
 264 
 265                                 August 28, 2019                             ()