URI: 
       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"))