diff --git a/content/helpers.go b/content/helpers.go index 86b853685bf4..83c31d917e1e 100644 --- a/content/helpers.go +++ b/content/helpers.go @@ -77,7 +77,7 @@ func Copy(ctx context.Context, cw Writer, r io.Reader, size int64, expected dige r, err = seekReader(r, ws.Offset, size) if err != nil { if !isUnseekable(err) { - return errors.Wrapf(err, "unabled to resume write to %v", ws.Ref) + return errors.Wrapf(err, "unable to resume write to %v", ws.Ref) } // reader is unseekable, try to move the writer back to the start. diff --git a/content/local/store_test.go b/content/local/store_test.go index 55a96189bbda..5c505240cf63 100644 --- a/content/local/store_test.go +++ b/content/local/store_test.go @@ -22,6 +22,7 @@ import ( "github.com/containerd/containerd/content/testsuite" "github.com/containerd/containerd/testutil" "github.com/opencontainers/go-digest" + "github.com/stretchr/testify/require" ) type memoryLabelStore struct { @@ -335,3 +336,42 @@ func checkWrite(ctx context.Context, t checker, cs content.Store, dgst digest.Di return dgst } + +func TestWriterTruncateRecoversFromIncompleteWrite(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "test-local-content-store-recover") + require.NoError(t, err) + defer os.RemoveAll(tmpdir) + + cs, err := NewStore(tmpdir) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ref := "ref" + content := []byte("this is the content") + total := int64(len(content)) + setupIncompleteWrite(ctx, t, cs, ref, total) + + writer, err := cs.Writer(ctx, ref, total, "") + require.NoError(t, err) + + require.NoError(t, writer.Truncate(0)) + + _, err = writer.Write(content) + require.NoError(t, err) + + dgst := digest.FromBytes(content) + err = writer.Commit(ctx, total, dgst) + require.NoError(t, err) +} + +func setupIncompleteWrite(ctx context.Context, t *testing.T, cs content.Store, ref string, total int64) { + writer, err := cs.Writer(ctx, ref, total, "") + require.NoError(t, err) + + _, err = writer.Write([]byte("bad data")) + require.NoError(t, err) + + require.NoError(t, writer.Close()) +} diff --git a/content/local/writer.go b/content/local/writer.go index fbf39f755a5c..e6b4276b421a 100644 --- a/content/local/writer.go +++ b/content/local/writer.go @@ -2,6 +2,7 @@ package local import ( "context" + "io" "os" "path/filepath" "runtime" @@ -167,5 +168,8 @@ func (w *writer) Truncate(size int64) error { } w.offset = 0 w.digester.Hash().Reset() + if _, err := w.fp.Seek(0, io.SeekStart); err != nil { + return err + } return w.fp.Truncate(0) }