Skip to content

Commit 25d220e

Browse files
committed
cli: add 'show boot-order' and 'set boot-order a b c' commands
Fixes #1032 Signed-off-by: Joachim Wiberg <[email protected]>
1 parent 7348f5c commit 25d220e

File tree

5 files changed

+195
-0
lines changed

5 files changed

+195
-0
lines changed

doc/ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ All notable changes to the project are documented in this file.
3838
kernel default (*inherit*), which in turn fixes reported issues with dropped
3939
OSPF Hello frames in GRE tunnels
4040
- [Document][bpi-r3-emmc-documentation] how to go from SD card to eMMC on bpi-r3
41+
- Add CLI commands for managing boot partition order: `show boot-order` and
42+
`set boot-order` allow viewing and changing the boot order from the CLI,
43+
complementing the existing YANG RPC support, issue #1032
4144

4245
### Fixes
4346

doc/upgrade.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,79 @@ YANG support for upgrading Infix, inspecting and _modifying_ the
2323
boot-order, is defined in [infix-system-software][2].
2424

2525

26+
## Changing Boot Order
27+
28+
The boot order can be manually changed using the `set boot-order` command.
29+
This is useful for rolling back to a previous version or changing the
30+
preferred boot source.
31+
32+
The command accepts one to three boot targets as separate arguments, in the
33+
desired boot order. Valid boot targets are:
34+
35+
- `primary` - The primary partition
36+
- `secondary` - The secondary partition
37+
- `net` - Network boot (if supported by bootloader)
38+
39+
The CLI provides tab-completion for boot targets, making it easy to enter
40+
valid values.
41+
42+
Example: View current boot order and change it:
43+
44+
```
45+
admin@example:/> show boot-order
46+
primary secondary net
47+
admin@example:/> set boot-order secondary primary net
48+
admin@example:/> show boot-order
49+
secondary primary net
50+
admin@example:/>
51+
```
52+
53+
Example: Set boot order to only try primary partition:
54+
55+
```
56+
admin@example:/> show boot-order
57+
secondary primary net
58+
admin@example:/> set boot-order primary
59+
admin@example:/> show boot-order
60+
primary
61+
admin@example:/>
62+
```
63+
64+
Example: Using tab-completion (press TAB to see available options):
65+
66+
```
67+
admin@example:/> set boot-order <TAB>
68+
net primary secondary
69+
admin@example:/> set boot-order secondary <TAB>
70+
net primary secondary
71+
admin@example:/> set boot-order secondary primary
72+
admin@example:/> show boot-order
73+
secondary primary
74+
admin@example:/>
75+
```
76+
77+
The new boot order takes effect on the next reboot and can be verified
78+
with `show boot-order` or `show software`:
79+
80+
```
81+
admin@example:/> show software
82+
BOOT ORDER
83+
secondary primary
84+
85+
NAME STATE VERSION DATE
86+
primary booted v25.01.0 2025-04-25T10:15:00+00:00
87+
secondary inactive v25.01.0 2025-04-25T10:07:20+00:00
88+
admin@example:/>
89+
```
90+
91+
> [!NOTE]
92+
> The boot order is automatically updated when performing an upgrade.
93+
> The newly installed image will be set as the first boot target.
94+
>
95+
> Duplicate boot targets are not allowed. The CLI will reject attempts to
96+
> specify the same target multiple times.
97+
98+
2699
## Upgrading
27100

28101
Upgrading Infix is done one partition at a time. If the system has

src/klish-plugin-infix/src/infix.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,89 @@ int infix_shell(kcontext_t *ctx)
263263
return rc;
264264
}
265265

