cmd/initContainer, test/system: Don't rely on D-Bus for /etc/timezone

This is one more step towards enabling toolbox(1) to be run as root.
When invoked as 'sudo toolbox ...' there's no user or session D-Bus
instance available for the root user, which prevents the use of D-Bus
services like org.freedesktop.Flatpak.SessionHelper.

The code is forgiving to runtime errors when reacting to file system
events because it's not worth abruptly terminating the entry point
because of what might be a passing error. However, it's a lot stricter
when initially configuring the container because the failure mode isn't
as surprising for the user and it's worth starting from a valid state.

https://github.com/containers/toolbox/issues/267
This commit is contained in:
Debarshi Ray 2020-10-22 21:37:47 +02:00
parent 4c9b80aee2
commit b9a0bd5f0c
4 changed files with 75 additions and 29 deletions

View file

@ -21,14 +21,13 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"syscall"
"github.com/containers/toolbox/pkg/shell"
"github.com/containers/toolbox/pkg/utils"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -177,6 +176,10 @@ func initContainer(cmd *cobra.Command, args []string) error {
}
}
if err := updateTimeZoneFromLocalTime(); err != nil {
return err
}
if _, err := os.Readlink("/etc/resolv.conf"); err != nil {
if err := redirectPath("/etc/resolv.conf",
"/run/host/etc/resolv.conf",
@ -197,18 +200,6 @@ func initContainer(cmd *cobra.Command, args []string) error {
}
}
}
if utils.PathExists("/run/host/monitor") {
logrus.Debug("Path /run/host/monitor exists")
if _, err := os.Readlink("/etc/timezone"); err != nil {
if err := redirectPath("/etc/timezone",
"/run/host/monitor/timezone",
false); err != nil {
return err
}
}
}
}
if initContainerFlags.mediaLink {
@ -267,6 +258,19 @@ func initContainer(cmd *cobra.Command, args []string) error {
}
}
logrus.Debug("Setting up watches for file system events")
watcherForHost, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcherForHost.Close()
if err := watcherForHost.Add("/run/host/etc"); err != nil {
return err
}
logrus.Debug("Finished initializing container")
toolboxRuntimeDirectory := runtimeDirectory + "/toolbox"
@ -297,22 +301,15 @@ func initContainer(cmd *cobra.Command, args []string) error {
return errors.New("failed to change ownership of initialization stamp")
}
logrus.Debug("Going to sleep")
logrus.Debug("Listening to file system events")
sleepBinary, err := exec.LookPath("sleep")
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
return errors.New("sleep(1) not found")
for {
select {
case event := <-watcherForHost.Events:
handleFileSystemEvent(event)
case err := <-watcherForHost.Errors:
logrus.Warnf("Received an error from the file system watcher: %v", err)
}
return errors.New("failed to lookup sleep(1)")
}
sleepArgs := []string{"sleep", "+Inf"}
env := os.Environ()
if err := syscall.Exec(sleepBinary, sleepArgs, env); err != nil {
return errors.New("failed to invoke sleep(1)")
}
return nil
@ -410,6 +407,17 @@ func configureUsers(targetUserUid int,
return nil
}
func handleFileSystemEvent(event fsnotify.Event) {
eventOpString := event.Op.String()
logrus.Debugf("Handling file system event: operation %s on %s", eventOpString, event.Name)
if event.Name == "/run/host/etc/localtime" {
if err := updateTimeZoneFromLocalTime(); err != nil {
logrus.Warnf("Failed to handle changes to the host's /etc/localtime: %v", err)
}
}
}
func mountBind(containerPath, source, flags string) error {
fi, err := os.Stat(source)
if err != nil {
@ -555,3 +563,39 @@ func sanitizeRedirectionTarget(target string) string {
return target
}
func updateTimeZoneFromLocalTime() error {
localTimeEvaled, err := filepath.EvalSymlinks("/etc/localtime")
if err != nil {
return fmt.Errorf("failed to resolve /etc/localtime: %w", err)
}
logrus.Debugf("Resolved /etc/localtime to %s", localTimeEvaled)
const zoneInfoRoot = "/run/host/usr/share/zoneinfo"
if !strings.HasPrefix(localTimeEvaled, zoneInfoRoot) {
return errors.New("/etc/localtime points to unknown location")
}
timeZone, err := filepath.Rel(zoneInfoRoot, localTimeEvaled)
if err != nil {
return fmt.Errorf("failed to extract time zone: %w", err)
}
const etcTimeZone = "/etc/timezone"
if err := os.Remove(etcTimeZone); err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("failed to remove old %s: %w", etcTimeZone, err)
}
}
timeZoneBytes := []byte(timeZone + "\n")
err = ioutil.WriteFile(etcTimeZone, timeZoneBytes, 0664)
if err != nil {
return fmt.Errorf("failed to create new %s: %w", etcTimeZone, err)
}
return nil
}

View file

@ -7,6 +7,7 @@ require (
github.com/acobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249
github.com/briandowns/spinner v1.10.0
github.com/docker/go-units v0.4.0
github.com/fsnotify/fsnotify v1.4.7
github.com/godbus/dbus/v5 v5.0.3
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5

View file

@ -16,6 +16,7 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=

View file

@ -113,7 +113,7 @@ function run_toolbox() {
function is_toolbox_ready() {
toolbox_container="$1"
expected_string="Going to sleep"
expected_string="Listening to file system events"
num_of_tries=5
timeout=2