diff --git a/.github/contributors.yaml b/.github/contributors.yaml index ee3ba7e4a..6716b95a6 100644 --- a/.github/contributors.yaml +++ b/.github/contributors.yaml @@ -161,3 +161,6 @@ users: Inuth0603: name: Divyam Agarwal email: iitdivyamagarwal@gmail.com + indrajithek: + name: Indrajith + email: 29561593+indrajithek@users.noreply.github.com diff --git a/docs/tutorials/existing-container-linux.md b/docs/tutorials/existing-container-linux.md index 0dde898de..620dd9aea 100644 --- a/docs/tutorials/existing-container-linux.md +++ b/docs/tutorials/existing-container-linux.md @@ -77,7 +77,7 @@ designed specifically for `urunc`. It performs the following actions: the container in a Kubernetes cluster, where there is a high chance that the gateway of the container might be in a different subnet than the IP. As a result, the Linux kernel will fail to set the gateway. -2. Groups multi-word arguments correctly. +2. Reads the application command and its arguments from the configuration. 3. Mounts `/proc` and `/sys`. 4. Reads and sets the environment variables for the application execution 5. Reads and sets the uid,gid and working directory for the application execution @@ -95,6 +95,8 @@ UCS UID: GID: WD: +ARC: +ARV: /* one line per argument */ UCE ``` diff --git a/pkg/unikontainers/unikernels/linux.go b/pkg/unikontainers/unikernels/linux.go index 69fc17edc..35571bb82 100644 --- a/pkg/unikontainers/unikernels/linux.go +++ b/pkg/unikontainers/unikernels/linux.go @@ -42,6 +42,7 @@ const ( type Linux struct { App string Command string + Args []string // application command, verbatim, for urunit Monitor string Env []string Net LinuxNet @@ -123,7 +124,11 @@ func (l *Linux) CommandString() (string, error) { bootParams += " URUNIT_DEFROUTE=1" } if l.App != "" { - initParams := rdinit + "init=" + l.App + " -- " + l.Command + initParams := rdinit + "init=" + l.App + // urunit takes the application command from its configuration + if !l.InitrdConf { + initParams += " -- " + l.Command + } bootParams += " " + initParams } @@ -275,6 +280,7 @@ func (l *Linux) parseCmdLine(cmdLine []string) error { } l.App = normalizedArgs[0] + l.Args = cmdLine[1:] if len(normalizedArgs) > 1 { l.Command = strings.Join(normalizedArgs[1:], " ") } else { @@ -337,6 +343,14 @@ func (l *Linux) buildUrunitConfig() string { sb.WriteString("WD:") sb.WriteString(l.ProcConfig.WorkDir) sb.WriteString("\n") + sb.WriteString("ARC:") + sb.WriteString(strconv.Itoa(len(l.Args))) + sb.WriteString("\n") + for _, arg := range l.Args { + sb.WriteString("ARV:") + sb.WriteString(arg) + sb.WriteString("\n") + } sb.WriteString(lpcEndMarker) sb.WriteString("\n") sb.WriteString(blkStartMarker) diff --git a/pkg/unikontainers/unikernels/linux_test.go b/pkg/unikontainers/unikernels/linux_test.go new file mode 100644 index 000000000..0d92a5d4b --- /dev/null +++ b/pkg/unikontainers/unikernels/linux_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2023-2026, Nubificus LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unikernels + +import ( + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestLinuxUrunitArgs covers the arguments that the kernel command line +// mangles: an empty argument and a literal "--". +func TestLinuxUrunitArgs(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + cmdLine []string + expected string + }{ + { + name: "empty argument", + cmdLine: []string{"/urunit", "/app", "a", "", "b"}, + expected: "ARC:4\nARV:/app\nARV:a\nARV:\nARV:b\nUCE\n", + }, + { + name: "literal --", + cmdLine: []string{"/urunit", "/app", "a", "--", "b", "c"}, + expected: "ARC:5\nARV:/app\nARV:a\nARV:--\nARV:b\nARV:c\nUCE\n", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + l := &Linux{InitrdConf: true, RootFsType: "virtiofs"} + require.NoError(t, l.parseCmdLine(tc.cmdLine)) + assert.Contains(t, l.buildUrunitConfig(), tc.expected) + + cmd, err := l.CommandString() + require.NoError(t, err) + assert.True(t, strings.HasSuffix(cmd, " init=/urunit"), "unexpected command line: %s", cmd) + }) + } +} + +// TestLinuxUrunitLongArgs covers an argument list longer than the kernel +// command line (2048 bytes) with more than 32 arguments. +func TestLinuxUrunitLongArgs(t *testing.T) { + t.Parallel() + + cmdLine := []string{"/urunit", "/app"} + for i := range 40 { + cmdLine = append(cmdLine, strconv.Itoa(i)+strings.Repeat("x", 100)) + } + l := &Linux{InitrdConf: true, RootFsType: "virtiofs"} + require.NoError(t, l.parseCmdLine(cmdLine)) + conf := l.buildUrunitConfig() + + assert.Contains(t, conf, "\nARC:41\n") + assert.Equal(t, 41, strings.Count(conf, "\nARV:")) + assert.Contains(t, conf, "\nARV:"+cmdLine[41]+"\nUCE\n") + + cmd, err := l.CommandString() + require.NoError(t, err) + assert.True(t, strings.HasSuffix(cmd, " init=/urunit"), "unexpected command line: %s", cmd) +}