summaryrefslogtreecommitdiff
path: root/vendor/github.com/coreos/ignition/internal/exec/util/file.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/coreos/ignition/internal/exec/util/file.go')
-rw-r--r--vendor/github.com/coreos/ignition/internal/exec/util/file.go235
1 files changed, 235 insertions, 0 deletions
diff --git a/vendor/github.com/coreos/ignition/internal/exec/util/file.go b/vendor/github.com/coreos/ignition/internal/exec/util/file.go
new file mode 100644
index 00000000..b51a9dee
--- /dev/null
+++ b/vendor/github.com/coreos/ignition/internal/exec/util/file.go
@@ -0,0 +1,235 @@
+// Copyright 2015 CoreOS, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package util
+
+import (
+ "bufio"
+ "compress/gzip"
+ "encoding/hex"
+ "hash"
+ "io"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "path/filepath"
+
+ "github.com/coreos/ignition/config/types"
+ "github.com/coreos/ignition/internal/log"
+ "github.com/coreos/ignition/internal/resource"
+
+ "golang.org/x/net/context"
+)
+
+const (
+ DefaultDirectoryPermissions os.FileMode = 0755
+ DefaultFilePermissions os.FileMode = 0644
+)
+
+type File struct {
+ io.ReadCloser
+ hash.Hash
+ Path string
+ Mode os.FileMode
+ Uid int
+ Gid int
+ expectedSum string
+}
+
+func (f File) Verify() error {
+ if f.Hash == nil {
+ return nil
+ }
+ sum := f.Sum(nil)
+ encodedSum := make([]byte, hex.EncodedLen(len(sum)))
+ hex.Encode(encodedSum, sum)
+
+ if string(encodedSum) != f.expectedSum {
+ return ErrHashMismatch{
+ Calculated: string(encodedSum),
+ Expected: f.expectedSum,
+ }
+ }
+ return nil
+}
+
+// newHashedReader returns a new ReadCloser that also writes to the provided hash.
+func newHashedReader(reader io.ReadCloser, hasher hash.Hash) io.ReadCloser {
+ return struct {
+ io.Reader
+ io.Closer
+ }{
+ Reader: io.TeeReader(reader, hasher),
+ Closer: reader,
+ }
+}
+
+// RenderFile returns a *File with a Reader that downloads, hashes, and decompresses the incoming data.
+// It returns nil if f had invalid options. Errors reading/verifying/decompressing the file will
+// present themselves when the Reader is actually read from.
+func RenderFile(l *log.Logger, c *resource.HttpClient, f types.File) *File {
+ var reader io.ReadCloser
+ var err error
+ var expectedSum string
+
+ // explicitly ignoring the error here because the config should already be
+ // validated by this point
+ u, _ := url.Parse(f.Contents.Source)
+
+ reader, err = resource.FetchAsReader(l, c, context.Background(), *u)
+ if err != nil {
+ l.Crit("Error fetching file %q: %v", f.Path, err)
+ return nil
+ }
+
+ fileHash, err := GetHasher(f.Contents.Verification)
+ if err != nil {
+ l.Crit("Error verifying file %q: %v", f.Path, err)
+ return nil
+ }
+
+ if fileHash != nil {
+ reader = newHashedReader(reader, fileHash)
+ // explicitly ignoring the error here because the config should already
+ // be validated by this point
+ _, expectedSum, _ = f.Contents.Verification.HashParts()
+ }
+
+ reader, err = decompressFileStream(l, f, reader)
+ if err != nil {
+ l.Crit("Error decompressing file %q: %v", f.Path, err)
+ return nil
+ }
+
+ return &File{
+ Path: f.Path,
+ ReadCloser: reader,
+ Hash: fileHash,
+ Mode: os.FileMode(f.Mode),
+ Uid: f.User.ID,
+ Gid: f.Group.ID,
+ expectedSum: expectedSum,
+ }
+}
+
+// gzipReader is a wrapper for gzip's reader that closes the stream it wraps as well
+// as itself when Close() is called.
+type gzipReader struct {
+ *gzip.Reader //actually a ReadCloser
+ source io.Closer
+}
+
+func newGzipReader(reader io.ReadCloser) (io.ReadCloser, error) {
+ gzReader, err := gzip.NewReader(reader)
+ if err != nil {
+ return nil, err
+ }
+ return gzipReader{
+ Reader: gzReader,
+ source: reader,
+ }, nil
+}
+
+func (gz gzipReader) Close() error {
+ if err := gz.Reader.Close(); err != nil {
+ return err
+ }
+ if err := gz.source.Close(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func decompressFileStream(l *log.Logger, f types.File, contents io.ReadCloser) (io.ReadCloser, error) {
+ switch f.Contents.Compression {
+ case "":
+ return contents, nil
+ case "gzip":
+ return newGzipReader(contents)
+ default:
+ return nil, types.ErrCompressionInvalid
+ }
+}
+
+func (u Util) WriteLink(s types.Link) error {
+ path := u.JoinPath(s.Path)
+
+ if err := MkdirForFile(path); err != nil {
+ return err
+ }
+
+ if s.Hard {
+ return os.Link(s.Target, path)
+ }
+ return os.Symlink(s.Target, path)
+}
+
+// WriteFile creates and writes the file described by f using the provided context.
+func (u Util) WriteFile(f *File) error {
+ defer f.Close()
+ var err error
+
+ path := u.JoinPath(string(f.Path))
+
+ if err := MkdirForFile(path); err != nil {
+ return err
+ }
+
+ // Create a temporary file in the same directory to ensure it's on the same filesystem
+ var tmp *os.File
+ if tmp, err = ioutil.TempFile(filepath.Dir(path), "tmp"); err != nil {
+ return err
+ }
+
+ defer func() {
+ tmp.Close()
+ if err != nil {
+ os.Remove(tmp.Name())
+ }
+ }()
+
+ fileWriter := bufio.NewWriter(tmp)
+
+ if _, err = io.Copy(fileWriter, f); err != nil {
+ return err
+ }
+ fileWriter.Flush()
+
+ if err = f.Verify(); err != nil {
+ return err
+ }
+
+ // XXX(vc): Note that we assume to be operating on the file we just wrote, this is only guaranteed
+ // by using syscall.Fchown() and syscall.Fchmod()
+
+ // Ensure the ownership and mode are as requested (since WriteFile can be affected by sticky bit)
+ if err = os.Chown(tmp.Name(), f.Uid, f.Gid); err != nil {
+ return err
+ }
+
+ if err = os.Chmod(tmp.Name(), f.Mode); err != nil {
+ return err
+ }
+
+ if err = os.Rename(tmp.Name(), path); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// MkdirForFile helper creates the directory components of path.
+func MkdirForFile(path string) error {
+ return os.MkdirAll(filepath.Dir(path), DefaultDirectoryPermissions)
+}