From 4df1d1e552c6279e9f0fd726fbd037befdbef1d5 Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Mon, 1 Apr 2019 14:27:23 +0800 Subject: [PATCH 01/12] walk and tar directory --- pkg/content/utils.go | 67 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/pkg/content/utils.go b/pkg/content/utils.go index ecbf0c170..c707f43e3 100644 --- a/pkg/content/utils.go +++ b/pkg/content/utils.go @@ -1,9 +1,74 @@ package content -import ocispec "github.com/opencontainers/image-spec/specs-go/v1" +import ( + "archive/tar" + "io" + "os" + "path/filepath" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) // ResolveName resolves name from descriptor func ResolveName(desc ocispec.Descriptor) (string, bool) { name, ok := desc.Annotations[ocispec.AnnotationTitle] return name, ok } + +// TarDirectory walks the directory specified by path, and tar those files with a new +// path prefix. +func TarDirectory(root, prefix string, w io.Writer) error { + tw := tar.NewWriter(w) + defer tw.Close() + if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return errors.Wrap(err, path) + } + + // Rename path + name, err := filepath.Rel(root, path) + if err != nil { + return errors.Wrap(err, path) + } + name = filepath.Join(prefix, name) + name = filepath.ToSlash(name) + + // Generate header + var link string + mode := info.Mode() + if mode&os.ModeSymlink != 0 { + if link, err = os.Readlink(link); err != nil { + return err + } + } + header, err := tar.FileInfoHeader(info, link) + if err != nil { + return err + } + header.Name = name + header.Uid = 0 + header.Gid = 0 + header.Uname = "" + header.Gname = "" + + // Write file + if err := tw.WriteHeader(header); err != nil { + return err + } + if mode.IsRegular() { + file, err := os.Open(path) + if err != nil { + return errors.Wrap(err, path) + } + if _, err := io.Copy(tw, file); err != nil { + return errors.Wrap(err, path) + } + } + + return nil + }); err != nil { + return err + } + return nil +} From 9604a429d9280da6d076925f7f524a27e67d833f Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Mon, 1 Apr 2019 14:30:28 +0800 Subject: [PATCH 02/12] better error output --- pkg/content/utils.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/content/utils.go b/pkg/content/utils.go index c707f43e3..9844e9328 100644 --- a/pkg/content/utils.go +++ b/pkg/content/utils.go @@ -39,12 +39,12 @@ func TarDirectory(root, prefix string, w io.Writer) error { mode := info.Mode() if mode&os.ModeSymlink != 0 { if link, err = os.Readlink(link); err != nil { - return err + return errors.Wrap(err, path) } } header, err := tar.FileInfoHeader(info, link) if err != nil { - return err + return errors.Wrap(err, path) } header.Name = name header.Uid = 0 @@ -54,7 +54,7 @@ func TarDirectory(root, prefix string, w io.Writer) error { // Write file if err := tw.WriteHeader(header); err != nil { - return err + return errors.Wrap(err, "tar") } if mode.IsRegular() { file, err := os.Open(path) From 776da2e9b1a65439cb24d121e877ac111b49d947 Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Mon, 1 Apr 2019 17:26:02 +0800 Subject: [PATCH 03/12] simplify err output --- pkg/content/utils.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/content/utils.go b/pkg/content/utils.go index 9844e9328..adb43e900 100644 --- a/pkg/content/utils.go +++ b/pkg/content/utils.go @@ -23,13 +23,13 @@ func TarDirectory(root, prefix string, w io.Writer) error { defer tw.Close() if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { - return errors.Wrap(err, path) + return err } // Rename path name, err := filepath.Rel(root, path) if err != nil { - return errors.Wrap(err, path) + return err } name = filepath.Join(prefix, name) name = filepath.ToSlash(name) @@ -38,8 +38,8 @@ func TarDirectory(root, prefix string, w io.Writer) error { var link string mode := info.Mode() if mode&os.ModeSymlink != 0 { - if link, err = os.Readlink(link); err != nil { - return errors.Wrap(err, path) + if link, err = os.Readlink(path); err != nil { + return err } } header, err := tar.FileInfoHeader(info, link) @@ -59,7 +59,7 @@ func TarDirectory(root, prefix string, w io.Writer) error { if mode.IsRegular() { file, err := os.Open(path) if err != nil { - return errors.Wrap(err, path) + return err } if _, err := io.Copy(tw, file); err != nil { return errors.Wrap(err, path) From 07f0b3915063a0dd736b44df43573899be469c68 Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Tue, 2 Apr 2019 16:13:11 +0800 Subject: [PATCH 04/12] push folder to the remote --- pkg/content/consts.go | 18 +++++++- pkg/content/file.go | 100 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/pkg/content/consts.go b/pkg/content/consts.go index b987c23a5..de80532cf 100644 --- a/pkg/content/consts.go +++ b/pkg/content/consts.go @@ -2,5 +2,19 @@ package content import ocispec "github.com/opencontainers/image-spec/specs-go/v1" -// DefaultBlobMediaType specifies the default blob media type -const DefaultBlobMediaType = ocispec.MediaTypeImageLayer +const ( + // DefaultBlobMediaType specifies the default blob media type + DefaultBlobMediaType = ocispec.MediaTypeImageLayer + // DefaultBlobDirMediaType specifies the default blob directory media type + DefaultBlobDirMediaType = ocispec.MediaTypeImageLayerGzip +) + +const ( + // TempFilePattern specifies the pattern to create temporary files + TempFilePattern = "oras" +) + +const ( + // AnnotationDigest is the annotation key for the digest of the uncompressed content + AnnotationDigest = "io.deis.oras.content.digest" +) diff --git a/pkg/content/file.go b/pkg/content/file.go index d708c3d26..c45be2995 100644 --- a/pkg/content/file.go +++ b/pkg/content/file.go @@ -1,8 +1,10 @@ package content import ( + "compress/gzip" "context" "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -30,6 +32,7 @@ type FileStore struct { root string descriptor *sync.Map // map[digest.Digest]ocispec.Descriptor pathMap *sync.Map + tmpFiles *sync.Map } // NewFileStore creats a new file store @@ -38,15 +41,12 @@ func NewFileStore(rootPath string) *FileStore { root: rootPath, descriptor: &sync.Map{}, pathMap: &sync.Map{}, + tmpFiles: &sync.Map{}, } } // Add adds a file reference func (s *FileStore) Add(name, mediaType, path string) (ocispec.Descriptor, error) { - if mediaType == "" { - mediaType = DefaultBlobMediaType - } - if path == "" { path = name } @@ -56,6 +56,26 @@ func (s *FileStore) Add(name, mediaType, path string) (ocispec.Descriptor, error if err != nil { return ocispec.Descriptor{}, err } + + var desc ocispec.Descriptor + if fileInfo.IsDir() { + desc, err = s.descFromDir(name, mediaType, path) + } else { + desc, err = s.descFromFile(fileInfo, mediaType, path) + } + if err != nil { + return ocispec.Descriptor{}, err + } + if desc.Annotations == nil { + desc.Annotations = make(map[string]string) + } + desc.Annotations[ocispec.AnnotationTitle] = name + + s.set(desc) + return desc, nil +} + +func (s *FileStore) descFromFile(info os.FileInfo, mediaType, path string) (ocispec.Descriptor, error) { file, err := os.Open(path) if err != nil { return ocispec.Descriptor{}, err @@ -66,17 +86,79 @@ func (s *FileStore) Add(name, mediaType, path string) (ocispec.Descriptor, error return ocispec.Descriptor{}, err } - desc := ocispec.Descriptor{ + if mediaType == "" { + mediaType = DefaultBlobMediaType + } + return ocispec.Descriptor{ MediaType: mediaType, Digest: digest, - Size: fileInfo.Size(), + Size: info.Size(), + }, nil +} + +func (s *FileStore) descFromDir(name, mediaType, root string) (ocispec.Descriptor, error) { + // generate temp file + file, err := s.tempFile() + if err != nil { + return ocispec.Descriptor{}, err + } + defer file.Close() + s.MapPath(name, file.Name()) + + // compress directory + digester := digest.Canonical.Digester() + zw := gzip.NewWriter(io.MultiWriter(file, digester.Hash())) + defer zw.Close() + tarDigester := digest.Canonical.Digester() + if err := TarDirectory(root, name, io.MultiWriter(zw, tarDigester.Hash())); err != nil { + return ocispec.Descriptor{}, err + } + + // flush all + if err := zw.Close(); err != nil { + return ocispec.Descriptor{}, err + } + if err := file.Sync(); err != nil { + return ocispec.Descriptor{}, err + } + + // generate descriptor + if mediaType == "" { + mediaType = DefaultBlobDirMediaType + } + info, err := file.Stat() + if err != nil { + return ocispec.Descriptor{}, err + } + return ocispec.Descriptor{ + MediaType: mediaType, + Digest: digester.Digest(), + Size: info.Size(), Annotations: map[string]string{ - ocispec.AnnotationTitle: name, + AnnotationDigest: tarDigester.Digest().String(), }, + }, nil +} + +func (s *FileStore) tempFile() (*os.File, error) { + file, err := ioutil.TempFile("", TempFilePattern) + if err != nil { + return nil, err } + s.tmpFiles.Store(file.Name(), file) + return file, nil +} - s.set(desc) - return desc, nil +// Close frees up resources used by the file store +func (s *FileStore) Close() error { + var errs []string + s.tmpFiles.Range(func(name, _ interface{}) bool { + if err := os.Remove(name.(string)); err != nil { + errs = append(errs, err.Error()) + } + return true + }) + return errors.New(strings.Join(errs, "; ")) } // ReaderAt provides contents From 97d70460544d8f4d34d1d95fab34b13ea88bc7b9 Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Tue, 2 Apr 2019 16:42:59 +0800 Subject: [PATCH 05/12] allow pull dir --- cmd/oras/pull.go | 2 +- pkg/content/consts.go | 2 ++ pkg/content/file.go | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/oras/pull.go b/cmd/oras/pull.go index 1a8fa39f1..7196a33d8 100644 --- a/cmd/oras/pull.go +++ b/cmd/oras/pull.go @@ -70,7 +70,7 @@ func runPull(opts pullOptions) error { if opts.allowAllMediaTypes { opts.allowedMediaTypes = nil } else if len(opts.allowedMediaTypes) == 0 { - opts.allowedMediaTypes = []string{content.DefaultBlobMediaType} + opts.allowedMediaTypes = []string{content.DefaultBlobMediaType, content.DefaultBlobDirMediaType} } resolver := newResolver(opts.username, opts.password, opts.configs...) diff --git a/pkg/content/consts.go b/pkg/content/consts.go index de80532cf..5c5960605 100644 --- a/pkg/content/consts.go +++ b/pkg/content/consts.go @@ -17,4 +17,6 @@ const ( const ( // AnnotationDigest is the annotation key for the digest of the uncompressed content AnnotationDigest = "io.deis.oras.content.digest" + // AnnotationUnpack is the annotation key for indication of unpacking + AnnotationUnpack = "io.deis.oras.content.unpack" ) diff --git a/pkg/content/file.go b/pkg/content/file.go index c45be2995..87022c635 100644 --- a/pkg/content/file.go +++ b/pkg/content/file.go @@ -136,6 +136,7 @@ func (s *FileStore) descFromDir(name, mediaType, root string) (ocispec.Descripto Size: info.Size(), Annotations: map[string]string{ AnnotationDigest: tarDigester.Digest().String(), + AnnotationUnpack: "true", }, }, nil } From 8737f9284ebac9dbb0f3ddb1aa6b112eb2049737 Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Tue, 2 Apr 2019 18:26:22 +0800 Subject: [PATCH 06/12] extract files --- pkg/content/file.go | 2 +- pkg/content/utils.go | 59 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/pkg/content/file.go b/pkg/content/file.go index 87022c635..62446af82 100644 --- a/pkg/content/file.go +++ b/pkg/content/file.go @@ -110,7 +110,7 @@ func (s *FileStore) descFromDir(name, mediaType, root string) (ocispec.Descripto zw := gzip.NewWriter(io.MultiWriter(file, digester.Hash())) defer zw.Close() tarDigester := digest.Canonical.Digester() - if err := TarDirectory(root, name, io.MultiWriter(zw, tarDigester.Hash())); err != nil { + if err := tarDirectory(root, name, io.MultiWriter(zw, tarDigester.Hash())); err != nil { return ocispec.Descriptor{}, err } diff --git a/pkg/content/utils.go b/pkg/content/utils.go index adb43e900..aa75d7ab9 100644 --- a/pkg/content/utils.go +++ b/pkg/content/utils.go @@ -2,6 +2,7 @@ package content import ( "archive/tar" + "fmt" "io" "os" "path/filepath" @@ -16,9 +17,9 @@ func ResolveName(desc ocispec.Descriptor) (string, bool) { return name, ok } -// TarDirectory walks the directory specified by path, and tar those files with a new +// tarDirectory walks the directory specified by path, and tar those files with a new // path prefix. -func TarDirectory(root, prefix string, w io.Writer) error { +func tarDirectory(root, prefix string, w io.Writer) error { tw := tar.NewWriter(w) defer tw.Close() if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { @@ -61,6 +62,7 @@ func TarDirectory(root, prefix string, w io.Writer) error { if err != nil { return err } + defer file.Close() if _, err := io.Copy(tw, file); err != nil { return errors.Wrap(err, path) } @@ -72,3 +74,56 @@ func TarDirectory(root, prefix string, w io.Writer) error { } return nil } + +// extractTarDirectory extracts tar file to a directory specified by the `root` +// parameter. The file name prefix is ensured to be the string specified by the +// `prefix` parameter and is trimmed. +func extractTarDirectory(root, prefix string, r io.Reader) error { + tr := tar.NewReader(r) + for { + header, err := tr.Next() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + + // Name check + name := header.Name + if rel, err := filepath.Rel(prefix, name); err != nil { + return err + } else if filepath.HasPrefix(rel, "../") { + return fmt.Errorf("%q does not have prefix %q", name, prefix) + } + path := filepath.Join(root, name) + + // Create content + switch header.Typeflag { + case tar.TypeReg: + err = writeFile(path, tr, header.FileInfo().Mode()) + case tar.TypeDir: + err = os.MkdirAll(path, header.FileInfo().Mode()) + case tar.TypeLink: + err = os.Symlink(header.Linkname, path) + default: + continue // Non-regular files are skipped + } + if err != nil { + return err + } + + // Change access time and modification time if possible (error ignored) + os.Chtimes(path, header.AccessTime, header.ModTime) + } +} + +func writeFile(path string, r io.Reader, perm os.FileMode) error { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(file, r) + return err +} From 70fc837d6a293b0b4560e069c7cbdd64fffa22dc Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Tue, 2 Apr 2019 18:27:13 +0800 Subject: [PATCH 07/12] ensure file store is closed --- cmd/oras/pull.go | 1 + cmd/oras/push.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd/oras/pull.go b/cmd/oras/pull.go index 7196a33d8..ef424e318 100644 --- a/cmd/oras/pull.go +++ b/cmd/oras/pull.go @@ -75,6 +75,7 @@ func runPull(opts pullOptions) error { resolver := newResolver(opts.username, opts.password, opts.configs...) store := content.NewFileStore(opts.output) + defer store.Close() store.DisableOverwrite = opts.keepOldFiles store.AllowPathTraversalOnWrite = opts.pathTraversal files, err := oras.Pull(context.Background(), resolver, opts.targetRef, store, opts.allowedMediaTypes...) diff --git a/cmd/oras/push.go b/cmd/oras/push.go index 79e6b0e3f..41c541daa 100644 --- a/cmd/oras/push.go +++ b/cmd/oras/push.go @@ -79,6 +79,7 @@ func runPush(opts pushOptions) error { store = content.NewFileStore("") pushOpts []oras.PushOpt ) + defer store.Close() if opts.manifestAnnotations != "" { if err := decodeJSON(opts.manifestAnnotations, &annotations); err != nil { return err From 3954482c9cebb3057114476b56c2370f32ac548c Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Wed, 3 Apr 2019 11:19:31 +0800 Subject: [PATCH 08/12] pull and unpack --- pkg/content/file.go | 37 +++++++++++++++++++++++++++++-------- pkg/content/utils.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/pkg/content/file.go b/pkg/content/file.go index 62446af82..335ce5309 100644 --- a/pkg/content/file.go +++ b/pkg/content/file.go @@ -221,10 +221,26 @@ func (s *FileStore) Writer(ctx context.Context, opts ...content.WriterOpt) (cont } } - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - return nil, err + var ( + file *os.File + err error + afterCommit func() error + ) + if value, ok := desc.Annotations[AnnotationUnpack]; ok && value == "true" { + if err := os.MkdirAll(path, 0755); err != nil { + return nil, err + } + file, err = s.tempFile() + afterCommit = func() error { + checksum := desc.Annotations[AnnotationDigest] + return extractTarGzip(path, name, file.Name(), checksum) + } + } else { + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return nil, err + } + file, err = os.Create(path) } - file, err := os.Create(path) if err != nil { return nil, err } @@ -241,6 +257,7 @@ func (s *FileStore) Writer(ctx context.Context, opts ...content.WriterOpt) (cont StartedAt: now, UpdatedAt: now, }, + afterCommit: afterCommit, }, nil } @@ -284,11 +301,12 @@ func (s *FileStore) get(desc ocispec.Descriptor) (ocispec.Descriptor, bool) { } type fileWriter struct { - store *FileStore - file *os.File - desc ocispec.Descriptor - digester digest.Digester - status content.Status + store *FileStore + file *os.File + desc ocispec.Descriptor + digester digest.Digester + status content.Status + afterCommit func() error } func (w *fileWriter) Status() (content.Status, error) { @@ -347,6 +365,9 @@ func (w *fileWriter) Commit(ctx context.Context, size int64, expected digest.Dig } w.store.set(w.desc) + if w.afterCommit != nil { + return w.afterCommit() + } return nil } diff --git a/pkg/content/utils.go b/pkg/content/utils.go index aa75d7ab9..80e97755c 100644 --- a/pkg/content/utils.go +++ b/pkg/content/utils.go @@ -2,11 +2,13 @@ package content import ( "archive/tar" + "compress/gzip" "fmt" "io" "os" "path/filepath" + digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -127,3 +129,31 @@ func writeFile(path string, r io.Reader, perm os.FileMode) error { _, err = io.Copy(file, r) return err } + +func extractTarGzip(root, prefix, filename, checksum string) error { + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() + zr, err := gzip.NewReader(file) + if err != nil { + return err + } + defer zr.Close() + var r io.Reader = zr + var verifier digest.Verifier + if checksum != "" { + if digest, err := digest.Parse(checksum); err == nil { + verifier = digest.Verifier() + r = io.TeeReader(r, verifier) + } + } + if err := extractTarDirectory(root, prefix, r); err != nil { + return err + } + if verifier != nil && !verifier.Verified() { + return errors.New("content digest mismatch") + } + return nil +} From fbdb619e4ac1a362ea62f530d210eaf14eee7dc4 Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Wed, 3 Apr 2019 11:30:15 +0800 Subject: [PATCH 09/12] bug fix: wrong untar path --- pkg/content/utils.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/content/utils.go b/pkg/content/utils.go index 80e97755c..a82e1aefd 100644 --- a/pkg/content/utils.go +++ b/pkg/content/utils.go @@ -93,12 +93,14 @@ func extractTarDirectory(root, prefix string, r io.Reader) error { // Name check name := header.Name - if rel, err := filepath.Rel(prefix, name); err != nil { + path, err := filepath.Rel(prefix, name) + if err != nil { return err - } else if filepath.HasPrefix(rel, "../") { + } + if filepath.HasPrefix(path, "../") { return fmt.Errorf("%q does not have prefix %q", name, prefix) } - path := filepath.Join(root, name) + path = filepath.Join(root, path) // Create content switch header.Typeflag { From 7909ccb7f5046b6e0a8f0ff6e608ae666f90affe Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Wed, 3 Apr 2019 11:37:20 +0800 Subject: [PATCH 10/12] support both hard and soft links --- pkg/content/utils.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/content/utils.go b/pkg/content/utils.go index a82e1aefd..8aa0d4263 100644 --- a/pkg/content/utils.go +++ b/pkg/content/utils.go @@ -109,6 +109,8 @@ func extractTarDirectory(root, prefix string, r io.Reader) error { case tar.TypeDir: err = os.MkdirAll(path, header.FileInfo().Mode()) case tar.TypeLink: + err = os.Link(header.Linkname, path) + case tar.TypeSymlink: err = os.Symlink(header.Linkname, path) default: continue // Non-regular files are skipped From 63d25a17940cd5b96c38bc381dcec9230197db9b Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Thu, 11 Apr 2019 16:14:42 +0800 Subject: [PATCH 11/12] use strings instead of filepath for HasPrefix --- pkg/content/utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/content/utils.go b/pkg/content/utils.go index 8aa0d4263..544baf6c0 100644 --- a/pkg/content/utils.go +++ b/pkg/content/utils.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "strings" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -97,7 +98,7 @@ func extractTarDirectory(root, prefix string, r io.Reader) error { if err != nil { return err } - if filepath.HasPrefix(path, "../") { + if strings.HasPrefix(path, "../") { return fmt.Errorf("%q does not have prefix %q", name, prefix) } path = filepath.Join(root, path) From 95f6ccf69a1f5cb088bd68db38245f5b029f267b Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Fri, 12 Apr 2019 13:45:31 +0800 Subject: [PATCH 12/12] refactor writer method --- pkg/content/file.go | 86 +++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/pkg/content/file.go b/pkg/content/file.go index 335ce5309..1e0600a6c 100644 --- a/pkg/content/file.go +++ b/pkg/content/file.go @@ -198,67 +198,75 @@ func (s *FileStore) Writer(ctx context.Context, opts ...content.WriterOpt) (cont if !ok { return nil, ErrNoName } + path, err := s.resolveWritePath(name) + if err != nil { + return nil, err + } + file, afterCommit, err := s.createWritePath(path, desc, name) + if err != nil { + return nil, err + } + + now := time.Now() + return &fileWriter{ + store: s, + file: file, + desc: desc, + digester: digest.Canonical.Digester(), + status: content.Status{ + Ref: name, + Total: desc.Size, + StartedAt: now, + UpdatedAt: now, + }, + afterCommit: afterCommit, + }, nil +} + +func (s *FileStore) resolveWritePath(name string) (string, error) { path := s.ResolvePath(name) if !s.AllowPathTraversalOnWrite { base, err := filepath.Abs(s.root) if err != nil { - return nil, err + return "", err } target, err := filepath.Abs(path) if err != nil { - return nil, err + return "", err } rel, err := filepath.Rel(base, target) if err != nil || strings.HasPrefix(filepath.ToSlash(rel), "../") { - return nil, ErrPathTraversalDisallowed + return "", ErrPathTraversalDisallowed } } if s.DisableOverwrite { if _, err := os.Stat(path); err == nil { - return nil, ErrOverwriteDisallowed + return "", ErrOverwriteDisallowed } else if !os.IsNotExist(err) { - return nil, err + return "", err } } + return path, nil +} - var ( - file *os.File - err error - afterCommit func() error - ) - if value, ok := desc.Annotations[AnnotationUnpack]; ok && value == "true" { - if err := os.MkdirAll(path, 0755); err != nil { - return nil, err - } - file, err = s.tempFile() - afterCommit = func() error { - checksum := desc.Annotations[AnnotationDigest] - return extractTarGzip(path, name, file.Name(), checksum) - } - } else { +func (s *FileStore) createWritePath(path string, desc ocispec.Descriptor, prefix string) (*os.File, func() error, error) { + if value, ok := desc.Annotations[AnnotationUnpack]; !ok || value != "true" { if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - return nil, err + return nil, nil, err } - file, err = os.Create(path) - } - if err != nil { - return nil, err + file, err := os.Create(path) + return file, nil, err } - now := time.Now() - return &fileWriter{ - store: s, - file: file, - desc: desc, - digester: digest.Canonical.Digester(), - status: content.Status{ - Ref: name, - Total: desc.Size, - StartedAt: now, - UpdatedAt: now, - }, - afterCommit: afterCommit, - }, nil + if err := os.MkdirAll(path, 0755); err != nil { + return nil, nil, err + } + file, err := s.tempFile() + checksum := desc.Annotations[AnnotationDigest] + afterCommit := func() error { + return extractTarGzip(path, prefix, file.Name(), checksum) + } + return file, afterCommit, err } // MapPath maps name to path