fix(system): write all files atomically

This commit is contained in:
Jonathan Boulle 2014-06-05 12:48:32 -07:00
parent 56f455f890
commit 0ddc08d55a
8 changed files with 75 additions and 48 deletions

View File

@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"path"
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml" "github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
@ -221,11 +220,11 @@ func Apply(cfg CloudConfig, env *Environment) error {
} }
for _, file := range cfg.WriteFiles { for _, file := range cfg.WriteFiles {
file.Path = path.Join(env.Root(), file.Path) path, err := system.WriteFile(&file, env.Root())
if err := system.WriteFile(&file); err != nil { if err != nil {
return err return err
} }
log.Printf("Wrote file %s to filesystem", file.Path) log.Printf("Wrote file %s to filesystem", path)
} }
commands := make(map[string]string, 0) commands := make(map[string]string, 0)

View File

@ -50,9 +50,7 @@ func TestEtcHostsWrittenToDisk(t *testing.T) {
t.Fatalf("manageEtcHosts returned nil file unexpectedly") t.Fatalf("manageEtcHosts returned nil file unexpectedly")
} }
f.Path = path.Join(dir, f.Path) if _, err := system.WriteFile(f, dir); err != nil {
if err := system.WriteFile(f); err != nil {
t.Fatalf("Error writing EtcHosts: %v", err) t.Fatalf("Error writing EtcHosts: %v", err)
} }

View File

@ -31,8 +31,7 @@ func TestOEMReleaseWrittenToDisk(t *testing.T) {
t.Fatalf("OEMRelease returned nil file unexpectedly") t.Fatalf("OEMRelease returned nil file unexpectedly")
} }
f.Path = path.Join(dir, f.Path) if _, err := system.WriteFile(f, dir); err != nil {
if err := system.WriteFile(f); err != nil {
t.Fatalf("Writing of OEMRelease failed: %v", err) t.Fatalf("Writing of OEMRelease failed: %v", err)
} }

View File

@ -205,8 +205,7 @@ func TestUpdateConfWrittenToDisk(t *testing.T) {
t.Fatal("Unexpectedly got nil updateconfig file") t.Fatal("Unexpectedly got nil updateconfig file")
} }
f.Path = path.Join(dir, f.Path) if _, err := system.WriteFile(f, dir); err != nil {
if err := system.WriteFile(f); err != nil {
t.Fatalf("Error writing update config: %v", err) t.Fatalf("Error writing update config: %v", err)
} }

View File

@ -3,6 +3,7 @@ package initialize
import ( import (
"io/ioutil" "io/ioutil"
"path" "path"
"strings"
"github.com/coreos/coreos-cloudinit/system" "github.com/coreos/coreos-cloudinit/system"
) )
@ -28,21 +29,23 @@ func PersistScriptInWorkspace(script system.Script, workspace string) (string, e
} }
tmp.Close() tmp.Close()
relpath := strings.TrimPrefix(tmp.Name(), workspace)
file := system.File{ file := system.File{
Path: tmp.Name(), Path: relpath,
RawFilePermissions: "0744", RawFilePermissions: "0744",
Content: string(script), Content: string(script),
} }
err = system.WriteFile(&file) return system.WriteFile(&file, workspace)
return file.Path, err
} }
func PersistUnitNameInWorkspace(name string, workspace string) error { func PersistUnitNameInWorkspace(name string, workspace string) error {
file := system.File{ file := system.File{
Path: path.Join(workspace, "scripts", "unit-name"), Path: path.Join("scripts", "unit-name"),
RawFilePermissions: "0644", RawFilePermissions: "0644",
Content: name, Content: name,
} }
return system.WriteFile(&file) _, err := system.WriteFile(&file, workspace)
return err
} }

View File

