Skip to content

Commit

Permalink
cmd/initContainer: Follow symlinks when redirecting
Browse files Browse the repository at this point in the history
There has been a long-standing issue[0] when symlinking /etc/resolv.conf
to /run/host/etc/resolv.conf (host's resolv.conf). Many solutions were
proposed. There are many ways how to go about it. Hardcode other paths,
where the file could be, completely rely on tools like
flatpak-session-helper that will track such files in a single directory,
or update the symlinking function to follow symlinks and update the
target path.

The first solution is the easiest short-term but not very good
long-term. The second solution is possibly the best long-term but there
is a problem with using flatpak-session-helper. It cannot be used as
root. That leaves the third option, follow symlinks and updating the
target path.

This commit takes the third approach to solve the issue. Now the target
of the symlinking is first tested if it is a symlink and if it is, it's
target will be set as the new target. This is repeated if the new target
is also a symlink. The function looks for the links in the chain under
/run/host where the host's filesystem is available. Relative symlinks
are handled by this.

Based on: containers#380

[0] containers#187
  • Loading branch information
HarryMichal committed Aug 13, 2020
1 parent 238a678 commit 5dbe2db
Showing 1 changed file with 64 additions and 4 deletions.
68 changes: 64 additions & 4 deletions src/cmd/initContainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"syscall"

Expand Down Expand Up @@ -397,22 +398,81 @@ func mountBind(containerPath, source, flags string) error {
return nil
}

// redirectPath serves for creating symlinks for mainly crucial system files to
// their counterparts on the host's filesystem.
//
// containerPath and target must be absolute paths
//
// folder signifies the target is a folder
//
// The files of the host's filesystem are available in a toolbox containers
// under /run/host.
//
// If the target is a symlink, redirectPath will follow the chain of links and
// try to lookup the real target in /run/host
//
// Example: systemd-resolved makes /etc/resolv.conf a link to
// /run/systemd/resolved/resolv.conf - in a container found under
// /run/host/run/systemd/resolved/resolv.conf).
func redirectPath(containerPath, target string, folder bool) error {
logrus.Debugf("Redirecting %s to %s", containerPath, target)

err := os.Remove(containerPath)
targetInfo, err := os.Lstat(target)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("target %s was not found in the container", target)
}
return fmt.Errorf("failed to lstat %s: %w", target, err)
}
isSymlink := (targetInfo.Mode() & os.ModeSymlink) != 0

// If the target is a link, follow the chain. If needed, repeat the process.
for isSymlink {
logrus.Debugf("Path %s is a symlink. Resolving.", target)
newTarget, err := os.Readlink(target)
if err != nil {
return fmt.Errorf("Could not read link %s: %w", target, err)
}

if !filepath.IsAbs(newTarget) {
newTarget = filepath.Join(filepath.Dir(target), newTarget)
}
// Only prepend /run/host to the filepath if it is not there already
// FIXME: Using strings.Contains may not be the best approach
if !strings.Contains(newTarget, "/run/host/") {
// Try to find the file inside of /run/host in the container where the
// available host's files are accessible.
newTarget = filepath.Join("/run/host", newTarget)
}
target = newTarget

targetInfo, err = os.Lstat(target)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("target %s was not found in the container", target)
}
return fmt.Errorf("failed to lstat %s: %w", target, err)
}

isSymlink = (targetInfo.Mode() & os.ModeSymlink) != 0
if !isSymlink {
logrus.Debugf("Resolved and redirecting %s to %s", containerPath, newTarget)
}
}

err = os.Remove(containerPath)
if folder {
if err != nil {
return fmt.Errorf("failed to redirect %s to %s", containerPath, target)
return fmt.Errorf("failed to redirect %s to %s: %w", containerPath, target, err)
}

if err := os.MkdirAll(target, 0755); err != nil {
return fmt.Errorf("failed to redirect %s to %s", containerPath, target)
return fmt.Errorf("failed to redirect %s to %s: %w", containerPath, target, err)
}
}

if err := os.Symlink(target, containerPath); err != nil {
return fmt.Errorf("failed to redirect %s to %s", containerPath, target)
return fmt.Errorf("failed to redirect %s to %s: %w", containerPath, target, err)
}

return nil
Expand Down

0 comments on commit 5dbe2db

Please sign in to comment.