From 75fb75ffdc87cfc22499f445a601a22a7ad8132b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Jun 2018 12:18:37 -0400 Subject: [PATCH 1/2] Do some best-effort cleanup in file backend If put results in an encoding error and after the file is closed we detect it's zero bytes, it could be caused by an out of space error on the disk since file info is often stored in filesystem metadata with reserved space. This tries to detect that scenario and perform best-effort cleanup. We only do this on zero length files to ensure that if an encode fails to write but the system hasn't already performed truncation, we leave the existing data alone. Vault should never write a zero-byte file (as opposed to a zero-byte value in the encoded JSON) so if this case is hit it's always an error. --- physical/file/file.go | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/physical/file/file.go b/physical/file/file.go index d93609a19b54..7c4d2680f8df 100644 --- a/physical/file/file.go +++ b/physical/file/file.go @@ -3,6 +3,7 @@ package file import ( "context" "encoding/json" + "errors" "fmt" "io" "os" @@ -208,20 +209,46 @@ func (b *FileBackend) PutInternal(ctx context.Context, entry *physical.Entry) er } // JSON encode the entry and write it + fullPath := filepath.Join(path, key) f, err := os.OpenFile( - filepath.Join(path, key), + fullPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) - if f != nil { - defer f.Close() - } if err != nil { + if f != nil { + f.Close() + } return err } + if f == nil { + return errors.New("could not successfully get a file handle") + } + enc := json.NewEncoder(f) - return enc.Encode(&fileEntry{ + encErr := enc.Encode(&fileEntry{ Value: entry.Value, }) + f.Close() + if encErr == nil { + return nil + } + + // Everything below is best-effort and will result in encErr being returned + + // See if we ended up with a zero-byte file and if so delete it, might be a + // case of disk being full but the file info is in metadata that is + // reserved. + fi, err := os.Stat(fullPath) + if err != nil { + return encErr + } + if fi == nil { + return encErr + } + if fi.Size() == 0 { + os.Remove(fullPath) + } + return encErr } func (b *FileBackend) List(ctx context.Context, prefix string) ([]string, error) { From 74ae12eae7af44fc5f7bc9a2ba5dfeb209b2c6ad Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 2 Jun 2018 19:21:54 -0400 Subject: [PATCH 2/2] Also run a check on Get --- physical/file/file.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/physical/file/file.go b/physical/file/file.go index 7c4d2680f8df..1edf7c749f4b 100644 --- a/physical/file/file.go +++ b/physical/file/file.go @@ -163,6 +163,18 @@ func (b *FileBackend) GetInternal(ctx context.Context, k string) (*physical.Entr path, key := b.expandPath(k) path = filepath.Join(path, key) + // If we stat it and it exists but is size zero, it may be left from some + // previous FS error like out-of-space. No Vault entry will ever be zero + // length, so simply remove it and return nil. + fi, err := os.Stat(path) + if err == nil { + if fi.Size() == 0 { + // Best effort, ignore errors + os.Remove(path) + return nil, nil + } + } + f, err := os.Open(path) if f != nil { defer f.Close()