diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index ec16d0029..cb5bf158f 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -68,6 +68,10 @@ "ImportPath": "github.com/dustin/go-humanize", "Rev": "b198514c204f20799b91c93b6ffd8b26be04c2c9" }, + { + "ImportPath": "github.com/facebookgo/atomicfile", + "Rev": "6f117f2e7f224fb03eb5e5fba370eade6e2b90c8" + }, { "ImportPath": "github.com/facebookgo/stack", "Rev": "4da6d991fc3c389efa512151354d643eb5fae4e2" diff --git a/Godeps/_workspace/src/github.com/facebookgo/atomicfile/.travis.yml b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/.travis.yml new file mode 100644 index 000000000..2cc62c5e8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/.travis.yml @@ -0,0 +1,24 @@ +language: go + +go: + - 1.2 + - 1.3 + +matrix: + fast_finish: true + +before_install: + - go get -v code.google.com/p/go.tools/cmd/vet + - go get -v github.com/golang/lint/golint + - go get -v code.google.com/p/go.tools/cmd/cover + +install: + - go install -race -v std + - go get -race -t -v ./... + - go install -race -v ./... + +script: + - go vet ./... + - $HOME/gopath/bin/golint . + - go test -cpu=2 -race -v ./... + - go test -cpu=2 -covermode=atomic ./... diff --git a/Godeps/_workspace/src/github.com/facebookgo/atomicfile/atomicfile.go b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/atomicfile.go new file mode 100644 index 000000000..60cda2a5b --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/atomicfile.go @@ -0,0 +1,54 @@ +// Package atomicfile provides the ability to write a file with an eventual +// rename on Close. This allows for a file to always be in a consistent state +// and never represent an in-progress write. +package atomicfile + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// File behaves like os.File, but does an atomic rename operation at Close. +type File struct { + *os.File + path string +} + +// New creates a new temporary file that will replace the file at the given +// path when Closed. +func New(path string, mode os.FileMode) (*File, error) { + f, err := ioutil.TempFile(filepath.Dir(path), filepath.Base(path)) + if err != nil { + return nil, err + } + if err := os.Chmod(f.Name(), mode); err != nil { + os.Remove(f.Name()) + return nil, err + } + return &File{File: f, path: path}, nil +} + +// Close the file replacing the configured file. +func (f *File) Close() error { + if err := f.File.Close(); err != nil { + return err + } + if err := os.Rename(f.Name(), f.path); err != nil { + return err + } + return nil +} + +// Abort closes the file and removes it instead of replacing the configured +// file. This is useful if after starting to write to the file you decide you +// don't want it anymore. +func (f *File) Abort() error { + if err := f.File.Close(); err != nil { + return err + } + if err := os.Remove(f.Name()); err != nil { + return err + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/facebookgo/atomicfile/atomicfile_test.go b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/atomicfile_test.go new file mode 100644 index 000000000..e18683a72 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/atomicfile_test.go @@ -0,0 +1,86 @@ +package atomicfile_test + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/facebookgo/atomicfile" +) + +func test(t *testing.T, dir, prefix string) { + t.Parallel() + + tmpfile, err := ioutil.TempFile(dir, prefix) + if err != nil { + t.Fatal(err) + } + name := tmpfile.Name() + + if err := os.Remove(name); err != nil { + t.Fatal(err) + } + + defer os.Remove(name) + f, err := atomicfile.New(name, os.FileMode(0666)) + if err != nil { + t.Fatal(err) + } + f.Write([]byte("foo")) + if _, err := os.Stat(name); !os.IsNotExist(err) { + t.Fatal("did not expect file to exist") + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(name); err != nil { + t.Fatalf("expected file to exist: %s", err) + } +} + +func TestCurrentDir(t *testing.T) { + cwd, _ := os.Getwd() + test(t, cwd, "atomicfile-current-dir-") +} + +func TestRootTmpDir(t *testing.T) { + test(t, "/tmp", "atomicfile-root-tmp-dir-") +} + +func TestDefaultTmpDir(t *testing.T) { + test(t, "", "atomicfile-default-tmp-dir-") +} + +func TestAbort(t *testing.T) { + contents := []byte("the answer is 42") + t.Parallel() + tmpfile, err := ioutil.TempFile("", "atomicfile-abort-") + if err != nil { + t.Fatal(err) + } + name := tmpfile.Name() + if _, err := tmpfile.Write(contents); err != nil { + t.Fatal(err) + } + defer os.Remove(name) + + f, err := atomicfile.New(name, os.FileMode(0666)) + if err != nil { + t.Fatal(err) + } + f.Write([]byte("foo")) + if err := f.Abort(); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(name); err != nil { + t.Fatalf("expected file to exist: %s", err) + } + actual, err := ioutil.ReadFile(name) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(contents, actual) { + t.Fatalf(`did not find expected "%s" instead found "%s"`, contents, actual) + } +} diff --git a/Godeps/_workspace/src/github.com/facebookgo/atomicfile/license b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/license new file mode 100644 index 000000000..4ce34257c --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/license @@ -0,0 +1,30 @@ +BSD License + +For atomicfile software + +Copyright (c) 2014, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/facebookgo/atomicfile/patents b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/patents new file mode 100644 index 000000000..887426cf1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/patents @@ -0,0 +1,23 @@ +Additional Grant of Patent Rights + +"Software" means the atomicfile software distributed by Facebook, Inc. + +Facebook hereby grants you a perpetual, worldwide, royalty-free, non-exclusive, +irrevocable (subject to the termination provision below) license under any +rights in any patent claims owned by Facebook, to make, have made, use, sell, +offer to sell, import, and otherwise transfer the Software. For avoidance of +doubt, no license is granted under Facebook’s rights in any patent claims that +are infringed by (i) modifications to the Software made by you or a third party, +or (ii) the Software in combination with any software or other technology +provided by you or a third party. + +The license granted hereunder will terminate, automatically and without notice, +for anyone that makes any claim (including by filing any lawsuit, assertion or +other action) alleging (a) direct, indirect, or contributory infringement or +inducement to infringe any patent: (i) by Facebook or any of its subsidiaries or +affiliates, whether or not such claim is related to the Software, (ii) by any +party if such claim arises in whole or in part from any software, product or +service of Facebook or any of its subsidiaries or affiliates, whether or not +such claim is related to the Software, or (iii) by any party relating to the +Software; or (b) that any right in any patent claim of Facebook is invalid or +unenforceable. diff --git a/Godeps/_workspace/src/github.com/facebookgo/atomicfile/readme.md b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/readme.md new file mode 100644 index 000000000..80038c3e0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/atomicfile/readme.md @@ -0,0 +1,4 @@ +atomicfile [![Build Status](https://secure.travis-ci.org/facebookgo/atomicfile.png)](http://travis-ci.org/facebookgo/atomicfile) +========== + +Documentation: http://godoc.org/github.com/facebookgo/atomicfile diff --git a/repo/fsrepo/serialize.go b/repo/fsrepo/serialize.go index c152b2936..dcbf4ccf1 100644 --- a/repo/fsrepo/serialize.go +++ b/repo/fsrepo/serialize.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/facebookgo/atomicfile" "github.com/jbenet/go-ipfs/repo/config" "github.com/jbenet/go-ipfs/util" "github.com/jbenet/go-ipfs/util/debugerror" @@ -34,7 +35,7 @@ func writeConfigFile(filename string, cfg interface{}) error { return err } - f, err := os.Create(filename) + f, err := atomicfile.New(filename, 0775) if err != nil { return err }