@ -31,33 +31,55 @@ func (f *File) Permissions() (os.FileMode, error) {
return os.FileMode(perm), nil return os.FileMode(perm), nil
} }
func WriteFile(f *File) error { func WriteFile(f *File, root string) (string, error) {
if f.Encoding != "" { if f.Encoding != "" {
return fmt.Errorf("Unable to write file with encoding %s", f.Encoding) return "", fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
} }
if err := os.MkdirAll(path.Dir(f.Path), os.FileMode(0755)); err != nil { fullpath := path.Join(root, f.Path)
return err dir := path.Dir(fullpath)
if err := EnsureDirectoryExists(dir); err != nil {
return "", err
} }
perm, err := f.Permissions() perm, err := f.Permissions()
if err != nil { if err != nil {
return err return "", err
} }
if err := ioutil.WriteFile(f.Path, []byte(f.Content), perm); err != nil { var tmp *os.File
return err // Create a temporary file in the same directory to ensure it's on the same filesystem
if tmp, err = ioutil.TempFile(dir, "cloudinit-temp"); err != nil {
return "", err
}
if err := ioutil.WriteFile(tmp.Name(), []byte(f.Content), perm); err != nil {
return "", err
}
if err := tmp.Close(); err != nil {
return "", err
}
// Ensure the permissions are as requested (since WriteFile can be affected by sticky bit)
if err := os.Chmod(tmp.Name(), perm); err != nil {
return "", err
} }
if f.Owner != "" { if f.Owner != "" {
// We shell out since we don't have a way to look up unix groups natively // We shell out since we don't have a way to look up unix groups natively
cmd := exec.Command("chown", f.Owner, f.Path) cmd := exec.Command("chown", f.Owner, tmp.Name())
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return err return "", err
} }
} }
return nil if err := os.Rename(tmp.Name(), fullpath); err != nil {
return "", err
}
return fullpath, nil
} }
func EnsureDirectoryExists(dir string) error { func EnsureDirectoryExists(dir string) error {

View File

@ -4,7 +4,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"syscall"
"testing" "testing"
) )
@ -13,18 +12,22 @@ func TestWriteFileUnencodedContent(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unable to create tempdir: %v", err) t.Fatalf("Unable to create tempdir: %v", err)
} }
defer syscall.Rmdir(dir) defer os.RemoveAll(dir)
fullPath := path.Join(dir, "tmp", "foo") fn := "foo"
fullPath := path.Join(dir, fn)
wf := File{ wf := File{
Path: fullPath, Path: fn,
Content: "bar", Content: "bar",
RawFilePermissions: "0644", RawFilePermissions: "0644",
} }
if err := WriteFile(&wf); err != nil { path, err := WriteFile(&wf, dir)
if err != nil {
t.Fatalf("Processing of WriteFile failed: %v", err) t.Fatalf("Processing of WriteFile failed: %v", err)
} else if path != fullPath {
t.Fatalf("WriteFile returned bad path: want %s, got %s", fullPath, path)
} }
fi, err := os.Stat(fullPath) fi, err := os.Stat(fullPath)
@ -51,15 +54,15 @@ func TestWriteFileInvalidPermission(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unable to create tempdir: %v", err) t.Fatalf("Unable to create tempdir: %v", err)
} }
defer syscall.Rmdir(dir) defer os.RemoveAll(dir)
wf := File{ wf := File{
Path: path.Join(dir, "tmp", "foo"), Path: path.Join(dir, "tmp", "foo"),
Content: "bar", Content: "bar",
RawFilePermissions: "pants", RawFilePermissions: "pants",
} }
if err := WriteFile(&wf); err == nil { if _, err := WriteFile(&wf, dir); err == nil {
t.Fatalf("Expected error to be raised when writing file with invalid permission") t.Fatalf("Expected error to be raised when writing file with invalid permission")
} }
} }
@ -69,17 +72,21 @@ func TestWriteFilePermissions(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unable to create tempdir: %v", err) t.Fatalf("Unable to create tempdir: %v", err)
} }
defer syscall.Rmdir(dir) defer os.RemoveAll(dir)
fullPath := path.Join(dir, "tmp", "foo") fn := "foo"
fullPath := path.Join(dir, fn)
wf := File{ wf := File{
Path: fullPath, Path: fn,
RawFilePermissions: "0755", RawFilePermissions: "0755",
} }
if err := WriteFile(&wf); err != nil { path, err := WriteFile(&wf, dir)
if err != nil {
t.Fatalf("Processing of WriteFile failed: %v", err) t.Fatalf("Processing of WriteFile failed: %v", err)
} else if path != fullPath {
t.Fatalf("WriteFile returned bad path: want %s, got %s", fullPath, path)
} }
fi, err := os.Stat(fullPath) fi, err := os.Stat(fullPath)
@ -97,15 +104,15 @@ func TestWriteFileEncodedContent(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unable to create tempdir: %v", err) t.Fatalf("Unable to create tempdir: %v", err)
} }
defer syscall.Rmdir(dir) defer os.RemoveAll(dir)
wf := File{ wf := File{
Path: path.Join(dir, "tmp", "foo"), Path: path.Join(dir, "tmp", "foo"),
Content: "", Content: "",
Encoding: "base64", Encoding: "base64",
} }
if err := WriteFile(&wf); err == nil { if _, err := WriteFile(&wf, dir); err == nil {
t.Fatalf("Expected error to be raised when writing file with encoding") t.Fatalf("Expected error to be raised when writing file with encoding")
} }
} }

View File

@ -78,12 +78,12 @@ func PlaceUnit(u *Unit, dst string) error {
} }
file := File{ file := File{
Path: dst, Path: filepath.Base(dst),
Content: u.Content, Content: u.Content,
RawFilePermissions: "0644", RawFilePermissions: "0644",
} }
err := WriteFile(&file) _, err := WriteFile(&file, dir)
if err != nil { if err != nil {
return err return err
} }