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

Copying /etc/resolv.conf to chroot while restoring the original file unless it has been changed during provisioning #160

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
123 changes: 123 additions & 0 deletions builder/step_setup_chroot.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package builder

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"os/exec"
Expand Down Expand Up @@ -65,6 +69,58 @@ func getMounts() (map[string]bool, error) {
return selected, nil
}

// renameAndCheck courtesy of https://stackoverflow.com/a/25940392/51016
func renameAndCheck(src, dst string) error {
err := os.Link(src, dst)
if err != nil {
return err
}
return os.Remove(src)
}

// deepCompare courtesy of https://stackoverflow.com/a/30038571/51016
const chunkSize = 64000

func deepCompare(file1, file2 string) (bool, error) {
// Check file size ...
f1, err := os.Open(file1)
if err != nil {
return false, err
}
defer f1.Close()

f2, err := os.Open(file2)
if err != nil {
return false, err
}
defer f2.Close()

for {
b1 := make([]byte, chunkSize)
_, err1 := f1.Read(b1)

b2 := make([]byte, chunkSize)
_, err2 := f2.Read(b2)

if err1 != nil || err2 != nil {
if err1 == io.EOF && err2 == io.EOF {
return true, nil
} else if err1 == io.EOF || err2 == io.EOF {
return false, nil
} else {
err := fmt.Errorf("file1: %w", err1)
err = fmt.Errorf("file2: %w", err2)
err = fmt.Errorf("error comparing files; %w", err)
return false, err
}
}

if !bytes.Equal(b1, b2) {
return false, nil
}
}
}

// StepSetupChroot prepares chroot environment by mounting specific locations (/dev /proc etc.)
type StepSetupChroot struct {
ImageMountPointKey string
Expand Down Expand Up @@ -95,6 +151,47 @@ func (s *StepSetupChroot) Run(ctx context.Context, state multistep.StateBag) mul
}
}

ui.Message("patching file system for the chroot execution")

src := "/etc/resolv.conf"
dst := filepath.Join(imageMountpoint, src)
bak := dst + ".bak"

// backup /etc/resolv.conf if it exists
err := renameAndCheck(dst, bak)
if err == nil {
ui.Message(fmt.Sprintf("backed up '%s' to '%s'", dst, bak))
} else if errors.Is(err, fs.ErrNotExist) {
ui.Message(fmt.Sprintf("'%s' does not exist: %v", src, err))
} else if errors.Is(err, fs.ErrExist) {
ui.Error(fmt.Sprintf("'%s' already exists: %v", dst, err))
} else if errors.Is(err, fs.ErrPermission) {
ui.Error(fmt.Sprintf("could not create '%s': %v", bak, err))
}

source, err := os.Open(src)
if err != nil {
ui.Error(fmt.Sprintf("error while opening source: %v: '%s'", err, src))
return multistep.ActionHalt
}
defer source.Close()

destination, err := os.Create(dst)
if err != nil {
ui.Error(fmt.Sprintf("error while creating destination: %v: '%s'", err, dst))
return multistep.ActionHalt
}
defer destination.Close()

// copy /etc/resolv.conf to the chroot
_, err = io.Copy(destination, source)
if err == nil {
ui.Message(fmt.Sprintf("copied file from '%s' to '%s'", src, dst))
} else {
ui.Error(fmt.Sprintf("error while copying: %v: from '%s' to '%s'", err, src, dst))
return multistep.ActionHalt
}

return multistep.ActionContinue
}

Expand Down Expand Up @@ -141,4 +238,30 @@ func (s *StepSetupChroot) Cleanup(state multistep.StateBag) {
}
}
}

// restore backed up /etc/resolv.conf in chroot
resolve := filepath.Join(imageMountpoint, "/etc/resolv.conf")
result, err := deepCompare("/etc/resolv.conf", resolve)
if err != nil {
ui.Error(fmt.Sprintf("error comparing host and chroot resolv.conf: %v", err))
}
// restore the backup only if resolv.conf is unchanged
if result {
bak := resolve + ".bak"

err = os.Remove(resolve)
if err != nil {
ui.Error(fmt.Sprintf("could not remove file %v: %s", err, resolve))
}
err = renameAndCheck(bak, resolve)
if err == nil {
ui.Message(fmt.Sprintf("restored '%s'", resolve))
} else if errors.Is(err, fs.ErrNotExist) {
ui.Error(fmt.Sprintf("'%s' does not exist: %v", bak, err))
} else if errors.Is(err, fs.ErrExist) {
ui.Error(fmt.Sprintf("'%s' already exists: %v", resolve, err))
} else if errors.Is(err, fs.ErrPermission) {
ui.Error(fmt.Sprintf("could not restore '%s': %v", resolve, err))
}
}
}