cache/filecache: Recover from file corruption - hugo - [fork] hugo port for 9front
HTML git clone git@git.drkhsh.at/hugo.git
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
DIR commit 180195aa342777fece1b29a08ec89456d7996c61
DIR parent 4b286b9d2722909d0682e50eeecdfe16c1f47fd8
HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date: Mon, 21 Oct 2019 09:37:46 +0200
cache/filecache: Recover from file corruption
Fixes #6401
Diffstat:
M cache/filecache/filecache.go | 12 +++++++++++-
M cache/filecache/filecache_test.go | 50 +++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 1 deletion(-)
---
DIR diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go
@@ -15,6 +15,7 @@ package filecache
import (
"bytes"
+ "errors"
"io"
"io/ioutil"
"os"
@@ -31,6 +32,9 @@ import (
"github.com/spf13/afero"
)
+// ErrFatal can be used to signal an unrecoverable error.
+var ErrFatal = errors.New("fatal filecache error")
+
const (
filecacheRootDirname = "filecache"
)
@@ -137,7 +141,13 @@ func (c *Cache) ReadOrCreate(id string,
if r := c.getOrRemove(id); r != nil {
err = read(info, r)
defer r.Close()
- return
+ if err == nil || err == ErrFatal {
+ // See https://github.com/gohugoio/hugo/issues/6401
+ // To recover from file corruption we handle read errors
+ // as the cache item was not found.
+ // Any file permission issue will also fail in the next step.
+ return
+ }
}
f, err := helpers.OpenFileForWriting(c.Fs, id)
DIR diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go
@@ -14,6 +14,7 @@
package filecache
import (
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -243,6 +244,55 @@ dir = "/cache/c"
wg.Wait()
}
+func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ var result string
+
+ rf := func(failLevel int) func(info ItemInfo, r io.Reader) error {
+
+ return func(info ItemInfo, r io.Reader) error {
+ if failLevel > 0 {
+ if failLevel > 1 {
+ return ErrFatal
+ }
+ return errors.New("fail")
+ }
+
+ b, _ := ioutil.ReadAll(r)
+ result = string(b)
+
+ return nil
+ }
+ }
+
+ bf := func(s string) func(info ItemInfo, w io.WriteCloser) error {
+ return func(info ItemInfo, w io.WriteCloser) error {
+ defer w.Close()
+ result = s
+ _, err := w.Write([]byte(s))
+ return err
+ }
+ }
+
+ cache := NewCache(afero.NewMemMapFs(), 100*time.Hour, "")
+
+ const id = "a32"
+
+ _, err := cache.ReadOrCreate(id, rf(0), bf("v1"))
+ c.Assert(err, qt.IsNil)
+ c.Assert(result, qt.Equals, "v1")
+ _, err = cache.ReadOrCreate(id, rf(0), bf("v2"))
+ c.Assert(err, qt.IsNil)
+ c.Assert(result, qt.Equals, "v1")
+ _, err = cache.ReadOrCreate(id, rf(1), bf("v3"))
+ c.Assert(err, qt.IsNil)
+ c.Assert(result, qt.Equals, "v3")
+ _, err = cache.ReadOrCreate(id, rf(2), bf("v3"))
+ c.Assert(err, qt.Equals, ErrFatal)
+}
+
func TestCleanID(t *testing.T) {
c := qt.New(t)
c.Assert(cleanID(filepath.FromSlash("/a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))