266+
static const char *boot_targets[] = {
267+
"primary",
268+
"secondary",
269+
"net",
270+
NULL
271+
};
272+
273+
int infix_boot_targets(kcontext_t *ctx)
274+
{
275+
size_t i;
276+
277+
(void)ctx;
278+
279+
for (i = 0; boot_targets[i]; i++)
280+
puts(boot_targets[i]);
281+
282+
return 0;
283+
}
284+
285+
static const char *valid_boot_target(const kparg_t *parg)
286+
{
287+
const char *target;
288+
size_t i;
289+
290+
if (!parg)
291+
return NULL;
292+
293+
target = kparg_value(parg);
294+
for (i = 0; boot_targets[i]; i++) {
295+
if (!strcmp(target, boot_targets[i]))
296+
return target;
297+
}
298+
299+
return NULL;
300+
}
301+
302+
int infix_set_boot_order(kcontext_t *ctx)
303+
{
304+
char tmpfile[] = "/tmp/boot-order-XXXXXX";
305+
kpargv_t *pargv = kcontext_pargv(ctx);
306+
const char *targets[3];
307+
int fd, rc = 0;
308+
FILE *fp;
309+
310+
targets[0] = valid_boot_target(kpargv_find(pargv, "first"));
311+
targets[1] = valid_boot_target(kpargv_find(pargv, "second"));
312+
targets[2] = valid_boot_target(kpargv_find(pargv, "third"));
313+
314+
if (!targets[0]) {
315+
fprintf(stderr, ERRMSG "missing boot target\n");
316+
return -1;
317+
}
318+
319+
fd = mkstemp(tmpfile);
320+
if (fd == -1)
321+
goto fail;
322+
323+
fp = fdopen(fd, "w");
324+
if (!fp) {
325+
close(fd);
326+
unlink(tmpfile);
327+
fail:
328+
fprintf(stderr, ERRMSG "failed creating temporary file\n");
329+
return -1;
330+
}
331+
332+
fputs("{\"infix-system:set-boot-order\":{\"boot-order\":[", fp);
333+
for (size_t i = 0; i < NELEMS(targets); i++) {
334+
if (!targets[i])
335+
continue;
336+
337+
fprintf(fp, "%s\"%s\"", i > 0 ? "," : "", targets[i]);
338+
}
339+
fputs("]}}", fp);
340+
341+
fclose(fp);
342+
343+
rc = systemf("sysrepocfg -R %s -fjson 2>&1", tmpfile);
344+
unlink(tmpfile);
345+
346+
return rc;
347+
}
348+
266349
int kplugin_infix_fini(kcontext_t *ctx)
267350
{
268351
(void)ctx;
@@ -274,6 +357,7 @@ int kplugin_infix_init(kcontext_t *ctx)
274357
{
275358
kplugin_t *plugin = kcontext_plugin(ctx);
276359

360+
kplugin_add_syms(plugin, ksym_new("boot_targets", infix_boot_targets));
277361
kplugin_add_syms(plugin, ksym_new("copy", infix_copy));
278362
kplugin_add_syms(plugin, ksym_new("datastore", infix_datastore));
279363
kplugin_add_syms(plugin, ksym_new("erase", infix_erase));
@@ -282,6 +366,7 @@ int kplugin_infix_init(kcontext_t *ctx)
282366
kplugin_add_syms(plugin, ksym_new("firewall_zones", infix_firewall_zones));
283367
kplugin_add_syms(plugin, ksym_new("firewall_policies", infix_firewall_policies));
284368
kplugin_add_syms(plugin, ksym_new("firewall_services", infix_firewall_services));
369+
kplugin_add_syms(plugin, ksym_new("set_boot_order", infix_set_boot_order));
285370
kplugin_add_syms(plugin, ksym_new("shell", infix_shell));
286371

287372
return 0;

src/klish-plugin-infix/xml/infix.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@
134134
<ACTION sym="STRING"/>
135135
</PTYPE>
136136

137+
<PTYPE name="BOOT_TARGET">
138+
<COMPL>
139+
<ACTION sym="boot_targets@infix"/>
140+
</COMPL>
141+
<ACTION sym="STRING"/>
142+
</PTYPE>
143+
137144
<VIEW name="main">
138145
<HOTKEY key="^D" cmd="exit"/>
139146

@@ -243,6 +250,13 @@
243250
<PARAM name="current-datetime" ptype="/STRING" help="yyyy-mm-ddThh:mm:ss(Z|+/-hh:mm)"/>
244251
<ACTION sym="srp_rpc@sysrepo">/ietf-system:set-current-datetime</ACTION>
245252
</COMMAND>
253+
254+
<COMMAND name="boot-order" help="Set boot partition order">
255+
<PARAM name="first" ptype="/BOOT_TARGET" help="First boot target"/>
256+
<PARAM name="second" ptype="/BOOT_TARGET" help="Second boot target" min="0"/>
257+
<PARAM name="third" ptype="/BOOT_TARGET" help="Third boot target" min="0"/>
258+
<ACTION sym="set_boot_order@infix"/>
259+
</COMMAND>
246260
</COMMAND>
247261

248262
<COMMAND name="dhcp-server" help="DHCP server tools" mode="switch">
@@ -353,6 +367,12 @@
353367
</ACTION>
354368
</COMMAND>
355369

370+
<COMMAND name="boot-order" help="Show boot partition order">
371+
<ACTION sym="script">
372+
show boot-order
373+
</ACTION>
374+
</COMMAND>
375+
356376
<COMMAND name="system" help="Show system overview">
357377
<ACTION sym="script" in="tty" out="tty" interrupt="true">
358378
show system |pager

src/show/show.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,19 @@ def software(args: List[str]) -> None:
142142
else:
143143
print("Too many arguments provided. Only one name is expected.")
144144

145+
def boot_order(args: List[str]) -> None:
146+
data = run_sysrepocfg("/ietf-system:system-state/infix-system:software")
147+
if not data:
148+
print("No software data retrieved.")
149+
return
150+
151+
try:
152+
boot_order_list = data.get("ietf-system:system-state", {}).get("infix-system:software", {}).get("boot-order", [])
153+
if boot_order_list:
154+
print(" ".join(boot_order_list))
155+
except (KeyError, TypeError):
156+
print("No boot order data available.")
157+
145158
def services(args: List[str]) -> None:
146159
data = run_sysrepocfg("/ietf-system:system-state/infix-system:services")
147160
if not data:
@@ -425,6 +438,7 @@ def human_readable(kib_val):
425438
def execute_command(command: str, args: List[str]):
426439
command_mapping = {
427440
'bfd': bfd,
441+
'boot-order': boot_order,
428442
'dhcp': dhcp,
429443
'hardware': hardware,
430444
'interface': interface,

0 commit comments

Comments
 (0)