Skip to content

Commit

Permalink
libct/init: unify init, fix its error logic
Browse files Browse the repository at this point in the history
This commit does two things:

1. Consolidate StartInitialization calling logic into Init().
2. Fix init error handling logic.

The main issues at hand are:
- the "unable to convert _LIBCONTAINER_INITPIPE" error from
  StartInitialization is never shown;
- errors from WriteSync and WriteJSON are never shown;
- the StartInit calling code is triplicated;
- using panic is questionable.

Generally, our goals are:
 - if there's any error, do our best to show it;
 - but only show each error once;
 - simplify the code, unify init implementations.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
  • Loading branch information
kolyshkin committed May 22, 2023
1 parent e13bb9e commit 159d2d9
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 49 deletions.
11 changes: 1 addition & 10 deletions init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"os"
"runtime"

"github.com/opencontainers/runc/libcontainer"
_ "github.com/opencontainers/runc/libcontainer/nsenter"
Expand All @@ -12,14 +11,6 @@ func init() {
if len(os.Args) > 1 && os.Args[1] == "init" {
// This is the golang entry point for runc init, executed
// before main() but after libcontainer/nsenter's nsexec().
runtime.GOMAXPROCS(1)
runtime.LockOSThread()

if err := libcontainer.StartInitialization(); err != nil {
// as the error is sent back to the parent there is no need to log
// or write it to stderr because the parent process will handle this
os.Exit(1)
}
panic("libcontainer: container init failed to exec")
libcontainer.Init()
}
}
19 changes: 3 additions & 16 deletions libcontainer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,9 @@ function as the entry of "bootstrap".
In addition to the go init function the early stage bootstrap is handled by importing
[nsenter](https://github.com/opencontainers/runc/blob/master/libcontainer/nsenter/README.md).

```go
import (
_ "github.com/opencontainers/runc/libcontainer/nsenter"
)

func init() {
if len(os.Args) > 1 && os.Args[1] == "init" {
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
if err := libcontainer.StartInitialization(); err != nil {
logrus.Fatal(err)
}
panic("--this line should have never been executed, congratulations--")
}
}
```
For details on how runc implements such "init", see
[init.go](https://github.com/opencontainers/runc/blob/master/init.go)
and [libcontainer/init_linux.go](https://github.com/opencontainers/runc/blob/master/libcontainer/init_linux.go).

Then to create a container you first have to create a configuration
struct describing how the container is to be created. A sample would look similar to this:
Expand Down
39 changes: 31 additions & 8 deletions libcontainer/init_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"net"
"os"
"runtime"
"runtime/debug"
"strconv"
"strings"
Expand Down Expand Up @@ -73,10 +74,30 @@ type initConfig struct {
Cgroup2Path string `json:"cgroup2_path,omitempty"`
}

// StartInitialization loads a container by opening the pipe fd from the parent
// to read the configuration and state. This is a low level implementation
// detail of the reexec and should not be consumed externally.
func StartInitialization() (retErr error) {
// Init is part of "runc init" implementation.
func Init() {
runtime.GOMAXPROCS(1)
runtime.LockOSThread()

if err := startInitialization(); err != nil {
// If the error is returned, it was not communicated
// back to the parent (which is not a common case),
// so print it to stderr here as a last resort.
//
// Do not use logrus as we are not sure if it has been
// set up yet, but most important, if the parent is
// alive (and its log forwarding is working).
fmt.Fprintln(os.Stderr, err)
}
// Normally, StartInitialization() never returns, meaning
// if we are here, it had failed.
os.Exit(1)
}

// Normally, this function does not return. If it returns, with or without an
// error, it means the initialization has failed. If the error is returned,
// it means the error can not be communicated back to the parent.
func startInitialization() (retErr error) {
// Get the INITPIPE.
envInitPipe := os.Getenv("_LIBCONTAINER_INITPIPE")
pipefd, err := strconv.Atoi(envInitPipe)
Expand All @@ -87,16 +108,18 @@ func StartInitialization() (retErr error) {
defer pipe.Close()

defer func() {
// We have an error during the initialization of the container's init,
// send it back to the parent process in the form of an initError.
// If this defer is ever called, this means initialization has failed.
// Send the error back to the parent process in the form of an initError.
if err := writeSync(pipe, procError); err != nil {
fmt.Fprintln(os.Stderr, retErr)
fmt.Fprintln(os.Stderr, err)
return
}
if err := utils.WriteJSON(pipe, &initError{Message: retErr.Error()}); err != nil {
fmt.Fprintln(os.Stderr, retErr)
fmt.Fprintln(os.Stderr, err)
return
}
// The error is sent, no need to also return it (or it will be reported twice).
retErr = nil
}()

// Set up logging. This is used rarely, and mostly for init debugging.
Expand Down
17 changes: 2 additions & 15 deletions libcontainer/integration/init_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package integration

import (
"fmt"
"os"
"runtime"
"testing"

"github.com/opencontainers/runc/libcontainer"
Expand All @@ -14,20 +12,9 @@ import (

// Same as ../../init.go but for libcontainer/integration.
func init() {
if len(os.Args) < 2 || os.Args[1] != "init" {
return
if len(os.Args) > 1 && os.Args[1] == "init" {
libcontainer.Init()
}
// This is the golang entry point for runc init, executed
// before TestMain() but after libcontainer/nsenter's nsexec().
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
if err := libcontainer.StartInitialization(); err != nil {
// logrus is not initialized
fmt.Fprintln(os.Stderr, err)
}
// Normally, StartInitialization() never returns, meaning
// if we are here, it had failed.
os.Exit(1)
}

func TestMain(m *testing.M) {
Expand Down

0 comments on commit 159d2d9

Please sign in to comment.