Skip to content

Conversation

@ZeyadYasser
Copy link
Contributor

This draft PR allows snapd to inject XKB configs as a kernel cmdline argument to be consumed by plymouth-set-keymap.service from #16269 early in boot to set the correct keyboard layout configuration for plymouth.

This is only a draft to discuss and converge on the approach, afterwards it will be closed and split into several tested PRs.

I went with a generic approach for "extra snapd kcmdline args" that we might utilize in the future when we need to inject similar configurations for early boot in FDE systems.

@ZeyadYasser ZeyadYasser added Needs Samuele review Needs a review from Samuele before it can land FDE Marathon labels Dec 3, 2025
@github-actions github-actions bot added the Run only one system Only runs spread tests on one system label Dec 3, 2025
Signed-off-by: Zeyad Gouda <zeyad.gouda@canonical.com>
@ZeyadYasser ZeyadYasser force-pushed the extra-snapd-kcmdline-args-spike branch from 7fc6274 to 77fbd44 Compare December 3, 2025 20:18
@github-actions
Copy link

github-actions bot commented Dec 3, 2025

Wed Dec 3 20:29:54 UTC 2025
The following results are from: https://github.com/canonical/snapd/actions/runs/19907553269

No spread failures reported


config := &XKBConfig{
Layouts: strings.Split(vals["X11Layout"], ","),
Models: strings.Split(vals["X11Model"], ","),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The xkb configuration file only supports specifying one model. It might be a good idea to change the datatype from []string to just string

)

type XKBConfig struct {
Models []string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration file can only specify one model so we may want to rename this to Model and change the datatype to just string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would likely mean that the Model field needs to be parsed separately from the map used by the other fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I do not think we can have multiple models.

}

func CurrentXKBConfig() (*XKBConfig, error) {
conn, err := dbusutil.SystemBus()
Copy link
Contributor

@BAMF0 BAMF0 Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this function verify the configuration as well? AFAIK, the default model and layout if not specified are pc105 and us, but if Variants is not empty we expect the same number of elements in Layouts. I would say that verification is unnecessary however, if there are existing assertions that prevent an invalid state.

config := &XKBConfig{
Layouts: strings.Split(vals["X11Layout"], ","),
Models: strings.Split(vals["X11Model"], ","),
Variants: strings.Split(vals["X11Variant"], ","),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check that Variants and Layouts have the same lentgth and warn if not.

return nil, err
}

xkbConfigFile := filepath.Join(dirs.GlobalRootDir, "/etc/default/keyboard")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does inotify_add_watch follow symbolic link? If not we should also watch /etc/vconsole.conf. I expect at some point /etc/default/keyboard becomes the symlink as a backward compliant file.


simplified := fmt.Sprintf("%s,%s,%s,%s", layout, model, variant, opts)

updated, err := setExtraSnapdKernelCommandLineArg(m.state, extraSnapdKernelCmdlineArgXKB, simplified)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not cache here the value? I suppose we can get lots of notifications, and not all will modify the keyboard configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the config didn't change, setExtraSnapdKernelCommandLineArg is a no-op.

@pedronis
Copy link
Collaborator

pedronis commented Dec 9, 2025

@ZeyadYasser there's a conflict here now

Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some questions

taskParam string) (string, error) {

var value string
err := tsk.Get(taskParam, &value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this code used only in tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper was unchanged, only moved to a new file and renamed. I looked in the code base, This is used when setting cmdline-append config:

t := st.NewTask("update-gadget-cmdline",
"Update kernel command line from system configuration")
// Pass options to the task (changes in the options are not
// committed yet so the task cannot simply get them from the
// configuration)
for _, arg := range args {
t.Set(arg.name, arg.cmdline)
}

t := st.NewTask("update-managed-boot-config", summary)
t.Set("no-restart", true)

chg := st.NewChange("set-extra-snapd-kcmdline-arg", summary)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if this kind of changes happens between a applyCmdlineChangeKind (see configcore/kernel.go) and the point that code really commits the change to the config, will we use the additions? do we have conflict logic atm moment that prevent that one and the current handlers changes interfering? but that will not cover this case and we can't use conflicts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the first question, given that the additions are added to the state (with the state lock held and later released) shouldn't the additions always be taken into consideration when any of the kernel cmdline update tasks run regardless of the parent change? and since we create a change anyway, it shouldn't matter if some ongoing change that updates the kcmdline missed it.

For the second part regarding conflicts, you are right we can't use conflicts, instead we should block them from running in parallel using AddBlocked because during reseals the state might actually be unlocked. The blocking mechanism here might actually even be more important than fde/kcmdline blocking we were discussing before.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider that the configcore code is using a Change so I'm quite sure it needs to release the lock, so we are not keeping the lock that's why I'm worried about this clashing with such a change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought deeper about this, conflicts will minimize the window where this could happen but not completely fix it, which indicates that this is a bug in snapd not specific to xkb arg injection (e.g. imagine a gadget refresh change runs right after the core config change finished but before the config transaction was committed).

One thing we can do, is extend the conflict detection CheckUpdateKernelCommandLineConflict to fail if there is some hook running (which should cover the core configs) using HookManager.NumRunningHooks, I wonder if there is a way to only fail for core config "hijacked" hook to avoid conflict detection failing for irrelevant cases.

This should fix the conflict detection logic for snapd and gadget refresh changes.

For xkb configs, it can simply wait until the conflict detection doesn't return an error because the listener runs in it own go routine, or to make things simpler it can just fail and the new values will be picked up in the next ensure loop.

If I understand correctly, this is only a problem if some other kcmdline changing change is running after core config change is triggered were the config transaction wouldn't have been committed yet but running before is fine because of the conflict detection in place, no?

Copy link
Contributor Author

@ZeyadYasser ZeyadYasser Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a better idea, we could do blocking/conflict detection on the core configure-hook run-hook task. We could add some helper to hookstate to check if a given task is a core configure hook, we can even add extra logic to detect if is a core configure hook affecting specific parameters since we only care about cmdline-append and dangerous-cmdline-append. To make it generic it could be parameterized by the hookName and snapName.

This works because it the config transaction should have been committed if the parent run-hook task finishes.

This sounds like an existing bug that was only highlighted by this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A much simpler approach to achieve the same outcome is to inject the parent run-hook task-id into applyCmdlineChangeKind change's state, and update the conflict detection to detect that and fail if the parent run-hook task is not ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FDE Marathon Needs Samuele review Needs a review from Samuele before it can land Run only one system Only runs spread tests on one system

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants