Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/run: Optimize 'enter' and 'run' in the non-fallback case #813

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 116 additions & 96 deletions src/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ var (
distro string
release string
}

runFallbackCommands = [][]string{{"/bin/bash", "-l"}}
runFallbackWorkDirs = []string{"" /* $HOME */}
)

var runCmd = &cobra.Command{
Expand Down Expand Up @@ -265,104 +268,101 @@ func runCommand(container string,

logrus.Debugf("Container %s is initialized", container)

if _, err := isCommandPresent(container, command[0]); err != nil {
if fallbackToBash {
fmt.Fprintf(os.Stderr,
"Error: command %s not found in container %s\n",
command[0],
container)
fmt.Fprintf(os.Stderr, "Using /bin/bash instead.\n")

command = []string{"/bin/bash", "-l"}
} else {
return fmt.Errorf("command %s not found in container %s", command[0], container)
}
if err := runCommandWithFallbacks(container, command, emitEscapeSequence, fallbackToBash); err != nil {
return err
}

if pathPresent, _ := isPathPresent(container, workingDirectory); !pathPresent {
fmt.Fprintf(os.Stderr, "Error: path %s not found in container %s\n",
workingDirectory, container)
fmt.Fprintf(os.Stderr, "Using %s instead.\n",
currentUser.HomeDir)

workingDirectory = currentUser.HomeDir
}
return nil
}

func runCommandWithFallbacks(container string, command []string, emitEscapeSequence, fallbackToBash bool) error {
logrus.Debug("Checking if 'podman exec' supports disabling the detach keys")

var detachKeys []string
var detachKeysSupported bool

if podman.CheckVersion("1.8.1") {
logrus.Debug("'podman exec' supports disabling the detach keys")
detachKeys = []string{"--detach-keys", ""}
detachKeysSupported = true
}

envOptions := utils.GetEnvOptionsForPreservedVariables()
logLevelString := podman.LogLevel.String()

execArgs := []string{
"--log-level", logLevelString,
"exec",
}

execArgs = append(execArgs, detachKeys...)

execArgs = append(execArgs, []string{
"--interactive",
"--tty",
"--user", currentUser.Username,
"--workdir", workingDirectory,
}...)

execArgs = append(execArgs, envOptions...)
runFallbackCommandsIndex := 0
runFallbackWorkDirsIndex := 0
workDir := workingDirectory

execArgs = append(execArgs, []string{
container,
"capsh", "--caps=", "--", "-c", "exec \"$@\"", "/bin/sh",
}...)

execArgs = append(execArgs, command...)
for {
execArgs := constructExecArgs(container, command, detachKeysSupported, envOptions, workDir)

if emitEscapeSequence {
fmt.Printf("\033]777;container;push;%s;toolbox;%s\033\\", container, currentUser.Uid)
}

logrus.Debugf("Running in container %s:", container)
logrus.Debug("podman")
for _, arg := range execArgs {
logrus.Debugf("%s", arg)
}
if emitEscapeSequence {
fmt.Printf("\033]777;container;push;%s;toolbox;%s\033\\", container, currentUser.Uid)
}

exitCode, err := shell.RunWithExitCode("podman", os.Stdin, os.Stdout, nil, execArgs...)
logrus.Debugf("Running in container %s:", container)
logrus.Debug("podman")
for _, arg := range execArgs {
logrus.Debugf("%s", arg)
}

if emitEscapeSequence {
fmt.Printf("\033]777;container;pop;;;%s\033\\", currentUser.Uid)
}
exitCode, err := shell.RunWithExitCode("podman", os.Stdin, os.Stdout, nil, execArgs...)

switch exitCode {
case 0:
if err != nil {
panic("unexpected error: 'podman exec' finished successfully")
}
case 125:
err = fmt.Errorf("failed to invoke 'podman exec' in container %s", container)
case 126:
err = fmt.Errorf("failed to invoke command %s in container %s", command[0], container)
case 127:
if pathPresent, _ := isPathPresent(container, workingDirectory); !pathPresent {
err = fmt.Errorf("directory %s not found in container %s", workingDirectory, container)
} else {
err = fmt.Errorf("command %s not found in container %s", command[0], container)
if emitEscapeSequence {
fmt.Printf("\033]777;container;pop;;;%s\033\\", currentUser.Uid)
}
default:
err = nil
}

if err != nil {
return err
switch exitCode {
case 0:
if err != nil {
panic("unexpected error: 'podman exec' finished successfully")
}
return nil
case 125:
err = fmt.Errorf("failed to invoke 'podman exec' in container %s", container)
return err
case 126:
err = fmt.Errorf("failed to invoke command %s in container %s", command[0], container)
return err
case 127:
if pathPresent, _ := isPathPresent(container, workDir); !pathPresent {
if runFallbackWorkDirsIndex < len(runFallbackWorkDirs) {
fmt.Fprintf(os.Stderr,
"Error: directory %s not found in container %s\n",
workDir,
container)

workDir = runFallbackWorkDirs[runFallbackWorkDirsIndex]
if workDir == "" {
workDir = currentUser.HomeDir
}

fmt.Fprintf(os.Stderr, "Using %s instead.\n", workDir)
runFallbackWorkDirsIndex++
} else {
err = fmt.Errorf("directory %s not found in container %s", workDir, container)
return err
}
} else {
Copy link
Member Author

Choose a reason for hiding this comment

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

We can't assume that an exit code of 127 from podman exec means that either the working directory or the command was absent. The given shell might also return with 127, which will get forwarded by podman exec, if the last command that was attempted inside the shell was absent. In such cases this will create a loop.

See #872

if fallbackToBash && runFallbackCommandsIndex < len(runFallbackCommands) {
fmt.Fprintf(os.Stderr,
"Error: command %s not found in container %s\n",
command[0],
container)

command = runFallbackCommands[runFallbackCommandsIndex]
fmt.Fprintf(os.Stderr, "Using %s instead.\n", command[0])

runFallbackCommandsIndex++
} else {
err = fmt.Errorf("command %s not found in container %s", command[0], container)
return err
}
}
default:
return nil
}
}

return nil
panic("code should not be reached")
}

func runHelp(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -417,6 +417,45 @@ func callFlatpakSessionHelper(container string) error {
return nil
}

func constructExecArgs(container string,
command []string,
detachKeysSupported bool,
envOptions []string,
workDir string) []string {
var detachKeys []string

if detachKeysSupported {
detachKeys = []string{"--detach-keys", ""}
}

logLevelString := podman.LogLevel.String()

execArgs := []string{
"--log-level", logLevelString,
"exec",
}

execArgs = append(execArgs, detachKeys...)

execArgs = append(execArgs, []string{
"--interactive",
"--tty",
"--user", currentUser.Username,
"--workdir", workDir,
}...)

execArgs = append(execArgs, envOptions...)

execArgs = append(execArgs, []string{
container,
"capsh", "--caps=", "--", "-c", "exec \"$@\"", "/bin/sh",
}...)

execArgs = append(execArgs, command...)

return execArgs
}

func getEntryPointAndPID(container string) (string, int, error) {
logrus.Debugf("Inspecting entry point of container %s", container)

Expand Down Expand Up @@ -449,25 +488,6 @@ func getEntryPointAndPID(container string) (string, int, error) {
return entryPoint, entryPointPIDInt, nil
}

func isCommandPresent(container, command string) (bool, error) {
logrus.Debugf("Looking for command %s in container %s", command, container)

logLevelString := podman.LogLevel.String()
args := []string{
"--log-level", logLevelString,
"exec",
"--user", currentUser.Username,
container,
"sh", "-c", "command -v \"$1\"", "sh", command,
}

if err := shell.Run("podman", nil, nil, nil, args...); err != nil {
return false, err
}

return true, nil
}

func isPathPresent(container, path string) (bool, error) {
logrus.Debugf("Looking for path %s in container %s", path, container)

Expand Down