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 ;